[REF] qweb, component: small code refactoring

This commit is contained in:
Géry Debongnie
2019-05-02 09:47:33 +02:00
parent 9df6c0aede
commit 6350d9598d
4 changed files with 95 additions and 91 deletions
+21 -20
View File
@@ -1,6 +1,6 @@
import { EventBus } from "./event_bus";
import { Observer } from "./observer";
import { QWeb } from "./qweb";
import { QWeb, CompiledTemplate } from "./qweb";
import { h, patch, VNode } from "./vdom";
//------------------------------------------------------------------------------
@@ -28,6 +28,7 @@ interface Meta<T extends Env, Props> {
renderPromise: Promise<VNode> | null;
boundHandlers: { [key: number]: any };
observer?: Observer;
render?: CompiledTemplate;
}
//------------------------------------------------------------------------------
@@ -319,7 +320,6 @@ export class Component<
}
}
set(target: any, key: string | number, value: any) {
this.__owl__.observer!.set(target, key, value);
}
@@ -349,26 +349,28 @@ export class Component<
this.__owl__.vnode = patch(document.createElement(vnode.sel!), vnode);
}
}
async _prepare(): Promise<VNode> {
_prepare(): Promise<VNode> {
this.__owl__.renderProps = this.props;
this.__owl__.renderPromise = this.willStart().then(() => {
if (this.__owl__.isDestroyed) {
return Promise.resolve(h("div"));
}
this.__owl__.isStarted = true;
if (this.inlineTemplate) {
this.env.qweb.addTemplate(
this.inlineTemplate,
this.inlineTemplate,
true
);
}
this._observeState();
return this._render();
});
this.__owl__.renderPromise = this._prepareAndRender();
return this.__owl__.renderPromise;
}
async _prepareAndRender(): Promise<VNode> {
await this.willStart();
if (this.__owl__.isDestroyed) {
return Promise.resolve(h("div"));
}
this.__owl__.isStarted = true;
if (this.inlineTemplate) {
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
}
this.__owl__.render = this.env.qweb.render.bind(
this.env.qweb,
this.inlineTemplate || this.template
);
this._observeState();
return this._render();
}
async _render(
force: boolean = false,
patchQueue: any[] = []
@@ -378,11 +380,10 @@ export class Component<
}
this.__owl__.renderId++;
const promises: Promise<void>[] = [];
const template = this.inlineTemplate || this.template;
if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = false;
}
let vnode = this.env.qweb.render(template, this, {
let vnode = this.__owl__.render!(this, {
promises,
handlers: this.__owl__.boundHandlers,
forceUpdate: force,
+67 -64
View File
@@ -5,9 +5,12 @@ import { VNode, h } from "./vdom";
//------------------------------------------------------------------------------
export type EvalContext = { [key: string]: any };
export type RawTemplate = string;
export type CompiledTemplate<T> = (context: EvalContext, extra: any) => T;
type ProcessedTemplate = Element;
export type CompiledTemplate = (context: EvalContext, extra: any) => VNode;
interface Template {
elem: Element;
fn: CompiledTemplate;
}
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date".split(
","
@@ -34,6 +37,32 @@ const DISABLED_TAGS = [
const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g;
function parseXML(xml: string): Document {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
return doc;
}
const UTILS = {
h: h,
getFragment(str: string): DocumentFragment {
const temp = document.createElement("template");
temp.innerHTML = str;
return temp.content;
},
objectToAttrString(obj: Object): string {
let classes: string[] = [];
for (let k in obj) {
if (obj[k]) {
classes.push(k);
}
}
return classes.join(" ");
}
};
//------------------------------------------------------------------------------
// Compilation Context
//------------------------------------------------------------------------------
@@ -181,12 +210,11 @@ export class Context {
//------------------------------------------------------------------------------
// QWeb rendering engine
//------------------------------------------------------------------------------
export class QWeb {
processedTemplates: { [name: string]: ProcessedTemplate } = {};
templates: { [name: string]: CompiledTemplate<VNode> } = {};
templates: { [name: string]: Template } = {};
directives: Directive[] = [];
directiveNames: { [key: string]: 1 };
utils = UTILS;
constructor(data?: string) {
this.directiveNames = {
@@ -221,24 +249,6 @@ export class QWeb {
}
}
utils = {
h: h,
getFragment(str: string): DocumentFragment {
const temp = document.createElement("template");
temp.innerHTML = str;
return temp.content;
},
objectToAttrString(obj: Object): string {
let classes: string[] = [];
for (let k in obj) {
if (obj[k]) {
classes.push(k);
}
}
return classes.join(" ");
}
};
addDirective(dir: Directive) {
this.directives.push(dir);
this.directiveNames[dir.name] = 1;
@@ -251,28 +261,33 @@ export class QWeb {
*/
addTemplate(
name: string,
template: RawTemplate,
xmlString: string,
allowDuplicates: boolean = false
) {
if (name in this.processedTemplates) {
if (allowDuplicates) {
return;
} else {
throw new Error(`Template ${name} already defined`);
}
if (name in this.templates && allowDuplicates) {
return;
}
const parser = new DOMParser();
const doc = parser.parseFromString(template, "text/xml");
const doc = parseXML(xmlString);
if (!doc.firstChild) {
throw new Error("Invalid template (should not be empty)");
}
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
let elem = doc.firstChild as Element;
this._processTemplate(elem);
this._addTemplate(name, <Element>doc.firstChild);
}
this.processedTemplates[name] = elem;
_addTemplate(name: string, elem: Element) {
if (name in this.templates) {
throw new Error(`Template ${name} already defined`);
}
this._processTemplate(elem);
const template = {
elem,
fn: (context, extra) => {
const compiledFunction = this._compile(name, elem);
template.fn = compiledFunction;
return compiledFunction.call(this, context, extra);
}
};
this.templates[name] = template;
}
_processTemplate(elem: Element) {
@@ -322,19 +337,14 @@ export class QWeb {
* the name given by the t-name attribute.
*/
loadTemplates(xmlstr: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlstr, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
const doc = parseXML(xmlstr);
const templates = doc.getElementsByTagName("templates")[0];
if (!templates) {
return;
}
for (let elem of <any>templates.children) {
const name = elem.getAttribute("t-name");
this._processTemplate(elem);
this.processedTemplates[name] = elem;
this._addTemplate(name, elem);
}
}
/**
@@ -343,22 +353,17 @@ export class QWeb {
* @param {string} name the template should already have been added
*/
render(name: string, context: EvalContext = {}, extra: any = null): VNode {
if (!(name in this.processedTemplates)) {
const template = this.templates[name];
if (!template) {
throw new Error(`Template ${name} does not exist`);
}
const template = this.templates[name] || this._compile(name);
return template.call(this, context, extra);
return template.fn.call(this, context, extra);
}
_compile(name: string): CompiledTemplate<VNode> {
if (name in this.templates) {
return this.templates[name];
}
const mainNode = this.processedTemplates[name];
const isDebug = (<Element>mainNode).attributes.hasOwnProperty("t-debug");
_compile(name: string, elem: Element): CompiledTemplate {
const isDebug = elem.attributes.hasOwnProperty("t-debug");
const ctx = new Context(name);
this._compileNode(mainNode, ctx);
this._compileNode(elem, ctx);
if (ctx.shouldProtectContext) {
ctx.code.unshift(" context = Object.create(context);");
@@ -379,7 +384,7 @@ export class QWeb {
"context",
"extra",
ctx.code.join("\n")
) as CompiledTemplate<VNode>;
) as CompiledTemplate;
} catch (e) {
throw new Error(
`Invalid generated code while compiling template '${ctx.templateName.replace(
@@ -390,12 +395,10 @@ export class QWeb {
}
if (isDebug) {
console.log(
`Template: ${
this.processedTemplates[name].outerHTML
}\nCompiled code:\n` + template.toString()
`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
template.toString()
);
}
this.templates[name] = template;
return template;
}
@@ -845,7 +848,7 @@ const callDirective: Directive = {
throw new Error("Invalid tag for t-call directive (should be 't')");
}
const subTemplate = node.getAttribute("t-call")!;
const nodeTemplate = qweb.processedTemplates[subTemplate];
const nodeTemplate = qweb.templates[subTemplate];
if (!nodeTemplate) {
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
}
@@ -882,7 +885,7 @@ const callDirective: Directive = {
.subContext("variables", Object.create(vars))
.subContext("definedVariables", Object.create(definedVariables));
qweb._compileNode(nodeTemplate, subCtx);
qweb._compileNode(nodeTemplate.elem, subCtx);
// close new scope
if (hasNewVariables) {
+3 -3
View File
@@ -1088,7 +1088,7 @@ describe("composition", () => {
</div>
`)
);
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
});
test("sub widgets between t-ifs", async () => {
@@ -1358,7 +1358,7 @@ describe("random stuff/miscellaneous", () => {
const widget = new Parent(env);
await widget.mount(fixture);
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
});
test("t-props should not be undefined (snapshotting)", async () => {
@@ -1374,7 +1374,7 @@ describe("random stuff/miscellaneous", () => {
const widget = new Parent(env);
await widget.mount(fixture);
expect(env.qweb.templates.parent.toString()).toMatchSnapshot();
expect(env.qweb.templates.parent.fn.toString()).toMatchSnapshot();
});
});
+4 -4
View File
@@ -29,7 +29,7 @@ function renderToDOM(
// we snapshot here the compiled code. This is useful to prevent unwanted code
// change.
expect(qweb.templates[template].toString()).toMatchSnapshot();
expect(qweb.templates[template].fn.toString()).toMatchSnapshot();
if (vnode.sel === undefined) {
return document.createTextNode(vnode.text!);
@@ -991,7 +991,7 @@ describe("loading templates", () => {
test("does not crash if string does not have templates", () => {
const data = "";
qweb.loadTemplates(data);
expect(qweb.processedTemplates).toEqual({});
expect(qweb.templates).toEqual({});
});
});
@@ -1104,7 +1104,7 @@ describe("debugging", () => {
`<div t-debug="1"><t t-if="true"><span t-debug="1">hey</span></t></div>`
);
qweb.render('test');
expect(qweb.templates.test.toString()).toMatchSnapshot();
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
expect(console.log).toHaveBeenCalledTimes(1);
console.log = consoleLog;
@@ -1122,7 +1122,7 @@ describe("debugging", () => {
</div>`
);
qweb.render('test');
expect(qweb.templates.test.toString()).toMatchSnapshot();
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
expect(console.log).toHaveBeenCalledWith(45);
console.log = consoleLog;