From e6768501cd401e765675cdf32268a12b401186e2 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Mon, 25 Nov 2024 10:30:53 +0100 Subject: [PATCH] [REL] v2.5.0 # v2.5.0 - [IMP] app: add global values at compile time - [IMP] parser: add support for custom directives - [FIX] runtime: properly handle error caused in mounted hook --- docs/owl.js | 78 ++++++++++++++++++++++++++++++++++++++--------- package-lock.json | 2 +- package.json | 2 +- src/version.ts | 2 +- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/docs/owl.js b/docs/owl.js index f04a603fc..48148b994 100644 --- a/docs/owl.js +++ b/docs/owl.js @@ -3227,6 +3227,9 @@ class TemplateSet { } } this.getRawTemplate = config.getTemplate; + this.customDirectives = config.customDirectives || {}; + this.runtimeUtils = { ...helpers, __globals__: config.globalValues || {} }; + this.hasGlobalValues = Boolean(config.globalValues && Object.keys(config.globalValues).length); } static registerTemplate(name, fn) { globalTemplates[name] = fn; @@ -3283,7 +3286,7 @@ class TemplateSet { this.templates[name] = function (context, parent) { return templates[name].call(this, context, parent); }; - const template = templateFn(this, bdom, helpers); + const template = templateFn(this, bdom, this.runtimeUtils); this.templates[name] = template; } return this.templates[name]; @@ -3334,7 +3337,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate); //------------------------------------------------------------------------------ // Misc types, constants and helpers //------------------------------------------------------------------------------ -const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(","); +const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__".split(","); const WORD_REPLACEMENT = Object.assign(Object.create(null), { and: "&&", or: "||", @@ -3806,6 +3809,9 @@ class CodeGenerator { this.dev = options.dev || false; this.ast = ast; this.templateName = options.name; + if (options.hasGlobalValues) { + this.helpers.add("__globals__"); + } } generateCode() { const ast = this.ast; @@ -4844,29 +4850,33 @@ class CodeGenerator { // Parser // ----------------------------------------------------------------------------- const cache = new WeakMap(); -function parse(xml) { +function parse(xml, customDir) { + const ctx = { + inPreTag: false, + customDirectives: customDir, + }; if (typeof xml === "string") { const elem = parseXML(`${xml}`).firstChild; - return _parse(elem); + return _parse(elem, ctx); } let ast = cache.get(xml); if (!ast) { // we clone here the xml to prevent modifying it in place - ast = _parse(xml.cloneNode(true)); + ast = _parse(xml.cloneNode(true), ctx); cache.set(xml, ast); } return ast; } -function _parse(xml) { +function _parse(xml, ctx) { normalizeXML(xml); - const ctx = { inPreTag: false }; return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" }; } function parseNode(node, ctx) { if (!(node instanceof Element)) { return parseTextCommentNode(node, ctx); } - return (parseTDebugLog(node, ctx) || + return (parseTCustom(node, ctx) || + parseTDebugLog(node, ctx) || parseTForEach(node, ctx) || parseTIf(node, ctx) || parseTPortal(node, ctx) || @@ -4908,6 +4918,35 @@ function parseTextCommentNode(node, ctx) { } return null; } +function parseTCustom(node, ctx) { + if (!ctx.customDirectives) { + return null; + } + const nodeAttrsNames = node.getAttributeNames(); + for (let attr of nodeAttrsNames) { + if (attr === "t-custom" || attr === "t-custom-") { + throw new OwlError("Missing custom directive name with t-custom directive"); + } + if (attr.startsWith("t-custom-")) { + const directiveName = attr.split(".")[0].slice(9); + const customDirective = ctx.customDirectives[directiveName]; + if (!customDirective) { + throw new OwlError(`Custom directive "${directiveName}" is not defined`); + } + const value = node.getAttribute(attr); + const modifier = attr.split(".").length > 1 ? attr.split(".")[1] : undefined; + node.removeAttribute(attr); + try { + customDirective(node, value, modifier); + } + catch (error) { + throw new OwlError(`Custom directive "${directiveName}" throw the following error: ${error}`); + } + return parseNode(node, ctx); + } + } + return null; +} // ----------------------------------------------------------------------------- // debugging // ----------------------------------------------------------------------------- @@ -5539,9 +5578,11 @@ function normalizeXML(el) { normalizeTEscTOut(el); } -function compile(template, options = {}) { +function compile(template, options = { + hasGlobalValues: false, +}) { // parsing - const ast = parse(template); + const ast = parse(template, options.customDirectives); // some work const hasSafeContext = template instanceof Node ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null @@ -5563,7 +5604,7 @@ function compile(template, options = {}) { } // do not modify manually. This file is generated by the release script. -const version = "2.4.1"; +const version = "2.5.0"; // ----------------------------------------------------------------------------- // Scheduler @@ -5642,7 +5683,14 @@ class Scheduler { if (!hasError) { fiber.complete(); } - this.tasks.delete(fiber); + // at this point, the fiber should have been applied to the DOM, so we can + // remove it from the task list. If it is not the case, it means that there + // was an error and an error handler triggered a new rendering that recycled + // the fiber, so in that case, we actually want to keep the fiber around, + // otherwise it will just be ignored. + if (fiber.appliedToDom) { + this.tasks.delete(fiber); + } } } } @@ -6026,12 +6074,14 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat dev: this.dev, translateFn: this.translateFn, translatableAttributes: this.translatableAttributes, + customDirectives: this.customDirectives, + hasGlobalValues: this.hasGlobalValues, }); }; export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; -__info__.date = '2024-10-31T09:42:30.824Z'; -__info__.hash = 'b8d09e5'; +__info__.date = '2024-11-25T09:30:45.930Z'; +__info__.hash = '6b24864'; __info__.url = 'https://github.com/odoo/owl'; diff --git a/package-lock.json b/package-lock.json index dbfa65e00..8cf1c1963 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.4.1", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8ea93e261..6b9e96ba6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.4.1", + "version": "2.5.0", "description": "Odoo Web Library (OWL)", "main": "dist/owl.cjs.js", "module": "dist/owl.es.js", diff --git a/src/version.ts b/src/version.ts index 11d565540..f71ca48ee 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // do not modify manually. This file is generated by the release script. -export const version = "2.4.1"; +export const version = "2.5.0";