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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Foo',
asset: true,
root: true,
props: [[]],
},
])
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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Example',
asset: false,
},
])
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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Example',
asset: true,
},
])
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,46 +180,44 @@ describe('compiler: element transform', () => {
class: () => ("bar")
}`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
asset: true,
root: true,
props: [
[
{
key: {
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
asset: true,
root: true,
props: [
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
content: 'foo',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
],
],
},
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'class',
isStatic: true,
},
{
key: {
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'class',
content: 'bar',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'bar',
isStatic: true,
},
],
},
],
],
},
],
},
])
],
})
})
test('v-bind="obj"', () => {
@ -234,18 +226,16 @@ describe('compiler: element transform', () => {
expect(code).contains(`[
() => (_ctx.obj)
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj', isStatic: false },
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj', isStatic: false },
},
],
})
})
test('v-bind="obj" after static prop', () => {
@ -259,19 +249,17 @@ describe('compiler: element transform', () => {
() => (_ctx.obj)
]
}`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
],
})
})
test('v-bind="obj" before static prop', () => {
@ -283,19 +271,17 @@ describe('compiler: element transform', () => {
() => (_ctx.obj),
{ id: () => ("foo") }
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
})
})
test('v-bind="obj" between static props', () => {
@ -310,20 +296,18 @@ describe('compiler: element transform', () => {
{ class: () => ("bar") }
]
}`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
})
})
test.todo('props merging: event handlers', () => {
@ -368,19 +352,17 @@ describe('compiler: element transform', () => {
expect(code).contains(`[
() => (_toHandlers(_ctx.obj))
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
handler: true,
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
handler: true,
},
],
})
})
test('v-on expression is inline statement', () => {
@ -390,21 +372,19 @@ 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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
},
])
],
})
})
test('v-on expression is a function call', () => {
@ -416,21 +396,19 @@ describe('compiler: element transform', () => {
expect(code).contains(
`const _on_bar = $event => (_ctx.handleBar($event))`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
},
])
],
})
})
test('cache v-on expression with unique handler name', () => {
@ -444,34 +422,33 @@ describe('compiler: element transform', () => {
)
expect(code).contains(`onBar: () => _on_bar1`)
expect(code).contains(`const _on_bar1 = () => _ctx.handler`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar' }],
},
],
},
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Bar',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar1' }],
},
],
],
})
expect(ir.block.dynamic.children[1].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Bar',
props: [
[
{
key: { content: 'bar' },
handler: true,
values: [{ content: '_on_bar1' }],
},
],
},
])
],
})
})
})
@ -482,20 +459,18 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
])
})
})
test('capitalized version w/ static binding', () => {
@ -504,20 +479,18 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
])
})
})
test('dynamic binding', () => {
@ -526,20 +499,18 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: false,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: false,
},
])
})
})
test('dynamic binding shorthand', () => {
@ -547,20 +518,18 @@ describe('compiler: element transform', () => {
compileWithElementTransform(`<component :is />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'is',
isStatic: false,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'component',
asset: true,
root: true,
props: [[]],
dynamic: {
type: NodeTypes.SIMPLE_EXPRESSION,
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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'custom-input',
asset: true,
root: true,
props: [[{ key: { content: 'is' }, values: [{ content: 'foo' }] }]],
},
])
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,24 +860,22 @@ describe('compiler: element transform', () => {
`<Foo :[foo-bar]="bar" :[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
},
],
})
})
test('component with dynamic event arguments', () => {
@ -918,26 +883,24 @@ describe('compiler: element transform', () => {
`<Foo @[foo-bar]="bar" @[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
handler: true,
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
handler: true,
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
handler: true,
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
handler: true,
},
],
})
})
test('component event with once modifier', () => {

View File

@ -31,67 +31,59 @@ describe('compiler: transform <slot> outlets', () => {
expect(code).toMatchSnapshot()
expect(helpers).toContain('createSlot')
expect(ir.block.effect).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'default',
isStatic: true,
},
props: [],
fallback: undefined,
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'default',
isStatic: true,
},
])
props: [],
fallback: undefined,
})
})
test('statically named slot outlet', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot name="foo" />`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
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([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo + bar',
isStatic: false,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
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([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
])
})
})
test('default slot outlet with props', () => {
@ -99,19 +91,17 @@ describe('compiler: transform <slot> outlets', () => {
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'default' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
{ key: { content: 'fooBar' }, values: [{ content: 'foo-bar' }] },
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'default' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
{ key: { content: 'fooBar' }, values: [{ content: 'foo-bar' }] },
],
},
])
],
})
})
test('statically named slot outlet with props', () => {
@ -119,18 +109,16 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo" foo="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
],
},
])
],
})
})
test('statically named slot outlet with v-bind="obj"', () => {
@ -138,17 +126,15 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo" foo="bar" v-bind="obj" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }],
{ value: { content: 'obj', isStatic: false } },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
name: { content: 'foo' },
props: [
[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }],
{ value: { content: 'obj', isStatic: false } },
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
],
})
})
test('statically named slot outlet with v-on', () => {
@ -156,36 +142,32 @@ describe('compiler: transform <slot> outlets', () => {
`<slot @click="foo" v-on="bar" :baz="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.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' }] }],
],
},
])
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([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
])
})
})
test('named slot outlet with fallback', () => {
@ -194,20 +176,18 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
])
})
})
test('default slot outlet with props & fallback', () => {
@ -216,21 +196,19 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'default' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
},
])
})
})
test('named slot outlet with props & fallback', () => {
@ -239,21 +217,19 @@ describe('compiler: transform <slot> outlets', () => {
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
returns: [2],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
name: { content: 'foo' },
props: [[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }]],
fallback: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0, id: 2 }],
},
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,38 +33,38 @@ describe('compiler: v-for', () => {
expect(code).matchSnapshot()
expect(helpers).contains('createFor')
expect(ir.template).toEqual(['<div> </div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.FOR,
id: 0,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'items',
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
},
key: undefined,
index: undefined,
render: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
keyProp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item.id',
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
source: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'items',
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
},
key: undefined,
index: undefined,
render: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
])
keyProp: {
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,21 +90,22 @@ 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([
{
type: IRNodeTypes.FOR,
id: 0,
source: { content: 'list' },
value: { content: 'i' },
render: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
const parentOp = ir.block.dynamic.children[0].operation
expect(parentOp).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
source: { content: 'list' },
value: { content: 'i' },
render: {
type: IRNodeTypes.BLOCK,
dynamic: {
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,23 +33,23 @@ describe('compiler: v-if', () => {
expect(helpers).contains('createIf')
expect(ir.template).toEqual(['<div> </div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.IF,
id: 0,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
const op = ir.block.dynamic.children[0].operation
expect(op).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
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,29 +117,27 @@ describe('compiler: v-if', () => {
expect(helpers).contains('createIf')
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.IF,
id: 0,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
negative: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
])
negative: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
},
})
expect(ir.block.returns).toEqual([0])
})
@ -149,37 +148,35 @@ 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: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
negative: {
type: IRNodeTypes.IF,
id: 0,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'ok',
content: 'orNot',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
negative: {
type: IRNodeTypes.IF,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'orNot',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
children: [{ template: 1 }],
},
},
},
])
})
expect(ir.block.returns).toEqual([0])
})
@ -191,33 +188,31 @@ 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: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
negative: {
type: IRNodeTypes.IF,
id: 0,
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
children: [{ template: 1 }],
},
},
negative: {
type: IRNodeTypes.IF,
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
},
negative: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 2 }],
},
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 2 }],
},
},
},
])
})
})
test('comment between branches', () => {

View File

@ -206,22 +206,20 @@ describe('compiler: vModel transform', () => {
expect(code).contains(
`"onUpdate:modelValue": () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'modelValue', isStatic: true },
model: true,
modelModifiers: [],
values: [{ content: 'foo', isStatic: false }],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'modelValue', isStatic: true },
model: true,
modelModifiers: [],
values: [{ content: 'foo', isStatic: false }],
},
],
},
])
],
})
})
test('v-model with arguments for component should work', () => {
@ -231,22 +229,20 @@ describe('compiler: vModel transform', () => {
expect(code).contains(
`"onUpdate:bar": () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'bar', isStatic: true },
model: true,
modelModifiers: [],
values: [{ content: 'foo', isStatic: false }],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'bar', isStatic: true },
model: true,
modelModifiers: [],
values: [{ content: 'foo', isStatic: false }],
},
],
},
])
],
})
})
test('v-model with dynamic arguments for component should work', () => {
@ -256,20 +252,18 @@ describe('compiler: vModel transform', () => {
`[_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => _value => (_ctx.foo = _value)`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
{
key: { content: 'arg', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: [],
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
{
key: { content: 'arg', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: [],
},
],
})
})
test('v-model for component should generate modelModifiers', () => {
@ -280,22 +274,20 @@ describe('compiler: vModel transform', () => {
expect(code).contain(
`modelModifiers: () => ({ trim: true, "bar-baz": true })`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'modelValue', isStatic: true },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim', 'bar-baz'],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'modelValue', isStatic: true },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim', 'bar-baz'],
},
],
},
])
],
})
})
test('v-model with arguments for component should generate modelModifiers', () => {
@ -305,28 +297,26 @@ describe('compiler: vModel transform', () => {
expect(code).toMatchSnapshot()
expect(code).contain(`fooModifiers: () => ({ trim: true })`)
expect(code).contain(`barModifiers: () => ({ number: true })`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'foo', isStatic: true },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: true },
values: [{ content: 'bar', isStatic: false }],
model: true,
modelModifiers: ['number'],
},
],
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'foo', isStatic: true },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: true },
values: [{ content: 'bar', isStatic: false }],
model: true,
modelModifiers: ['number'],
},
],
},
])
],
})
})
test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
@ -336,26 +326,24 @@ 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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
{
key: { content: 'foo', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: false },
values: [{ content: 'bar', isStatic: false }],
model: true,
modelModifiers: ['number'],
},
],
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
{
key: { content: 'foo', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: false },
values: [{ content: 'bar', isStatic: false }],
model: true,
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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0,
tag: 'Comp',
once: true,
parent: 1,
},
])
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,24 +153,22 @@ describe('compiler: v-once', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.IF,
id: 0,
once: true,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'expr',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
once: true,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'expr',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
])
})
})
test('with v-if/else', () => {
@ -182,42 +178,38 @@ describe('compiler: v-once', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.IF,
id: 0,
once: true,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'expr',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
negative: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 1 }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
id: 0,
once: true,
condition: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'expr',
isStatic: false,
},
positive: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
])
negative: {
type: IRNodeTypes.BLOCK,
dynamic: {
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([
{
type: IRNodeTypes.FOR,
id: 0,
once: true,
},
])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
once: true,
})
})
})

View File

@ -36,27 +36,25 @@ describe('compiler: transform slot', () => {
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
},
],
},
])
},
],
})
expect(ir.block.returns).toEqual([1])
expect(ir.block.dynamic).toMatchObject({
children: [{ id: 1 }],
@ -72,31 +70,29 @@ describe('compiler: transform slot', () => {
expect(code).contains(`"default": (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
ast: {
type: 'ArrowFunctionExpression',
params: [{ type: 'ObjectPattern' }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
ast: {
type: 'ArrowFunctionExpression',
params: [{ type: 'ObjectPattern' }],
},
},
},
},
],
},
])
},
],
})
})
test('on component named slot', () => {
@ -108,26 +104,24 @@ describe('compiler: transform slot', () => {
expect(code).contains(`"named": (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
named: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
named: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
},
},
],
},
])
},
],
})
})
test('on component dynamically named slot', () => {
@ -139,28 +133,26 @@ describe('compiler: transform slot', () => {
expect(code).contains(`fn: (_slotProps0) =>`)
expect(code).contains(`_slotProps0["foo"] + _ctx.bar`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'named',
isStatic: false,
},
fn: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'named',
isStatic: false,
},
fn: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
content: '{ foo }',
},
},
],
},
])
},
],
})
})
test('named slots w/ implicit default slot', () => {
@ -172,33 +164,31 @@ describe('compiler: transform slot', () => {
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['foo', 'bar', '<span></span>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
one: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
one: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{}, { template: 1 }, { template: 2 }],
},
},
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{}, { template: 1 }, { template: 2 }],
},
},
},
],
},
])
},
],
})
})
test('nested slots scoping', () => {
@ -219,29 +209,28 @@ 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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
const outerOp = ir.block.dynamic.children[0].operation
expect(outerOp).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [[]],
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ foo }',
},
},
},
],
},
])
},
],
})
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,23 +258,20 @@ describe('compiler: transform slot', () => {
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
fn: { type: IRNodeTypes.BLOCK },
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
],
},
])
fn: { type: IRNodeTypes.BLOCK },
},
],
})
})
test('dynamic slots name w/ v-for', () => {
@ -299,28 +285,25 @@ 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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
},
fn: { type: IRNodeTypes.BLOCK },
loop: {
source: { content: 'list' },
value: { content: 'item' },
index: undefined,
},
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
},
],
},
])
fn: { type: IRNodeTypes.BLOCK },
loop: {
source: { content: 'list' },
value: { content: 'item' },
index: undefined,
},
},
],
})
})
test('dynamic slots name w/ v-for and provide absent key', () => {
@ -330,30 +313,27 @@ describe('compiler: transform slot', () => {
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'index',
isStatic: false,
},
fn: { type: IRNodeTypes.BLOCK },
loop: {
source: { content: 'list' },
value: undefined,
index: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'index',
isStatic: false,
},
fn: { type: IRNodeTypes.BLOCK },
loop: {
source: { content: 'list' },
value: undefined,
index: {
type: NodeTypes.SIMPLE_EXPRESSION,
},
},
},
],
},
])
},
],
})
})
test('dynamic slots name w/ v-if / v-else[-if]', () => {
@ -368,30 +348,27 @@ 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([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: [
{
slotType: IRSlotType.CONDITIONAL,
condition: { content: 'condition' },
positive: {
slotType: IRSlotType.DYNAMIC,
},
negative: {
slotType: IRSlotType.CONDITIONAL,
condition: { content: 'condition' },
condition: { content: 'anotherCondition' },
positive: {
slotType: IRSlotType.DYNAMIC,
},
negative: {
slotType: IRSlotType.CONDITIONAL,
condition: { content: 'anotherCondition' },
positive: {
slotType: IRSlotType.DYNAMIC,
},
negative: { slotType: IRSlotType.DYNAMIC },
},
negative: { slotType: IRSlotType.DYNAMIC },
},
],
},
])
},
],
})
})
test('quote slot name', () => {
@ -405,29 +382,31 @@ describe('compiler: transform slot', () => {
test('nested component slot', () => {
const { ir, code } = compileWithSlots(`<A><B/></A>`)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'A',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
operation: [
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'A',
slots: [
{
slotType: IRSlotType.STATIC,
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'B',
slots: [],
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,14 +33,23 @@ export function genOperations(
): CodeFragment[] {
const [frag, push] = buildCodeFragment()
for (const operation of opers) {
if (isTypeThatNeedsInsertionState(operation) && operation.parent) {
push(...genInsertionstate(operation, context))
}
push(...genOperation(operation, context))
push(...genOperationWithInsertionState(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
}
export function genOperation(
oper: OperationNode,
context: CodegenContext,

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 = genCall(helper('child'), from)
if (elementIndex === 1) {
init = genCall(helper('next'), init)
} else if (elementIndex > 1) {
init = genCall(helper('nthChild'), from, String(elementIndex))
}
let init
for (const i of resolvedPath) {
init = init
? genCall(helper('child'), init)
: skipFirstChild
? resolvedFrom
: genCall(helper('child'), resolvedFrom)
if (i === 1) {
init = genCall(helper('next'), init)
} else if (i > 1) {
init = genCall(helper('nthChild'), resolvedFrom, String(i))
}
}
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)
}