diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
deleted file mode 100644
index 95e0ca79c..000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-name: "\U0001F41E Bug report"
-description: Create a report to help us improve
-body:
- - type: markdown
- attributes:
- value: |
- **Before You Start...**
-
- This form is only for submitting bug reports. If you have a usage question
- or are unsure if this is really a bug, make sure to:
-
- - Read the [docs](https://vuejs.org/)
- - Ask on [Discord Chat](https://chat.vuejs.org/)
- - Ask on [GitHub Discussions](https://github.com/vuejs/core/discussions)
- - Look for / ask questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=vue.js)
-
- Also try to search for your issue - it may have already been answered or even fixed in the development branch.
- However, if you find that an old, closed issue still persists in the latest version,
- you should open a new issue using the form below instead of commenting on the old issue.
- - type: input
- id: version
- attributes:
- label: Vue version
- validations:
- required: true
- - type: input
- id: reproduction-link
- attributes:
- label: Link to minimal reproduction
- description: |
- The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://play.vuejs.org/).
- If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue).
- If neither of these are suitable, you can always provide a GitHub repository.
-
- The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed
- to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details.
-
- Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.
- placeholder: Reproduction Link
- validations:
- required: true
- - type: textarea
- id: steps-to-reproduce
- attributes:
- label: Steps to reproduce
- description: |
- What do we need to do after opening your repro in order to make the bug happen? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
- placeholder: Steps to reproduce
- validations:
- required: true
- - type: textarea
- id: expected
- attributes:
- label: What is expected?
- validations:
- required: true
- - type: textarea
- id: actually-happening
- attributes:
- label: What is actually happening?
- validations:
- required: true
- - type: textarea
- id: system-info
- attributes:
- label: System Info
- description: Output of `npx envinfo --system --npmPackages vue --binaries --browsers`
- render: shell
- placeholder: System, Binaries, Browsers
- - type: textarea
- id: additional-comments
- attributes:
- label: Any additional comments?
- description: e.g. some background/context of how you ran into this bug.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 02f99c6bf..000000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-blank_issues_enabled: false
-contact_links:
- - name: Feature Request
- url: https://github.com/vuejs/rfcs/discussions
- about: Suggest new features for consideration
- - name: Discord Chat
- url: https://chat.vuejs.org
- about: Ask questions and discuss with other Vue users in real time.
- - name: Questions & Discussions
- url: https://github.com/vuejs/core/discussions
- about: Use GitHub discussions for message-board style questions and discussions.
- - name: Patreon
- url: https://www.patreon.com/evanyou
- about: Love Vue.js? Please consider supporting us via Patreon.
- - name: Open Collective
- url: https://opencollective.com/vuejs/donate
- about: Love Vue.js? Please consider supporting us via Open Collective.
diff --git a/README.md b/README.md
index ca5a5a0f5..8d3e7c551 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru
- [ ] Fragment
- [x] multiple root nodes
- [x] all dynamic children
- - [ ] return `Node[]` for all dynamic children, instead of using `fragment` API
+ - [x] return `Node[]` for all dynamic children, instead of using `fragment` API
- [ ] Built-in Components
- [ ] Transition
- [ ] TransitionGroup
diff --git a/package.json b/package.json
index 29366391e..e2db412dc 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"build-dts": "tsc -p tsconfig.build.json && rollup -c rollup.dts.config.js",
"clean": "rimraf packages/*/dist temp .eslintcache",
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
- "size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
+ "size-global": "node scripts/build.js vue runtime-dom compiler-dom compiler-vapor -f global -p --size",
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
"size-esm": "node scripts/build.js runtime-dom runtime-vapor runtime-core reactivity shared -f esm-bundler",
"check": "tsc --incremental --noEmit",
diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
index 4fa319bd1..da4d0ad4c 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
@@ -8,16 +8,16 @@ export function render() {
const n0 = t0();
const {
0: [
- n1,
+ n3,
{
- 1: [n3],
+ 1: [n2],
},
],
} = children(n0);
- const n2 = createTextNode(count.value);
- insert(n2, n1, n3);
+ const n1 = createTextNode(count.value);
+ insert(n1, n3, n2);
watchEffect(() => {
- setText(n2, undefined, count.value);
+ setText(n1, undefined, count.value);
});
return n0;
}
@@ -110,22 +110,22 @@ export function render() {
`;
exports[`comile > directives > v-once > basic 1`] = `
-"import { template, children, createTextNode, insert, setText, setAttr } from 'vue/vapor';
+"import { template, children, createTextNode, setText, setAttr, prepend } from 'vue/vapor';
const t0 = template('
');
export function render() {
const n0 = t0();
const {
0: [
- n1,
+ n3,
{
- 1: [n3],
+ 1: [n2],
},
],
} = children(n0);
- const n2 = createTextNode(msg.value);
- insert(n2, n1, 0 /* InsertPosition.FIRST */);
- setText(n2, undefined, msg.value);
- setAttr(n3, 'class', undefined, clz.value);
+ const n1 = createTextNode(msg.value);
+ setText(n1, undefined, msg.value);
+ setAttr(n2, 'class', undefined, clz.value);
+ prepend(n3, n1);
return n0;
}
"
@@ -167,14 +167,13 @@ export function render() {
exports[`comile > dynamic root 1`] = `
"import { watchEffect } from 'vue';
-import { fragment, createTextNode, insert, setText } from 'vue/vapor';
+import { fragment, createTextNode, append, 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);
+ append(n0, n1, n2);
watchEffect(() => {
setText(n1, undefined, 1);
});
@@ -198,19 +197,49 @@ 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');
+import { template, children, createTextNode, prepend, insert, append, setText } from 'vue/vapor';
+const t0 = template('369');
export function render() {
const n0 = t0();
+ const {
+ 1: [n9],
+ 3: [n10],
+ } = children(n0);
const n1 = createTextNode(1);
- insert(n1, n0, 0 /* InsertPosition.FIRST */);
- const n2 = createTextNode(3);
- insert(n2, n0);
+ const n2 = createTextNode(2);
+ const n3 = createTextNode(4);
+ const n4 = createTextNode(5);
+ const n5 = createTextNode(7);
+ const n6 = createTextNode(8);
+ const n7 = createTextNode('A');
+ const n8 = createTextNode('B');
+ prepend(n0, n1, n2);
+ insert([n3, n4], n0, n9);
+ insert([n5, n6], n0, n10);
+ append(n0, n7, n8);
watchEffect(() => {
setText(n1, undefined, 1);
});
watchEffect(() => {
- setText(n2, undefined, 3);
+ setText(n2, undefined, 2);
+ });
+ watchEffect(() => {
+ setText(n3, undefined, 4);
+ });
+ watchEffect(() => {
+ setText(n4, undefined, 5);
+ });
+ watchEffect(() => {
+ setText(n5, undefined, 7);
+ });
+ watchEffect(() => {
+ setText(n6, undefined, 8);
+ });
+ watchEffect(() => {
+ setText(n7, undefined, 'A');
+ });
+ watchEffect(() => {
+ setText(n8, undefined, 'B');
});
return n0;
}
diff --git a/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap
index 0c20d725a..f90d49cf6 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/fixtures.test.ts.snap
@@ -3,7 +3,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'
+import { template, children, createTextNode, append, setText, on, setHtml } from 'vue/vapor'
const t0 = template(\\"Counter Count:
Double:
Increment
once:
{{ count }}
\\")
import { ref, computed } from 'vue'
@@ -20,19 +20,19 @@ const increment = () => count.value++
return (() => {
const n0 = t0()
-const { 1: [n1], 2: [n3], 3: [n5], 4: [n6], 6: [n7],} = children(n0)
-const n2 = createTextNode(count.value)
-insert(n2, n1)
-const n4 = createTextNode(double.value)
-insert(n4, n3)
-const n8 = createTextNode(count.value)
-insert(n8, n7)
-setText(n8, undefined, count.value)
+const { 1: [n2], 2: [n4], 3: [n5], 4: [n6], 6: [n8],} = children(n0)
+const n1 = createTextNode(count.value)
+append(n2, n1)
+const n3 = createTextNode(double.value)
+append(n4, n3)
+const n7 = createTextNode(count.value)
+setText(n7, undefined, count.value)
+append(n8, n7)
watchEffect(() => {
-setText(n2, undefined, count.value)
+setText(n1, undefined, count.value)
})
watchEffect(() => {
-setText(n4, undefined, double.value)
+setText(n3, undefined, double.value)
})
watchEffect(() => {
on(n5, \\"click\\", increment)
diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts
index 0f8bb90de..acb31e3e4 100644
--- a/packages/compiler-vapor/__tests__/compile.test.ts
+++ b/packages/compiler-vapor/__tests__/compile.test.ts
@@ -35,7 +35,9 @@ describe('comile', () => {
})
test('static + dynamic root', async () => {
- const code = await compile(`{{ 1 }}2{{ 3 }}`)
+ const code = await compile(
+ `{{ 1 }}{{ 2 }}3{{ 4 }}{{ 5 }}6{{ 7 }}{{ 8 }}9{{ 'A' }}{{ 'B' }}`,
+ )
expect(code).matchSnapshot()
})
diff --git a/packages/compiler-vapor/index.js b/packages/compiler-vapor/index.js
new file mode 100644
index 000000000..083860666
--- /dev/null
+++ b/packages/compiler-vapor/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('./dist/compiler-vapor.cjs.prod.js')
+} else {
+ module.exports = require('./dist/compiler-vapor.cjs.js')
+}
diff --git a/packages/compiler-vapor/package.json b/packages/compiler-vapor/package.json
index f021932ca..98124ac67 100644
--- a/packages/compiler-vapor/package.json
+++ b/packages/compiler-vapor/package.json
@@ -2,17 +2,26 @@
"name": "@vue/compiler-vapor",
"version": "0.0.0",
"description": "@vue/compiler-vapor",
- "main": "dist/compiler-vapor.cjs.js",
+ "main": "index.js",
+ "module": "dist/compiler-vapor.esm-bundler.js",
+ "types": "dist/compiler-vapor.d.ts",
+ "unpkg": "dist/compiler-vapor.global.js",
+ "jsdelivr": "dist/compiler-vapor.global.js",
"files": [
+ "index.js",
"dist"
],
+ "sideEffects": false,
"buildOptions": {
+ "name": "VueCompilerVapor",
+ "compat": true,
"formats": [
- "cjs"
- ],
- "prod": false
+ "esm-bundler",
+ "esm-browser",
+ "cjs",
+ "global"
+ ]
},
- "types": "dist/compiler-vapor.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core-vapor.git",
@@ -26,13 +35,9 @@
"bugs": {
"url": "https://github.com/vuejs/core-vapor/issues"
},
- "homepage": "https://github.com/vuejs/core-vapor/tree/dev/packages/compiler-vapor#readme",
+ "homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-vapor#readme",
"dependencies": {
"@vue/shared": "3.3.8",
- "@vue/compiler-dom": "3.3.8",
- "ast-kit": "^0.11.2"
- },
- "devDependencies": {
- "@babel/types": "^7.23.0"
+ "@vue/compiler-dom": "3.3.8"
}
}
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index f1d20ab0b..f87f5fab5 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -33,11 +33,11 @@ export function generate(
})
{
- code += `const n${ir.children.id} = t0()\n`
- if (Object.keys(ir.children.children).length) {
- code += `const {${genChildren(ir.children.children)}} = children(n${
- ir.children.id
- })\n`
+ code += `const n${ir.dynamic.id} = t0()\n`
+
+ const children = genChildren(ir.dynamic.children)
+ if (children) {
+ code += `const ${children} = children(n${ir.dynamic.id})\n`
vaporHelpers.add('children')
}
@@ -56,7 +56,7 @@ export function generate(
}
// TODO multiple-template
// TODO return statement in IR
- code += `return n${ir.children.id}\n`
+ code += `return n${ir.dynamic.id}\n`
}
if (vaporHelpers.size)
@@ -79,59 +79,69 @@ export function generate(
preamble,
}
- function genOperation(operation: OperationNode) {
+ function genOperation(oper: OperationNode) {
let code = ''
// TODO: cache old value
- switch (operation.type) {
+ switch (oper.type) {
case IRNodeTypes.SET_PROP: {
- code = `setAttr(n${operation.element}, ${JSON.stringify(
- operation.name,
- )}, undefined, ${operation.value})\n`
+ code = `setAttr(n${oper.element}, ${JSON.stringify(
+ oper.name,
+ )}, undefined, ${oper.value})\n`
vaporHelpers.add('setAttr')
break
}
case IRNodeTypes.SET_TEXT: {
- code = `setText(n${operation.element}, undefined, ${operation.value})\n`
+ code = `setText(n${oper.element}, undefined, ${oper.value})\n`
vaporHelpers.add('setText')
break
}
case IRNodeTypes.SET_EVENT: {
- code = `on(n${operation.element}, ${JSON.stringify(operation.name)}, ${
- operation.value
+ code = `on(n${oper.element}, ${JSON.stringify(oper.name)}, ${
+ oper.value
})\n`
vaporHelpers.add('on')
break
}
case IRNodeTypes.SET_HTML: {
- code = `setHtml(n${operation.element}, undefined, ${operation.value})\n`
+ code = `setHtml(n${oper.element}, undefined, ${oper.value})\n`
vaporHelpers.add('setHtml')
break
}
case IRNodeTypes.CREATE_TEXT_NODE: {
- code = `const n${operation.id} = createTextNode(${operation.value})\n`
+ code = `const n${oper.id} = createTextNode(${oper.value})\n`
vaporHelpers.add('createTextNode')
break
}
case IRNodeTypes.INSERT_NODE: {
- let anchor = ''
- if (typeof operation.anchor === 'number') {
- anchor = `, n${operation.anchor}`
- } else if (operation.anchor === 'first') {
- anchor = `, 0 /* InsertPosition.FIRST */`
- }
- code = `insert(n${operation.element}, n${operation.parent}${anchor})\n`
+ const elements = ([] as number[]).concat(oper.element)
+ let element = elements.map((el) => `n${el}`).join(', ')
+ if (elements.length > 1) element = `[${element}]`
+ code = `insert(${element}, n${oper.parent}${`, n${oper.anchor}`})\n`
vaporHelpers.add('insert')
break
}
-
+ case IRNodeTypes.PREPEND_NODE: {
+ code = `prepend(n${oper.parent}, ${oper.elements
+ .map((el) => `n${el}`)
+ .join(', ')})\n`
+ vaporHelpers.add('prepend')
+ break
+ }
+ case IRNodeTypes.APPEND_NODE: {
+ code = `append(n${oper.parent}, ${oper.elements
+ .map((el) => `n${el}`)
+ .join(', ')})\n`
+ vaporHelpers.add('append')
+ break
+ }
default:
- checkNever(operation)
+ checkNever(oper)
}
return code
@@ -139,16 +149,27 @@ export function generate(
}
function genChildren(children: DynamicChildren) {
- let str = ''
+ let code = ''
+ // TODO
+ let offset = 0
for (const [index, child] of Object.entries(children)) {
- str += ` ${index}: [`
- if (child.store) {
- str += `n${child.id}`
+ const childrenLength = Object.keys(child.children).length
+ if (child.ghost && child.placeholder === null && childrenLength === 0) {
+ offset--
+ continue
}
- if (Object.keys(child.children).length) {
- str += `, {${genChildren(child.children)}}`
- }
- str += '],'
+
+ code += ` ${Number(index) + offset}: [`
+
+ const id = child.ghost ? child.placeholder : child.id
+ if (id !== null) code += `n${id}`
+
+ const childrenString = childrenLength && genChildren(child.children)
+ if (childrenString) code += `, ${childrenString}`
+
+ code += '],'
}
- return str
+
+ if (!code) return ''
+ return `{${code}}`
}
diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts
index d69e522b4..136f11fc0 100644
--- a/packages/compiler-vapor/src/ir.ts
+++ b/packages/compiler-vapor/src/ir.ts
@@ -11,6 +11,8 @@ export const enum IRNodeTypes {
SET_HTML,
INSERT_NODE,
+ PREPEND_NODE,
+ APPEND_NODE,
CREATE_TEXT_NODE,
}
@@ -22,7 +24,7 @@ export interface IRNode {
export interface RootIRNode extends IRNode {
type: IRNodeTypes.ROOT
template: Array
- children: DynamicChild
+ dynamic: DynamicInfo
// TODO multi-expression effect
effect: Record
operation: OperationNode[]
@@ -73,9 +75,21 @@ export interface CreateTextNodeIRNode extends IRNode {
export interface InsertNodeIRNode extends IRNode {
type: IRNodeTypes.INSERT_NODE
- element: number
+ element: number | number[]
+ parent: number
+ anchor: number
+}
+
+export interface PrependNodeIRNode extends IRNode {
+ type: IRNodeTypes.PREPEND_NODE
+ elements: number[]
+ parent: number
+}
+
+export interface AppendNodeIRNode extends IRNode {
+ type: IRNodeTypes.APPEND_NODE
+ elements: number[]
parent: number
- anchor: number | 'first' | 'last'
}
export type OperationNode =
@@ -85,11 +99,15 @@ export type OperationNode =
| SetHtmlIRNode
| CreateTextNodeIRNode
| InsertNodeIRNode
+ | PrependNodeIRNode
+ | AppendNodeIRNode
-export interface DynamicChild {
+export interface DynamicInfo {
id: number | null
- store: boolean
+ referenced: boolean
+ /** created by DOM API */
ghost: boolean
+ placeholder: number | null
children: DynamicChildren
}
-export type DynamicChildren = Record
+export type DynamicChildren = Record
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 81a186370..4d10ffd98 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -11,10 +11,10 @@ import {
ExpressionNode,
} from '@vue/compiler-dom'
import {
- type DynamicChildren,
type OperationNode,
type RootIRNode,
IRNodeTypes,
+ DynamicInfo,
} from './ir'
import { isVoidTag } from '@vue/shared'
import {
@@ -30,14 +30,13 @@ export interface TransformContext {
root: TransformContext
index: number
options: TransformOptions
+
template: string
- children: DynamicChildren
- store: boolean
- ghost: boolean
+ dynamic: DynamicInfo
+
once: boolean
- id: number | null
- getId(): number
+ reference(): number
incraseId(): number
registerTemplate(): number
registerEffect(expr: string, operation: OperationNode): void
@@ -57,20 +56,21 @@ function createRootContext(
node,
parent: null,
index: 0,
- root: undefined as any, // set later
+ root: null!, // set later
options,
- children: {},
- store: false,
- ghost: false,
+ dynamic: ir.dynamic,
once: false,
- id: null,
incraseId: () => globalId++,
- getId() {
- if (this.id !== null) return this.id
- return (this.id = this.incraseId())
+ reference() {
+ if (this.dynamic.id !== null) return this.dynamic.id
+ this.dynamic.referenced = true
+ return (this.dynamic.id = this.incraseId())
},
registerEffect(expr, operation) {
+ if (this.once) {
+ return this.registerOpration(operation)
+ }
if (!effect[expr]) effect[expr] = []
effect[expr].push(operation)
},
@@ -103,6 +103,7 @@ function createRootContext(
},
}
ctx.root = ctx
+ ctx.reference()
return ctx
}
@@ -111,27 +112,19 @@ function createContext(
parent: TransformContext,
index: number,
): TransformContext {
- const children = {}
-
const ctx: TransformContext = {
...parent,
- id: null,
node,
parent,
index,
- get template() {
- return parent.template
- },
- set template(t) {
- parent.template = t
- },
- children,
- store: false,
- registerEffect(expr, operation) {
- if (ctx.once) {
- return ctx.registerOpration(operation)
- }
- return parent.registerEffect(expr, operation)
+
+ template: '',
+ dynamic: {
+ id: null,
+ referenced: false,
+ ghost: false,
+ placeholder: null,
+ children: {},
},
}
return ctx
@@ -149,7 +142,13 @@ export function transform(
type: IRNodeTypes.ROOT,
loc: root.loc,
template: [],
- children: {} as any,
+ dynamic: {
+ id: null,
+ referenced: true,
+ ghost: true,
+ placeholder: null,
+ children: {},
+ },
effect: Object.create(null),
operation: [],
helpers: new Set([]),
@@ -157,16 +156,9 @@ export function transform(
}
const ctx = createRootContext(ir, root, options)
- const rootId = ctx.getId()
// TODO: transform presets, see packages/compiler-core/src/transforms
transformChildren(ctx, true)
- ir.children = {
- id: rootId,
- store: true,
- ghost: false,
- children: ctx.children,
- }
if (ir.template.length === 0) {
ir.template.push({
type: IRNodeTypes.FRAGMENT_FACTORY,
@@ -184,15 +176,62 @@ function transformChildren(
const {
node: { children },
} = ctx
- let index = 0
+ const childrenTemplate: string[] = []
children.forEach((child, i) => walkNode(child, i))
+ let prevChildren: DynamicInfo[] = []
+ let hasStatic = false
+
+ for (let index = 0; index < children.length; index++) {
+ const child = ctx.dynamic.children[index]
+
+ if (!child || !child.ghost) {
+ if (prevChildren.length)
+ if (hasStatic) {
+ childrenTemplate[index - prevChildren.length] = ``
+ const anchor = (prevChildren[0].placeholder = ctx.incraseId())
+
+ ctx.registerOpration({
+ type: IRNodeTypes.INSERT_NODE,
+ loc: ctx.node.loc,
+ element: prevChildren.map((child) => child.id!),
+ parent: ctx.reference(),
+ anchor,
+ })
+ } else {
+ ctx.registerOpration({
+ type: IRNodeTypes.PREPEND_NODE,
+ loc: ctx.node.loc,
+ elements: prevChildren.map((child) => child.id!),
+ parent: ctx.reference(),
+ })
+ }
+ hasStatic = true
+ prevChildren = []
+ continue
+ }
+
+ prevChildren.push(child)
+
+ if (index === children.length - 1) {
+ ctx.registerOpration({
+ type: IRNodeTypes.APPEND_NODE,
+ loc: ctx.node.loc,
+ elements: prevChildren.map((child) => child.id!),
+ parent: ctx.reference(),
+ })
+ }
+ }
+
+ ctx.template += childrenTemplate.join('')
+
+ // finalize template
if (root) ctx.registerTemplate()
- function walkNode(node: TemplateChildNode, i: number) {
+ function walkNode(node: TemplateChildNode, index: number) {
const child = createContext(node, ctx, index)
- const isFirst = i === 0
- const isLast = i === children.length - 1
+ const isFirst = index === 0
+ const isLast = index === children.length - 1
switch (node.type) {
case 1 satisfies NodeTypes.ELEMENT: {
@@ -200,11 +239,11 @@ function transformChildren(
break
}
case 2 satisfies NodeTypes.TEXT: {
- ctx.template += node.content
+ child.template += node.content
break
}
case 3 satisfies NodeTypes.COMMENT: {
- ctx.template += ``
+ child.template += ``
break
}
case 5 satisfies NodeTypes.INTERPOLATION: {
@@ -224,19 +263,20 @@ function transformChildren(
// IfNode
// IfBranchNode
// ForNode
- ctx.template += `[type: ${node.type}]`
+ child.template += `[type: ${node.type}]`
}
}
- if (Object.keys(child.children).length > 0 || child.store)
- ctx.children[index] = {
- id: child.store ? child.getId() : null,
- store: child.store,
- children: child.children,
- ghost: child.ghost,
- }
+ childrenTemplate.push(child.template)
- if (!child.ghost) index++
+ if (
+ child.dynamic.ghost ||
+ child.dynamic.referenced ||
+ child.dynamic.placeholder ||
+ Object.keys(child.dynamic.children).length
+ ) {
+ ctx.dynamic.children[index] = child.dynamic
+ }
}
}
@@ -264,62 +304,38 @@ function transformInterpolation(
) {
const { node } = ctx
- if (node.content.type === (4 satisfies NodeTypes.SIMPLE_EXPRESSION)) {
- const expr = processExpression(ctx, node.content)!
-
- const parent = ctx.parent!
- const parentId = parent.getId()
- parent.store = true
-
- if (isFirst && isLast) {
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: parentId,
- value: expr,
- })
- } else {
- let id: number
- let anchor: number | 'first' | 'last'
-
- if (!isFirst && !isLast) {
- id = ctx.incraseId()
- anchor = ctx.getId()
- ctx.template += ''
- ctx.store = true
- } else {
- id = ctx.getId()
- ctx.ghost = true
- anchor = isFirst ? 'first' : 'last'
- }
-
- ctx.registerOpration(
- {
- type: IRNodeTypes.CREATE_TEXT_NODE,
- loc: node.loc,
- id,
- value: expr,
- },
- {
- type: IRNodeTypes.INSERT_NODE,
- loc: node.loc,
- element: id,
- parent: parentId,
- anchor,
- },
- )
-
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: id,
- value: expr,
- })
- }
+ if (node.content.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
+ // TODO: CompoundExpressionNode: {{ count + 1 }}
return
}
- // TODO: CompoundExpressionNode: {{ count + 1 }}
+ const expr = processExpression(ctx, node.content)!
+
+ if (isFirst && isLast) {
+ const parent = ctx.parent!
+ const parentId = parent.reference()
+ ctx.registerEffect(expr, {
+ type: IRNodeTypes.SET_TEXT,
+ loc: node.loc,
+ element: parentId,
+ value: expr,
+ })
+ } else {
+ const id = ctx.reference()
+ ctx.dynamic.ghost = true
+ ctx.registerOpration({
+ type: IRNodeTypes.CREATE_TEXT_NODE,
+ loc: node.loc,
+ id,
+ value: expr,
+ })
+ ctx.registerEffect(expr, {
+ type: IRNodeTypes.SET_TEXT,
+ loc: node.loc,
+ element: id,
+ value: expr,
+ })
+ }
}
function transformProp(
@@ -339,7 +355,6 @@ function transformProp(
const { exp, loc, modifiers } = node
- ctx.store = true
const expr = processExpression(ctx, exp)
switch (name) {
case 'bind': {
@@ -370,7 +385,7 @@ function transformProp(
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_PROP,
loc: node.loc,
- element: ctx.getId(),
+ element: ctx.reference(),
name: node.arg.content,
value: expr,
})
@@ -401,7 +416,7 @@ function transformProp(
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_EVENT,
loc: node.loc,
- element: ctx.getId(),
+ element: ctx.reference(),
name: node.arg.content,
value: expr,
})
@@ -412,7 +427,7 @@ function transformProp(
ctx.registerEffect(value, {
type: IRNodeTypes.SET_HTML,
loc: node.loc,
- element: ctx.getId(),
+ element: ctx.reference(),
value,
})
break
@@ -422,7 +437,7 @@ function transformProp(
ctx.registerEffect(value, {
type: IRNodeTypes.SET_TEXT,
loc: node.loc,
- element: ctx.getId(),
+ element: ctx.reference(),
value,
})
break
diff --git a/packages/runtime-vapor/__tests__/template.spec.ts b/packages/runtime-vapor/__tests__/template.spec.ts
index 32ea085f0..df7117221 100644
--- a/packages/runtime-vapor/__tests__/template.spec.ts
+++ b/packages/runtime-vapor/__tests__/template.spec.ts
@@ -18,12 +18,13 @@ describe('api: template', () => {
test('create fragment', () => {
const frag = fragment()
+
const root = frag()
- expect(root).toBeInstanceOf(DocumentFragment)
- expect(root.childNodes.length).toBe(0)
+ expect(root).toBeInstanceOf(Array)
+ expect(root.length).toBe(0)
- const div2 = frag()
- expect(div2).toBeInstanceOf(DocumentFragment)
- expect(div2).not.toBe(root)
+ const root2 = frag()
+ expect(root2).toBeInstanceOf(Array)
+ expect(root2).not.toBe(root)
})
})
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 43ce1ad93..ed276c356 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -7,6 +7,7 @@ import {
import { isArray } from '@vue/shared'
export type Block = Node | Fragment | Block[]
+export type ParentBlock = ParentNode | Node[]
export type Fragment = { nodes: Block; anchor: Node }
export type BlockFn = (props?: any) => Block
@@ -57,6 +58,24 @@ export function insert(
// }
}
+export function prepend(parent: ParentBlock, ...nodes: Node[]) {
+ if (parent instanceof Node) {
+ // TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1
+ parent.prepend(...nodes)
+ } else if (isArray(parent)) {
+ parent.unshift(...nodes)
+ }
+}
+
+export function append(parent: ParentBlock, ...nodes: Node[]) {
+ if (parent instanceof Node) {
+ // TODO use insertBefore for better performance
+ parent.append(...nodes)
+ } else if (isArray(parent)) {
+ parent.push(...nodes)
+ }
+}
+
export function remove(block: Block, parent: ParentNode) {
if (block instanceof Node) {
parent.removeChild(block)
@@ -124,6 +143,6 @@ export function children(n: ChildNode): Children {
return { ...Array.from(n.childNodes).map(n => [n, children(n)]) }
}
-export function createTextNode(data: string): Text {
- return document.createTextNode(data)
+export function createTextNode(val: unknown): Text {
+ return document.createTextNode(toDisplayString(val))
}
diff --git a/packages/runtime-vapor/src/template.ts b/packages/runtime-vapor/src/template.ts
index 9f0ae6c49..aa3858b24 100644
--- a/packages/runtime-vapor/src/template.ts
+++ b/packages/runtime-vapor/src/template.ts
@@ -19,6 +19,6 @@ export const template = (str: string): (() => Node) => {
}
}
-export function fragment(): () => DocumentFragment {
- return () => document.createDocumentFragment()
+export function fragment(): () => Node[] {
+ return () => []
}
diff --git a/playground/src/all-dynamic.vue b/playground/src/dynamic-all.vue
similarity index 100%
rename from playground/src/all-dynamic.vue
rename to playground/src/dynamic-all.vue
diff --git a/playground/src/dynamic-mixed-mid.vue b/playground/src/dynamic-mixed-mid.vue
new file mode 100644
index 000000000..6376819f2
--- /dev/null
+++ b/playground/src/dynamic-mixed-mid.vue
@@ -0,0 +1,4 @@
+
+ 1{{ 2 }}{{ 3 }}4
+ div
+
diff --git a/playground/src/dynamic.vue b/playground/src/dynamic-mixed.vue
similarity index 100%
rename from playground/src/dynamic.vue
rename to playground/src/dynamic-mixed.vue
diff --git a/playground/src/dynamic-mixed2.vue b/playground/src/dynamic-mixed2.vue
new file mode 100644
index 000000000..8a3787e7b
--- /dev/null
+++ b/playground/src/dynamic-mixed2.vue
@@ -0,0 +1,3 @@
+
+ {{ 1 }}{{ 2 }}3{{ 4 }}{{ 5 }}6{{ 7 }}{{ 8 }}9{{ 'A' }}{{ 'B' }}
+
diff --git a/playground/src/App-fragment.vue b/playground/src/fragment.vue
similarity index 100%
rename from playground/src/App-fragment.vue
rename to playground/src/fragment.vue
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fcdc682d9..4496dfcd9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -261,17 +261,10 @@ importers:
dependencies:
'@vue/compiler-dom':
specifier: 3.3.8
- version: link:../compiler-dom
+ version: 3.3.8
'@vue/shared':
specifier: 3.3.8
- version: link:../shared
- ast-kit:
- specifier: ^0.11.2
- version: 0.11.2(rollup@4.1.4)
- devDependencies:
- '@babel/types':
- specifier: ^7.23.0
- version: 7.23.3
+ version: 3.3.8
packages/dts-built-test:
dependencies:
@@ -1361,12 +1354,14 @@ packages:
estree-walker: 2.0.2
picomatch: 2.3.1
rollup: 4.1.4
+ dev: true
/@rollup/rollup-android-arm-eabi@4.1.4:
resolution: {integrity: sha512-WlzkuFvpKl6CLFdc3V6ESPt7gq5Vrimd2Yv9IzKXdOpgbH4cdDSS1JLiACX8toygihtH5OlxyQzhXOph7Ovlpw==}
cpu: [arm]
os: [android]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-android-arm-eabi@4.4.1:
@@ -1382,6 +1377,7 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-android-arm64@4.4.1:
@@ -1397,6 +1393,7 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-darwin-arm64@4.4.1:
@@ -1412,6 +1409,7 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-darwin-x64@4.4.1:
@@ -1427,6 +1425,7 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.4.1:
@@ -1442,6 +1441,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-linux-arm64-gnu@4.4.1:
@@ -1457,6 +1457,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-linux-arm64-musl@4.4.1:
@@ -1472,6 +1473,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-linux-x64-gnu@4.4.1:
@@ -1487,6 +1489,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-linux-x64-musl@4.4.1:
@@ -1502,6 +1505,7 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-win32-arm64-msvc@4.4.1:
@@ -1517,6 +1521,7 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-win32-ia32-msvc@4.4.1:
@@ -1532,6 +1537,7 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@rollup/rollup-win32-x64-msvc@4.4.1:
@@ -1567,6 +1573,7 @@ packages:
/@types/estree@1.0.3:
resolution: {integrity: sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==}
+ dev: true
/@types/hash-sum@1.0.2:
resolution: {integrity: sha512-UP28RddqY8xcU0SCEp9YKutQICXpaAq9N8U2klqF5hegGha7KzTOL8EdhIIV3bOSGBzjEpN9bU/d+nNZBdJYVw==}
@@ -1808,6 +1815,22 @@ packages:
pretty-format: 29.7.0
dev: true
+ /@vue/compiler-core@3.3.8:
+ resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==}
+ dependencies:
+ '@babel/parser': 7.23.3
+ '@vue/shared': 3.3.8
+ estree-walker: 2.0.2
+ source-map-js: 1.0.2
+ dev: false
+
+ /@vue/compiler-dom@3.3.8:
+ resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==}
+ dependencies:
+ '@vue/compiler-core': 3.3.8
+ '@vue/shared': 3.3.8
+ dev: false
+
/@vue/consolidate@0.17.3:
resolution: {integrity: sha512-nl0SWcTMzaaTnJ5G6V8VlMDA1CVVrNnaQKF1aBZU3kXtjgU9jtHMsEAsgjoRUx+T0EVJk9TgbmxGhK3pOk22zw==}
engines: {node: '>= 0.12.0'}
@@ -1817,6 +1840,10 @@ packages:
resolution: {integrity: sha512-zzyb+tVvzmOePv8Gp4sefP/7CKidx4WiJDfKPP698b9bN5jSFtmSOg4nvPoJEE1ICKeAEgdRKVneYJ8Mp7C/WA==}
dev: false
+ /@vue/shared@3.3.8:
+ resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
+ dev: false
+
/@zeit/schemas@2.29.0:
resolution: {integrity: sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==}
dev: true
@@ -2019,17 +2046,6 @@ packages:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
- /ast-kit@0.11.2(rollup@4.1.4):
- resolution: {integrity: sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==}
- engines: {node: '>=16.14.0'}
- dependencies:
- '@babel/parser': 7.23.3
- '@rollup/pluginutils': 5.0.5(rollup@4.1.4)
- pathe: 1.1.1
- transitivePeerDependencies:
- - rollup
- dev: false
-
/ast-types@0.13.4:
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
engines: {node: '>=4'}
@@ -3313,6 +3329,7 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
+ dev: true
optional: true
/function-bind@1.1.2:
@@ -4805,6 +4822,7 @@ packages:
/pathe@1.1.1:
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
+ dev: true
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
@@ -4820,6 +4838,7 @@ packages:
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
+ dev: true
/pidtree@0.3.1:
resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
@@ -5356,6 +5375,7 @@ packages:
'@rollup/rollup-win32-ia32-msvc': 4.1.4
'@rollup/rollup-win32-x64-msvc': 4.1.4
fsevents: 2.3.3
+ dev: true
/rollup@4.4.1:
resolution: {integrity: sha512-idZzrUpWSblPJX66i+GzrpjKE3vbYrlWirUHteoAbjKReZwa0cohAErOYA5efoMmNCdvG9yrJS+w9Kl6csaH4w==}