From: 三咲智子 Kevin Deng Date: Sat, 25 Nov 2023 19:53:47 +0000 (+0800) Subject: feat: dynamic root nodes X-Git-Tag: v3.6.0-alpha.1~16^2~810 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12187fbc854724ab9b8b78846cb44bff15098755;p=thirdparty%2Fvuejs%2Fcore.git feat: dynamic root nodes --- diff --git a/README.md b/README.md index f6342487a4..ca5a5a0f58 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru - [ ] Remove DOM API in codegen - [ ] Fragment - [x] multiple root nodes + - [x] all dynamic children + - [ ] return `Node[]` for all dynamic children, instead of using `fragment` API - [ ] Built-in Components - [ ] Transition - [ ] TransitionGroup diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap index 3cf060766c..4fa319bd18 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap @@ -3,7 +3,7 @@ exports[`comile > bindings 1`] = ` "import { watchEffect } from 'vue'; import { template, children, createTextNode, insert, setText } from 'vue/vapor'; -const t0 = template(\`
count is .
\`); +const t0 = template('
count is .
'); export function render() { const n0 = t0(); const { @@ -27,7 +27,7 @@ export function render() { exports[`comile > directives > v-bind > simple expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setAttr } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -44,7 +44,7 @@ export function render() { exports[`comile > directives > v-html > no expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setHtml } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -61,7 +61,7 @@ export function render() { exports[`comile > directives > v-html > simple expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setHtml } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -78,7 +78,7 @@ export function render() { exports[`comile > directives > v-on > simple expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, on } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -95,7 +95,7 @@ export function render() { exports[`comile > directives > v-once > as root node 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setAttr } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -111,7 +111,7 @@ export function render() { exports[`comile > directives > v-once > basic 1`] = ` "import { template, children, createTextNode, insert, setText, setAttr } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -134,7 +134,7 @@ export function render() { exports[`comile > directives > v-text > no expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setText } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -151,7 +151,7 @@ export function render() { exports[`comile > directives > v-text > simple expression 1`] = ` "import { watchEffect } from 'vue'; import { template, children, setText } from 'vue/vapor'; -const t0 = template(\`
\`); +const t0 = template('
'); export function render() { const n0 = t0(); const { @@ -165,9 +165,30 @@ export function render() { " `; +exports[`comile > dynamic root 1`] = ` +"import { watchEffect } from 'vue'; +import { fragment, createTextNode, insert, setText } from 'vue/vapor'; +export function render() { + const t0 = fragment(); + const n0 = t0(); + const n1 = createTextNode(1); + insert(n1, n0, 0 /* InsertPosition.FIRST */); + const n2 = createTextNode(2); + insert(n2, n0); + watchEffect(() => { + setText(n1, undefined, 1); + }); + watchEffect(() => { + setText(n2, undefined, 2); + }); + return n0; +} +" +`; + exports[`comile > fragment 1`] = ` "import { template } from 'vue/vapor'; -const t0 = template(\`

\`); +const t0 = template('

'); export function render() { const n0 = t0(); return n0; @@ -178,7 +199,7 @@ export function render() { exports[`comile > static + dynamic root 1`] = ` "import { watchEffect } from 'vue'; import { template, createTextNode, insert, setText } from 'vue/vapor'; -const t0 = template(\`2\`); +const t0 = template('2'); export function render() { const n0 = t0(); const n1 = createTextNode(1); @@ -198,7 +219,7 @@ export function render() { exports[`comile > static template 1`] = ` "import { template } from 'vue/vapor'; -const t0 = template(\`

hello

\`); +const t0 = template('

hello

'); export function render() { const n0 = t0(); return n0; diff --git a/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap index ea13872b7a..0c20d725a3 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap @@ -4,7 +4,7 @@ exports[`fixtures 1`] = ` "import { defineComponent as _defineComponent } from 'vue' import { watchEffect } from 'vue' import { template, children, createTextNode, insert, setText, on, setHtml } from 'vue/vapor' -const t0 = template(\`

Counter

Count:

Double:

once:

{{ count }}

\`) +const t0 = template(\\"

Counter

Count:

Double:

once:

{{ count }}

\\") import { ref, computed } from 'vue' const html = 'HTML' diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts index 71773b35e5..35aa87dab0 100644 --- a/packages/compiler-vapor/__tests__/compile.test.ts +++ b/packages/compiler-vapor/__tests__/compile.test.ts @@ -28,6 +28,11 @@ describe('comile', () => { expect(code).matchSnapshot() }) + test('dynamic root', async () => { + const code = await compile(`{{ 1 }}{{ 2 }}`) + expect(code).matchSnapshot() + }) + test('static + dynamic root', async () => { const code = await compile(`{{ 1 }}2{{ 3 }}`) expect(code).matchSnapshot() diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index baac961ab3..f1d20ab0b3 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -18,14 +18,19 @@ export function generate( let preamble = '' const { helpers, vaporHelpers } = ir - if (ir.template.length) { - preamble += ir.template - .map( - (template, i) => `const t${i} = template(\`${template.template}\`)\n`, - ) - .join('') - vaporHelpers.add('template') - } + + ir.template.forEach((template, i) => { + if (template.type === IRNodeTypes.TEMPLATE_FACTORY) { + preamble += `const t${i} = template(${JSON.stringify( + template.template, + )})\n` + vaporHelpers.add('template') + } else { + // fragment + code += `const t0 = fragment()\n` + vaporHelpers.add('fragment') + } + }) { code += `const n${ir.children.id} = t0()\n` @@ -50,6 +55,7 @@ export function generate( code += scope } // TODO multiple-template + // TODO return statement in IR code += `return n${ir.children.id}\n` } diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index ef426e0fa7..d69e522b42 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -2,7 +2,9 @@ import type { SourceLocation } from '@vue/compiler-dom' export const enum IRNodeTypes { ROOT, - TEMPLATE_GENERATOR, + TEMPLATE_FACTORY, + FRAGMENT_FACTORY, + SET_PROP, SET_TEXT, SET_EVENT, @@ -19,7 +21,7 @@ export interface IRNode { export interface RootIRNode extends IRNode { type: IRNodeTypes.ROOT - template: Array + template: Array children: DynamicChild // TODO multi-expression effect effect: Record @@ -28,11 +30,15 @@ export interface RootIRNode extends IRNode { vaporHelpers: Set } -export interface TemplateGeneratorIRNode extends IRNode { - type: IRNodeTypes.TEMPLATE_GENERATOR +export interface TemplateFactoryIRNode extends IRNode { + type: IRNodeTypes.TEMPLATE_FACTORY template: string } +export interface FragmentFactoryIRNode extends IRNode { + type: IRNodeTypes.FRAGMENT_FACTORY +} + export interface SetPropIRNode extends IRNode { type: IRNodeTypes.SET_PROP element: number diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 965b7221e5..00e9de330b 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -73,11 +73,15 @@ function createRootContext( registerTemplate() { if (!ctx.template) return -1 - const idx = ir.template.findIndex((t) => t.template === ctx.template) + const idx = ir.template.findIndex( + (t) => + t.type === IRNodeTypes.TEMPLATE_FACTORY && + t.template === ctx.template, + ) if (idx !== -1) return idx ir.template.push({ - type: IRNodeTypes.TEMPLATE_GENERATOR, + type: IRNodeTypes.TEMPLATE_FACTORY, template: ctx.template, loc: node.loc, }) @@ -153,6 +157,12 @@ export function transform( ghost: false, children: ctx.children, } + if (ir.template.length === 0) { + ir.template.push({ + type: IRNodeTypes.FRAGMENT_FACTORY, + loc: root.loc, + }) + } return ir } diff --git a/packages/runtime-vapor/src/template.ts b/packages/runtime-vapor/src/template.ts index 1f02b32c34..9f0ae6c49e 100644 --- a/packages/runtime-vapor/src/template.ts +++ b/packages/runtime-vapor/src/template.ts @@ -18,3 +18,7 @@ export const template = (str: string): (() => Node) => { } } } + +export function fragment(): () => DocumentFragment { + return () => document.createDocumentFragment() +} diff --git a/playground/src/all-dynamic.vue b/playground/src/all-dynamic.vue new file mode 100644 index 0000000000..cf72b315e4 --- /dev/null +++ b/playground/src/all-dynamic.vue @@ -0,0 +1 @@ +