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,15 +29,16 @@ const t0 = _template("<div></div>")
|
|||
export function render(_ctx) {
|
||||
const _component_Bar = _resolveComponent("Bar")
|
||||
const _component_Comp = _resolveComponent("Comp")
|
||||
const n0 = _createIf(() => (true), () => {
|
||||
const n3 = t0()
|
||||
const n2 = _createComponent(_component_Bar)
|
||||
_withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
|
||||
_insert(n2, n3)
|
||||
return n3
|
||||
})
|
||||
_insert(n0, n4)
|
||||
const n4 = _createComponent(_component_Comp, null, true)
|
||||
const n4 = _createComponent(_component_Comp, null, { default: () => {
|
||||
const n0 = _createIf(() => (true), () => {
|
||||
const n3 = t0()
|
||||
const n2 = _createComponent(_component_Bar)
|
||||
_withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
|
||||
_insert(n2, n3)
|
||||
return n3
|
||||
})
|
||||
return n0
|
||||
} }, null, true)
|
||||
_withDirectives(n4, [[_resolveDirective("vTest")]])
|
||||
return n4
|
||||
}"
|
||||
|
|
|
@ -5,7 +5,7 @@ exports[`compiler: element transform > component > do not resolve component from
|
|||
|
||||
export function render(_ctx) {
|
||||
const _component_Example = _resolveComponent("Example")
|
||||
const n0 = _createComponent(_component_Example, null, true)
|
||||
const n0 = _createComponent(_component_Example, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -25,7 +25,7 @@ exports[`compiler: element transform > component > generate single root componen
|
|||
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_ctx.Comp, null, true)
|
||||
const n0 = _createComponent(_ctx.Comp, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -35,21 +35,21 @@ exports[`compiler: element transform > component > import + resolve component 1`
|
|||
|
||||
export function render(_ctx) {
|
||||
const _component_Foo = _resolveComponent("Foo")
|
||||
const n0 = _createComponent(_component_Foo, null, true)
|
||||
const n0 = _createComponent(_component_Foo, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
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
|
||||
})()"
|
||||
`;
|
||||
|
||||
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
|
||||
})()"
|
||||
`;
|
||||
|
@ -58,14 +58,14 @@ exports[`compiler: element transform > component > resolve component from setup
|
|||
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_ctx.Example, null, true)
|
||||
const n0 = _createComponent(_ctx.Example, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
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
|
||||
})()"
|
||||
`;
|
||||
|
@ -74,14 +74,14 @@ exports[`compiler: element transform > component > resolve namespaced component
|
|||
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_ctx.Foo.Example, null, true)
|
||||
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
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
|
||||
})()"
|
||||
`;
|
||||
|
@ -90,7 +90,7 @@ exports[`compiler: element transform > component > resolve namespaced component
|
|||
"import { createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_ctx.Foo.Example, null, true)
|
||||
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -102,7 +102,7 @@ export function render(_ctx) {
|
|||
const _component_Foo = _resolveComponent("Foo")
|
||||
const n0 = _createComponent(_component_Foo, [
|
||||
{ onBar: () => $event => (_ctx.handleBar($event)) }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -117,7 +117,7 @@ export function render(_ctx) {
|
|||
id: () => ("foo"),
|
||||
class: () => ("bar")
|
||||
}
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -129,7 +129,7 @@ export function render(_ctx) {
|
|||
const _component_Foo = _resolveComponent("Foo")
|
||||
const n0 = _createComponent(_component_Foo, [
|
||||
() => (_ctx.obj)
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -142,7 +142,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Foo, [
|
||||
{ id: () => ("foo") },
|
||||
() => (_ctx.obj)
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -155,7 +155,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Foo, [
|
||||
() => (_ctx.obj),
|
||||
{ id: () => ("foo") }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -169,7 +169,7 @@ export function render(_ctx) {
|
|||
{ id: () => ("foo") },
|
||||
() => (_ctx.obj),
|
||||
{ class: () => ("bar") }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -181,7 +181,7 @@ export function render(_ctx) {
|
|||
const _component_Foo = _resolveComponent("Foo")
|
||||
const n0 = _createComponent(_component_Foo, [
|
||||
() => (_toHandlers(_ctx.obj))
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -195,7 +195,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Foo, [
|
||||
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
|
||||
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -208,7 +208,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Foo, [
|
||||
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
|
||||
() => ({ [_ctx.baz]: _ctx.qux })
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -9,7 +9,7 @@ export function render(_ctx) {
|
|||
{ modelValue: () => (_ctx.foo),
|
||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
|
||||
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -22,7 +22,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Comp, [
|
||||
{ modelValue: () => (_ctx.foo),
|
||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -41,7 +41,7 @@ export function render(_ctx) {
|
|||
"onUpdate:bar": () => $event => (_ctx.bar = $event),
|
||||
barModifiers: () => ({ number: true })
|
||||
}
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -54,7 +54,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Comp, [
|
||||
{ bar: () => (_ctx.foo),
|
||||
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -71,7 +71,7 @@ export function render(_ctx) {
|
|||
() => ({ [_ctx.bar]: _ctx.bar,
|
||||
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
|
||||
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
|
||||
], true)
|
||||
], null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
@ -84,7 +84,7 @@ export function render(_ctx) {
|
|||
const n0 = _createComponent(_component_Comp, [
|
||||
() => ({ [_ctx.arg]: _ctx.foo,
|
||||
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
|
||||
], true)
|
||||
], null, null, true)
|
||||
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 },
|
||||
})
|
||||
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', () => {
|
||||
|
|
|
@ -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 { transformComment } from './transforms/transformComment'
|
||||
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
import { transformVSlot } from './transforms/vSlot'
|
||||
import type { HackOptions } from './ir'
|
||||
|
||||
export { wrapTemplate } from './transforms/utils'
|
||||
|
@ -108,6 +109,7 @@ export function getBaseTransformPreset(
|
|||
transformTemplateRef,
|
||||
transformText,
|
||||
transformElement,
|
||||
transformVSlot,
|
||||
transformComment,
|
||||
transformChildren,
|
||||
],
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { camelize, extend, isArray } from '@vue/shared'
|
||||
import type { CodegenContext } from '../generate'
|
||||
import {
|
||||
type ComponentDynamicSlot,
|
||||
type ComponentSlots,
|
||||
type CreateComponentIRNode,
|
||||
IRDynamicPropsKind,
|
||||
type IRProp,
|
||||
|
@ -10,6 +12,7 @@ import {
|
|||
import {
|
||||
type CodeFragment,
|
||||
NEWLINE,
|
||||
SEGMENTS_ARRAY,
|
||||
SEGMENTS_ARRAY_NEWLINE,
|
||||
SEGMENTS_OBJECT,
|
||||
SEGMENTS_OBJECT_NEWLINE,
|
||||
|
@ -22,8 +25,8 @@ import { createSimpleExpression } from '@vue/compiler-dom'
|
|||
import { genEventHandler } from './event'
|
||||
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
|
||||
import { genModelHandler } from './modelValue'
|
||||
import { genBlock } from './block'
|
||||
|
||||
// TODO: generate component slots
|
||||
export function genCreateComponent(
|
||||
oper: CreateComponentIRNode,
|
||||
context: CodegenContext,
|
||||
|
@ -31,7 +34,7 @@ export function genCreateComponent(
|
|||
const { vaporHelper } = context
|
||||
|
||||
const tag = genTag()
|
||||
const isRoot = oper.root
|
||||
const { root, slots, dynamicSlots } = oper
|
||||
const rawProps = genRawProps(oper.props, context)
|
||||
|
||||
return [
|
||||
|
@ -40,8 +43,14 @@ export function genCreateComponent(
|
|||
...genCall(
|
||||
vaporHelper('createComponent'),
|
||||
tag,
|
||||
rawProps || (isRoot ? 'null' : false),
|
||||
isRoot && 'true',
|
||||
rawProps || (slots || dynamicSlots || root ? 'null' : false),
|
||||
slots ? genSlots(slots, context) : dynamicSlots || root ? 'null' : false,
|
||||
dynamicSlots
|
||||
? genDynamicSlots(dynamicSlots, context)
|
||||
: root
|
||||
? 'null'
|
||||
: false,
|
||||
root && 'true',
|
||||
),
|
||||
...genDirectivesForElement(oper.id, context),
|
||||
]
|
||||
|
@ -134,3 +143,28 @@ function genModelModifiers(
|
|||
const modifiersVal = genDirectiveModifiers(modelModifiers)
|
||||
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 { transformComment } from './transforms/transformComment'
|
||||
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
export { transformVSlot } from './transforms/vSlot'
|
||||
|
|
|
@ -199,12 +199,24 @@ export interface WithDirectiveIRNode extends BaseIRNode {
|
|||
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 {
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE
|
||||
id: number
|
||||
tag: string
|
||||
props: IRProps[]
|
||||
// TODO slots
|
||||
|
||||
slots?: ComponentSlots
|
||||
dynamicSlots?: ComponentDynamicSlot[]
|
||||
|
||||
resolve: boolean
|
||||
root: boolean
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
||||
import {
|
||||
type BlockIRNode,
|
||||
type ComponentDynamicSlot,
|
||||
type ComponentSlots,
|
||||
DynamicFlag,
|
||||
type HackOptions,
|
||||
type IRDynamicInfo,
|
||||
|
@ -77,11 +79,13 @@ export class TransformContext<T extends AllNode = AllNode> {
|
|||
|
||||
comment: CommentNode[] = []
|
||||
component: Set<string> = this.ir.component
|
||||
slots?: ComponentSlots
|
||||
dynamicSlots?: ComponentDynamicSlot[]
|
||||
|
||||
private globalId = 0
|
||||
|
||||
constructor(
|
||||
private ir: RootIRNode,
|
||||
public ir: RootIRNode,
|
||||
public node: T,
|
||||
options: TransformOptions = {},
|
||||
) {
|
||||
|
@ -90,11 +94,14 @@ export class TransformContext<T extends AllNode = AllNode> {
|
|||
}
|
||||
|
||||
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.dynamic = ir.dynamic
|
||||
this.template = ''
|
||||
this.childrenTemplate = []
|
||||
this.slots = undefined
|
||||
this.dynamicSlots = undefined
|
||||
isVFor && this.inVFor++
|
||||
return () => {
|
||||
// exit
|
||||
|
@ -103,6 +110,8 @@ export class TransformContext<T extends AllNode = AllNode> {
|
|||
this.template = template
|
||||
this.dynamic = dynamic
|
||||
this.childrenTemplate = childrenTemplate
|
||||
this.slots = slots
|
||||
this.dynamicSlots = dynamicSlots
|
||||
isVFor && this.inVFor--
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir'
|
|||
export const transformChildren: NodeTransform = (node, context) => {
|
||||
const isFragment =
|
||||
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
|
||||
|
||||
|
|
|
@ -104,7 +104,11 @@ function transformComponentElement(
|
|||
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
|
||||
resolve,
|
||||
root,
|
||||
slots: context.slots,
|
||||
dynamicSlots: context.dynamicSlots,
|
||||
})
|
||||
context.slots = undefined
|
||||
context.dynamicSlots = undefined
|
||||
}
|
||||
|
||||
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,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
findDir as _findDir,
|
||||
findProp as _findProp,
|
||||
createSimpleExpression,
|
||||
isLiteralWhitelisted,
|
||||
|
@ -19,6 +20,13 @@ export const findProp = _findProp as (
|
|||
allowEmpty?: boolean,
|
||||
) => 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) {
|
||||
return prop.type === NodeTypes.ATTRIBUTE
|
||||
? prop.value
|
||||
|
|
Loading…
Reference in New Issue