From e4b810c0279fd3035ac0294e91f96c005dcaac98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 26 May 2022 12:11:10 +0200 Subject: [PATCH] [ADD] add basic infrastructure to buid owl-runtime without compiler --- package.json | 5 +- rollup.config.js | 64 +++++++++++++---------- src/app/template_set.ts | 9 +--- src/index.runtime.ts | 58 +++++++++++++++++++++ src/index.ts | 68 +++++------------------- src/portal.ts | 2 +- tools/compile_xml.js | 113 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 92 deletions(-) create mode 100644 src/index.runtime.ts create mode 100644 tools/compile_xml.js diff --git a/package.json b/package.json index 36072187..463a5f9b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "scripts": { "build:bundle": "rollup -c --failAfterWarnings", + "build:runtime": "rollup -c --failAfterWarnings runtime", + "build:compiler": "rollup -c --failAfterWarnings compiler", "build": "npm run build:bundle", "test": "jest", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000", @@ -25,7 +27,8 @@ "prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write", "check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check", "publish": "npm run build && npm publish", - "release": "node tools/release.js" + "release": "node tools/release.js", + "compile_templates": "node tools/compile_xml.js" }, "repository": { "type": "git", diff --git a/rollup.config.js b/rollup.config.js index bc12fc86..b47f2492 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -3,13 +3,9 @@ import git from "git-rev-sync"; import typescript from 'rollup-plugin-typescript2'; import { terser } from "rollup-plugin-terser"; -const name = "owl"; -const extend = true; -/** - * Meta data to be added on the __info__ object. - * Used to let external tools know the current owl version. - */ +let input, output; + const outro = ` __info__.version = '${pkg.version}'; __info__.date = '${new Date().toISOString()}'; @@ -17,13 +13,39 @@ __info__.hash = '${git.short()}'; __info__.url = 'https://github.com/odoo/owl'; `; +switch (process.argv[4]) { + case "compiler": + input = "src/compiler/index.ts", + output = [ + getConfigForFormat('cjs', 'dist/compiler.js', ''), + ] + break; + case "runtime": + input = "src/index.runtime.ts"; + output = [ + getConfigForFormat('esm', addSuffix(pkg.module, 'runtime'), outro), + getConfigForFormat('cjs', addSuffix(pkg.main, 'runtime'), outro), + getConfigForFormat('iife', addSuffix(pkg.browser, 'runtime'), outro), + getConfigForFormat('iife', addSuffix(pkg.browser, 'runtime'), outro, true), + ] + break; + default: + input = "src/index.ts", + output = [ + getConfigForFormat('esm', pkg.module, outro), + getConfigForFormat('cjs', pkg.main, outro), + getConfigForFormat('iife', pkg.browser, outro), + getConfigForFormat('iife', pkg.browser, outro, true), + ] + } + /** * Generate from a string depicting a path a new path for the minified version. * @param {string} pkgFileName file name */ -function generateMinifiedNameFromPkgName(pkgFileName) { +function addSuffix(pkgFileName, suffix) { const parts = pkgFileName.split('.'); - parts.splice(parts.length - 1, 0, "min"); + parts.splice(parts.length - 1, 0, suffix); return parts.join('.'); } @@ -33,12 +55,12 @@ function generateMinifiedNameFromPkgName(pkgFileName) { * @param {string} generatedFileName generated file name * @param {boolean} minified should it be minified */ -function getConfigForFormat(format, generatedFileName, minified = false) { +function getConfigForFormat(format, generatedFileName, outro, minified = false) { return { - file: minified ? generateMinifiedNameFromPkgName(generatedFileName) : generatedFileName, + file: minified ? addSuffix(generatedFileName, "min") : generatedFileName, format: format, - name: name, - extend: extend, + name: "owl", + extend: true, outro: outro, freeze: false, plugins: minified ? [terser()] : [], @@ -47,22 +69,8 @@ function getConfigForFormat(format, generatedFileName, minified = false) { } export default { - input: "src/index.ts", - output: [ - - /** - * Read about module formats: - * https://auth0.com/blog/javascript-module-systems-showdown/ - * https://medium.com/@kelin2025/so-you-wanna-use-es6-modules-714f48b3a953 - */ - - getConfigForFormat('esm', pkg.module), - getConfigForFormat('esm', pkg.module, true), - getConfigForFormat('cjs', pkg.main), - getConfigForFormat('cjs', pkg.main, true), - getConfigForFormat('iife', pkg.browser), - getConfigForFormat('iife', pkg.browser, true), - ], + input, + output, plugins: [ typescript({ useTsconfigDeclarationDir: true diff --git a/src/app/template_set.ts b/src/app/template_set.ts index 0fb1b2bf..abf39c4d 100644 --- a/src/app/template_set.ts +++ b/src/app/template_set.ts @@ -118,13 +118,8 @@ export class TemplateSet { return this.templates[name]; } - _compileTemplate(name: string, template: string | Element) { - return compile(template, { - name, - dev: this.dev, - translateFn: this.translateFn, - translatableAttributes: this.translatableAttributes, - }); + _compileTemplate(name: string, template: string | Element): ReturnType { + throw new Error(`Unable to compile a template. Please use owl full build instead`); } callTemplate(owner: any, subTemplate: string, ctx: any, parent: any, key: any): any { diff --git a/src/index.runtime.ts b/src/index.runtime.ts new file mode 100644 index 00000000..2f260fc5 --- /dev/null +++ b/src/index.runtime.ts @@ -0,0 +1,58 @@ +import { + config, + createBlock, + html, + list, + mount as blockMount, + multi, + patch, + remove, + text, + toggler, + comment, +} from "./blockdom"; +import { mainEventHandler } from "./component/handler"; +export type { Reactive } from "./reactivity"; + +config.shouldNormalizeDom = false; +config.mainEventHandler = mainEventHandler; + +export const blockDom = { + config, + // bdom entry points + mount: blockMount, + patch, + remove, + // bdom block types + list, + multi, + text, + toggler, + createBlock, + html, + comment, +}; + +export { App, mount } from "./app/app"; +export { xml } from "./app/template_set"; +export { Component } from "./component/component"; +export { useComponent, useState } from "./component/component_node"; +export { status } from "./component/status"; +export { reactive, markRaw, toRaw } from "./reactivity"; +export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks"; +export { EventBus, whenReady, loadFile, markup } from "./utils"; +export { + onWillStart, + onMounted, + onWillUnmount, + onWillUpdateProps, + onWillPatch, + onPatched, + onWillRender, + onRendered, + onWillDestroy, + onError, +} from "./component/lifecycle_hooks"; +export { validate } from "./validation"; + +export const __info__ = {}; diff --git a/src/index.ts b/src/index.ts index 2f260fc5..2dbf0df3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,58 +1,16 @@ -import { - config, - createBlock, - html, - list, - mount as blockMount, - multi, - patch, - remove, - text, - toggler, - comment, -} from "./blockdom"; -import { mainEventHandler } from "./component/handler"; -export type { Reactive } from "./reactivity"; +import { TemplateSet } from "./app/template_set"; +import { compile } from "./compiler"; -config.shouldNormalizeDom = false; -config.mainEventHandler = mainEventHandler; +export * from "./index.runtime"; -export const blockDom = { - config, - // bdom entry points - mount: blockMount, - patch, - remove, - // bdom block types - list, - multi, - text, - toggler, - createBlock, - html, - comment, +TemplateSet.prototype._compileTemplate = function _compileTemplate( + name: string, + template: string | Element +) { + return compile(template, { + name, + dev: this.dev, + translateFn: this.translateFn, + translatableAttributes: this.translatableAttributes, + }); }; - -export { App, mount } from "./app/app"; -export { xml } from "./app/template_set"; -export { Component } from "./component/component"; -export { useComponent, useState } from "./component/component_node"; -export { status } from "./component/status"; -export { reactive, markRaw, toRaw } from "./reactivity"; -export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks"; -export { EventBus, whenReady, loadFile, markup } from "./utils"; -export { - onWillStart, - onMounted, - onWillUnmount, - onWillUpdateProps, - onWillPatch, - onPatched, - onWillRender, - onRendered, - onWillDestroy, - onError, -} from "./component/lifecycle_hooks"; -export { validate } from "./validation"; - -export const __info__ = {}; diff --git a/src/portal.ts b/src/portal.ts index ca3211e4..64389ff8 100644 --- a/src/portal.ts +++ b/src/portal.ts @@ -1,5 +1,4 @@ import { onWillUnmount } from "./component/lifecycle_hooks"; -// import { xml } from "./app/template_set"; import { BDom, text, VNode } from "./blockdom"; import { Component } from "./component/component"; @@ -62,6 +61,7 @@ export function portalTemplate(app: any, bdom: any, helpers: any) { return callSlot(ctx, node, key, "default", false, null); }; } + export class Portal extends Component { static template = "__portal__"; static props = { diff --git a/tools/compile_xml.js b/tools/compile_xml.js new file mode 100644 index 00000000..40f657b6 --- /dev/null +++ b/tools/compile_xml.js @@ -0,0 +1,113 @@ +const fs = require("fs"); +const path = require("path"); +const jsdom = require("jsdom"); + +// ----------------------------------------------------------------------------- +// add global DOM stuff for compiler +// ----------------------------------------------------------------------------- +var document = new jsdom.JSDOM("", {}); +var window = document.window; +global.document = window.document; +global.window = window; +global.DOMParser = window.DOMParser; +global.Element = window.Element; +global.Node = window.Node; +// this needs to be below the jsdom stuff +const { compile } = require("../dist/compiler.js"); + +// ----------------------------------------------------------------------------- +// helpers +// ----------------------------------------------------------------------------- +async function getXmlFiles(dir) { + let xmls = []; + const files = await fs.promises.readdir(dir); + const filesStats = await Promise.all(files.map((file) => fs.promises.stat(path.join(dir, file)))); + for (let i in files) { + const name = path.join(dir, files[i]); + if (filesStats[i].isDirectory()) { + xmls = xmls.concat(await getXmlFiles(name)); + } else { + if (name.endsWith(".xml")) { + xmls.push(name); + } + } + } + return xmls; +} + +function writeToFile(filepath, data) { + if (!fs.existsSync(path.dirname(filepath))) { + fs.mkdirSync(path.dirname(filepath), { recursive: true }); + } + fs.writeFile(filepath, data, (err) => { + if (err) { + process.stdout.write(`Error while writing file ${filepath}: ${err}`); + return; + } + }); +} + +// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1 +const a = "·_,:;"; +const p = new RegExp(a.split("").join("|"), "g"); + +function slugify(str) { + return str + .replace(/\//g, "") // remove / + .replace(/\./g, "_") // Replace . with _ + .replace(p, (c) => '_') // Replace special characters + .replace(/&/g, "_and_") // Replace & with ‘and’ + .replace(/[^\w\-]+/g, "") // Remove all non-word characters +} + +// ----------------------------------------------------------------------------- +// main +// ----------------------------------------------------------------------------- + +async function compileTemplates(files) { + process.stdout.write(`Processing ${files.length} files`); + let xmlStrings = await Promise.all(files.map((file) => fs.promises.readFile(file, "utf8"))); + + const templates = []; + const errors = []; + + for (let i = 0; i < files.length; i++) { + const fileName = files[i]; + const fileContent = xmlStrings[i]; + process.stdout.write(`.`); + const parser = new DOMParser(); + const doc = parser.parseFromString(fileContent, "text/xml"); + for (const template of doc.querySelectorAll("[t-name]")) { + const name = template.getAttribute("t-name"); + if (template.hasAttribute("owl")) { + template.removeAttribute("owl") + } + const fnName = slugify(name); + try { + const fn = compile(template).toString().replace('anonymous', fnName); + templates.push(`owl.App.registerTemplate("${name}", ${fn});\n`); + } catch (e) { + errors.push({ name, fileName, e }); + } + } + } + process.stdout.write(`\n`); + + for (let { name, fileName, e } of errors) { + console.warn(`Error while compiling '${name}' (in file ${fileName})`); + console.error(e); + } + console.log(`${templates.length} templates compiled`); + + return templates.join("\n"); +} + +const templatesPath = process.argv[2]; +if (templatesPath && templatesPath.length) { + getXmlFiles(templatesPath).then(async (files) => { + const result = await compileTemplates(files); + writeToFile("templates.js", result); + }); +} else { + console.log("Please provide a path"); +}