Skip to content

Commit 5fbcb50

Browse files
committed
[IMP] qweb: Make t-call parametric, enable lazy XML evaluation
The goal is to apply to OWL the same type of changes that were made in odoo/odoo#197296.
1 parent fb7d25b commit 5fbcb50

File tree

10 files changed

+2073
-62
lines changed

10 files changed

+2073
-62
lines changed

src/compiler/code_generator.ts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
} from "./parser";
3333
import { OwlError } from "../common/owl_error";
3434

35+
const zero = Symbol("zero");
36+
3537
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
3638
const whitespaceRE = /\s+/g;
3739

@@ -279,7 +281,7 @@ export class CodeGenerator {
279281
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
280282
ast: AST;
281283
staticDefs: { id: string; expr: string }[] = [];
282-
slotNames: Set<String> = new Set();
284+
slotNames: Set<String | Symbol> = new Set();
283285
helpers: Set<string> = new Set();
284286

285287
constructor(ast: AST, options: CodeGenOptions) {
@@ -791,12 +793,22 @@ export class CodeGenerator {
791793
return block!.varName;
792794
}
793795

796+
compileZero() {
797+
this.helpers.add("zero");
798+
const isMultiple = this.slotNames.has(zero);
799+
this.slotNames.add(zero);
800+
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
801+
if (isMultiple) {
802+
key = this.generateComponentKey(key);
803+
}
804+
return `ctx[zero]?.(node, ${key})`;
805+
}
806+
794807
compileTEsc(ast: ASTTEsc, ctx: Context): string {
795808
let { block, forceNewBlock } = ctx;
796809
let expr: string;
797810
if (ast.expr === "0") {
798-
this.helpers.add("zero");
799-
expr = `ctx[zero]`;
811+
expr = this.compileZero();
800812
} else {
801813
expr = compileExpr(ast.expr);
802814
if (ast.defaultValue) {
@@ -824,8 +836,7 @@ export class CodeGenerator {
824836
block = this.createBlock(block, "html", ctx);
825837
let blockStr;
826838
if (ast.expr === "0") {
827-
this.helpers.add("zero");
828-
blockStr = `ctx[zero]`;
839+
blockStr = this.compileZero();
829840
} else if (ast.body) {
830841
let bodyValue = null;
831842
bodyValue = BlockDescription.nextBlockId;
@@ -1044,8 +1055,12 @@ export class CodeGenerator {
10441055

10451056
compileTCall(ast: ASTTCall, ctx: Context): string {
10461057
let { block, forceNewBlock } = ctx;
1058+
1059+
const attrs: string[] = ast.attrs
1060+
? this.formatPropObject(ast.attrs, ast.attrsTranslationCtx, ctx.translationCtx)
1061+
: [];
10471062
let ctxVar = ctx.ctxVar || "ctx";
1048-
if (ast.context) {
1063+
if (!ast.attrs && ast.context) {
10491064
ctxVar = generateId("ctx");
10501065
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
10511066
}
@@ -1056,38 +1071,53 @@ export class CodeGenerator {
10561071
}
10571072
block = this.createBlock(block, "multi", ctx);
10581073
if (ast.body) {
1059-
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
1060-
this.addLine(`${ctxVar}[isBoundary] = 1;`);
1061-
this.helpers.add("isBoundary");
1062-
const subCtx = createContext(ctx, { ctxVar });
1063-
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
1064-
if (bl) {
1074+
if (ast.attrs) {
10651075
this.helpers.add("zero");
1066-
this.addLine(`${ctxVar}[zero] = ${bl};`);
1076+
const name = this.compileInNewTarget("callBody", ast.body, ctx);
1077+
const zeroStr = generateId("lazyBlock");
1078+
this.define(zeroStr, `${name}.bind(this, Object.create(ctx))`);
1079+
attrs.push(`[zero]: ${zeroStr}`);
1080+
} else {
1081+
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
1082+
this.addLine(`${ctxVar}[isBoundary] = 1;`);
1083+
this.helpers.add("isBoundary");
1084+
const subCtx = createContext(ctx, { ctxVar });
1085+
const bl = this.compileAST(ast.body, subCtx);
1086+
if (bl) {
1087+
this.helpers.add("zero");
1088+
this.addLine(`${ctxVar}[zero] = () => ${bl};`);
1089+
}
10671090
}
10681091
}
10691092

1093+
let ctxString = `{${attrs.join(", ")}}`;
1094+
if (ast.attrs && ast.context) {
1095+
const dynCtxVar = generateId("ctx");
1096+
this.addLine(`let ${dynCtxVar} = ${compileExpr(ast.context)};`);
1097+
ctxString = `Object.assign({}, ${dynCtxVar}${attrs.length ? ", " + ctxString : ""})`;
1098+
}
1099+
const ctxExpr = ast.attrs ? ctxString : ctxVar;
10701100
const key = this.generateComponentKey();
10711101
if (isDynamic) {
10721102
const templateVar = generateId("template");
10731103
if (!this.staticDefs.find((d) => d.id === "call")) {
10741104
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
10751105
}
10761106
this.define(templateVar, subTemplate);
1077-
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block!, {
1107+
this.insertBlock(`call(this, ${templateVar}, ${ctxExpr}, node, ${key})`, block!, {
10781108
...ctx,
10791109
forceNewBlock: !block,
10801110
});
10811111
} else {
10821112
const id = generateId(`callTemplate_`);
10831113
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
1084-
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block!, {
1114+
this.insertBlock(`${id}.call(this, ${ctxExpr}, node, ${key})`, block!, {
10851115
...ctx,
10861116
forceNewBlock: !block,
10871117
});
10881118
}
1089-
if (ast.body && !ctx.isLast) {
1090-
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
1119+
if (!ast.attrs && ast.body && !ctx.isLast) {
1120+
this.addLine(`${ctxExpr} = ${ctxExpr}.__proto__;`);
10911121
}
10921122
return block.varName;
10931123
}

src/compiler/parser.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ export interface ASTTKey extends BaseAST {
126126
export interface ASTTCall extends BaseAST {
127127
type: ASTType.TCall;
128128
name: string;
129-
body: AST[] | null;
129+
attrs: Attrs | null;
130+
attrsTranslationCtx: Attrs | null;
131+
body: AST | null;
130132
context: string | null;
131133
}
132134

@@ -642,9 +644,35 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
642644
node.removeAttribute("t-call");
643645
node.removeAttribute("t-call-context");
644646

647+
let attrs: Attrs | null = null;
648+
let attrsTranslationCtx: Attrs | null = null;
649+
for (let attributeName of node.getAttributeNames()) {
650+
const value = node.getAttribute(attributeName)!;
651+
if (attributeName.startsWith("t-translation-context-")) {
652+
const attrName = attributeName.slice(22);
653+
attrsTranslationCtx = attrsTranslationCtx || {};
654+
attrsTranslationCtx[attrName] = value;
655+
} else {
656+
attrs = attrs || {};
657+
attrs[attributeName] = value;
658+
}
659+
}
660+
645661
if (node.tagName !== "t") {
662+
if (attrs) {
663+
throw new OwlError(
664+
`Directive 't-call' with params can only be used on <t> nodes (used on a <${node.tagName}>)`
665+
);
666+
}
646667
const ast = parseNode(node, ctx);
647-
const tcall: AST = { type: ASTType.TCall, name: subTemplate, body: null, context };
668+
const tcall: AST = {
669+
type: ASTType.TCall,
670+
name: subTemplate,
671+
attrs: null,
672+
attrsTranslationCtx: null,
673+
body: null,
674+
context,
675+
};
648676
if (ast && ast.type === ASTType.DomNode) {
649677
ast.content = [tcall];
650678
return ast;
@@ -664,12 +692,13 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
664692
};
665693
}
666694
}
667-
const body = parseChildren(node, ctx);
668-
695+
const body = parseChildNodes(node, ctx);
669696
return {
670697
type: ASTType.TCall,
671698
name: subTemplate,
672-
body: body.length ? body : null,
699+
attrs,
700+
attrsTranslationCtx,
701+
body,
673702
context,
674703
};
675704
}

tests/compiler/__snapshots__/misc.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ exports[`misc global 1`] = `
118118
setContextValue(ctx, \\"foo\\", 'bbb');
119119
const b9 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
120120
const b6 = multi([b7, b8, b9]);
121-
ctx[zero] = b6;
121+
ctx[zero] = () => b6;
122122
const b5 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
123123
ctx = ctx.__proto__;
124124
c_block2[i1] = withKey(multi([b4, b5]), key1);
@@ -156,7 +156,7 @@ exports[`misc global 3`] = `
156156
157157
return function template(ctx, node, key = \\"\\") {
158158
let attr1 = 'agüero';
159-
const b2 = ctx[zero];
159+
const b2 = ctx[zero]?.(node, key);
160160
return block1([attr1], [b2]);
161161
}
162162
}"

0 commit comments

Comments
 (0)