mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] portal: portal as a Directive
Before this commit, portal was a Component, now is a directive. This commit also clean some unused code, and fix an issue on the clean optimization when a portal is found in a condition or a loop.
This commit is contained in:
committed by
Aaron Bohy
parent
c221721d7f
commit
90167c5436
@@ -19,6 +19,7 @@ import {
|
||||
ASTTSet,
|
||||
ASTTranslation,
|
||||
ASTType,
|
||||
ASTTPortal,
|
||||
} from "./parser";
|
||||
|
||||
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
|
||||
@@ -64,13 +65,15 @@ class BlockDescription {
|
||||
type: BlockType;
|
||||
parentVar: string = "";
|
||||
id: number;
|
||||
deepRemove: boolean;
|
||||
|
||||
constructor(target: CodeTarget, type: BlockType) {
|
||||
constructor(target: CodeTarget, type: BlockType, deepRemove: boolean = false) {
|
||||
this.id = BlockDescription.nextBlockId++;
|
||||
this.varName = "b" + this.id;
|
||||
this.blockName = "block" + this.id;
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
this.deepRemove = deepRemove;
|
||||
}
|
||||
|
||||
insertData(str: string, prefix: string = "d"): number {
|
||||
@@ -99,7 +102,7 @@ class BlockDescription {
|
||||
}
|
||||
return `${this.blockName}(${params})`;
|
||||
} else if (this.type === "list") {
|
||||
return `list(c_block${this.id})`;
|
||||
return `list(c_block${this.id}${this.deepRemove ? ", true" : ""})`;
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
@@ -250,6 +253,8 @@ export class CodeGenerator {
|
||||
mainCode.push(`const ${id} = getTemplate(${template});`);
|
||||
}
|
||||
|
||||
const deepRemove = "deepRemove" in this.ast ? this.ast.deepRemove : false;
|
||||
|
||||
// define all blocks
|
||||
if (this.blocks.length) {
|
||||
mainCode.push(``);
|
||||
@@ -259,9 +264,15 @@ export class CodeGenerator {
|
||||
if (block.dynamicTagName) {
|
||||
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
||||
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
||||
mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
|
||||
mainCode.push(
|
||||
`let ${block.blockName} = tag => createBlock(\`${xmlString}\`${
|
||||
deepRemove ? ", true" : ""
|
||||
});`
|
||||
);
|
||||
} else {
|
||||
mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
|
||||
mainCode.push(
|
||||
`let ${block.blockName} = createBlock(\`${xmlString}\`${deepRemove ? ", true" : ""});`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,10 +328,11 @@ export class CodeGenerator {
|
||||
createBlock(
|
||||
parentBlock: BlockDescription | null,
|
||||
type: BlockType,
|
||||
ctx: Context
|
||||
ctx: Context,
|
||||
deepRemove: boolean = false
|
||||
): BlockDescription {
|
||||
const hasRoot = this.target.hasRoot;
|
||||
const block = new BlockDescription(this.target, type);
|
||||
const block = new BlockDescription(this.target, type, deepRemove);
|
||||
if (!hasRoot && !ctx.preventRoot) {
|
||||
this.target.hasRoot = true;
|
||||
block.isRoot = true;
|
||||
@@ -445,6 +457,8 @@ export class CodeGenerator {
|
||||
case ASTType.TTranslation:
|
||||
this.compileTTranslation(ast, ctx);
|
||||
break;
|
||||
case ASTType.TPortal:
|
||||
this.compileTPortal(ast, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +718,7 @@ export class CodeGenerator {
|
||||
if (ast.body) {
|
||||
const nextId = BlockDescription.nextBlockId;
|
||||
const subCtx: Context = createContext(ctx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||
this.helpers.add("withDefault");
|
||||
expr = `withDefault(${expr}, b${nextId})`;
|
||||
}
|
||||
@@ -765,7 +779,7 @@ export class CodeGenerator {
|
||||
|
||||
// note: this part is duplicated from end of compilemulti:
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
||||
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,7 +788,7 @@ export class CodeGenerator {
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "list", ctx);
|
||||
block = this.createBlock(block, "list", ctx, ast.deepRemove);
|
||||
this.target.loopLevel++;
|
||||
const loopVar = `i${this.target.loopLevel}`;
|
||||
this.addLine(`ctx = Object.create(ctx);`);
|
||||
@@ -909,7 +923,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
const args = block!.children.map((c) => c.varName).join(", ");
|
||||
this.insertBlock(`multi([${args}])`, block!, ctx)!;
|
||||
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,7 +935,7 @@ export class CodeGenerator {
|
||||
this.helpers.add("isBoundary");
|
||||
const nextId = BlockDescription.nextBlockId;
|
||||
const subCtx: Context = createContext(ctx, { preventRoot: true });
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
|
||||
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx);
|
||||
if (nextId !== BlockDescription.nextBlockId) {
|
||||
this.helpers.add("zero");
|
||||
this.addLine(`ctx[zero] = b${nextId};`);
|
||||
@@ -976,7 +990,7 @@ export class CodeGenerator {
|
||||
const expr = ast.value ? compileExpr(ast.value || "") : "null";
|
||||
if (ast.body) {
|
||||
this.helpers.add("LazyValue");
|
||||
const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
|
||||
const bodyAst: AST = { type: ASTType.Multi, content: ast.body, deepRemove: false };
|
||||
const name = this.compileInNewTarget("value", bodyAst, ctx);
|
||||
let value = `new LazyValue(${name}, ctx, node)`;
|
||||
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
|
||||
@@ -1162,4 +1176,15 @@ export class CodeGenerator {
|
||||
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
|
||||
}
|
||||
}
|
||||
compileTPortal(ast: ASTTPortal, ctx: Context) {
|
||||
this.helpers.add("callPortal");
|
||||
let { block } = ctx;
|
||||
const name = this.compileInNewTarget("portalContent", ast.content, ctx);
|
||||
const blockString = `callPortal(ctx, node, key, ${ast.target}, ${name})`;
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
|
||||
}
|
||||
}
|
||||
|
||||
+77
-43
@@ -20,6 +20,7 @@ export const enum ASTType {
|
||||
TSlot,
|
||||
TCallBlock,
|
||||
TTranslation,
|
||||
TPortal,
|
||||
}
|
||||
|
||||
export interface ASTText {
|
||||
@@ -55,6 +56,7 @@ export interface ASTDomNode {
|
||||
export interface ASTMulti {
|
||||
type: ASTType.Multi;
|
||||
content: AST[];
|
||||
deepRemove: boolean;
|
||||
}
|
||||
|
||||
export interface ASTTEsc {
|
||||
@@ -75,6 +77,7 @@ export interface ASTTif {
|
||||
content: AST;
|
||||
tElif: { condition: string; content: AST }[] | null;
|
||||
tElse: AST | null;
|
||||
deepRemove: boolean;
|
||||
}
|
||||
|
||||
export interface ASTTSet {
|
||||
@@ -92,8 +95,7 @@ export interface ASTTForEach {
|
||||
key: string | null;
|
||||
body: AST;
|
||||
memo: string;
|
||||
isOnlyChild: boolean;
|
||||
hasNoComponent: boolean;
|
||||
deepRemove: boolean;
|
||||
hasNoFirst: boolean;
|
||||
hasNoLast: boolean;
|
||||
hasNoIndex: boolean;
|
||||
@@ -149,6 +151,12 @@ export interface ASTTranslation {
|
||||
content: AST | null;
|
||||
}
|
||||
|
||||
export interface ASTTPortal {
|
||||
type: ASTType.TPortal;
|
||||
target: string;
|
||||
content: AST;
|
||||
}
|
||||
|
||||
export type AST =
|
||||
| ASTText
|
||||
| ASTComment
|
||||
@@ -166,7 +174,8 @@ export type AST =
|
||||
| ASTTCallBlock
|
||||
| ASTLog
|
||||
| ASTDebug
|
||||
| ASTTranslation;
|
||||
| ASTTranslation
|
||||
| ASTTPortal;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Parser
|
||||
@@ -195,6 +204,7 @@ function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
||||
parseTDebugLog(node, ctx) ||
|
||||
parseTForEach(node, ctx) ||
|
||||
parseTIf(node, ctx) ||
|
||||
parseTPortal(node, ctx) ||
|
||||
parseTCall(node, ctx) ||
|
||||
parseTCallBlock(node, ctx) ||
|
||||
parseTEscNode(node, ctx) ||
|
||||
@@ -351,9 +361,6 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
attrs[attr] = value;
|
||||
}
|
||||
}
|
||||
if (children.length === 1 && children[0].type === ASTType.TForEach) {
|
||||
children[0].isOnlyChild = true;
|
||||
}
|
||||
return {
|
||||
type: ASTType.DomNode,
|
||||
tag: tagName,
|
||||
@@ -477,9 +484,8 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
elem,
|
||||
body,
|
||||
memo,
|
||||
deepRemove: needDeepRemove(body),
|
||||
key,
|
||||
isOnlyChild: false,
|
||||
hasNoComponent: hasNoComponent(body),
|
||||
hasNoFirst,
|
||||
hasNoLast,
|
||||
hasNoIndex,
|
||||
@@ -488,54 +494,40 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if we are sure the ast does not contain any component
|
||||
* @returns true if we are sure that a deep remove (without optimisation) is needed, for exemple
|
||||
* if there is a portal.
|
||||
*/
|
||||
function hasNoComponent(ast: AST): boolean {
|
||||
function needDeepRemove(ast: AST): boolean {
|
||||
switch (ast.type) {
|
||||
case ASTType.Multi:
|
||||
case ASTType.TForEach:
|
||||
case ASTType.TIf:
|
||||
return ast.deepRemove;
|
||||
|
||||
case ASTType.TPortal:
|
||||
return true;
|
||||
|
||||
case ASTType.TComponent:
|
||||
case ASTType.TOut:
|
||||
case ASTType.TCall:
|
||||
case ASTType.TCallBlock:
|
||||
case ASTType.TSlot:
|
||||
return false;
|
||||
case ASTType.TSet:
|
||||
case ASTType.Text:
|
||||
case ASTType.Comment:
|
||||
case ASTType.TEsc:
|
||||
return true;
|
||||
return false;
|
||||
|
||||
case ASTType.TKey:
|
||||
return hasNoComponent(ast.content);
|
||||
return needDeepRemove(ast.content);
|
||||
case ASTType.TDebug:
|
||||
case ASTType.TLog:
|
||||
case ASTType.TTranslation:
|
||||
return ast.content ? hasNoComponent(ast.content) : true;
|
||||
case ASTType.TForEach:
|
||||
return ast.hasNoComponent;
|
||||
case ASTType.Multi:
|
||||
case ASTType.DomNode: {
|
||||
for (let elem of ast.content) {
|
||||
if (!hasNoComponent(elem)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ASTType.TIf: {
|
||||
if (!hasNoComponent(ast.content)) {
|
||||
return false;
|
||||
}
|
||||
if (ast.tElif) {
|
||||
for (let elem of ast.tElif) {
|
||||
if (!hasNoComponent(elem.content)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ast.tElse && !hasNoComponent(ast.tElse)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return ast.content ? needDeepRemove(ast.content) : false;
|
||||
case ASTType.TSet:
|
||||
return ast.body ? ast.body.some((ast) => needDeepRemove(ast)) : false;
|
||||
|
||||
case ASTType.DomNode:
|
||||
return ast.content.some((ast) => needDeepRemove(ast));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,12 +628,21 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null {
|
||||
nextElement.remove();
|
||||
}
|
||||
|
||||
let deepRemove = needDeepRemove(content);
|
||||
if (tElifs) {
|
||||
deepRemove = deepRemove || tElifs.some((ast) => needDeepRemove(ast));
|
||||
}
|
||||
if (tElse) {
|
||||
deepRemove = deepRemove || needDeepRemove(tElse);
|
||||
}
|
||||
|
||||
return {
|
||||
type: ASTType.TIf,
|
||||
condition,
|
||||
content,
|
||||
tElif: tElifs.length ? tElifs : null,
|
||||
tElse,
|
||||
deepRemove,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -805,6 +806,35 @@ function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Portal
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-portal")) {
|
||||
return null;
|
||||
}
|
||||
if (node.tagName !== "t") {
|
||||
throw new Error(
|
||||
`Directive 't-portal' can only be used on <t> nodes (used on a <${node.tagName}>)`
|
||||
);
|
||||
}
|
||||
const target = node.getAttribute("t-portal")!;
|
||||
node.removeAttribute("t-portal");
|
||||
const content = parseNode(node, ctx);
|
||||
if (!content) {
|
||||
return {
|
||||
type: ASTType.Text,
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: ASTType.TPortal,
|
||||
target,
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -839,7 +869,11 @@ function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
|
||||
case 1:
|
||||
return children[0];
|
||||
default:
|
||||
return { type: ASTType.Multi, content: children };
|
||||
return {
|
||||
type: ASTType.Multi,
|
||||
content: children,
|
||||
deepRemove: children.some((ast) => needDeepRemove(ast)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user