diff --git a/README.md b/README.md
index 018a33d17..f6342487a 100644
--- 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
diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
index 43bd84de0..3ec2aa6ab 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
@@ -5,86 +5,106 @@ exports[`comile > bindings 1`] = `
import { template, children, insert, setText } from 'vue/vapor';
const t0 = template(\`
count is .
\`);
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(\`\`);
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(\`\`);
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(\`\`);
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(\`\`);
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(\`\`);
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(\`
\`);
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(\`\`);
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(\`\`);
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(\`\`);
+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(\`\`);
export function render() {
- const n0 = t0();
- return n0;
+ const root = t0();
+ const {
+ 0: [n1],
+ } = children(root);
+ return root;
}
"
`;
diff --git a/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap
index 6ade34c03..a95e75c53 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, 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'
@@ -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
})();
}
diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts
index 2c1eacca5..fb17bd0c8 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('fragment', async () => {
+ const code = await compile(``)
+ expect(code).matchSnapshot()
+ })
+
test('bindings', async () => {
const code = await compile(`count is {{ count }}.
`, {
bindingMetadata: {
diff --git a/packages/compiler-vapor/__tests__/fixtures/counter.vue b/packages/compiler-vapor/__tests__/fixtures/counter.vue
index d09cfd2e0..c780b459d 100644
--- a/packages/compiler-vapor/__tests__/fixtures/counter.vue
+++ b/packages/compiler-vapor/__tests__/fixtures/counter.vue
@@ -10,14 +10,12 @@ const html = 'HTML'
-
-
Counter
-
Count: {{ count }}
-
Double: {{ double }}
-
-
-
-
once: {{ count }}
-
{{ count }}
-
+ Counter
+ Count: {{ count }}
+ Double: {{ double }}
+
+
+
+ once: {{ count }}
+ {{ count }}
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index b14c98407..a21860373 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -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)
diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts
index 7e0e8da05..a5c60056d 100644
--- a/packages/compiler-vapor/src/ir.ts
+++ b/packages/compiler-vapor/src/ir.ts
@@ -20,7 +20,7 @@ export interface IRNode {
export interface RootIRNode extends IRNode {
type: IRNodeTypes.ROOT
template: Array
- children: DynamicChildren
+ children: DynamicChild
// TODO multi-expression effect
effect: Record
operation: OperationNode[]
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 3623766ae..797833db9 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -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
}
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index b155f8b09..add59f46b 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -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)
}
// }
}
diff --git a/packages/runtime-vapor/src/template.ts b/packages/runtime-vapor/src/template.ts
index 4f66b1572..1f02b32c3 100644
--- a/packages/runtime-vapor/src/template.ts
+++ b/packages/runtime-vapor/src/template.ts
@@ -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
index 000000000..a1fea15e7
--- /dev/null
+++ b/playground/src/App-fragment.vue
@@ -0,0 +1,4 @@
+
+ hello
+ world
+
diff --git a/playground/src/main.ts b/playground/src/main.ts
index 261042c33..06b2c60ad 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -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')
})