]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: dynamic root nodes
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 25 Nov 2023 19:53:47 +0000 (03:53 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 25 Nov 2023 19:53:47 +0000 (03:53 +0800)
README.md
packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap
packages/compiler-vapor/__tests__/compile.test.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transform.ts
packages/runtime-vapor/src/template.ts
playground/src/all-dynamic.vue [new file with mode: 0644]

index f6342487a41d46bbcaf4f38eedb59d5274efb19d..ca5a5a0f58c867afd9d53bb8ce963d13e34558c2 100644 (file)
--- 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
index 3cf060766c94408e3a14f6452711be20b7168b55..4fa319bd189f7a0faf2bfd825dfb4a9043479ef7 100644 (file)
@@ -3,7 +3,7 @@
 exports[`comile > bindings 1`] = `
 "import { watchEffect } from 'vue';
 import { template, children, createTextNode, insert, setText } from 'vue/vapor';
-const t0 = template(\`<div>count is <!>.</div>\`);
+const t0 = template('<div>count is <!>.</div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div> <span></span></div>\`);
+const t0 = template('<div> <span></span></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<div></div>\`);
+const t0 = template('<div></div>');
 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(\`<p></p><span></span><div></div>\`);
+const t0 = template('<p></p><span></span><div></div>');
 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(\`<div><p>hello</p><input><span></span></div>\`);
+const t0 = template('<div><p>hello</p><input><span></span></div>');
 export function render() {
   const n0 = t0();
   return n0;
index ea13872b7a73c0ee79d535a65fe5c5a33fb02da2..0c20d725a39bc3c69050dd28f4dabcb01ff63b06 100644 (file)
@@ -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(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button><div></div><input type=\\"text\\"><p>once: </p><p>{{ count }}</p>\`)
+const t0 = template(\\"<h1 id=\\\\\\"title\\\\\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button><div></div><input type=\\\\\\"text\\\\\\"><p>once: </p><p>{{ count }}</p>\\")
 import { ref, computed } from 'vue'
 
 const html = '<b>HTML</b>'
index 71773b35e55127810cfc9c5686efb114db8b4436..35aa87dab019157e0ef1e16d5f5ac29d596683f3 100644 (file)
@@ -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()
index baac961ab3d512712f31fdda1a179ba3fbb3892a..f1d20ab0b37e538205c52b604515cccce0fb6537 100644 (file)
@@ -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`
   }
 
index ef426e0fa725dc6043e1fcc5b37ff040375f7e83..d69e522b425e96baa91c395188f279ccfb44c60f 100644 (file)
@@ -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<TemplateGeneratorIRNode>
+  template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
   children: DynamicChild
   // TODO multi-expression effect
   effect: Record<string /* expr */, OperationNode[]>
@@ -28,11 +30,15 @@ export interface RootIRNode extends IRNode {
   vaporHelpers: Set<string>
 }
 
-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
index 965b7221e5a987dfdb764ee5fba032c79e3ca4c0..00e9de330b81d4729d75dca9d9151d2808932bf2 100644 (file)
@@ -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
 }
index 1f02b32c34e2578e35e6cd71819890ae9fcbdc7c..9f0ae6c49ebf97f6c61545ad2145ebb9b5668a5d 100644 (file)
@@ -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 (file)
index 0000000..cf72b31
--- /dev/null
@@ -0,0 +1 @@
+<template>{{ '1' }}{{ '2' }}</template>