mirror of https://github.com/vuejs/core.git
feat(compiler-vapor): implement basic usage of `v-slot` (#203)
Co-authored-by: Doctorwu <doctorwu@moego.pet> Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
1c54cae29a
commit
0c33ace61c
|
@ -29,6 +29,7 @@ const t0 = _template("<div></div>")
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const _component_Bar = _resolveComponent("Bar")
|
const _component_Bar = _resolveComponent("Bar")
|
||||||
const _component_Comp = _resolveComponent("Comp")
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n4 = _createComponent(_component_Comp, null, { default: () => {
|
||||||
const n0 = _createIf(() => (true), () => {
|
const n0 = _createIf(() => (true), () => {
|
||||||
const n3 = t0()
|
const n3 = t0()
|
||||||
const n2 = _createComponent(_component_Bar)
|
const n2 = _createComponent(_component_Bar)
|
||||||
|
@ -36,8 +37,8 @@ export function render(_ctx) {
|
||||||
_insert(n2, n3)
|
_insert(n2, n3)
|
||||||
return n3
|
return n3
|
||||||
})
|
})
|
||||||
_insert(n0, n4)
|
return n0
|
||||||
const n4 = _createComponent(_component_Comp, null, true)
|
} }, null, true)
|
||||||
_withDirectives(n4, [[_resolveDirective("vTest")]])
|
_withDirectives(n4, [[_resolveDirective("vTest")]])
|
||||||
return n4
|
return n4
|
||||||
}"
|
}"
|
||||||
|
|
|
@ -5,7 +5,7 @@ exports[`compiler: element transform > component > do not resolve component from
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const _component_Example = _resolveComponent("Example")
|
const _component_Example = _resolveComponent("Example")
|
||||||
const n0 = _createComponent(_component_Example, null, true)
|
const n0 = _createComponent(_component_Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -25,7 +25,7 @@ exports[`compiler: element transform > component > generate single root componen
|
||||||
"import { createComponent as _createComponent } from 'vue/vapor';
|
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createComponent(_ctx.Comp, null, true)
|
const n0 = _createComponent(_ctx.Comp, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -35,21 +35,21 @@ exports[`compiler: element transform > component > import + resolve component 1`
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const _component_Foo = _resolveComponent("Foo")
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
const n0 = _createComponent(_component_Foo, null, true)
|
const n0 = _createComponent(_component_Foo, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
|
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
|
||||||
"(() => {
|
"(() => {
|
||||||
const n0 = _createComponent(Example, null, true)
|
const n0 = _createComponent(Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
})()"
|
})()"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
|
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
|
||||||
"(() => {
|
"(() => {
|
||||||
const n0 = _createComponent(_unref(Example), null, true)
|
const n0 = _createComponent(_unref(Example), null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
})()"
|
})()"
|
||||||
`;
|
`;
|
||||||
|
@ -58,14 +58,14 @@ exports[`compiler: element transform > component > resolve component from setup
|
||||||
"import { createComponent as _createComponent } from 'vue/vapor';
|
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createComponent(_ctx.Example, null, true)
|
const n0 = _createComponent(_ctx.Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
|
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
|
||||||
"(() => {
|
"(() => {
|
||||||
const n0 = _createComponent(Foo.Example, null, true)
|
const n0 = _createComponent(Foo.Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
})()"
|
})()"
|
||||||
`;
|
`;
|
||||||
|
@ -74,14 +74,14 @@ exports[`compiler: element transform > component > resolve namespaced component
|
||||||
"import { createComponent as _createComponent } from 'vue/vapor';
|
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createComponent(_ctx.Foo.Example, null, true)
|
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
|
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
|
||||||
"(() => {
|
"(() => {
|
||||||
const n0 = _createComponent(Foo.Example, null, true)
|
const n0 = _createComponent(Foo.Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
})()"
|
})()"
|
||||||
`;
|
`;
|
||||||
|
@ -90,7 +90,7 @@ exports[`compiler: element transform > component > resolve namespaced component
|
||||||
"import { createComponent as _createComponent } from 'vue/vapor';
|
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createComponent(_ctx.Foo.Example, null, true)
|
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -102,7 +102,7 @@ export function render(_ctx) {
|
||||||
const _component_Foo = _resolveComponent("Foo")
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
{ onBar: () => $event => (_ctx.handleBar($event)) }
|
{ onBar: () => $event => (_ctx.handleBar($event)) }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -117,7 +117,7 @@ export function render(_ctx) {
|
||||||
id: () => ("foo"),
|
id: () => ("foo"),
|
||||||
class: () => ("bar")
|
class: () => ("bar")
|
||||||
}
|
}
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -129,7 +129,7 @@ export function render(_ctx) {
|
||||||
const _component_Foo = _resolveComponent("Foo")
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
() => (_ctx.obj)
|
() => (_ctx.obj)
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -142,7 +142,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
{ id: () => ("foo") },
|
{ id: () => ("foo") },
|
||||||
() => (_ctx.obj)
|
() => (_ctx.obj)
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -155,7 +155,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
() => (_ctx.obj),
|
() => (_ctx.obj),
|
||||||
{ id: () => ("foo") }
|
{ id: () => ("foo") }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -169,7 +169,7 @@ export function render(_ctx) {
|
||||||
{ id: () => ("foo") },
|
{ id: () => ("foo") },
|
||||||
() => (_ctx.obj),
|
() => (_ctx.obj),
|
||||||
{ class: () => ("bar") }
|
{ class: () => ("bar") }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -181,7 +181,7 @@ export function render(_ctx) {
|
||||||
const _component_Foo = _resolveComponent("Foo")
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
() => (_toHandlers(_ctx.obj))
|
() => (_toHandlers(_ctx.obj))
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -195,7 +195,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
|
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
|
||||||
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
|
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -208,7 +208,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Foo, [
|
const n0 = _createComponent(_component_Foo, [
|
||||||
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
|
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
|
||||||
() => ({ [_ctx.baz]: _ctx.qux })
|
() => ({ [_ctx.baz]: _ctx.qux })
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -9,7 +9,7 @@ export function render(_ctx) {
|
||||||
{ modelValue: () => (_ctx.foo),
|
{ modelValue: () => (_ctx.foo),
|
||||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
|
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
|
||||||
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
|
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -22,7 +22,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Comp, [
|
const n0 = _createComponent(_component_Comp, [
|
||||||
{ modelValue: () => (_ctx.foo),
|
{ modelValue: () => (_ctx.foo),
|
||||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
|
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -41,7 +41,7 @@ export function render(_ctx) {
|
||||||
"onUpdate:bar": () => $event => (_ctx.bar = $event),
|
"onUpdate:bar": () => $event => (_ctx.bar = $event),
|
||||||
barModifiers: () => ({ number: true })
|
barModifiers: () => ({ number: true })
|
||||||
}
|
}
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -54,7 +54,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Comp, [
|
const n0 = _createComponent(_component_Comp, [
|
||||||
{ bar: () => (_ctx.foo),
|
{ bar: () => (_ctx.foo),
|
||||||
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
|
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -71,7 +71,7 @@ export function render(_ctx) {
|
||||||
() => ({ [_ctx.bar]: _ctx.bar,
|
() => ({ [_ctx.bar]: _ctx.bar,
|
||||||
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
|
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
|
||||||
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
|
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -84,7 +84,7 @@ export function render(_ctx) {
|
||||||
const n0 = _createComponent(_component_Comp, [
|
const n0 = _createComponent(_component_Comp, [
|
||||||
() => ({ [_ctx.arg]: _ctx.foo,
|
() => ({ [_ctx.arg]: _ctx.foo,
|
||||||
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
|
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
|
||||||
], true)
|
], null, null, true)
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > dynamic slots name 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
|
||||||
|
const t0 = _template("foo")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n2 = _createComponent(_component_Comp, null, null, () => [{
|
||||||
|
name: _ctx.name,
|
||||||
|
fn: () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
}], true)
|
||||||
|
return n2
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > implicit default slot 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
|
||||||
|
const t0 = _template("<div></div>")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n1 = _createComponent(_component_Comp, null, { default: () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
} }, null, true)
|
||||||
|
return n1
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > named slots w/ implicit default slot 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
|
||||||
|
const t0 = _template("foo")
|
||||||
|
const t1 = _template("bar")
|
||||||
|
const t2 = _template("<span></span>")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n4 = _createComponent(_component_Comp, null, {
|
||||||
|
one: () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
default: () => {
|
||||||
|
const n2 = t1()
|
||||||
|
const n3 = t2()
|
||||||
|
return [n2, n3]
|
||||||
|
}
|
||||||
|
}, null, true)
|
||||||
|
return n4
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > nested slots 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
|
||||||
|
const t0 = _template("<div></div>")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Bar = _resolveComponent("Bar")
|
||||||
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
|
const n3 = _createComponent(_component_Foo, null, { one: () => {
|
||||||
|
const n1 = _createComponent(_component_Bar, null, { default: () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
} })
|
||||||
|
return n1
|
||||||
|
} }, null, true)
|
||||||
|
return n3
|
||||||
|
}"
|
||||||
|
`;
|
|
@ -182,7 +182,9 @@ describe('compiler: element transform', () => {
|
||||||
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
|
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
|
||||||
})
|
})
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
expect(code).contains('_createComponent(_ctx.Comp, null, true)')
|
expect(code).contains(
|
||||||
|
'_createComponent(_ctx.Comp, null, null, null, true)',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('generate multi root component', () => {
|
test('generate multi root component', () => {
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
|
||||||
|
import {
|
||||||
|
IRNodeTypes,
|
||||||
|
transformChildren,
|
||||||
|
transformElement,
|
||||||
|
transformSlotOutlet,
|
||||||
|
transformText,
|
||||||
|
transformVBind,
|
||||||
|
transformVFor,
|
||||||
|
transformVIf,
|
||||||
|
transformVOn,
|
||||||
|
transformVSlot,
|
||||||
|
} from '../../src'
|
||||||
|
import { makeCompile } from './_utils'
|
||||||
|
|
||||||
|
const compileWithSlots = makeCompile({
|
||||||
|
nodeTransforms: [
|
||||||
|
transformText,
|
||||||
|
transformVIf,
|
||||||
|
transformVFor,
|
||||||
|
transformSlotOutlet,
|
||||||
|
transformElement,
|
||||||
|
transformVSlot,
|
||||||
|
transformChildren,
|
||||||
|
],
|
||||||
|
directiveTransforms: {
|
||||||
|
bind: transformVBind,
|
||||||
|
on: transformVOn,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('compiler: transform slot', () => {
|
||||||
|
test('implicit default slot', () => {
|
||||||
|
const { ir, code } = compileWithSlots(`<Comp><div/></Comp>`)
|
||||||
|
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: {
|
||||||
|
default: {
|
||||||
|
type: IRNodeTypes.BLOCK,
|
||||||
|
dynamic: {
|
||||||
|
children: [{ template: 0 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(ir.block.returns).toEqual([1])
|
||||||
|
expect(ir.block.dynamic).toMatchObject({
|
||||||
|
children: [{ id: 1 }],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named slots w/ implicit default slot', () => {
|
||||||
|
const { ir, code } = compileWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template #one>foo</template>bar<span/>
|
||||||
|
</Comp>`,
|
||||||
|
)
|
||||||
|
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: {
|
||||||
|
one: {
|
||||||
|
type: IRNodeTypes.BLOCK,
|
||||||
|
dynamic: {
|
||||||
|
children: [{ template: 0 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
type: IRNodeTypes.BLOCK,
|
||||||
|
dynamic: {
|
||||||
|
children: [{}, { template: 1 }, { template: 2 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested slots', () => {
|
||||||
|
const { code } = compileWithSlots(
|
||||||
|
`<Foo>
|
||||||
|
<template #one><Bar><div/></Bar></template>
|
||||||
|
</Foo>`,
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamic slots name', () => {
|
||||||
|
const { ir, code } = compileWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template #[name]>foo</template>
|
||||||
|
</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: undefined,
|
||||||
|
dynamicSlots: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'name',
|
||||||
|
isStatic: false,
|
||||||
|
},
|
||||||
|
fn: { type: IRNodeTypes.BLOCK },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
test('error on extraneous children w/ named default slot', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
||||||
|
compileWithSlots(source, { onError })
|
||||||
|
const index = source.indexOf('bar')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
|
loc: {
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 3,
|
||||||
|
line: 1,
|
||||||
|
column: index + 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error on duplicated slot names', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
const source = `<Comp><template #foo></template><template #foo></template></Comp>`
|
||||||
|
compileWithSlots(source, { onError })
|
||||||
|
const index = source.lastIndexOf('#foo')
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
||||||
|
loc: {
|
||||||
|
start: {
|
||||||
|
offset: index,
|
||||||
|
line: 1,
|
||||||
|
column: index + 1,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: index + 4,
|
||||||
|
line: 1,
|
||||||
|
column: index + 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -28,6 +28,7 @@ import { transformVIf } from './transforms/vIf'
|
||||||
import { transformVFor } from './transforms/vFor'
|
import { transformVFor } from './transforms/vFor'
|
||||||
import { transformComment } from './transforms/transformComment'
|
import { transformComment } from './transforms/transformComment'
|
||||||
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
|
import { transformVSlot } from './transforms/vSlot'
|
||||||
import type { HackOptions } from './ir'
|
import type { HackOptions } from './ir'
|
||||||
|
|
||||||
export { wrapTemplate } from './transforms/utils'
|
export { wrapTemplate } from './transforms/utils'
|
||||||
|
@ -108,6 +109,7 @@ export function getBaseTransformPreset(
|
||||||
transformTemplateRef,
|
transformTemplateRef,
|
||||||
transformText,
|
transformText,
|
||||||
transformElement,
|
transformElement,
|
||||||
|
transformVSlot,
|
||||||
transformComment,
|
transformComment,
|
||||||
transformChildren,
|
transformChildren,
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { camelize, extend, isArray } from '@vue/shared'
|
import { camelize, extend, isArray } from '@vue/shared'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodegenContext } from '../generate'
|
||||||
import {
|
import {
|
||||||
|
type ComponentDynamicSlot,
|
||||||
|
type ComponentSlots,
|
||||||
type CreateComponentIRNode,
|
type CreateComponentIRNode,
|
||||||
IRDynamicPropsKind,
|
IRDynamicPropsKind,
|
||||||
type IRProp,
|
type IRProp,
|
||||||
|
@ -10,6 +12,7 @@ import {
|
||||||
import {
|
import {
|
||||||
type CodeFragment,
|
type CodeFragment,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
SEGMENTS_ARRAY,
|
||||||
SEGMENTS_ARRAY_NEWLINE,
|
SEGMENTS_ARRAY_NEWLINE,
|
||||||
SEGMENTS_OBJECT,
|
SEGMENTS_OBJECT,
|
||||||
SEGMENTS_OBJECT_NEWLINE,
|
SEGMENTS_OBJECT_NEWLINE,
|
||||||
|
@ -22,8 +25,8 @@ import { createSimpleExpression } from '@vue/compiler-dom'
|
||||||
import { genEventHandler } from './event'
|
import { genEventHandler } from './event'
|
||||||
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
|
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
|
||||||
import { genModelHandler } from './modelValue'
|
import { genModelHandler } from './modelValue'
|
||||||
|
import { genBlock } from './block'
|
||||||
|
|
||||||
// TODO: generate component slots
|
|
||||||
export function genCreateComponent(
|
export function genCreateComponent(
|
||||||
oper: CreateComponentIRNode,
|
oper: CreateComponentIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
|
@ -31,7 +34,7 @@ export function genCreateComponent(
|
||||||
const { vaporHelper } = context
|
const { vaporHelper } = context
|
||||||
|
|
||||||
const tag = genTag()
|
const tag = genTag()
|
||||||
const isRoot = oper.root
|
const { root, slots, dynamicSlots } = oper
|
||||||
const rawProps = genRawProps(oper.props, context)
|
const rawProps = genRawProps(oper.props, context)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -40,8 +43,14 @@ export function genCreateComponent(
|
||||||
...genCall(
|
...genCall(
|
||||||
vaporHelper('createComponent'),
|
vaporHelper('createComponent'),
|
||||||
tag,
|
tag,
|
||||||
rawProps || (isRoot ? 'null' : false),
|
rawProps || (slots || dynamicSlots || root ? 'null' : false),
|
||||||
isRoot && 'true',
|
slots ? genSlots(slots, context) : dynamicSlots || root ? 'null' : false,
|
||||||
|
dynamicSlots
|
||||||
|
? genDynamicSlots(dynamicSlots, context)
|
||||||
|
: root
|
||||||
|
? 'null'
|
||||||
|
: false,
|
||||||
|
root && 'true',
|
||||||
),
|
),
|
||||||
...genDirectivesForElement(oper.id, context),
|
...genDirectivesForElement(oper.id, context),
|
||||||
]
|
]
|
||||||
|
@ -134,3 +143,28 @@ function genModelModifiers(
|
||||||
const modifiersVal = genDirectiveModifiers(modelModifiers)
|
const modifiersVal = genDirectiveModifiers(modelModifiers)
|
||||||
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
|
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genSlots(slots: ComponentSlots, context: CodegenContext) {
|
||||||
|
const slotList = Object.entries(slots)
|
||||||
|
return genMulti(
|
||||||
|
slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT,
|
||||||
|
...slotList.map(([name, slot]) => [name, ': ', ...genBlock(slot, context)]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genDynamicSlots(
|
||||||
|
dynamicSlots: ComponentDynamicSlot[],
|
||||||
|
context: CodegenContext,
|
||||||
|
) {
|
||||||
|
const slotsExpr = genMulti(
|
||||||
|
dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
|
||||||
|
...dynamicSlots.map(({ name, fn }) =>
|
||||||
|
genMulti(
|
||||||
|
SEGMENTS_OBJECT_NEWLINE,
|
||||||
|
['name: ', ...genExpression(name, context)],
|
||||||
|
['fn: ', ...genBlock(fn, context)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return ['() => ', ...slotsExpr]
|
||||||
|
}
|
||||||
|
|
|
@ -49,3 +49,4 @@ export { transformVFor } from './transforms/vFor'
|
||||||
export { transformVModel } from './transforms/vModel'
|
export { transformVModel } from './transforms/vModel'
|
||||||
export { transformComment } from './transforms/transformComment'
|
export { transformComment } from './transforms/transformComment'
|
||||||
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
|
export { transformVSlot } from './transforms/vSlot'
|
||||||
|
|
|
@ -199,12 +199,24 @@ export interface WithDirectiveIRNode extends BaseIRNode {
|
||||||
builtin?: VaporHelper
|
builtin?: VaporHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComponentSlotBlockIRNode extends BlockIRNode {
|
||||||
|
// TODO slot props
|
||||||
|
}
|
||||||
|
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
|
||||||
|
export interface ComponentDynamicSlot {
|
||||||
|
name: SimpleExpressionNode
|
||||||
|
fn: ComponentSlotBlockIRNode
|
||||||
|
key?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateComponentIRNode extends BaseIRNode {
|
export interface CreateComponentIRNode extends BaseIRNode {
|
||||||
type: IRNodeTypes.CREATE_COMPONENT_NODE
|
type: IRNodeTypes.CREATE_COMPONENT_NODE
|
||||||
id: number
|
id: number
|
||||||
tag: string
|
tag: string
|
||||||
props: IRProps[]
|
props: IRProps[]
|
||||||
// TODO slots
|
|
||||||
|
slots?: ComponentSlots
|
||||||
|
dynamicSlots?: ComponentDynamicSlot[]
|
||||||
|
|
||||||
resolve: boolean
|
resolve: boolean
|
||||||
root: boolean
|
root: boolean
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {
|
||||||
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type BlockIRNode,
|
type BlockIRNode,
|
||||||
|
type ComponentDynamicSlot,
|
||||||
|
type ComponentSlots,
|
||||||
DynamicFlag,
|
DynamicFlag,
|
||||||
type HackOptions,
|
type HackOptions,
|
||||||
type IRDynamicInfo,
|
type IRDynamicInfo,
|
||||||
|
@ -77,11 +79,13 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
|
|
||||||
comment: CommentNode[] = []
|
comment: CommentNode[] = []
|
||||||
component: Set<string> = this.ir.component
|
component: Set<string> = this.ir.component
|
||||||
|
slots?: ComponentSlots
|
||||||
|
dynamicSlots?: ComponentDynamicSlot[]
|
||||||
|
|
||||||
private globalId = 0
|
private globalId = 0
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private ir: RootIRNode,
|
public ir: RootIRNode,
|
||||||
public node: T,
|
public node: T,
|
||||||
options: TransformOptions = {},
|
options: TransformOptions = {},
|
||||||
) {
|
) {
|
||||||
|
@ -90,11 +94,14 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
|
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
|
||||||
const { block, template, dynamic, childrenTemplate } = this
|
const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } =
|
||||||
|
this
|
||||||
this.block = ir
|
this.block = ir
|
||||||
this.dynamic = ir.dynamic
|
this.dynamic = ir.dynamic
|
||||||
this.template = ''
|
this.template = ''
|
||||||
this.childrenTemplate = []
|
this.childrenTemplate = []
|
||||||
|
this.slots = undefined
|
||||||
|
this.dynamicSlots = undefined
|
||||||
isVFor && this.inVFor++
|
isVFor && this.inVFor++
|
||||||
return () => {
|
return () => {
|
||||||
// exit
|
// exit
|
||||||
|
@ -103,6 +110,8 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
this.template = template
|
this.template = template
|
||||||
this.dynamic = dynamic
|
this.dynamic = dynamic
|
||||||
this.childrenTemplate = childrenTemplate
|
this.childrenTemplate = childrenTemplate
|
||||||
|
this.slots = slots
|
||||||
|
this.dynamicSlots = dynamicSlots
|
||||||
isVFor && this.inVFor--
|
isVFor && this.inVFor--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@ import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir'
|
||||||
export const transformChildren: NodeTransform = (node, context) => {
|
export const transformChildren: NodeTransform = (node, context) => {
|
||||||
const isFragment =
|
const isFragment =
|
||||||
node.type === NodeTypes.ROOT ||
|
node.type === NodeTypes.ROOT ||
|
||||||
(node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE)
|
(node.type === NodeTypes.ELEMENT &&
|
||||||
|
(node.tagType === ElementTypes.TEMPLATE ||
|
||||||
|
node.tagType === ElementTypes.COMPONENT))
|
||||||
|
|
||||||
if (!isFragment && node.type !== NodeTypes.ELEMENT) return
|
if (!isFragment && node.type !== NodeTypes.ELEMENT) return
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,11 @@ function transformComponentElement(
|
||||||
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
|
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
|
||||||
resolve,
|
resolve,
|
||||||
root,
|
root,
|
||||||
|
slots: context.slots,
|
||||||
|
dynamicSlots: context.dynamicSlots,
|
||||||
})
|
})
|
||||||
|
context.slots = undefined
|
||||||
|
context.dynamicSlots = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSetupReference(name: string, context: TransformContext) {
|
function resolveSetupReference(name: string, context: TransformContext) {
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
import {
|
||||||
|
type ElementNode,
|
||||||
|
ElementTypes,
|
||||||
|
ErrorCodes,
|
||||||
|
NodeTypes,
|
||||||
|
type TemplateChildNode,
|
||||||
|
createCompilerError,
|
||||||
|
isTemplateNode,
|
||||||
|
isVSlot,
|
||||||
|
} from '@vue/compiler-core'
|
||||||
|
import type { NodeTransform, TransformContext } from '../transform'
|
||||||
|
import { newBlock } from './utils'
|
||||||
|
import { type BlockIRNode, DynamicFlag, type VaporDirectiveNode } from '../ir'
|
||||||
|
import { findDir, resolveExpression } from '../utils'
|
||||||
|
|
||||||
|
// TODO dynamic slots
|
||||||
|
export const transformVSlot: NodeTransform = (node, context) => {
|
||||||
|
if (node.type !== NodeTypes.ELEMENT) return
|
||||||
|
|
||||||
|
let dir: VaporDirectiveNode | undefined
|
||||||
|
const { tagType, children } = node
|
||||||
|
const { parent } = context
|
||||||
|
|
||||||
|
const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length
|
||||||
|
const isSlotTemplate =
|
||||||
|
isTemplateNode(node) &&
|
||||||
|
parent &&
|
||||||
|
parent.node.type === NodeTypes.ELEMENT &&
|
||||||
|
parent.node.tagType === ElementTypes.COMPONENT
|
||||||
|
|
||||||
|
if (isDefaultSlot) {
|
||||||
|
const defaultChildren = children.filter(
|
||||||
|
n =>
|
||||||
|
isNonWhitespaceContent(node) &&
|
||||||
|
!(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const [block, onExit] = createSlotBlock(
|
||||||
|
node,
|
||||||
|
context as TransformContext<ElementNode>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const slots = (context.slots ||= {})
|
||||||
|
const dynamicSlots = (context.dynamicSlots ||= [])
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
onExit()
|
||||||
|
|
||||||
|
if (defaultChildren.length) {
|
||||||
|
if (slots.default) {
|
||||||
|
context.options.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
|
defaultChildren[0].loc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
slots.default = block
|
||||||
|
}
|
||||||
|
context.slots = slots
|
||||||
|
} else if (Object.keys(slots).length) {
|
||||||
|
context.slots = slots
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynamicSlots.length) context.dynamicSlots = dynamicSlots
|
||||||
|
}
|
||||||
|
} else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) {
|
||||||
|
let { arg } = dir
|
||||||
|
|
||||||
|
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
|
||||||
|
|
||||||
|
const slots = context.slots!
|
||||||
|
const dynamicSlots = context.dynamicSlots!
|
||||||
|
|
||||||
|
const [block, onExit] = createSlotBlock(
|
||||||
|
node,
|
||||||
|
context as TransformContext<ElementNode>,
|
||||||
|
)
|
||||||
|
|
||||||
|
arg &&= resolveExpression(arg)
|
||||||
|
|
||||||
|
if (!arg || arg.isStatic) {
|
||||||
|
const slotName = arg ? arg.content : 'default'
|
||||||
|
|
||||||
|
if (slots[slotName]) {
|
||||||
|
context.options.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
||||||
|
dir.loc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
slots[slotName] = block
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicSlots.push({
|
||||||
|
name: arg,
|
||||||
|
fn: block,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () => onExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSlotBlock(
|
||||||
|
slotNode: ElementNode,
|
||||||
|
context: TransformContext<ElementNode>,
|
||||||
|
): [BlockIRNode, () => void] {
|
||||||
|
const branch: BlockIRNode = newBlock(slotNode)
|
||||||
|
const exitBlock = context.enterBlock(branch)
|
||||||
|
return [branch, exitBlock]
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNonWhitespaceContent(node: TemplateChildNode): boolean {
|
||||||
|
if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
|
||||||
|
return true
|
||||||
|
return node.type === NodeTypes.TEXT
|
||||||
|
? !!node.content.trim()
|
||||||
|
: isNonWhitespaceContent(node.content)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {
|
||||||
type ElementNode,
|
type ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
|
findDir as _findDir,
|
||||||
findProp as _findProp,
|
findProp as _findProp,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
isLiteralWhitelisted,
|
isLiteralWhitelisted,
|
||||||
|
@ -19,6 +20,13 @@ export const findProp = _findProp as (
|
||||||
allowEmpty?: boolean,
|
allowEmpty?: boolean,
|
||||||
) => AttributeNode | VaporDirectiveNode | undefined
|
) => AttributeNode | VaporDirectiveNode | undefined
|
||||||
|
|
||||||
|
/** find directive */
|
||||||
|
export const findDir = _findDir as (
|
||||||
|
node: ElementNode,
|
||||||
|
name: string | RegExp,
|
||||||
|
allowEmpty?: boolean,
|
||||||
|
) => VaporDirectiveNode | undefined
|
||||||
|
|
||||||
export function propToExpression(prop: AttributeNode | VaporDirectiveNode) {
|
export function propToExpression(prop: AttributeNode | VaporDirectiveNode) {
|
||||||
return prop.type === NodeTypes.ATTRIBUTE
|
return prop.type === NodeTypes.ATTRIBUTE
|
||||||
? prop.value
|
? prop.value
|
||||||
|
|
Loading…
Reference in New Issue