mirror of https://github.com/vuejs/core.git
feat(compiler-vapor): slot outlet (#182)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
bfb52502f8
commit
2b0def3ba5
|
@ -0,0 +1,136 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: transform <slot> outlets > default slot outlet 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > default slot outlet with fallback 1`] = `
|
||||
"import { createSlot as _createSlot, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", null, () => {
|
||||
const n2 = t0()
|
||||
return n2
|
||||
})
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > default slot outlet with props 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", [
|
||||
{
|
||||
foo: () => ("bar"),
|
||||
baz: () => (_ctx.qux),
|
||||
fooBar: () => (_ctx.foo-_ctx.bar)
|
||||
}
|
||||
])
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > dynamically named slot outlet 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot(() => (_ctx.foo + _ctx.bar), null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > dynamically named slot outlet with v-bind shorthand 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot(() => (_ctx.name), null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > error on unexpected custom directive on <slot> 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > error on unexpected custom directive with v-show on <slot> 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > named slot outlet with fallback 1`] = `
|
||||
"import { createSlot as _createSlot, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<div></div>")
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("foo", null, () => {
|
||||
const n2 = t0()
|
||||
return n2
|
||||
})
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > statically named slot outlet 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("foo", null)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > statically named slot outlet with props 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("foo", [
|
||||
{
|
||||
foo: () => ("bar"),
|
||||
baz: () => (_ctx.qux)
|
||||
}
|
||||
])
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-bind="obj" 1`] = `
|
||||
"import { createSlot as _createSlot } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("foo", [
|
||||
{ foo: () => ("bar") },
|
||||
() => (_ctx.obj),
|
||||
{ baz: () => (_ctx.qux) }
|
||||
])
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-on 1`] = `
|
||||
"import { createSlot as _createSlot, toHandlers as _toHandlers } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createSlot("default", [
|
||||
{ onClick: () => _ctx.foo },
|
||||
() => (_toHandlers(_ctx.bar)),
|
||||
{ baz: () => (_ctx.qux) }
|
||||
])
|
||||
return n0
|
||||
}"
|
||||
`;
|
|
@ -0,0 +1,258 @@
|
|||
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
|
||||
import {
|
||||
IRNodeTypes,
|
||||
transformChildren,
|
||||
transformElement,
|
||||
transformSlotOutlet,
|
||||
transformText,
|
||||
transformVBind,
|
||||
transformVOn,
|
||||
transformVShow,
|
||||
} from '../../src'
|
||||
import { makeCompile } from './_utils'
|
||||
|
||||
const compileWithSlotsOutlet = makeCompile({
|
||||
nodeTransforms: [
|
||||
transformText,
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
transformChildren,
|
||||
],
|
||||
directiveTransforms: {
|
||||
bind: transformVBind,
|
||||
on: transformVOn,
|
||||
show: transformVShow,
|
||||
},
|
||||
})
|
||||
|
||||
describe('compiler: transform <slot> outlets', () => {
|
||||
test('default slot outlet', () => {
|
||||
const { ir, code, vaporHelpers } = compileWithSlotsOutlet(`<slot />`)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(vaporHelpers).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,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('default slot outlet with props', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(
|
||||
`<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' }] },
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('statically named slot outlet with props', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(
|
||||
`<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' }] },
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('statically named slot outlet with v-bind="obj"', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(
|
||||
`<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' }] }],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('statically named slot outlet with v-on', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(
|
||||
`<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' }] }],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('default slot outlet with fallback', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(`<slot><div/></slot>`)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(ir.template[0]).toMatchObject('<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],
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('named slot outlet with fallback', () => {
|
||||
const { ir, code } = compileWithSlotsOutlet(
|
||||
`<slot name="foo"><div/></slot>`,
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(ir.template[0]).toMatchObject('<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],
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('error on unexpected custom directive on <slot>', () => {
|
||||
const onError = vi.fn()
|
||||
const source = `<slot v-foo />`
|
||||
const index = source.indexOf('v-foo')
|
||||
const { code } = compileWithSlotsOutlet(source, { onError })
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||
loc: {
|
||||
start: {
|
||||
offset: index,
|
||||
line: 1,
|
||||
column: index + 1,
|
||||
},
|
||||
end: {
|
||||
offset: index + 5,
|
||||
line: 1,
|
||||
column: index + 6,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('error on unexpected custom directive with v-show on <slot>', () => {
|
||||
const onError = vi.fn()
|
||||
const source = `<slot v-show="ok" />`
|
||||
const index = source.indexOf('v-show="ok"')
|
||||
const { code } = compileWithSlotsOutlet(source, { onError })
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||
code: ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||
loc: {
|
||||
start: {
|
||||
offset: index,
|
||||
line: 1,
|
||||
column: index + 1,
|
||||
},
|
||||
end: {
|
||||
offset: index + 11,
|
||||
line: 1,
|
||||
column: index + 12,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
|
@ -27,6 +27,7 @@ import { transformVModel } from './transforms/vModel'
|
|||
import { transformVIf } from './transforms/vIf'
|
||||
import { transformVFor } from './transforms/vFor'
|
||||
import { transformComment } from './transforms/transformComment'
|
||||
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
import type { HackOptions } from './ir'
|
||||
|
||||
export { wrapTemplate } from './transforms/utils'
|
||||
|
@ -103,6 +104,7 @@ export function getBaseTransformPreset(
|
|||
transformOnce,
|
||||
transformVIf,
|
||||
transformVFor,
|
||||
transformSlotOutlet,
|
||||
transformTemplateRef,
|
||||
transformText,
|
||||
transformElement,
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
buildCodeFragment,
|
||||
} from './utils'
|
||||
import { genCreateComponent } from './component'
|
||||
import { genSlotOutlet } from './slotOutlet'
|
||||
|
||||
export function genOperations(opers: OperationNode[], context: CodegenContext) {
|
||||
const [frag, push] = buildCodeFragment()
|
||||
|
@ -61,6 +62,8 @@ export function genOperation(
|
|||
return genCreateComponent(oper, context)
|
||||
case IRNodeTypes.DECLARE_OLD_REF:
|
||||
return genDeclareOldRef(oper)
|
||||
case IRNodeTypes.SLOT_OUTLET_NODE:
|
||||
return genSlotOutlet(oper, context)
|
||||
}
|
||||
|
||||
return []
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import type { CodegenContext } from '../generate'
|
||||
import type { SlotOutletIRNode } from '../ir'
|
||||
import { genBlock } from './block'
|
||||
import { genExpression } from './expression'
|
||||
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
|
||||
import { genRawProps } from './component'
|
||||
|
||||
export function genSlotOutlet(oper: SlotOutletIRNode, context: CodegenContext) {
|
||||
const { vaporHelper } = context
|
||||
const { id, name, fallback } = oper
|
||||
const [frag, push] = buildCodeFragment()
|
||||
|
||||
const nameExpr = name.isStatic
|
||||
? genExpression(name, context)
|
||||
: ['() => (', ...genExpression(name, context), ')']
|
||||
|
||||
let fallbackArg: CodeFragment[] | undefined
|
||||
if (fallback) {
|
||||
fallbackArg = genBlock(fallback, context)
|
||||
}
|
||||
|
||||
push(
|
||||
NEWLINE,
|
||||
`const n${id} = `,
|
||||
...genCall(
|
||||
vaporHelper('createSlot'),
|
||||
nameExpr,
|
||||
genRawProps(oper.props, context) || 'null',
|
||||
fallbackArg,
|
||||
),
|
||||
)
|
||||
|
||||
return frag
|
||||
}
|
|
@ -48,3 +48,4 @@ export { transformVIf } from './transforms/vIf'
|
|||
export { transformVFor } from './transforms/vFor'
|
||||
export { transformVModel } from './transforms/vModel'
|
||||
export { transformComment } from './transforms/transformComment'
|
||||
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
|
|
|
@ -30,6 +30,7 @@ export enum IRNodeTypes {
|
|||
PREPEND_NODE,
|
||||
CREATE_TEXT_NODE,
|
||||
CREATE_COMPONENT_NODE,
|
||||
SLOT_OUTLET_NODE,
|
||||
|
||||
WITH_DIRECTIVE,
|
||||
DECLARE_OLD_REF, // consider make it more general
|
||||
|
@ -214,6 +215,14 @@ export interface DeclareOldRefIRNode extends BaseIRNode {
|
|||
id: number
|
||||
}
|
||||
|
||||
export interface SlotOutletIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.SLOT_OUTLET_NODE
|
||||
id: number
|
||||
name: SimpleExpressionNode
|
||||
props: IRProps[]
|
||||
fallback?: BlockIRNode
|
||||
}
|
||||
|
||||
export type IRNode = OperationNode | RootIRNode
|
||||
export type OperationNode =
|
||||
| SetPropIRNode
|
||||
|
@ -232,6 +241,7 @@ export type OperationNode =
|
|||
| ForIRNode
|
||||
| CreateComponentIRNode
|
||||
| DeclareOldRefIRNode
|
||||
| SlotOutletIRNode
|
||||
|
||||
export enum DynamicFlag {
|
||||
NONE = 0,
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
type IRProp,
|
||||
type IRProps,
|
||||
type IRPropsDynamicAttribute,
|
||||
type IRPropsStatic,
|
||||
type VaporDirectiveNode,
|
||||
} from '../ir'
|
||||
import { EMPTY_EXPRESSION } from './utils'
|
||||
|
@ -125,7 +126,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
|
|||
|
||||
function transformNativeElement(
|
||||
tag: string,
|
||||
propsResult: ReturnType<typeof buildProps>,
|
||||
propsResult: PropsResult,
|
||||
context: TransformContext<ElementNode>,
|
||||
) {
|
||||
const { scopeId } = context.options
|
||||
|
@ -179,9 +180,9 @@ function transformNativeElement(
|
|||
|
||||
export type PropsResult =
|
||||
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
|
||||
| [dynamic: false, props: IRProp[]]
|
||||
| [dynamic: false, props: IRPropsStatic]
|
||||
|
||||
function buildProps(
|
||||
export function buildProps(
|
||||
node: ElementNode,
|
||||
context: TransformContext<ElementNode>,
|
||||
isComponent: boolean,
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import {
|
||||
type AttributeNode,
|
||||
type ElementNode,
|
||||
ElementTypes,
|
||||
ErrorCodes,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
createCompilerError,
|
||||
createSimpleExpression,
|
||||
isStaticArgOf,
|
||||
isStaticExp,
|
||||
} from '@vue/compiler-core'
|
||||
import type { NodeTransform, TransformContext } from '../transform'
|
||||
import {
|
||||
type BlockIRNode,
|
||||
DynamicFlag,
|
||||
IRNodeTypes,
|
||||
type IRProps,
|
||||
type VaporDirectiveNode,
|
||||
type WithDirectiveIRNode,
|
||||
} from '../ir'
|
||||
import { camelize, extend } from '@vue/shared'
|
||||
import { newBlock } from './utils'
|
||||
import { buildProps } from './transformElement'
|
||||
|
||||
export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||
if (node.type !== NodeTypes.ELEMENT || node.tag !== 'slot') {
|
||||
return
|
||||
}
|
||||
const id = context.reference()
|
||||
context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
|
||||
const [fallback, exitBlock] = createFallback(
|
||||
node,
|
||||
context as TransformContext<ElementNode>,
|
||||
)
|
||||
|
||||
let slotName: SimpleExpressionNode | undefined
|
||||
const slotProps: (AttributeNode | VaporDirectiveNode)[] = []
|
||||
for (const prop of node.props as (AttributeNode | VaporDirectiveNode)[]) {
|
||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||
if (prop.value) {
|
||||
if (prop.name === 'name') {
|
||||
slotName = createSimpleExpression(prop.value.content, true, prop.loc)
|
||||
} else {
|
||||
slotProps.push(extend({}, prop, { name: camelize(prop.name) }))
|
||||
}
|
||||
}
|
||||
} else if (prop.name === 'bind' && isStaticArgOf(prop.arg, 'name')) {
|
||||
if (prop.exp) {
|
||||
slotName = prop.exp!
|
||||
} else {
|
||||
// v-bind shorthand syntax
|
||||
slotName = createSimpleExpression(
|
||||
camelize(prop.arg!.content),
|
||||
false,
|
||||
prop.arg!.loc,
|
||||
)
|
||||
slotName.ast = null
|
||||
}
|
||||
} else {
|
||||
let slotProp = prop
|
||||
if (
|
||||
slotProp.name === 'bind' &&
|
||||
slotProp.arg &&
|
||||
isStaticExp(slotProp.arg)
|
||||
) {
|
||||
slotProp = extend({}, prop, {
|
||||
arg: extend({}, slotProp.arg, {
|
||||
content: camelize(slotProp.arg!.content),
|
||||
}),
|
||||
})
|
||||
}
|
||||
slotProps.push(slotProp)
|
||||
}
|
||||
}
|
||||
|
||||
slotName ||= createSimpleExpression('default', true)
|
||||
let irProps: IRProps[] = []
|
||||
if (slotProps.length) {
|
||||
const [isDynamic, props] = buildProps(
|
||||
extend({}, node, { props: slotProps }),
|
||||
context as TransformContext<ElementNode>,
|
||||
true,
|
||||
)
|
||||
irProps = isDynamic ? props : [props]
|
||||
|
||||
const runtimeDirective = context.block.operation.find(
|
||||
(oper): oper is WithDirectiveIRNode =>
|
||||
oper.type === IRNodeTypes.WITH_DIRECTIVE && oper.element === id,
|
||||
)
|
||||
if (runtimeDirective) {
|
||||
context.options.onError(
|
||||
createCompilerError(
|
||||
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||
runtimeDirective.dir.loc,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
exitBlock && exitBlock()
|
||||
context.registerOperation({
|
||||
type: IRNodeTypes.SLOT_OUTLET_NODE,
|
||||
id,
|
||||
name: slotName,
|
||||
props: irProps,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createFallback(
|
||||
node: ElementNode,
|
||||
context: TransformContext<ElementNode>,
|
||||
): [block?: BlockIRNode, exit?: () => void] {
|
||||
if (!node.children.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
context.node = node = extend({}, node, {
|
||||
type: NodeTypes.ELEMENT,
|
||||
tag: 'template',
|
||||
props: [],
|
||||
tagType: ElementTypes.TEMPLATE,
|
||||
children: node.children,
|
||||
})
|
||||
|
||||
const fallback = newBlock(node)
|
||||
const exitBlock = context.enterBlock(fallback)
|
||||
context.reference()
|
||||
return [fallback, exitBlock]
|
||||
}
|
|
@ -5,7 +5,7 @@ import {
|
|||
createCompilerError,
|
||||
createSimpleExpression,
|
||||
} from '@vue/compiler-dom'
|
||||
import { camelize } from '@vue/shared'
|
||||
import { camelize, extend } from '@vue/shared'
|
||||
import type { DirectiveTransform, TransformContext } from '../transform'
|
||||
import { resolveExpression } from '../utils'
|
||||
import { isReservedProp } from './transformElement'
|
||||
|
@ -58,7 +58,7 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
|||
let camel = false
|
||||
if (modifiers.includes('camel')) {
|
||||
if (arg.isStatic) {
|
||||
arg.content = camelize(arg.content)
|
||||
arg = extend({}, arg, { content: camelize(arg.content) })
|
||||
} else {
|
||||
camel = true
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ const delegatedEvents = /*#__PURE__*/ makeMap(
|
|||
export const transformVOn: DirectiveTransform = (dir, node, context) => {
|
||||
let { arg, exp, loc, modifiers } = dir
|
||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||
const isSlotOutlet = node.tag === 'slot'
|
||||
|
||||
if (!exp && !modifiers.length) {
|
||||
context.options.onError(
|
||||
|
@ -60,7 +61,7 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (isComponent) {
|
||||
if (isComponent || isSlotOutlet) {
|
||||
const handler = exp || EMPTY_EXPRESSION
|
||||
return {
|
||||
key: arg,
|
||||
|
|
Loading…
Reference in New Issue