# HG changeset patch # User Goffi # Date 1700667092 -3600 # Node ID 038d4bfdd967c609624a135fedad7d7613e98f06 # Parent 54ba0f74a488716262963d999a8aa851cdfdbc73 server (tasks/JS modules): add new way to generate modules + support of CSS files diff -r 54ba0f74a488 -r 038d4bfdd967 libervia/web/pages/_browser/proxy.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/web/pages/_browser/proxy.py Wed Nov 22 16:31:32 2023 +0100 @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Libervia XMPP +# Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +class JSProxy: + def __init__(self): + self._module = None + + @property + def js_module(self): + return self._module + + @js_module.setter + def js_module(self, module): + if self._module is not None: + raise Exception("Module is already set!") + self._module = module + + def __getattr__(self, name): + if self._module is None: + raise RuntimeError("The module has not been loaded yet") + return getattr(self._module, name) + + def __call__(self, *args): + if self._module is None: + raise RuntimeError("The module has not been loaded yet") + return self._module(*args) diff -r 54ba0f74a488 -r 038d4bfdd967 libervia/web/server/tasks/implicit/task_js_modules.py --- a/libervia/web/server/tasks/implicit/task_js_modules.py Wed Nov 22 15:25:52 2023 +0100 +++ b/libervia/web/server/tasks/implicit/task_js_modules.py Wed Nov 22 16:31:32 2023 +0100 @@ -44,26 +44,116 @@ if ' ' in module_name: raise ValueError( f"module {module_name!r} has space(s), it must not!") - module_path = js_modules_path / f"{module_name}.py" + module_python_name = module_name.replace(".", "_").replace("-", "_") + if module_python_name != module_name: + log.info("{module_python_name!r} will be used as python module name") + module_path = js_modules_path / f"{module_python_name}.py" if isinstance(module_data, str): - module_data = {'path': module_data} + module_data = {'path': [module_data]} try: - js_path = module_data.pop('path') + js_paths = module_data.pop('path') except KeyError: raise ValueError( f'module data for {module_name} must have a "path" key') - module_data['path'] = Path('node_modules') / js_path.strip(' /') - export = module_data.get('export') or [module_name] - export_objects = '\n'.join(f'{e} = window.{e}' for e in export) - extra_kwargs = {"build_dir": C.BUILD_DIR} + if isinstance(js_paths, str): + js_paths = [js_paths] + module_data['path'] = [ + Path('node_modules') / js_path.strip(' /') for js_path in js_paths + ] + to_export = module_data.get("export", [module_name]) + if isinstance(to_export, str): + to_export = to_export.split(",") + to_export = [e.strip() for e in to_export] + + init_code = [] + + # CSS if any + css_to_load = module_data.get("css") + if css_to_load: + if isinstance(css_to_load, str): + css_to_load = [css_to_load] + + add_styles_lines = [] + for css_path in css_to_load: + normalized_css_path = Path("node_modules") / css_path.strip(" /") + css_href = Path("/").joinpath(C.BUILD_DIR, normalized_css_path) + style_tag = ( + f'' + ) + add_style_code = ( + 'document.head.insertAdjacentHTML(' + '"beforeend", ' + f"'{style_tag}')" + ) + add_styles_lines.append(add_style_code) + + add_styles = "\n".join(add_styles_lines) + else: + add_styles = "" with module_path.open('w') as f: - f.write(f"""\ -#!/usr/bin/env python3 -from browser import window, load -{module_data.get('extra_import', '')} + browser_imports = ["aio", "document"] + modules_import = [] + script_paths = [ + str(Path('/').joinpath(C.BUILD_DIR, path)) + for path in module_data["path"] + ] + import_type = module_data.get('import_type', "load") + if import_type == 'module': + modules_import.append('javascript') + modules_import.append('proxy') + callback_function_name = f"on_{module_python_name}_loaded" + declare_obj = "\n".join(f"{e} = proxy.JSProxy()" for e in to_export) + export_str = "\n ".join(f"{e}.js_module=module.{e}" for e in to_export) + load_js_libraries = ( + f"\n{declare_obj}\n\n" + f"def {callback_function_name}(module):\n" + f" {export_str}\n" + f" loaded.set_result(True)\n\n" + f"javascript.import_modules({script_paths}, {callback_function_name})\n" + ) + elif import_type == 'script': + browser_imports.append("window") + script_tags = [ + f"" for src in script_paths + ] + load_js_libraries = "\n".join( + f'document.head.insertAdjacentHTML("beforeend", "{tag}")' + for tag in script_tags + ) + init_code.append('\n'.join(f'{e} = window.{e}' for e in to_export)) + init_code.append("loaded.set_result(True)") + elif import_type == "load": + browser_imports.append("load") + browser_imports.append("window") + load_calls = [f'load("{src}")' for src in script_paths] + load_js_libraries = "\n".join(load_calls) + init_code.append('\n'.join(f'{e} = window.{e}' for e in to_export)) + init_code.append("loaded.set_result(True)") + else: + raise ValueError("Invalid import type: {import_type!r}") -load("{Path('/').joinpath(C.BUILD_DIR, module_data['path'])}") -{export_objects} -{module_data.get('extra_init', '').format(**extra_kwargs)} -""") + extra_init = module_data.get("extra_init") + if extra_init is not None: + if not isinstance(extra_init, str): + raise ValueError(f"Invalid extra_init: {extra_init!r}") + init_code.append( + extra_init.format( + build_dir = C.BUILD_DIR + ) + ) + + init_code_str = "\n".join(init_code) + imports = [f"from browser import {', '.join(browser_imports)}"] + for module in modules_import: + imports.append(f"import {module}") + imports_str = "\n".join(imports) + + f.write( + "#!/usr/bin/env python3\n" + f"{imports_str}\n\n" + f"loaded = aio.Future()\n" + f"{add_styles}\n" + f"{load_js_libraries}\n" + f"{init_code_str}" + )