]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: fragment
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 25 Nov 2023 18:13:59 +0000 (02:13 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 25 Nov 2023 18:13:59 +0000 (02:13 +0800)
12 files changed:
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/__tests__/fixtures/counter.vue
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transform.ts
packages/runtime-vapor/src/render.ts
packages/runtime-vapor/src/template.ts
playground/src/App-fragment.vue [new file with mode: 0644]
playground/src/main.ts

index 018a33d17c594a720a2eeca45b1cf44ca06373b8..f6342487a41d46bbcaf4f38eedb59d5274efb19d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru
     - [ ] compiler
 - [ ] Remove DOM API in codegen
 - [ ] Fragment
+  - [x] multiple root nodes
 - [ ] Built-in Components
   - [ ] Transition
   - [ ] TransitionGroup
index 43bd84de08f2b824a2911596556894e047eb9da9..3ec2aa6ab6350ca3f06a8f2598fe0361f8f04a79 100644 (file)
@@ -5,86 +5,106 @@ exports[`comile > bindings 1`] = `
 import { template, children, insert, setText } from 'vue/vapor';
 const t0 = template(\`<div>count is <!>.</div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
   const {
-    1: [n2],
-  } = children(n0);
-  const n1 = document.createTextNode(count.value);
-  insert(n1, n0, n2);
+    0: [
+      n1,
+      {
+        1: [n3],
+      },
+    ],
+  } = children(root);
+  const n2 = document.createTextNode(count.value);
+  insert(n2, n1, n3);
   watchEffect(() => {
-    setText(n1, undefined, count.value);
+    setText(n2, undefined, count.value);
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-bind > simple expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setAttr } from 'vue/vapor';
+import { template, children, setAttr } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setAttr(n0, 'id', undefined, id.value);
+    setAttr(n1, 'id', undefined, id.value);
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-html > no expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setHtml } from 'vue/vapor';
+import { template, children, setHtml } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setHtml(n0, undefined, '');
+    setHtml(n1, undefined, '');
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-html > simple expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setHtml } from 'vue/vapor';
+import { template, children, setHtml } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setHtml(n0, undefined, code.value);
+    setHtml(n1, undefined, code.value);
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-on > simple expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, on } from 'vue/vapor';
+import { template, children, on } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    on(n0, 'click', handleClick);
+    on(n1, 'click', handleClick);
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-once > as root node 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setAttr } from 'vue/vapor';
+import { template, children, setAttr } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setAttr(n0, 'id', undefined, foo);
+    setAttr(n1, 'id', undefined, foo);
   });
-  return n0;
+  return root;
 }
 "
 `;
@@ -93,53 +113,82 @@ exports[`comile > directives > v-once > basic 1`] = `
 "import { template, children, insert, setText, setAttr } from 'vue/vapor';
 const t0 = template(\`<div> <span></span></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
   const {
-    1: [n2],
-  } = children(n0);
-  const n1 = document.createTextNode(msg.value);
-  insert(n1, n0, 0 /* InsertPosition.FIRST */);
-  setText(n1, undefined, msg.value);
-  setAttr(n2, 'class', undefined, clz.value);
-  return n0;
+    0: [
+      n1,
+      {
+        1: [n3],
+      },
+    ],
+  } = children(root);
+  const n2 = document.createTextNode(msg.value);
+  insert(n2, n1, 0 /* InsertPosition.FIRST */);
+  setText(n2, undefined, msg.value);
+  setAttr(n3, 'class', undefined, clz.value);
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-text > no expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setText } from 'vue/vapor';
+import { template, children, setText } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setText(n0, undefined, '');
+    setText(n1, undefined, '');
   });
-  return n0;
+  return root;
 }
 "
 `;
 
 exports[`comile > directives > v-text > simple expression 1`] = `
 "import { watchEffect } from 'vue';
-import { template, setText } from 'vue/vapor';
+import { template, children, setText } from 'vue/vapor';
 const t0 = template(\`<div></div>\`);
 export function render() {
-  const n0 = t0();
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
   watchEffect(() => {
-    setText(n0, undefined, str.value);
+    setText(n1, undefined, str.value);
   });
-  return n0;
+  return root;
+}
+"
+`;
+
+exports[`comile > fragment 1`] = `
+"import { template, children } from 'vue/vapor';
+const t0 = template(\`<p></p><span></span><div></div>\`);
+export function render() {
+  const root = t0();
+  const {
+    0: [n1],
+    1: [n2],
+    2: [n3],
+  } = children(root);
+  return root;
 }
 "
 `;
 
 exports[`comile > static template 1`] = `
-"import { template } from 'vue/vapor';
+"import { template, children } from 'vue/vapor';
 const t0 = template(\`<div><p>hello</p><input><span></span></div>\`);
 export function render() {
-  const n0 = t0();
-  return n0;
+  const root = t0();
+  const {
+    0: [n1],
+  } = children(root);
+  return root;
 }
 "
 `;
index 6ade34c03ca0667d854cc3f7367039e474f5bef2..a95e75c53959bf86a69e38020a3a044d9d800e05 100644 (file)
@@ -4,7 +4,7 @@ exports[`fixtures 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 import { watchEffect } from 'vue'
 import { template, children, insert, setText, on, setHtml } from 'vue/vapor'
-const t0 = template(\`<div><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></div>\`)
+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>'
@@ -19,28 +19,28 @@ const increment = () => count.value++
 
 
 return (() => {
-const n0 = t0()
-const { 1: [n1], 2: [n3], 3: [n5], 4: [n6], 6: [n7],} = children(n0)
-const n2 = document.createTextNode(count.value)
-insert(n2, n1)
-const n4 = document.createTextNode(double.value)
-insert(n4, n3)
-const n8 = document.createTextNode(count.value)
-insert(n8, n7)
-setText(n8, undefined, count.value)
+const root = t0()
+const { 0: [n1], 1: [n2], 2: [n4], 3: [n6], 4: [n7], 5: [n8], 6: [n9], 7: [n11],} = children(root)
+const n3 = document.createTextNode(count.value)
+insert(n3, n2)
+const n5 = document.createTextNode(double.value)
+insert(n5, n4)
+const n10 = document.createTextNode(count.value)
+insert(n10, n9)
+setText(n10, undefined, count.value)
 watchEffect(() => {
-setText(n2, undefined, count.value)
+setText(n3, undefined, count.value)
 })
 watchEffect(() => {
-setText(n4, undefined, double.value)
+setText(n5, undefined, double.value)
 })
 watchEffect(() => {
-on(n5, \\"click\\", increment)
+on(n6, \\"click\\", increment)
 })
 watchEffect(() => {
-setHtml(n6, undefined, html)
+setHtml(n7, undefined, html)
 })
-return n0
+return root
 
 })();
 }
index 2c1eacca5c5198f89f9b550f735dba2fbe0a6a24..fb17bd0c8d2a3d8118f6f8e479a9dd8f4c38f008 100644 (file)
@@ -28,6 +28,11 @@ describe('comile', () => {
     expect(code).matchSnapshot()
   })
 
+  test('fragment', async () => {
+    const code = await compile(`<p/><span/><div/>`)
+    expect(code).matchSnapshot()
+  })
+
   test('bindings', async () => {
     const code = await compile(`<div>count is {{ count }}.</div>`, {
       bindingMetadata: {
index d09cfd2e07349839b76bb5e334a3951083a8f827..c780b459dec2d80316c4e6c2ba81679242bdbf65 100644 (file)
@@ -10,14 +10,12 @@ const html = '<b>HTML</b>'
 </script>
 
 <template>
-  <div>
-    <h1 id="title">Counter</h1>
-    <p>Count: {{ count }}</p>
-    <p>Double: {{ double }}</p>
-    <button @click="increment">Increment</button>
-    <div v-html="html" />
-    <input type="text" />
-    <p v-once>once: {{ count }}</p>
-    <p v-pre>{{ count }}</p>
-  </div>
+  <h1 id="title">Counter</h1>
+  <p>Count: {{ count }}</p>
+  <p>Double: {{ double }}</p>
+  <button @click="increment">Increment</button>
+  <div v-html="html" />
+  <input type="text" />
+  <p v-once>once: {{ count }}</p>
+  <p v-pre>{{ count }}</p>
 </template>
index b14c98407b0201203350f62493b51244cf29f631..a21860373503ac47f15894c70ed2dc9000fa0988 100644 (file)
@@ -27,18 +27,14 @@ export function generate(
     vaporHelpers.add('template')
   }
 
-  for (const [, { id, children }] of Object.entries(ir.children)) {
-    code += `const n${id} = t0()\n`
-
-    if (Object.keys(children).length) {
-      code += `const {${genChildren(children)}} = children(n${id})\n`
-      vaporHelpers.add('children')
-    }
+  {
+    code += `const root = t0()\n`
+    code += `const {${genChildren(ir.children.children)}} = children(root)\n`
+    vaporHelpers.add('children')
 
     for (const operation of ir.operation) {
       code += genOperation(operation)
     }
-
     for (const [_expr, operations] of Object.entries(ir.effect)) {
       // TODO don't use watchEffect from vue/core, implement `effect` function in runtime-vapor package
       let scope = `watchEffect(() => {\n`
@@ -49,9 +45,8 @@ export function generate(
       scope += '})\n'
       code += scope
     }
-
     // TODO multiple-template
-    code += `return n${id}\n`
+    code += `return root\n`
   }
 
   if (vaporHelpers.size)
index 7e0e8da0502e7e3936a0c0676ae21307de44e74c..a5c60056da346b1172aee65f6ec3a2e71a7a9ec2 100644 (file)
@@ -20,7 +20,7 @@ export interface IRNode {
 export interface RootIRNode extends IRNode {
   type: IRNodeTypes.ROOT
   template: Array<TemplateGeneratorIRNode>
-  children: DynamicChildren
+  children: DynamicChild
   // TODO multi-expression effect
   effect: Record<string /* expr */, OperationNode[]>
   operation: OperationNode[]
index 3623766ae8d936f91f16282e17c1ee8b338c8af7..797833db94809bd4e072906131065e0c4e398f78 100644 (file)
@@ -135,17 +135,22 @@ export function transform(
     type: IRNodeTypes.ROOT,
     loc: root.loc,
     template: [],
-    children: {},
+    children: {} as any,
     effect: Object.create(null),
     operation: [],
     helpers: new Set([]),
     vaporHelpers: new Set([]),
   }
   const ctx = createRootContext(ir, root, options)
+  const rootId = ctx.getElementId()
 
   // TODO: transform presets, see packages/compiler-core/src/transforms
   transformChildren(ctx, true)
-  ir.children = ctx.children
+  ir.children = {
+    store: true,
+    id: rootId,
+    children: ctx.children,
+  }
 
   return ir
 }
index b155f8b091168758576f05a1a4b5eab1eadd0dd3..add59f46b6c4f8cb2206cb32533f368822b47a28 100644 (file)
@@ -7,7 +7,7 @@ import {
 import { isArray } from '@vue/shared'
 
 export type Block = Node | Fragment | Block[]
-export type Fragment = { nodes: Block; anchor?: Node }
+export type Fragment = { nodes: Block; anchor: Node }
 export type BlockFn = (props?: any) => Block
 
 export function render(
@@ -52,7 +52,7 @@ export function insert(
     for (const child of block) insert(child, parent, anchor)
   } else {
     insert(block.nodes, parent, anchor)
-    block.anchor && parent.insertBefore(block.anchor, anchor)
+    parent.insertBefore(block.anchor, anchor)
   }
   // }
 }
index 4f66b157296384130bb4552c5933990aafec9c2a..1f02b32c34e2578e35e6cd71819890ae9fcbdc7c 100644 (file)
@@ -1,6 +1,6 @@
 export const template = (str: string): (() => Node) => {
   let cached = false
-  let node: Node
+  let node: DocumentFragment
   return () => {
     if (!cached) {
       cached = true
@@ -9,7 +9,7 @@ export const template = (str: string): (() => Node) => {
       // first render: insert the node directly.
       // this removes it from the template fragment to avoid keeping two copies
       // of the inserted tree in memory, even if the template is used only once.
-      return (node = t.content.firstChild!)
+      return (node = t.content)
     } else {
       // repeated renders: clone from cache. This is more performant and
       // efficient when dealing with big lists where the template is repeated
diff --git a/playground/src/App-fragment.vue b/playground/src/App-fragment.vue
new file mode 100644 (file)
index 0000000..a1fea15
--- /dev/null
@@ -0,0 +1,4 @@
+<template>
+  <p>hello</p>
+  <p>world</p>
+</template>
index 261042c33f648e958852771d740c4b28c9e125b2..06b2c60ad079d4c99ef54c904ed77c1db69ce930 100644 (file)
@@ -5,7 +5,7 @@ const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
 
 mod.then(({ default: m }) => {
   render(() => {
-    const returned = m.setup({}, { expose() {} })
+    const returned = m.setup?.({}, { expose() {} })
     return m.render(returned)
   }, '#app')
 })