feat: dynamic root nodes

This commit is contained in:
三咲智子 Kevin Deng 2023-11-26 03:53:47 +08:00
parent f1e5bee7d5
commit 12187fbc85
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
9 changed files with 82 additions and 27 deletions

View File

@ -35,6 +35,8 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru
- [ ] Remove DOM API in codegen - [ ] Remove DOM API in codegen
- [ ] Fragment - [ ] Fragment
- [x] multiple root nodes - [x] multiple root nodes
- [x] all dynamic children
- [ ] return `Node[]` for all dynamic children, instead of using `fragment` API
- [ ] Built-in Components - [ ] Built-in Components
- [ ] Transition - [ ] Transition
- [ ] TransitionGroup - [ ] TransitionGroup

View File

@ -3,7 +3,7 @@
exports[`comile > bindings 1`] = ` exports[`comile > bindings 1`] = `
"import { watchEffect } from 'vue'; "import { watchEffect } from 'vue';
import { template, children, createTextNode, insert, setText } from 'vue/vapor'; 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() { export function render() {
const n0 = t0(); const n0 = t0();
const { const {
@ -27,7 +27,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -44,7 +44,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -61,7 +61,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -78,7 +78,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -95,7 +95,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -111,7 +111,7 @@ export function render() {
exports[`comile > directives > v-once > basic 1`] = ` exports[`comile > directives > v-once > basic 1`] = `
"import { template, children, createTextNode, insert, setText, setAttr } from 'vue/vapor'; "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() { export function render() {
const n0 = t0(); const n0 = t0();
const { const {
@ -134,7 +134,7 @@ export function render() {
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, children, 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 n0 = t0();
const { const {
@ -151,7 +151,7 @@ export function render() {
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, children, 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 n0 = t0();
const { 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`] = ` exports[`comile > fragment 1`] = `
"import { template } from 'vue/vapor'; "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() { export function render() {
const n0 = t0(); const n0 = t0();
return n0; return n0;
@ -178,7 +199,7 @@ export function render() {
exports[`comile > static + dynamic root 1`] = ` exports[`comile > static + dynamic root 1`] = `
"import { watchEffect } from 'vue'; "import { watchEffect } from 'vue';
import { template, createTextNode, insert, setText } from 'vue/vapor'; import { template, createTextNode, insert, setText } from 'vue/vapor';
const t0 = template(\`2\`); const t0 = template('2');
export function render() { export function render() {
const n0 = t0(); const n0 = t0();
const n1 = createTextNode(1); const n1 = createTextNode(1);
@ -198,7 +219,7 @@ export function render() {
exports[`comile > static template 1`] = ` exports[`comile > static template 1`] = `
"import { template } from 'vue/vapor'; "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() { export function render() {
const n0 = t0(); const n0 = t0();
return n0; return n0;

View File

@ -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, createTextNode, insert, setText, on, setHtml } from 'vue/vapor' 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' import { ref, computed } from 'vue'
const html = '<b>HTML</b>' const html = '<b>HTML</b>'

View File

@ -28,6 +28,11 @@ describe('comile', () => {
expect(code).matchSnapshot() expect(code).matchSnapshot()
}) })
test('dynamic root', async () => {
const code = await compile(`{{ 1 }}{{ 2 }}`)
expect(code).matchSnapshot()
})
test('static + dynamic root', async () => { test('static + dynamic root', async () => {
const code = await compile(`{{ 1 }}2{{ 3 }}`) const code = await compile(`{{ 1 }}2{{ 3 }}`)
expect(code).matchSnapshot() expect(code).matchSnapshot()

View File

@ -18,14 +18,19 @@ export function generate(
let preamble = '' let preamble = ''
const { helpers, vaporHelpers } = ir const { helpers, vaporHelpers } = ir
if (ir.template.length) {
preamble += ir.template ir.template.forEach((template, i) => {
.map( if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
(template, i) => `const t${i} = template(\`${template.template}\`)\n`, preamble += `const t${i} = template(${JSON.stringify(
) template.template,
.join('') )})\n`
vaporHelpers.add('template') vaporHelpers.add('template')
} } else {
// fragment
code += `const t0 = fragment()\n`
vaporHelpers.add('fragment')
}
})
{ {
code += `const n${ir.children.id} = t0()\n` code += `const n${ir.children.id} = t0()\n`
@ -50,6 +55,7 @@ export function generate(
code += scope code += scope
} }
// TODO multiple-template // TODO multiple-template
// TODO return statement in IR
code += `return n${ir.children.id}\n` code += `return n${ir.children.id}\n`
} }

View File

@ -2,7 +2,9 @@ import type { SourceLocation } from '@vue/compiler-dom'
export const enum IRNodeTypes { export const enum IRNodeTypes {
ROOT, ROOT,
TEMPLATE_GENERATOR, TEMPLATE_FACTORY,
FRAGMENT_FACTORY,
SET_PROP, SET_PROP,
SET_TEXT, SET_TEXT,
SET_EVENT, SET_EVENT,
@ -19,7 +21,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<TemplateFactoryIRNode | FragmentFactoryIRNode>
children: DynamicChild children: DynamicChild
// TODO multi-expression effect // TODO multi-expression effect
effect: Record<string /* expr */, OperationNode[]> effect: Record<string /* expr */, OperationNode[]>
@ -28,11 +30,15 @@ export interface RootIRNode extends IRNode {
vaporHelpers: Set<string> vaporHelpers: Set<string>
} }
export interface TemplateGeneratorIRNode extends IRNode { export interface TemplateFactoryIRNode extends IRNode {
type: IRNodeTypes.TEMPLATE_GENERATOR type: IRNodeTypes.TEMPLATE_FACTORY
template: string template: string
} }
export interface FragmentFactoryIRNode extends IRNode {
type: IRNodeTypes.FRAGMENT_FACTORY
}
export interface SetPropIRNode extends IRNode { export interface SetPropIRNode extends IRNode {
type: IRNodeTypes.SET_PROP type: IRNodeTypes.SET_PROP
element: number element: number

View File

@ -73,11 +73,15 @@ function createRootContext(
registerTemplate() { registerTemplate() {
if (!ctx.template) return -1 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 if (idx !== -1) return idx
ir.template.push({ ir.template.push({
type: IRNodeTypes.TEMPLATE_GENERATOR, type: IRNodeTypes.TEMPLATE_FACTORY,
template: ctx.template, template: ctx.template,
loc: node.loc, loc: node.loc,
}) })
@ -153,6 +157,12 @@ export function transform(
ghost: false, ghost: false,
children: ctx.children, children: ctx.children,
} }
if (ir.template.length === 0) {
ir.template.push({
type: IRNodeTypes.FRAGMENT_FACTORY,
loc: root.loc,
})
}
return ir return ir
} }

View File

@ -18,3 +18,7 @@ export const template = (str: string): (() => Node) => {
} }
} }
} }
export function fragment(): () => DocumentFragment {
return () => document.createDocumentFragment()
}

View File

@ -0,0 +1 @@
<template>{{ '1' }}{{ '2' }}</template>