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
- [ ] 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

View 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;

View 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>'

View 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()

View 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`
}

View 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

View 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
}

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>