changeset 1257:1ec41ac1e7cf

server: seperation between production build dir and dev build dir: LiberviaRootResource instances's `build_path` is the path where generated files are put and served by the HTTP server, while `dev_buid_path` is used for temporary files/libraries needed for the generation/compilation of files.
author Goffi <goffi@goffi.org>
date Sun, 03 May 2020 18:25:11 +0200
parents 08cd652dea14
children 92ff09cdd6dd
files libervia/pages/_browser/browser_meta.json libervia/server/constants.py libervia/server/server.py libervia/server/tasks/implicit/task_sass.py
diffstat 4 files changed, 113 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/pages/_browser/browser_meta.json	Sun May 03 18:25:11 2020 +0200
@@ -0,0 +1,18 @@
+{
+    "js": {
+        "package": {
+            "dependencies": {
+                "nunjucks": "latest",
+                "ogv": "latest"
+            }
+        },
+        "brython_map": {
+            "nunjucks": "nunjucks/browser/nunjucks.min.js",
+            "ogv": {
+                "path": "ogv/dist/ogv.js",
+                "export": ["OGVCompat", "OGVLoader", "OGVMediaError", "OGVMediaType", "OGVTimeRanges", "OGVPlayer", "OGVVersion"],
+                "extra_init": "OGVLoader.base='/{build_dir}/node_modules/ogv/dist'"
+            }
+        }
+    }
+}
--- a/libervia/server/constants.py	Sun May 03 18:15:22 2020 +0200
+++ b/libervia/server/constants.py	Sun May 03 18:25:11 2020 +0200
@@ -36,8 +36,13 @@
     TASKS_DIR = "tasks"
     LIBERVIA_CACHE = "libervia"
     SITE_NAME_DEFAULT = "default"
+    # generated files will be accessible there
     BUILD_DIR = "__b"
     BUILD_DIR_DYN = "dyn"
+    # directory where build files are served to the client
+    PRODUCTION_BUILD_DIR = "sites"
+    # directory used for files needed temporarily (e.g. for compiling other files)
+    DEV_BUILD_DIR = "dev_build"
 
     TPL_RESOURCE = '_t'
 
--- a/libervia/server/server.py	Sun May 03 18:15:22 2020 +0200
+++ b/libervia/server/server.py	Sun May 03 18:25:11 2020 +0200
@@ -192,12 +192,14 @@
         self.pages_redirects = {}
         self.cached_urls = {}
         self.main_menu = None
-        build_path = host.getBuildPath(site_name)
-        build_path.mkdir(parents=True, exist_ok=True)
+        self.build_path = host.getBuildPath(site_name)
+        self.build_path.mkdir(parents=True, exist_ok=True)
+        self.dev_build_path = host.getBuildPath(site_name, dev=True)
+        self.dev_build_path.mkdir(parents=True, exist_ok=True)
         self.putChild(
             C.BUILD_DIR.encode(),
             ProtectedFile(
-                build_path,
+                self.build_path,
                 defaultType="application/octet-stream"),
         )
 
@@ -825,7 +827,7 @@
                 res = existing_vhosts[encoded_site_name]
             else:
                 # for root path we first check if there is a global static dir
-                # if not, we use default template's static dic
+                # if not, we use default template's static dir
                 root_path = os.path.join(site_path, C.TEMPLATE_STATIC_DIR)
                 if not os.path.isdir(root_path):
                     root_path = os.path.join(
@@ -1307,16 +1309,22 @@
         for root in self.roots:
             root.putChild(path, wrapped_res)
 
-    def getBuildPath(self, site_name):
+    def getBuildPath(self, site_name: str, dev: bool=False) -> Path:
         """Generate build path for a given site name
 
-        @param site_name(unicode): name of the site
-        @return (Path): path to the build directory
+        @param site_name: name of the site
+        @param dev: return dev build dir if True, production one otherwise
+            dev build dir is used for installing dependencies needed temporarily (e.g.
+            to compile files), while production build path is the one served by the
+            HTTP server, where final files are downloaded.
+        @return: path to the build directory
         """
+        sub_dir = C.DEV_BUILD_DIR if dev else C.PRODUCTION_BUILD_DIR
         build_path_elts = [
             config.getConfig(self.main_conf, "", "local_dir"),
             C.CACHE_DIR,
             C.LIBERVIA_CACHE,
+            sub_dir,
             regex.pathEscape(site_name or C.SITE_NAME_DEFAULT)]
         build_path = Path("/".join(build_path_elts))
         return build_path.expanduser().resolve()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/server/tasks/implicit/task_sass.py	Sun May 03 18:25:11 2020 +0200
@@ -0,0 +1,75 @@
+#!/ur/bin/env python3
+
+import json
+from pathlib import Path
+from sat.core.i18n import _
+from sat.core.log import getLogger
+from sat.core import exceptions
+from libervia.server.constants import Const as C
+from libervia.server.tasks import task
+
+
+log = getLogger(__name__)
+
+SASS_SUFFIXES = ('.sass', '.scss')
+
+
+class Task(task.Task):
+    """Compile .sass and .scss files found in themes browser paths"""
+    AFTER = ['js_modules']
+
+    async def prepare(self):
+        # we look for any Sass file, and cancel this task if none is found
+        sass_dirs = set()
+        for browser_path in self.resource.browser_modules.get('themes_browser_paths', []):
+            for p in browser_path.iterdir():
+                if p.suffix in SASS_SUFFIXES:
+                    sass_dirs.add(browser_path)
+                    break
+
+        if not sass_dirs:
+            raise exceptions.CancelError("No Sass file found")
+
+        # we have some Sass files, we need to install the compiler
+        d_path = self.resource.dev_build_path
+        package_path = d_path / "package.json"
+        try:
+            with package_path.open() as f:
+                package = json.load(f)
+        except FileNotFoundError:
+            package = {}
+        except Exception as e:
+            log.error(f"Unexepected exception while parsing package.json: {e}")
+
+        if 'node-sass' not in package.setdefault('dependencies', {}):
+            package['dependencies']['node-sass'] = 'latest'
+            with package_path.open('w') as f:
+                json.dump(package, f, indent=4)
+
+        try:
+            cmd = self.findCommand('yarn')
+        except exceptions.NotFound:
+            cmd = self.findCommand('npm')
+        await self.runCommand(cmd, 'install', path=str(d_path))
+
+        self.WATCH_DIRS = list(sass_dirs)
+
+    async def onDirEvent(self, host, filepath, flags):
+        if filepath.suffix in SASS_SUFFIXES:
+            await self.manager.runTaskInstance(self)
+
+    async def start(self):
+        d_path = self.resource.dev_build_path
+        node_sass = d_path / 'node_modules' / 'node-sass' / 'bin' / 'node-sass'
+        for browser_path in self.resource.browser_modules['themes_browser_paths']:
+            for p in browser_path.iterdir():
+                if p.suffix not in SASS_SUFFIXES:
+                    continue
+                await self.runCommand(
+                    str(node_sass),
+                    "--omit-source-map-url",
+                    "--output-style", "compressed",
+                    "--output", str(self.build_path),
+                    str(p),
+                    path=str(self.build_path)
+                )