wip(vapor): adjust children and block generation order for hydration

This commit is contained in:
Evan You 2025-03-11 15:09:36 +08:00
parent e3a33e6092
commit 9722574744
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
26 changed files with 946 additions and 1009 deletions

View File

@ -149,7 +149,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
`;
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
"import { resolveComponent as _resolveComponent, child as _child, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
const t1 = _template("<div> </div>")
@ -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)
_setInsertionState(n3, 0)
const n1 = _createComponentWithFallback(_component_Comp)
const n2 = _child(n3)
_renderEffect(() => {
_setText(n2, _toDisplayString(_ctx.bar))
_setProp(n3, "id", _ctx.foo)

View File

@ -40,10 +40,10 @@ const t0 = _template("<div><div>x</div><div><span> </span></div><div><span> </sp
export function render(_ctx) {
const n3 = t0()
const p0 = _next(_child(n3))
const n0 = _child(p0)
const p1 = _next(p0)
const n1 = _child(p1)
const p2 = _next(p1)
const n0 = _child(p0)
const n1 = _child(p1)
const n2 = _child(p2)
const x0 = _child(n0)
const x1 = _child(n1)

View File

@ -29,8 +29,8 @@ exports[`compiler: element transform > component > generate multi root component
const t0 = _template("123")
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n1 = t0()
const n0 = _createComponent(_ctx.Comp)
const n1 = t0()
return [n0, n1]
}"
`;
@ -321,8 +321,8 @@ const t2 = _template("<form></form>")
export function render(_ctx) {
const n1 = t1()
const n0 = t0()
const n3 = t2()
const n0 = t0()
const n2 = t2()
_insert(n0, n1)
_insert(n2, n3)

View File

@ -25,7 +25,6 @@ const t4 = _template("fine")
const t5 = _template("<div> </div>")
export function render(_ctx) {
const n13 = t5()
const n0 = _createIf(() => (_ctx.ok), () => {
const n2 = t0()
return n2
@ -38,6 +37,7 @@ export function render(_ctx) {
const n11 = t4()
return [n10, n11]
}))
const n13 = t5()
const x13 = _child(n13)
_renderEffect(() => _setText(x13, _toDisplayString(_ctx.text)))
return [n0, n13]

View File

@ -167,7 +167,6 @@ export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n5 = _createComponentWithFallback(_component_Comp, null, {
"default": (_slotProps0) => {
const n3 = t0()
const n1 = _createComponentWithFallback(_component_Inner, null, {
"default": (_slotProps1) => {
const n0 = t0()
@ -175,6 +174,7 @@ export function render(_ctx) {
return n0
}
})
const n3 = t0()
_renderEffect(() => _setText(n3, " " + _toDisplayString(_slotProps0["foo"] + _ctx.bar + _ctx.baz)))
return [n1, n3]
}

View File

@ -29,16 +29,14 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(helpers).contains.all.keys('resolveComponent')
expect(helpers).contains.all.keys('createComponentWithFallback')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Foo',
asset: true,
root: true,
props: [[]],
},
])
})
})
test.todo('resolve implicitly self-referencing component', () => {
@ -57,13 +55,11 @@ describe('compiler: element transform', () => {
})
expect(code).toMatchSnapshot()
expect(helpers).not.toContain('resolveComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Example',
asset: false,
},
])
})
})
test('resolve component from setup bindings (inline)', () => {
@ -149,14 +145,12 @@ describe('compiler: element transform', () => {
})
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Example',
asset: true,
},
])
})
})
test('generate single root component', () => {
@ -186,8 +180,7 @@ describe('compiler: element transform', () => {
class: () => ("bar")
}`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
asset: true,
@ -224,8 +217,7 @@ describe('compiler: element transform', () => {
},
],
],
},
])
})
})
test('v-bind="obj"', () => {
@ -234,8 +226,7 @@ describe('compiler: element transform', () => {
expect(code).contains(`[
() => (_ctx.obj)
]`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -244,8 +235,7 @@ describe('compiler: element transform', () => {
value: { content: 'obj', isStatic: false },
},
],
},
])
})
})
test('v-bind="obj" after static prop', () => {
@ -259,8 +249,7 @@ describe('compiler: element transform', () => {
() => (_ctx.obj)
]
}`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -270,8 +259,7 @@ describe('compiler: element transform', () => {
value: { content: 'obj' },
},
],
},
])
})
})
test('v-bind="obj" before static prop', () => {
@ -283,8 +271,7 @@ describe('compiler: element transform', () => {
() => (_ctx.obj),
{ id: () => ("foo") }
]`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -294,8 +281,7 @@ describe('compiler: element transform', () => {
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
])
})
})
test('v-bind="obj" between static props', () => {
@ -310,8 +296,7 @@ describe('compiler: element transform', () => {
{ class: () => ("bar") }
]
}`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -322,8 +307,7 @@ describe('compiler: element transform', () => {
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
])
})
})
test.todo('props merging: event handlers', () => {
@ -368,8 +352,7 @@ describe('compiler: element transform', () => {
expect(code).contains(`[
() => (_toHandlers(_ctx.obj))
]`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -379,8 +362,7 @@ describe('compiler: element transform', () => {
handler: true,
},
],
},
])
})
})
test('v-on expression is inline statement', () => {
@ -390,8 +372,7 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(code).contains(`onBar: () => _on_bar`)
expect(code).contains(`const _on_bar = () => _ctx.handler`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -403,8 +384,7 @@ describe('compiler: element transform', () => {
},
],
],
},
])
})
})
test('v-on expression is a function call', () => {
@ -416,8 +396,7 @@ describe('compiler: element transform', () => {
expect(code).contains(
`const _on_bar = $event => (_ctx.handleBar($event))`,
)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -429,8 +408,7 @@ describe('compiler: element transform', () => {
},
],
],
},
])
})
})
test('cache v-on expression with unique handler name', () => {
@ -444,8 +422,7 @@ describe('compiler: element transform', () => {
)
expect(code).contains(`onBar: () => _on_bar1`)
expect(code).contains(`const _on_bar1 = () => _ctx.handler`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -457,8 +434,9 @@ describe('compiler: element transform', () => {
},
],
],
},
{
})
expect(ir.block.dynamic.children[1].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Bar',
props: [
@ -470,8 +448,7 @@ describe('compiler: element transform', () => {
},
],
],
},
])
})
})
})
@ -482,8 +459,7 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
@ -494,8 +470,7 @@ describe('compiler: element transform', () => {
content: 'foo',
isStatic: true,
},
},
])
})
})
test('capitalized version w/ static binding', () => {
@ -504,8 +479,7 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Component',
asset: true,
@ -516,8 +490,7 @@ describe('compiler: element transform', () => {
content: 'foo',
isStatic: true,
},
},
])
})
})
test('dynamic binding', () => {
@ -526,8 +499,7 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
@ -538,8 +510,7 @@ describe('compiler: element transform', () => {
content: 'foo',
isStatic: false,
},
},
])
})
})
test('dynamic binding shorthand', () => {
@ -547,8 +518,7 @@ describe('compiler: element transform', () => {
compileWithElementTransform(`<component :is />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
@ -559,8 +529,7 @@ describe('compiler: element transform', () => {
content: 'is',
isStatic: false,
},
},
])
})
})
// #3934
@ -574,15 +543,13 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveComponent')
expect(helpers).not.toContain('resolveDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'custom-input',
asset: true,
root: true,
props: [[{ key: { content: 'is' }, values: [{ content: 'foo' }] }]],
},
])
})
})
})
@ -893,8 +860,7 @@ describe('compiler: element transform', () => {
`<Foo :[foo-bar]="bar" :[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -909,8 +875,7 @@ describe('compiler: element transform', () => {
values: [{ content: 'qux' }],
},
],
},
])
})
})
test('component with dynamic event arguments', () => {
@ -918,8 +883,7 @@ describe('compiler: element transform', () => {
`<Foo @[foo-bar]="bar" @[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
@ -936,8 +900,7 @@ describe('compiler: element transform', () => {
handler: true,
},
],
},
])
})
})
test('component event with once modifier', () => {

View File

@ -31,8 +31,7 @@ describe('compiler: transform <slot> outlets', () => {
expect(code).toMatchSnapshot()
expect(helpers).toContain('createSlot')
expect(ir.block.effect).toEqual([])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
@ -42,15 +41,13 @@ describe('compiler: transform <slot> outlets', () => {
},
props: [],
fallback: undefined,
},
])
})
})
test('statically named slot outlet', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot name="foo" />`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
@ -58,15 +55,13 @@ describe('compiler: transform <slot> outlets', () => {
content: 'foo',
isStatic: true,
},
},
])
})
})
test('dynamically named slot outlet', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot :name="foo + bar" />`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
@ -74,15 +69,13 @@ describe('compiler: transform <slot> outlets', () => {
content: 'foo + bar',
isStatic: false,
},
},
])
})
})
test('dynamically named slot outlet with v-bind shorthand', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot :name />`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
@ -90,8 +83,7 @@ describe('compiler: transform <slot> outlets', () => {
content: 'name',
isStatic: false,
},
},
])
})
})
test('default slot outlet with props', () => {
@ -99,8 +91,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'default' },
props: [
@ -110,8 +101,7 @@ describe('compiler: transform <slot> outlets', () => {
{ key: { content: 'fooBar' }, values: [{ content: 'foo-bar' }] },
],
],
},
])
})
})
test('statically named slot outlet with props', () => {
@ -119,8 +109,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo" foo="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
@ -129,8 +118,7 @@ describe('compiler: transform <slot> outlets', () => {
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
],
],
},
])
})
})
test('statically named slot outlet with v-bind="obj"', () => {
@ -138,8 +126,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo" foo="bar" v-bind="obj" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
@ -147,8 +134,7 @@ describe('compiler: transform <slot> outlets', () => {
{ value: { content: 'obj', isStatic: false } },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
},
])
})
})
test('statically named slot outlet with v-on', () => {
@ -156,24 +142,21 @@ describe('compiler: transform <slot> outlets', () => {
`<slot @click="foo" v-on="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
props: [
[{ key: { content: 'click' }, values: [{ content: 'foo' }] }],
{ value: { content: 'bar' }, handler: true },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
},
])
})
})
test('default slot outlet with fallback', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot><div/></slot>`)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
@ -184,8 +167,7 @@ describe('compiler: transform <slot> outlets', () => {
},
returns: [2],
},
},
])
})
})
test('named slot outlet with fallback', () => {
@ -194,8 +176,7 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
@ -206,8 +187,7 @@ describe('compiler: transform <slot> outlets', () => {
},
returns: [2],
},
},
])
})
})
test('default slot outlet with props & fallback', () => {
@ -216,8 +196,7 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
@ -229,8 +208,7 @@ describe('compiler: transform <slot> outlets', () => {
},
returns: [2],
},
},
])
})
})
test('named slot outlet with props & fallback', () => {
@ -239,8 +217,7 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
@ -252,8 +229,7 @@ describe('compiler: transform <slot> outlets', () => {
},
returns: [2],
},
},
])
})
})
test('error on unexpected custom directive on <slot>', () => {

View File

@ -86,10 +86,10 @@ describe('compiler: template ref transform', () => {
`<div ref="foo" v-if="true" />`,
)
expect(ir.block.operation).lengthOf(1)
expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF)
const op = ir.block.dynamic.children[0].operation as IfIRNode
expect(op.type).toBe(IRNodeTypes.IF)
const { positive } = ir.block.operation[0] as IfIRNode
const { positive } = op
expect(positive.operation).toMatchObject([
{
type: IRNodeTypes.SET_TEMPLATE_REF,
@ -111,7 +111,7 @@ describe('compiler: template ref transform', () => {
`<div ref="foo" v-for="item in [1,2,3]" />`,
)
const { render } = ir.block.operation[0] as ForIRNode
const { render } = ir.block.dynamic.children[0].operation as ForIRNode
expect(render.operation).toMatchObject([
{
type: IRNodeTypes.SET_TEMPLATE_REF,

View File

@ -33,8 +33,7 @@ describe('compiler: v-for', () => {
expect(code).matchSnapshot()
expect(helpers).contains('createFor')
expect(ir.template).toEqual(['<div> </div>'])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
source: {
@ -57,14 +56,15 @@ describe('compiler: v-for', () => {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item.id',
},
},
])
})
expect(ir.block.returns).toEqual([0])
expect(ir.block.dynamic).toMatchObject({
children: [{ id: 0 }],
})
expect(ir.block.effect).toEqual([])
expect((ir.block.operation[0] as ForIRNode).render.effect).lengthOf(1)
expect(
(ir.block.dynamic.children[0].operation as ForIRNode).render.effect,
).lengthOf(1)
})
test('multi effect', () => {
@ -90,8 +90,8 @@ describe('compiler: v-for', () => {
)
expect(code).contains(`_for_item1.value+_for_item0.value`)
expect(ir.template).toEqual(['<span> </span>', '<div></div>'])
expect(ir.block.operation).toMatchObject([
{
const parentOp = ir.block.dynamic.children[0].operation
expect(parentOp).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
source: { content: 'list' },
@ -102,9 +102,10 @@ describe('compiler: v-for', () => {
children: [{ template: 1 }],
},
},
},
])
expect((ir.block.operation[0] as any).render.operation[0]).toMatchObject({
})
expect(
(parentOp as any).render.dynamic.children[0].children[0].operation,
).toMatchObject({
type: IRNodeTypes.FOR,
id: 2,
source: { content: 'i' },
@ -123,7 +124,7 @@ describe('compiler: v-for', () => {
'<span v-for="({ id, value }) in items" :key="id">{{ id }}{{ value }}</span>',
)
expect(code).matchSnapshot()
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -152,7 +153,7 @@ describe('compiler: v-for', () => {
)
expect(code).matchSnapshot()
expect(code).toContain('_getRestElement(_for_item0.value, ["id"])')
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -183,7 +184,7 @@ describe('compiler: v-for', () => {
`<div v-for="([id, other], index) in list" :key="id">{{ id + other + index }}</div>`,
)
expect(code).matchSnapshot()
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -215,7 +216,7 @@ describe('compiler: v-for', () => {
)
expect(code).matchSnapshot()
expect(code).toContain('_for_item0.value.slice(1)')
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
@ -252,7 +253,7 @@ describe('compiler: v-for', () => {
expect(code).toContain(
`_getDefaultValue(_for_item0.value.baz[0], _ctx.quux)`,
)
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,

View File

@ -33,8 +33,9 @@ describe('compiler: v-if', () => {
expect(helpers).contains('createIf')
expect(ir.template).toEqual(['<div> </div>'])
expect(ir.block.operation).toMatchObject([
{
const op = ir.block.dynamic.children[0].operation
expect(op).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
condition: {
@ -48,8 +49,7 @@ describe('compiler: v-if', () => {
children: [{ template: 0 }],
},
},
},
])
})
expect(ir.block.returns).toEqual([0])
expect(ir.block.dynamic).toMatchObject({
@ -57,7 +57,7 @@ describe('compiler: v-if', () => {
})
expect(ir.block.effect).toEqual([])
expect((ir.block.operation[0] as IfIRNode).positive.effect).lengthOf(1)
expect((op as IfIRNode).positive.effect).lengthOf(1)
expect(code).matchSnapshot()
})
@ -70,7 +70,8 @@ describe('compiler: v-if', () => {
expect(ir.template).toEqual(['<div></div>', 'hello', '<p> </p>'])
expect(ir.block.effect).toEqual([])
expect((ir.block.operation[0] as IfIRNode).positive.effect).toMatchObject([
const op = ir.block.dynamic.children[0].operation as IfIRNode
expect(op.positive.effect).toMatchObject([
{
operations: [
{
@ -87,7 +88,7 @@ describe('compiler: v-if', () => {
],
},
])
expect((ir.block.operation[0] as IfIRNode).positive.dynamic).toMatchObject({
expect(op.positive.dynamic).toMatchObject({
id: 1,
children: {
2: {
@ -116,8 +117,7 @@ describe('compiler: v-if', () => {
expect(helpers).contains('createIf')
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
condition: {
@ -137,8 +137,7 @@ describe('compiler: v-if', () => {
children: [{ template: 1 }],
},
},
},
])
})
expect(ir.block.returns).toEqual([0])
})
@ -149,8 +148,7 @@ describe('compiler: v-if', () => {
expect(code).matchSnapshot()
expect(ir.template).toEqual(['<div></div>', '<p></p>'])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
condition: {
@ -178,8 +176,7 @@ describe('compiler: v-if', () => {
},
},
},
},
])
})
expect(ir.block.returns).toEqual([0])
})
@ -191,8 +188,7 @@ describe('compiler: v-if', () => {
expect(ir.template).toEqual(['<div></div>', '<p></p>', 'fine'])
expect(ir.block.returns).toEqual([0])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
positive: {
@ -216,8 +212,7 @@ describe('compiler: v-if', () => {
},
},
},
},
])
})
})
test('comment between branches', () => {

View File

@ -206,8 +206,7 @@ describe('compiler: vModel transform', () => {
expect(code).contains(
`"onUpdate:modelValue": () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -220,8 +219,7 @@ describe('compiler: vModel transform', () => {
},
],
],
},
])
})
})
test('v-model with arguments for component should work', () => {
@ -231,8 +229,7 @@ describe('compiler: vModel transform', () => {
expect(code).contains(
`"onUpdate:bar": () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -245,8 +242,7 @@ describe('compiler: vModel transform', () => {
},
],
],
},
])
})
})
test('v-model with dynamic arguments for component should work', () => {
@ -256,8 +252,7 @@ describe('compiler: vModel transform', () => {
`[_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -268,8 +263,7 @@ describe('compiler: vModel transform', () => {
modelModifiers: [],
},
],
},
])
})
})
test('v-model for component should generate modelModifiers', () => {
@ -280,8 +274,7 @@ describe('compiler: vModel transform', () => {
expect(code).contain(
`modelModifiers: () => ({ trim: true, "bar-baz": true })`,
)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -294,8 +287,7 @@ describe('compiler: vModel transform', () => {
},
],
],
},
])
})
})
test('v-model with arguments for component should generate modelModifiers', () => {
@ -305,8 +297,7 @@ describe('compiler: vModel transform', () => {
expect(code).toMatchSnapshot()
expect(code).contain(`fooModifiers: () => ({ trim: true })`)
expect(code).contain(`barModifiers: () => ({ number: true })`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -325,8 +316,7 @@ describe('compiler: vModel transform', () => {
},
],
],
},
])
})
})
test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
@ -336,8 +326,7 @@ describe('compiler: vModel transform', () => {
expect(code).toMatchSnapshot()
expect(code).contain(`[_ctx.foo + "Modifiers"]: () => ({ trim: true })`)
expect(code).contain(`[_ctx.bar + "Modifiers"]: () => ({ number: true })`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
@ -354,8 +343,7 @@ describe('compiler: vModel transform', () => {
modelModifiers: ['number'],
},
],
},
])
})
})
})
})

View File

@ -126,15 +126,13 @@ describe('compiler: v-once', () => {
const { ir, code } = compileWithOnce(`<div><Comp :id="foo" v-once /></div>`)
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Comp',
once: true,
parent: 1,
},
])
})
})
test.todo('on slot outlet')
@ -155,8 +153,7 @@ describe('compiler: v-once', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
once: true,
@ -171,8 +168,7 @@ describe('compiler: v-once', () => {
children: [{ template: 0 }],
},
},
},
])
})
})
test('with v-if/else', () => {
@ -182,8 +178,7 @@ describe('compiler: v-once', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
once: true,
@ -204,20 +199,17 @@ describe('compiler: v-once', () => {
children: [{ template: 1 }],
},
},
},
])
})
})
test('with v-for', () => {
const { ir, code } = compileWithOnce(`<div v-for="i in list" v-once />`)
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
once: true,
},
])
})
})
})

View File

@ -36,8 +36,7 @@ describe('compiler: transform slot', () => {
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
tag: 'Comp',
@ -55,8 +54,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
expect(ir.block.returns).toEqual([1])
expect(ir.block.dynamic).toMatchObject({
children: [{ id: 1 }],
@ -72,8 +70,7 @@ describe('compiler: transform slot', () => {
expect(code).contains(`"default": (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
@ -95,8 +92,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('on component named slot', () => {
@ -108,8 +104,7 @@ describe('compiler: transform slot', () => {
expect(code).contains(`"named": (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -126,8 +121,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('on component dynamically named slot', () => {
@ -139,8 +133,7 @@ describe('compiler: transform slot', () => {
expect(code).contains(`fn: (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -159,8 +152,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('named slots w/ implicit default slot', () => {
@ -172,8 +164,7 @@ describe('compiler: transform slot', () => {
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['foo', 'bar', '<span></span>'])
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
tag: 'Comp',
@ -197,8 +188,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('nested slots scoping', () => {
@ -219,8 +209,8 @@ describe('compiler: transform slot', () => {
expect(code).contains(`_slotProps0["foo"] + _slotProps1["bar"] + _ctx.baz`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar + _ctx.baz`)
expect(ir.block.operation).toMatchObject([
{
const outerOp = ir.block.dynamic.children[0].operation
expect(outerOp).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
@ -238,10 +228,9 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
expect(
(ir.block.operation[0] as any).slots[0].slots.default.operation[0],
(outerOp as any).slots[0].slots.default.dynamic.children[0].operation,
).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Inner',
@ -269,9 +258,7 @@ describe('compiler: transform slot', () => {
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -284,8 +271,7 @@ describe('compiler: transform slot', () => {
fn: { type: IRNodeTypes.BLOCK },
},
],
},
])
})
})
test('dynamic slots name w/ v-for', () => {
@ -299,9 +285,7 @@ describe('compiler: transform slot', () => {
expect(code).contains(`fn: (_slotProps0) =>`)
expect(code).contains(`_setText(n0, _toDisplayString(_slotProps0["bar"]))`)
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -319,8 +303,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('dynamic slots name w/ v-for and provide absent key', () => {
@ -330,9 +313,7 @@ describe('compiler: transform slot', () => {
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -352,8 +333,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('dynamic slots name w/ v-if / v-else[-if]', () => {
@ -368,9 +348,7 @@ describe('compiler: transform slot', () => {
expect(code).contains(`fn: (_slotProps0) =>`)
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
@ -390,8 +368,7 @@ describe('compiler: transform slot', () => {
},
},
],
},
])
})
})
test('quote slot name', () => {
@ -405,8 +382,7 @@ describe('compiler: transform slot', () => {
test('nested component slot', () => {
const { ir, code } = compileWithSlots(`<A><B/></A>`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'A',
slots: [
@ -415,19 +391,22 @@ describe('compiler: transform slot', () => {
slots: {
default: {
type: IRNodeTypes.BLOCK,
operation: [
dynamic: {
children: [
{
operation: {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'B',
slots: [],
},
},
],
},
},
},
},
],
},
])
})
})
describe('errors', () => {

View File

@ -11,7 +11,7 @@ import {
} from './utils'
import type { CodegenContext } from '../generate'
import { genEffects, genOperations } from './operation'
import { genChildren } from './template'
import { genChildren, genSelf } from './template'
import { toValidAssetId } from '@vue/compiler-dom'
export function genBlock(
@ -48,6 +48,9 @@ export function genBlockContent(
genResolveAssets('directive', 'resolveDirective')
}
for (const child of dynamic.children) {
push(...genSelf(child, context))
}
for (const child of dynamic.children) {
push(...genChildren(child, context, `n${child.id!}`))
}

View File

@ -3,7 +3,7 @@ import {
IRNodeTypes,
type InsertionStateTypes,
type OperationNode,
isTypeThatNeedsInsertionState,
isBlockOperation,
} from '../ir'
import type { CodegenContext } from '../generate'
import { genInsertNode, genPrependNode } from './dom'
@ -33,11 +33,20 @@ export function genOperations(
): CodeFragment[] {
const [frag, push] = buildCodeFragment()
for (const operation of opers) {
if (isTypeThatNeedsInsertionState(operation) && operation.parent) {
push(...genInsertionstate(operation, context))
push(...genOperationWithInsertionState(operation, context))
}
push(...genOperation(operation, context))
return frag
}
export function genOperationWithInsertionState(
oper: OperationNode,
context: CodegenContext,
): CodeFragment[] {
const [frag, push] = buildCodeFragment()
if (isBlockOperation(oper) && oper.parent) {
push(...genInsertionstate(oper, context))
}
push(...genOperation(oper, context))
return frag
}

View File

@ -1,6 +1,7 @@
import type { CodegenContext } from '../generate'
import { DynamicFlag, type IRDynamicInfo } from '../ir'
import { genDirectivesForElement } from './directive'
import { genOperationWithInsertionState } from './operation'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
export function genTemplates(
@ -18,24 +19,38 @@ export function genTemplates(
.join('')
}
export function genChildren(
export function genSelf(
dynamic: IRDynamicInfo,
context: CodegenContext,
from: string,
path: number[] = [],
knownPaths: [id: string, path: number[]][] = [],
): CodeFragment[] {
const { helper } = context
const [frag, push] = buildCodeFragment()
let offset = 0
const { children, id, template } = dynamic
const { id, template, operation } = dynamic
if (id !== undefined && template !== undefined) {
push(NEWLINE, `const n${id} = t${template}()`)
push(...genDirectivesForElement(id, context))
}
if (operation) {
push(...genOperationWithInsertionState(operation, context))
}
return frag
}
export function genChildren(
dynamic: IRDynamicInfo,
context: CodegenContext,
from: string = `n${dynamic.id}`,
): CodeFragment[] {
const { helper } = context
const [frag, push] = buildCodeFragment()
const { children } = dynamic
let offset = 0
let prev: [variable: string, elementIndex: number] | undefined
const childrenToGen: [IRDynamicInfo, string][] = []
for (const [index, child] of children.entries()) {
if (child.flags & DynamicFlag.NON_TEMPLATE) {
offset--
@ -49,17 +64,11 @@ export function genChildren(
: undefined
if (id === undefined && !child.hasDynamicChild) {
const { id, template } = child
if (id !== undefined && template !== undefined) {
push(NEWLINE, `const n${id} = t${template}()`)
push(...genDirectivesForElement(id, context))
}
push(...genSelf(child, context))
continue
}
const elementIndex = Number(index) + offset
const newPath = [...path, elementIndex]
// p for "placeholder" variables that are meant for possible reuse by
// other access paths
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
@ -72,58 +81,28 @@ export function genChildren(
push(...genCall(helper('nthChild'), from, String(elementIndex)))
}
} else {
if (newPath.length === 1 && newPath[0] === 0) {
if (elementIndex === 0) {
push(...genCall(helper('child'), from))
} else {
// check if there's a node that we can reuse from
let resolvedFrom = from
let resolvedPath = newPath
let skipFirstChild = false
outer: for (const [from, path] of knownPaths) {
const l = path.length
const tail = newPath.slice(l)
for (let i = 0; i < l; i++) {
const parentSeg = path[i]
const thisSeg = newPath[i]
if (parentSeg !== thisSeg) {
if (i === l - 1) {
// last bit is reusable
resolvedFrom = from
resolvedPath = [thisSeg - parentSeg, ...tail]
skipFirstChild = true
break outer
}
break
} else if (i === l - 1) {
// full overlap
resolvedFrom = from
resolvedPath = tail
break outer
}
}
}
let init
for (const i of resolvedPath) {
init = init
? genCall(helper('child'), init)
: skipFirstChild
? resolvedFrom
: genCall(helper('child'), resolvedFrom)
if (i === 1) {
let init = genCall(helper('child'), from)
if (elementIndex === 1) {
init = genCall(helper('next'), init)
} else if (i > 1) {
init = genCall(helper('nthChild'), resolvedFrom, String(i))
} else if (elementIndex > 1) {
init = genCall(helper('nthChild'), from, String(elementIndex))
}
}
push(...init!)
push(...init)
}
}
if (id !== undefined) {
push(...genDirectivesForElement(id, context))
}
knownPaths.unshift([variable, newPath])
prev = [variable, elementIndex]
push(...genChildren(child, context, variable))
childrenToGen.push([child, variable])
}
for (const [child, from] of childrenToGen) {
push(...genChildren(child, context, from))
}
return frag

View File

@ -270,6 +270,7 @@ export interface IRDynamicInfo {
children: IRDynamicInfo[]
template?: number
hasDynamicChild?: boolean
operation?: OperationNode
}
export interface IREffect {
@ -304,9 +305,7 @@ export type InsertionStateTypes =
| SlotOutletIRNode
| CreateComponentIRNode
export function isTypeThatNeedsInsertionState(
op: OperationNode,
): op is InsertionStateTypes {
export function isBlockOperation(op: OperationNode): op is InsertionStateTypes {
const type = op.type
return (
type === IRNodeTypes.CREATE_COMPONENT_NODE ||

View File

@ -8,7 +8,7 @@ import {
DynamicFlag,
type IRDynamicInfo,
IRNodeTypes,
isTypeThatNeedsInsertionState as isBlockOperation,
isBlockOperation,
} from '../ir'
export const transformChildren: NodeTransform = (node, context) => {
@ -102,14 +102,10 @@ function registerInsertion(
parent: context.reference(),
anchor,
})
} else {
} else if (child.operation && isBlockOperation(child.operation)) {
// block types
for (const op of context.block.operation) {
if (isBlockOperation(op) && op.id === child.id) {
op.parent = context.reference()
op.anchor = anchor
}
}
child.operation.parent = context.reference()
child.operation.anchor = anchor
}
}
}

View File

@ -124,7 +124,7 @@ function transformComponentElement(
}
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
context.registerOperation({
context.dynamic.operation = {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: context.reference(),
tag,
@ -134,7 +134,7 @@ function transformComponentElement(
slots: [...context.slots],
once: context.inVOnce,
dynamic: dynamicComponent,
})
}
context.slots = []
}

View File

@ -100,13 +100,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
return () => {
exitBlock && exitBlock()
context.registerOperation({
context.dynamic.operation = {
type: IRNodeTypes.SLOT_OUTLET_NODE,
id,
name: slotName,
props: irProps,
fallback,
})
}
}
}

View File

@ -67,7 +67,7 @@ export function processFor(
parent.block.node !== parent.node &&
parent.node.children.length === 1
context.registerOperation({
context.dynamic.operation = {
type: IRNodeTypes.FOR,
id,
source: source as SimpleExpressionNode,
@ -84,6 +84,6 @@ export function processFor(
),
component: isComponent,
onlyChild: !!isOnlyChild,
})
}
}
}

View File

@ -46,7 +46,7 @@ export function processIf(
return () => {
onExit()
context.registerOperation({
context.dynamic.operation = {
type: IRNodeTypes.IF,
id,
condition: dir.exp!,
@ -54,14 +54,20 @@ export function processIf(
once:
context.inVOnce ||
isStaticExpression(dir.exp!, context.options.bindingMetadata),
})
}
}
} else {
// check the adjacent v-if
const siblingIf = getSiblingIf(context, true)
const { operation } = context.block
let lastIfNode = operation[operation.length - 1]
const siblings = context.parent && context.parent.dynamic.children
let lastIfNode
if (siblings) {
let i = siblings.length
while (i--) {
if (siblings[i].operation) lastIfNode = siblings[i].operation
}
}
if (
// check if v-if is the sibling node

View File

@ -257,6 +257,63 @@ describe('SSR hydration', () => {
)
})
test('nested fragment components', async () => {
const t0 = template('<div> </div>')
const t1 = template(' ')
const msg = ref('foo')
const Comp = {
setup() {
const n0 = t0() as Element
const n1 = t1() as Text
const x0 = child(n0) as Text
renderEffect(() => {
const _msg = msg.value
setText(x0, toDisplayString(_msg))
setText(n1, toDisplayString(_msg))
})
return [n0, n1]
},
}
const t2 = template('<div></div>')
const Parent = {
setup() {
const n0 = t2()
const n1 = createComponent(Comp)
const n2 = t2()
return [n0, n1, n2]
},
}
const t3 = template('<div><span></span></div>', true)
const { container } = mountWithHydration(
'<div><!--[-->' +
'<div></div><!--[--><div>foo</div>foo<!--]--><div></div>' +
'<!--]--><span></span></div>',
() => {
const n1 = t3() as Element
setInsertionState(n1, 0)
createComponent(Parent)
return n1
},
)
expect(container.innerHTML).toBe(
'<div><!--[-->' +
'<div></div><!--[--><div>foo</div>foo<!--]--><div></div>' +
'<!--]--><span></span></div>',
)
msg.value = 'bar'
await nextTick()
expect(container.innerHTML).toBe(
'<div><!--[-->' +
'<div></div><!--[--><div>bar</div>bar<!--]--><div></div>' +
'<!--]--><span></span></div>',
)
})
// test('element with ref', () => {
// const el = ref()
// const { vnode, container } = mountWithHydration('<div></div>', () =>

View File

@ -59,11 +59,7 @@ import {
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration'
import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
import { insertionAnchor, insertionParent } from './insertionState'
export { currentInstance } from '@vue/runtime-dom'
@ -142,6 +138,8 @@ export function createComponent(
currentInstance.appContext) ||
emptyContext,
): VaporComponentInstance {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
}
@ -263,9 +261,8 @@ export function createComponent(
onScopeDispose(() => unmountComponent(instance), true)
if (!isHydrating && insertionParent) {
insert(instance.block, insertionParent, insertionAnchor)
resetInsertionState()
if (!isHydrating && _insertionParent) {
insert(instance.block, _insertionParent, _insertionAnchor)
}
return instance

View File

@ -28,6 +28,7 @@ export function withHydration(container: ParentNode, fn: () => void): void {
setInsertionState(container, 0)
const res = fn()
resetInsertionState()
currentHydrationNode = null
isHydrating = false
return res
}
@ -75,10 +76,6 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
}
function locateHydrationNodeImpl() {
if (__DEV__ && !insertionParent) {
warn('Hydration error: missing insertion state.')
}
let node: Node | null
// prepend / firstChild
@ -87,7 +84,9 @@ function locateHydrationNodeImpl() {
} else {
node = insertionAnchor
? insertionAnchor.previousSibling
: insertionParent!.lastChild
: insertionParent
? insertionParent.lastChild
: currentHydrationNode
if (node && isComment(node, ']')) {
// fragment backward search
@ -120,10 +119,11 @@ function locateHydrationNodeImpl() {
}
}
currentHydrationNode = node
if (__DEV__ && !currentHydrationNode) {
if (__DEV__ && !node) {
// TODO more info
warn('Hydration mismatch in ', insertionParent)
}
resetInsertionState()
currentHydrationNode = node
}

View File

@ -1,5 +1,3 @@
import { setCurrentHydrationNode } from './dom/hydration'
export let insertionParent: ParentNode | undefined
export let insertionAnchor: Node | 0 | undefined
@ -15,5 +13,4 @@ export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
export function resetInsertionState(): void {
insertionParent = insertionAnchor = undefined
setCurrentHydrationNode(null)
}