From da5fa6d45e18527ff08b5c6b9c5e3e9ea1cc9f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 14 Mar 2022 12:15:59 +0100 Subject: [PATCH] poc: xml function returns function --- src/app/template_set.ts | 15 +- src/component/component.ts | 4 +- src/utils.ts | 38 ++++- .../__snapshots__/basics.test.ts.snap | 6 +- .../__snapshots__/slots.test.ts.snap | 134 +++++++++--------- .../__snapshots__/t_call.test.ts.snap | 30 ++-- tests/components/hooks.test.ts | 6 +- tests/helpers.ts | 26 ++-- 8 files changed, 151 insertions(+), 108 deletions(-) diff --git a/src/app/template_set.ts b/src/app/template_set.ts index c043f19a..0ad28ecf 100644 --- a/src/app/template_set.ts +++ b/src/app/template_set.ts @@ -1,10 +1,9 @@ import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom"; -import { compile, Template } from "../compiler"; +import { compile, Template, TemplateFunction } from "../compiler"; import { markRaw } from "../reactivity"; import { Portal } from "../portal"; import { component, getCurrent } from "../component/component_node"; import { helpers } from "./template_helpers"; -import { globalTemplates } from "../utils"; const bdom = { text, createBlock, list, multi, html, toggler, component, comment }; @@ -48,7 +47,7 @@ function makeHelpers(getTemplate: (name: string) => Template): any { markRaw, getTemplate, call: (owner: any, subTemplate: string, ctx: any, parent: any, key: any) => { - const template = getTemplate(subTemplate); + const template = typeof subTemplate === "string" ? getTemplate(subTemplate) : subTemplate; return toggler(subTemplate, template.call(owner, ctx, parent, key)); }, }); @@ -63,11 +62,12 @@ export interface TemplateSetConfig { export class TemplateSet { dev: boolean; - rawTemplates: typeof globalTemplates = Object.create(globalTemplates); + rawTemplates: any = {}; //typeof globalTemplates = Object.create(globalTemplates); templates: { [name: string]: Template } = {}; translateFn?: (s: string) => string; translatableAttributes?: string[]; helpers: any; + bdom: any = bdom; constructor(config: TemplateSetConfig = {}) { this.dev = config.dev || false; @@ -102,8 +102,11 @@ export class TemplateSet { } } - getTemplate(name: string): Template { + getTemplate(name: string | any): Template { if (!(name in this.templates)) { + if (typeof name === "function") { + return name(this); + } const rawTemplate = this.rawTemplates[name]; if (rawTemplate === undefined) { let extraInfo = ""; @@ -126,7 +129,7 @@ export class TemplateSet { return this.templates[name]; } - _compileTemplate(name: string, template: string | Element) { + _compileTemplate(name: string, template: string | Element): TemplateFunction { return compile(template, { name, dev: this.dev, diff --git a/src/component/component.ts b/src/component/component.ts index 220fb094..820c26c4 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -1,4 +1,6 @@ import type { ComponentNode } from "./component_node"; +import type { TemplateFunction } from "../compiler/index"; +import type { App } from ".."; // ----------------------------------------------------------------------------- // Component Class @@ -20,7 +22,7 @@ export type ComponentConstructor

= (new ( StaticComponentProperties; export class Component { - static template: string = ""; + static template: string | ((app: App) => TemplateFunction) = ""; static props?: any; static defaultProps?: any; diff --git a/src/utils.ts b/src/utils.ts index 4b3cda78..89e1ef9b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -75,13 +75,41 @@ export function markup(value: any) { // ----------------------------------------------------------------------------- // xml tag helper // ----------------------------------------------------------------------------- -export const globalTemplates: { [key: string]: string | Element } = {}; -export function xml(...args: Parameters) { - const name = `__template__${xml.nextId++}`; +export function xml(...args: Parameters): any { + const subTemplates: any = {}; + let hasSubTemplates = false; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (typeof arg === "function") { + hasSubTemplates = true; + const id = `__template__${xml.nextId++}`; + subTemplates[id] = arg; + args[i] = `{{__owl__.subTemplates.${id}(__owl__.app)}}`; + } + } const value = String.raw(...args); - globalTemplates[name] = value; - return name; + + let cache = new WeakMap(); + + return (app: any) => { + debugger; + let renderFn = cache.get(app); + if (!renderFn) { + let templateFn = app._compileTemplate("", value); + renderFn = templateFn(app.bdom, app.helpers); + if (hasSubTemplates) { + let origRenderFn = renderFn; + renderFn = (context: any, vnode: any, key?: any) => { + debugger; + context.__owl__.subTemplates = subTemplates; + return origRenderFn(context, vnode, key); + }; + } + cache.set(app, renderFn); + } + return renderFn; + }; } xml.nextId = 1; diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap index 1b03db79..d891caae 100644 --- a/tests/components/__snapshots__/basics.test.ts.snap +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -169,11 +169,11 @@ exports[`basics can inject values in tagged templates 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { call } = helpers; return function template(ctx, node, key = \\"\\") { - return callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app)); + return call(this, template1, ctx, node, key + \`__1\`); } }" `; diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap index e98f2726..e82c5e38 100644 --- a/tests/components/__snapshots__/slots.test.ts.snap +++ b/tests/components/__snapshots__/slots.test.ts.snap @@ -249,11 +249,11 @@ exports[`slots can use t-call in default-content of t-slot 2`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callSlot, getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { callSlot, call } = helpers; function defaultContent1(ctx, node, key = \\"\\") { - return callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__3(ctx['__owl__'].app)); + return call(this, template1, ctx, node, key + \`__1\`); } return function template(ctx, node, key = \\"\\") { @@ -772,12 +772,12 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { capture, getTemplate, markRaw } = helpers; - const callTemplate_1 = getTemplate(\`__template__1002\`); + let { capture, call, markRaw } = helpers; function slot1(ctx, node, key = \\"\\") { const b2 = text(\`[A]\`); - const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__5(ctx['__owl__'].app)); + const b3 = call(this, template1, ctx, node, key + \`__1\`); return multi([b2, b3]); } @@ -790,41 +790,6 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil exports[`slots mix of slots, t-call, t-call with body, and giving own props child 4`] = ` "function anonymous(bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { isBoundary, withDefault, setContextValue, getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__1001\`); - - return function template(ctx, node, key = \\"\\") { - ctx = Object.create(ctx); - ctx[isBoundary] = 1 - const b2 = text(\`[sub1] \`); - setContextValue(ctx, \\"dummy\\", ctx['validate']); - ctx = Object.create(ctx); - ctx[isBoundary] = 1; - setContextValue(ctx, \\"v\\", ctx['props'].number); - const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`); - return multi([b2, b3]); - } -}" -`; - -exports[`slots mix of slots, t-call, t-call with body, and giving own props child 5`] = ` -"function anonymous(bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - - return function template(ctx, node, key = \\"\\") { - const b2 = text(\`[sub2\`); - const b3 = text(ctx['v']); - const b4 = text(\`]\`); - return multi([b2, b3, b4]); - } -}" -`; - -exports[`slots mix of slots, t-call, t-call with body, and giving own props child 6`] = ` -"function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; @@ -836,7 +801,7 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil }" `; -exports[`slots mix of slots, t-call, t-call with body, and giving own props child 7`] = ` +exports[`slots mix of slots, t-call, t-call with body, and giving own props child 5`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; @@ -850,6 +815,41 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil }" `; +exports[`slots mix of slots, t-call, t-call with body, and giving own props child 6`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + let { isBoundary, withDefault, setContextValue, call } = helpers; + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + ctx[isBoundary] = 1 + const b2 = text(\`[sub1] \`); + setContextValue(ctx, \\"dummy\\", ctx['validate']); + ctx = Object.create(ctx); + ctx[isBoundary] = 1; + setContextValue(ctx, \\"v\\", ctx['props'].number); + const template1 = (ctx['__owl__'].subTemplates.__template__4(ctx['__owl__'].app)); + const b3 = call(this, template1, ctx, node, key + \`__1\`); + return multi([b2, b3]); + } +}" +`; + +exports[`slots mix of slots, t-call, t-call with body, and giving own props child 7`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + const b2 = text(\`[sub2\`); + const b3 = text(ctx['v']); + const b4 = text(\`]\`); + return multi([b2, b3, b4]); + } +}" +`; + exports[`slots multiple roots are allowed in a default slot 1`] = ` "function anonymous(bdom, helpers ) { @@ -1486,13 +1486,13 @@ exports[`slots slot and (inline) t-call 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { capture, getTemplate, markRaw } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { capture, call, markRaw } = helpers; let block1 = createBlock(\`

\`); function slot1(ctx, node, key = \\"\\") { - return callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__2(ctx['__owl__'].app)); + return call(this, template1, ctx, node, key + \`__1\`); } return function template(ctx, node, key = \\"\\") { @@ -1505,19 +1505,6 @@ exports[`slots slot and (inline) t-call 1`] = ` exports[`slots slot and (inline) t-call 2`] = ` "function anonymous(bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - - let block1 = createBlock(\`

sokka

\`); - - return function template(ctx, node, key = \\"\\") { - return block1(); - } -}" -`; - -exports[`slots slot and (inline) t-call 3`] = ` -"function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { callSlot } = helpers; @@ -1531,17 +1518,30 @@ exports[`slots slot and (inline) t-call 3`] = ` }" `; +exports[`slots slot and (inline) t-call 3`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + let block1 = createBlock(\`

sokka

\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`slots slot and t-call 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { capture, getTemplate, markRaw } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { capture, call, markRaw } = helpers; let block1 = createBlock(\`
\`); function slot1(ctx, node, key = \\"\\") { - return callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app)); + return call(this, template1, ctx, node, key + \`__1\`); } return function template(ctx, node, key = \\"\\") { @@ -1556,11 +1556,13 @@ exports[`slots slot and t-call 2`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + let { callSlot } = helpers; - let block1 = createBlock(\`

sokka

\`); + let block1 = createBlock(\`\`); return function template(ctx, node, key = \\"\\") { - return block1(); + const b2 = callSlot(ctx, node, key, 'default', false, null); + return block1([], [b2]); } }" `; @@ -1569,13 +1571,11 @@ exports[`slots slot and t-call 3`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callSlot } = helpers; - let block1 = createBlock(\`\`); + let block1 = createBlock(\`

sokka

\`); return function template(ctx, node, key = \\"\\") { - const b2 = callSlot(ctx, node, key, 'default', false, null); - return block1([], [b2]); + return block1(); } }" `; diff --git a/tests/components/__snapshots__/t_call.test.ts.snap b/tests/components/__snapshots__/t_call.test.ts.snap index 9d94e182..57eb6999 100644 --- a/tests/components/__snapshots__/t_call.test.ts.snap +++ b/tests/components/__snapshots__/t_call.test.ts.snap @@ -116,13 +116,13 @@ exports[`t-call handlers are properly bound through a t-call 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { call } = helpers; let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app)); + const b2 = call(this, template1, ctx, node, key + \`__1\`); let txt1 = ctx['counter']; return block1([txt1], [b2]); } @@ -147,13 +147,13 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`] "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { call } = helpers; let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__6(ctx['__owl__'].app)); + const b2 = call(this, template1, ctx, node, key + \`__1\`); return block1([], [b2]); } }" @@ -178,13 +178,13 @@ exports[`t-call parent is set within t-call 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { call } = helpers; let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__3(ctx['__owl__'].app)); + const b2 = call(this, template1, ctx, node, key + \`__1\`); return block1([], [b2]); } }" @@ -218,11 +218,11 @@ exports[`t-call parent is set within t-call with no parentNode 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { getTemplate } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { call } = helpers; return function template(ctx, node, key = \\"\\") { - return callTemplate_1.call(this, ctx, node, key + \`__1\`); + const template1 = (ctx['__owl__'].subTemplates.__template__5(ctx['__owl__'].app)); + return call(this, template1, ctx, node, key + \`__1\`); } }" `; @@ -353,8 +353,7 @@ exports[`t-call t-call in t-foreach and children component 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { prepareList, getTemplate, withKey } = helpers; - const callTemplate_1 = getTemplate(\`__template__999\`); + let { prepareList, call, withKey } = helpers; let block1 = createBlock(\`
\`); @@ -368,7 +367,8 @@ exports[`t-call t-call in t-foreach and children component 1`] = ` ctx[\`val_index\`] = i1; ctx[\`val_value\`] = k_block2[i1]; const key1 = ctx['val']; - c_block2[i1] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`), key1); + const template1 = (ctx['__owl__'].subTemplates.__template__4(ctx['__owl__'].app)); + c_block2[i1] = withKey(call(this, template1, ctx, node, key + \`__1__\${key1}\`), key1); } const b2 = list(c_block2); return block1([], [b2]); diff --git a/tests/components/hooks.test.ts b/tests/components/hooks.test.ts index 56a0ad58..0c222f16 100644 --- a/tests/components/hooks.test.ts +++ b/tests/components/hooks.test.ts @@ -444,6 +444,7 @@ describe("hooks", () => { let cleanupRun = 0; let steps = []; class MyComponent extends Component { + static template = xml`
`; state = useState({ value: 0, }); @@ -455,7 +456,6 @@ describe("hooks", () => { }); } } - MyComponent.template = xml`
`; const component = await mount(MyComponent, fixture); @@ -512,6 +512,7 @@ describe("hooks", () => { test("dependencies prevent effects from rerunning when unchanged", async () => { let steps = []; class MyComponent extends Component { + static template = xml`
`; state = useState({ a: 0, b: 0, @@ -540,7 +541,6 @@ describe("hooks", () => { ); } } - MyComponent.template = xml`
`; steps.push("before mount"); const component = await mount(MyComponent, fixture); steps.push("after mount"); @@ -597,6 +597,7 @@ describe("hooks", () => { test("effect with empty dependency list never reruns", async () => { let steps = []; class MyComponent extends Component { + static template = xml`
`; state = useState({ value: 0, }); @@ -610,7 +611,6 @@ describe("hooks", () => { ); } } - MyComponent.template = xml`
`; const component = await mount(MyComponent, fixture); diff --git a/tests/helpers.ts b/tests/helpers.ts index 44574c00..f21f31bb 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -13,13 +13,13 @@ import { onWillUpdateProps, status, useComponent, - xml, } from "../src"; import { helpers } from "../src/app/template_helpers"; import { TemplateSet } from "../src/app/template_set"; import { BDom } from "../src/blockdom"; import { compile } from "../src/compiler"; -import { globalTemplates } from "../src/utils"; +import { Portal } from "../src/portal"; +// import { globalTemplates } from "../src/utils"; const mount = blockDom.mount; @@ -39,9 +39,6 @@ export function makeTestFixture() { return fixture; } -beforeEach(() => { - xml.nextId = 999; -}); export async function nextTick(): Promise { await new Promise((resolve) => setTimeout(resolve)); await new Promise((resolve) => requestAnimationFrame(resolve)); @@ -117,16 +114,29 @@ export function snapshotEverything() { // this function has already been called return; } - const globalTemplateNames = new Set(Object.keys(globalTemplates)); shouldSnapshot = true; beforeEach(() => { snapshottedTemplates.clear(); }); + const getTemplate = TemplateSet.prototype.getTemplate; + TemplateSet.prototype.getTemplate = function (this: any, name: string) { + if (name === Portal.template) { + this.skipSnapshot = true; + } + const result = getTemplate.call(this, name); + this.skipSnapshot = false; + return result; + }; + const originalCompileTemplate = TemplateSet.prototype._compileTemplate; - TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) { + TemplateSet.prototype._compileTemplate = function ( + this: any, + name: string, + template: string | Element + ) { const fn = originalCompileTemplate.call(this, "", template); - if (!globalTemplateNames.has(name)) { + if (!this.skipSnapshot) { expect(fn.toString()).toMatchSnapshot(); } return fn;