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