[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
This commit is contained in:
Romeo Fragomeli
2024-11-25 10:30:53 +01:00
parent 6b2486473f
commit e6768501cd
4 changed files with 67 additions and 17 deletions
+64 -14
View File
@@ -3227,6 +3227,9 @@ class TemplateSet {
} }
} }
this.getRawTemplate = config.getTemplate; 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) { static registerTemplate(name, fn) {
globalTemplates[name] = fn; globalTemplates[name] = fn;
@@ -3283,7 +3286,7 @@ class TemplateSet {
this.templates[name] = function (context, parent) { this.templates[name] = function (context, parent) {
return templates[name].call(this, 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; this.templates[name] = template;
} }
return this.templates[name]; return this.templates[name];
@@ -3334,7 +3337,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Misc types, constants and helpers // 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), { const WORD_REPLACEMENT = Object.assign(Object.create(null), {
and: "&&", and: "&&",
or: "||", or: "||",
@@ -3806,6 +3809,9 @@ class CodeGenerator {
this.dev = options.dev || false; this.dev = options.dev || false;
this.ast = ast; this.ast = ast;
this.templateName = options.name; this.templateName = options.name;
if (options.hasGlobalValues) {
this.helpers.add("__globals__");
}
} }
generateCode() { generateCode() {
const ast = this.ast; const ast = this.ast;
@@ -4844,29 +4850,33 @@ class CodeGenerator {
// Parser // Parser
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const cache = new WeakMap(); const cache = new WeakMap();
function parse(xml) { function parse(xml, customDir) {
const ctx = {
inPreTag: false,
customDirectives: customDir,
};
if (typeof xml === "string") { if (typeof xml === "string") {
const elem = parseXML(`<t>${xml}</t>`).firstChild; const elem = parseXML(`<t>${xml}</t>`).firstChild;
return _parse(elem); return _parse(elem, ctx);
} }
let ast = cache.get(xml); let ast = cache.get(xml);
if (!ast) { if (!ast) {
// we clone here the xml to prevent modifying it in place // 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); cache.set(xml, ast);
} }
return ast; return ast;
} }
function _parse(xml) { function _parse(xml, ctx) {
normalizeXML(xml); normalizeXML(xml);
const ctx = { inPreTag: false };
return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" }; return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
} }
function parseNode(node, ctx) { function parseNode(node, ctx) {
if (!(node instanceof Element)) { if (!(node instanceof Element)) {
return parseTextCommentNode(node, ctx); return parseTextCommentNode(node, ctx);
} }
return (parseTDebugLog(node, ctx) || return (parseTCustom(node, ctx) ||
parseTDebugLog(node, ctx) ||
parseTForEach(node, ctx) || parseTForEach(node, ctx) ||
parseTIf(node, ctx) || parseTIf(node, ctx) ||
parseTPortal(node, ctx) || parseTPortal(node, ctx) ||
@@ -4908,6 +4918,35 @@ function parseTextCommentNode(node, ctx) {
} }
return null; 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 // debugging
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -5539,9 +5578,11 @@ function normalizeXML(el) {
normalizeTEscTOut(el); normalizeTEscTOut(el);
} }
function compile(template, options = {}) { function compile(template, options = {
hasGlobalValues: false,
}) {
// parsing // parsing
const ast = parse(template); const ast = parse(template, options.customDirectives);
// some work // some work
const hasSafeContext = template instanceof Node const hasSafeContext = template instanceof Node
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null ? !(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. // do not modify manually. This file is generated by the release script.
const version = "2.4.1"; const version = "2.5.0";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Scheduler // Scheduler
@@ -5642,7 +5683,14 @@ class Scheduler {
if (!hasError) { if (!hasError) {
fiber.complete(); 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, dev: this.dev,
translateFn: this.translateFn, translateFn: this.translateFn,
translatableAttributes: this.translatableAttributes, 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 }; 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__.date = '2024-11-25T09:30:45.930Z';
__info__.hash = 'b8d09e5'; __info__.hash = '6b24864';
__info__.url = 'https://github.com/odoo/owl'; __info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.4.1", "version": "2.5.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.4.1", "version": "2.5.0",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
export const version = "2.4.1"; export const version = "2.5.0";