diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
index 41e2e776b..d2cd54c9e 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
@@ -157,9 +157,9 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
const _component_Comp = _resolveComponent("Comp")
const n0 = t0()
const n3 = t1()
+ const n2 = _child(n3, 1)
_setInsertionState(n3, 0)
const n1 = _createComponentWithFallback(_component_Comp)
- const n2 = _child(n3)
_renderEffect(() => {
_setProp(n3, "id", _ctx.foo)
_setText(n2, _toDisplayString(_ctx.bar))
@@ -212,6 +212,30 @@ export function render(_ctx) {
}"
`;
+exports[`compile > execution order > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
+"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap
index 2d4da87c3..9dc329f21 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap
@@ -47,7 +47,7 @@ const t0 = _template("
", true)
export function render(_ctx) {
const n1 = t0()
- const n0 = _child(n1)
+ const n0 = _child(n1, 0)
const x1 = _txt(n1)
_renderEffect(() => {
const _foo = _ctx.foo
@@ -86,7 +86,7 @@ const t0 = _template("
", true)
export function render(_ctx) {
const n1 = t0()
- const n0 = _child(n1)
+ const n0 = _child(n1, 0)
const x1 = _txt(n1)
_renderEffect(() => {
const _String = String
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap
index c56b68332..99f64c8cf 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap
@@ -7,7 +7,7 @@ const t1 = _template("
", true)
export function render(_ctx) {
const n4 = t1()
- const n3 = _next(_child(n4))
+ const n3 = _next(_child(n4), 1)
_setInsertionState(n4, n3)
const n0 = _createIf(() => (1), () => {
const n2 = t0()
@@ -23,9 +23,9 @@ const t0 = _template("
", true)
export function render(_ctx) {
const n3 = t0()
- const n0 = _child(n3)
- const n1 = _next(n0)
- const n2 = _next(n1)
+ const n0 = _child(n3, 0)
+ const n1 = _next(n0, 1)
+ const n2 = _next(n1, 2)
const x0 = _txt(n0)
const x2 = _txt(n2)
_renderEffect(() => {
@@ -43,7 +43,7 @@ const t0 = _template("
", true)
export function render(_ctx) {
const n1 = t0()
- const n0 = _nthChild(n1, 2)
+ const n0 = _nthChild(n1, 2, 2)
const x0 = _txt(n0)
_renderEffect(() => _setText(x0, _toDisplayString(_ctx.msg)))
return n1
@@ -56,12 +56,12 @@ const t0 = _template("
x
(_ctx.list), (_for_item0) => {
const n3 = _createComponentWithFallback(_component_Comp)
- const n2 = _child(n3)
+ const n2 = _child(n3, 0)
_renderEffect(() => _setText(n2, _toDisplayString(_for_item0.value)))
return [n2, n3]
}, undefined, 2)
@@ -259,7 +259,7 @@ export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n3 = _createComponentWithFallback(_component_Comp)
- const n2 = _child(n3)
+ const n2 = _child(n3, 0)
_renderEffect(() => _setText(n2, _toDisplayString(_for_item0.value)))
return [n2, n3]
}, undefined, 2)
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
index 4ca745ef0..2fcd18da1 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
@@ -17,8 +17,8 @@ const t0 = _template("
", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n2 = t0()
- const n0 = _child(n2)
- const n1 = _next(n0)
+ const n0 = _child(n2, 0)
+ const n1 = _next(n0, 1)
_setText(n0, _toDisplayString(_ctx.msg) + " ")
_setClass(n1, _ctx.clz)
return n2
@@ -54,7 +54,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n1 = t0()
- const n0 = _child(n1)
+ const n0 = _child(n1, 0)
_setProp(n0, "id", _ctx.foo)
return n1
}"
diff --git a/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts
index 2d8ae8c96..d41ed2ec4 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts
@@ -56,7 +56,7 @@ describe('compiler: children transform', () => {
{{ msg }}
`,
)
- expect(code).contains(`const n0 = _nthChild(n1, 2)`)
+ expect(code).contains(`const n0 = _nthChild(n1, 2, 2)`)
expect(code).toMatchSnapshot()
})
@@ -69,7 +69,7 @@ describe('compiler: children transform', () => {
`,
)
// ensure the insertion anchor is generated before the insertion statement
- expect(code).toMatch(`const n3 = _next(_child(n4))`)
+ expect(code).toMatch(`const n3 = _next(_child(n4), 1)`)
expect(code).toMatch(`_setInsertionState(n4, n3)`)
expect(code).toMatchSnapshot()
})
diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts
index 9ad5da121..40fa8da63 100644
--- a/packages/compiler-vapor/src/generators/block.ts
+++ b/packages/compiler-vapor/src/generators/block.ts
@@ -71,7 +71,7 @@ export function genBlockContent(
}
for (const child of dynamic.children) {
if (!child.hasDynamicChild) {
- push(...genChildren(child, context, `n${child.id!}`))
+ push(...genChildren(child, context, push, `n${child.id!}`))
}
}
diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts
index c22d0bf98..96fbd92b0 100644
--- a/packages/compiler-vapor/src/generators/template.ts
+++ b/packages/compiler-vapor/src/generators/template.ts
@@ -1,5 +1,9 @@
import type { CodegenContext } from '../generate'
-import { DynamicFlag, type IRDynamicInfo } from '../ir'
+import {
+ DynamicFlag,
+ type IRDynamicInfo,
+ type InsertionStateTypes,
+} from '../ir'
import { genDirectivesForElement } from './directive'
import { genOperationWithInsertionState } from './operation'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
@@ -36,7 +40,7 @@ export function genSelf(
}
if (hasDynamicChild) {
- push(...genChildren(dynamic, context, `n${id}`))
+ push(...genChildren(dynamic, context, push, `n${id}`))
}
return frag
@@ -45,6 +49,7 @@ export function genSelf(
export function genChildren(
dynamic: IRDynamicInfo,
context: CodegenContext,
+ pushBlock: (...items: CodeFragment[]) => number,
from: string = `n${dynamic.id}`,
): CodeFragment[] {
const { helper } = context
@@ -53,10 +58,20 @@ export function genChildren(
let offset = 0
let prev: [variable: string, elementIndex: number] | undefined
+ let ifBranchCount = 0
+ let prependCount = 0
for (const [index, child] of children.entries()) {
+ if (
+ child.operation &&
+ (child.operation as InsertionStateTypes).anchor === -1
+ ) {
+ prependCount++
+ }
if (child.flags & DynamicFlag.NON_TEMPLATE) {
offset--
+ } else if (child.ifBranch) {
+ ifBranchCount++
}
const id =
@@ -72,29 +87,41 @@ export function genChildren(
}
const elementIndex = index + offset
+ const logicalIndex = elementIndex - ifBranchCount + prependCount
// p for "placeholder" variables that are meant for possible reuse by
// other access paths
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
- push(NEWLINE, `const ${variable} = `)
-
+ pushBlock(NEWLINE, `const ${variable} = `)
if (prev) {
if (elementIndex - prev[1] === 1) {
- push(...genCall(helper('next'), prev[0]))
+ pushBlock(...genCall(helper('next'), prev[0], String(logicalIndex)))
} else {
- push(...genCall(helper('nthChild'), from, String(elementIndex)))
+ pushBlock(
+ ...genCall(
+ helper('nthChild'),
+ from,
+ String(elementIndex),
+ String(logicalIndex),
+ ),
+ )
}
} else {
if (elementIndex === 0) {
- push(...genCall(helper('child'), from))
+ pushBlock(...genCall(helper('child'), from, String(logicalIndex)))
} else {
// check if there's a node that we can reuse from
let init = genCall(helper('child'), from)
if (elementIndex === 1) {
- init = genCall(helper('next'), init)
+ init = genCall(helper('next'), init, String(logicalIndex))
} else if (elementIndex > 1) {
- init = genCall(helper('nthChild'), from, String(elementIndex))
+ init = genCall(
+ helper('nthChild'),
+ from,
+ String(elementIndex),
+ String(logicalIndex),
+ )
}
- push(...init)
+ pushBlock(...init)
}
}
@@ -107,7 +134,7 @@ export function genChildren(
}
prev = [variable, elementIndex]
- push(...genChildren(child, context, variable))
+ push(...genChildren(child, context, pushBlock, variable))
}
return frag
diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts
index bce00283e..5bea27fc0 100644
--- a/packages/compiler-vapor/src/ir/index.ts
+++ b/packages/compiler-vapor/src/ir/index.ts
@@ -272,6 +272,7 @@ export interface IRDynamicInfo {
hasDynamicChild?: boolean
operation?: OperationNode
needsKey?: boolean
+ ifBranch?: boolean
}
export interface IREffect {
diff --git a/packages/compiler-vapor/src/transforms/transformText.ts b/packages/compiler-vapor/src/transforms/transformText.ts
index e9c273b85..dd81bec1e 100644
--- a/packages/compiler-vapor/src/transforms/transformText.ts
+++ b/packages/compiler-vapor/src/transforms/transformText.ts
@@ -16,6 +16,7 @@ import {
isConstantExpression,
isStaticExpression,
} from '../utils'
+import { escapeHtml } from '@vue/shared'
type TextLike = TextNode | InterpolationNode
const seen = new WeakMap<
@@ -82,7 +83,7 @@ export const transformText: NodeTransform = (node, context) => {
} else if (node.type === NodeTypes.INTERPOLATION) {
processInterpolation(context as TransformContext
)
} else if (node.type === NodeTypes.TEXT) {
- context.template += node.content
+ context.template += escapeHtml(node.content)
}
}
@@ -143,7 +144,7 @@ function processTextContainer(
const literals = values.map(getLiteralExpressionValue)
if (literals.every(l => l != null)) {
- context.childrenTemplate = literals.map(l => String(l))
+ context.childrenTemplate = literals.map(l => escapeHtml(String(l)))
} else {
context.childrenTemplate = [' ']
context.registerOperation({
diff --git a/packages/compiler-vapor/src/transforms/vIf.ts b/packages/compiler-vapor/src/transforms/vIf.ts
index 2426fa021..531d29b05 100644
--- a/packages/compiler-vapor/src/transforms/vIf.ts
+++ b/packages/compiler-vapor/src/transforms/vIf.ts
@@ -59,6 +59,7 @@ export function processIf(
} else {
// check the adjacent v-if
const siblingIf = getSiblingIf(context, true)
+ context.dynamic.ifBranch = true
const siblings = context.parent && context.parent.dynamic.children
let lastIfNode
diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts
index 346d2f813..0dc70bcd8 100644
--- a/packages/runtime-core/src/components/Teleport.ts
+++ b/packages/runtime-core/src/components/Teleport.ts
@@ -394,7 +394,7 @@ function moveTeleport(
}
}
-interface TeleportTargetElement extends Element {
+export interface TeleportTargetElement extends Element {
// last teleport target
_lpa?: Node | null
}
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index 34ae21809..1a5a3d2fd 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -659,6 +659,17 @@ export function createHydrationFunctions(
)
}
}
+
+ // the server output does not contain blank text nodes. It appears here that
+ // it is a dynamically inserted anchor, and needs to be skipped.
+ // e.g. vaporInteropImpl.mount() > selfAnchor
+ if (
+ node &&
+ node.nodeType === DOMNodeTypes.TEXT &&
+ !(node as Text).data.trim()
+ ) {
+ node = nextSibling(node)
+ }
return node
}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index bbbd3d183..b7d811710 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -584,6 +584,13 @@ export {
isTeleportDisabled,
isTeleportDeferred,
} from './components/Teleport'
+/**
+ * @internal
+ */
+export type { TeleportTargetElement } from './components/Teleport'
+/**
+ * @internal
+ */
export {
createAsyncComponentContext,
useAsyncComponentState,
diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts
index d8ca5a606..ad26b4fd9 100644
--- a/packages/runtime-vapor/__tests__/hydration.spec.ts
+++ b/packages/runtime-vapor/__tests__/hydration.spec.ts
@@ -5,6 +5,8 @@ import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
import * as VueServerRenderer from '@vue/server-renderer'
import { isString } from '@vue/shared'
+import type { VaporComponentInstance } from '../src/component'
+import type { TeleportFragment } from '../src/components/Teleport'
const formatHtml = (raw: string) => {
return raw
@@ -77,22 +79,34 @@ async function testWithVDOMApp(
})
}
+function compileVaporComponent(
+ code: string,
+ data: runtimeDom.Ref = ref({}),
+ components?: Record,
+ ssr = false,
+) {
+ return compile(`${code}`, data, components, {
+ vapor: true,
+ ssr,
+ })
+}
+
async function mountWithHydration(
html: string,
code: string,
- data: runtimeDom.Ref,
+ data: runtimeDom.Ref = ref({}),
+ components?: Record,
) {
const container = document.createElement('div')
container.innerHTML = html
+ document.body.appendChild(container)
- const clientComp = compile(`${code}`, data, undefined, {
- vapor: true,
- ssr: false,
- })
+ const clientComp = compileVaporComponent(code, data, components)
const app = createVaporSSRApp(clientComp)
app.mount(container)
return {
+ block: (app._instance! as VaporComponentInstance).block,
container,
}
}
@@ -298,7 +312,7 @@ describe('Vapor Mode hydration', () => {
`)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`""`)
- expect(`Hydration children mismatch in `).not.toHaveBeenWarned()
+ expect(`mismatch in
`).not.toHaveBeenWarned()
})
test('root with mixed element and text', async () => {
@@ -331,7 +345,7 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`"
"`,
)
- expect(`Hydration children mismatch in
`).not.toHaveBeenWarned()
+ expect(`mismatch in
`).not.toHaveBeenWarned()
})
test('element with binding and text children', async () => {
@@ -1588,7 +1602,8 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
-
abc"
+
abc
+ "
`,
)
@@ -1597,7 +1612,35 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
-
abcd"
+
abcd
+ "
+ `,
+ )
+ })
+
+ test('empty v-for', async () => {
+ const { container, data } = await testHydration(
+ `
+ {{ item }}
+ `,
+ undefined,
+ ref([]),
+ )
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `
+ "
+
+ "
+ `,
+ )
+
+ data.value.push('a')
+ await nextTick()
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `
+ "
+
a
+ "
`,
)
})
@@ -1619,7 +1662,8 @@ describe('Vapor Mode hydration', () => {
`
"
- abc
3
+
abc
+
3
"
`,
)
@@ -1630,7 +1674,8 @@ describe('Vapor Mode hydration', () => {
`
"
- abcd
4
+
abcd
+
4
"
`,
)
@@ -1651,7 +1696,8 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
- abc
"
+
abc
+
"
`,
)
@@ -1660,7 +1706,8 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
- abcd
"
+
abcd
+
"
`,
)
@@ -1669,7 +1716,8 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
- bcd
"
+ bcd
+ "
`,
)
})
@@ -1690,8 +1738,9 @@ describe('Vapor Mode hydration', () => {
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"