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 { 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 type { HackOptions } from './ir'
|
import type { HackOptions } from './ir'
|
||||||
|
|
||||||
export { wrapTemplate } from './transforms/utils'
|
export { wrapTemplate } from './transforms/utils'
|
||||||
|
@ -103,6 +104,7 @@ export function getBaseTransformPreset(
|
||||||
transformOnce,
|
transformOnce,
|
||||||
transformVIf,
|
transformVIf,
|
||||||
transformVFor,
|
transformVFor,
|
||||||
|
transformSlotOutlet,
|
||||||
transformTemplateRef,
|
transformTemplateRef,
|
||||||
transformText,
|
transformText,
|
||||||
transformElement,
|
transformElement,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
buildCodeFragment,
|
buildCodeFragment,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { genCreateComponent } from './component'
|
import { genCreateComponent } from './component'
|
||||||
|
import { genSlotOutlet } from './slotOutlet'
|
||||||
|
|
||||||
export function genOperations(opers: OperationNode[], context: CodegenContext) {
|
export function genOperations(opers: OperationNode[], context: CodegenContext) {
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
|
@ -61,6 +62,8 @@ export function genOperation(
|
||||||
return genCreateComponent(oper, context)
|
return genCreateComponent(oper, context)
|
||||||
case IRNodeTypes.DECLARE_OLD_REF:
|
case IRNodeTypes.DECLARE_OLD_REF:
|
||||||
return genDeclareOldRef(oper)
|
return genDeclareOldRef(oper)
|
||||||
|
case IRNodeTypes.SLOT_OUTLET_NODE:
|
||||||
|
return genSlotOutlet(oper, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
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 { 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'
|
||||||
|
|
|
@ -30,6 +30,7 @@ export enum IRNodeTypes {
|
||||||
PREPEND_NODE,
|
PREPEND_NODE,
|
||||||
CREATE_TEXT_NODE,
|
CREATE_TEXT_NODE,
|
||||||
CREATE_COMPONENT_NODE,
|
CREATE_COMPONENT_NODE,
|
||||||
|
SLOT_OUTLET_NODE,
|
||||||
|
|
||||||
WITH_DIRECTIVE,
|
WITH_DIRECTIVE,
|
||||||
DECLARE_OLD_REF, // consider make it more general
|
DECLARE_OLD_REF, // consider make it more general
|
||||||
|
@ -214,6 +215,14 @@ export interface DeclareOldRefIRNode extends BaseIRNode {
|
||||||
id: number
|
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 IRNode = OperationNode | RootIRNode
|
||||||
export type OperationNode =
|
export type OperationNode =
|
||||||
| SetPropIRNode
|
| SetPropIRNode
|
||||||
|
@ -232,6 +241,7 @@ export type OperationNode =
|
||||||
| ForIRNode
|
| ForIRNode
|
||||||
| CreateComponentIRNode
|
| CreateComponentIRNode
|
||||||
| DeclareOldRefIRNode
|
| DeclareOldRefIRNode
|
||||||
|
| SlotOutletIRNode
|
||||||
|
|
||||||
export enum DynamicFlag {
|
export enum DynamicFlag {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
type IRProp,
|
type IRProp,
|
||||||
type IRProps,
|
type IRProps,
|
||||||
type IRPropsDynamicAttribute,
|
type IRPropsDynamicAttribute,
|
||||||
|
type IRPropsStatic,
|
||||||
type VaporDirectiveNode,
|
type VaporDirectiveNode,
|
||||||
} from '../ir'
|
} from '../ir'
|
||||||
import { EMPTY_EXPRESSION } from './utils'
|
import { EMPTY_EXPRESSION } from './utils'
|
||||||
|
@ -125,7 +126,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
|
||||||
|
|
||||||
function transformNativeElement(
|
function transformNativeElement(
|
||||||
tag: string,
|
tag: string,
|
||||||
propsResult: ReturnType<typeof buildProps>,
|
propsResult: PropsResult,
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext<ElementNode>,
|
||||||
) {
|
) {
|
||||||
const { scopeId } = context.options
|
const { scopeId } = context.options
|
||||||
|
@ -179,9 +180,9 @@ function transformNativeElement(
|
||||||
|
|
||||||
export type PropsResult =
|
export type PropsResult =
|
||||||
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
|
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
|
||||||
| [dynamic: false, props: IRProp[]]
|
| [dynamic: false, props: IRPropsStatic]
|
||||||
|
|
||||||
function buildProps(
|
export function buildProps(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext<ElementNode>,
|
||||||
isComponent: boolean,
|
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,
|
createCompilerError,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { camelize } from '@vue/shared'
|
import { camelize, extend } from '@vue/shared'
|
||||||
import type { DirectiveTransform, TransformContext } from '../transform'
|
import type { DirectiveTransform, TransformContext } from '../transform'
|
||||||
import { resolveExpression } from '../utils'
|
import { resolveExpression } from '../utils'
|
||||||
import { isReservedProp } from './transformElement'
|
import { isReservedProp } from './transformElement'
|
||||||
|
@ -58,7 +58,7 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
||||||
let camel = false
|
let camel = false
|
||||||
if (modifiers.includes('camel')) {
|
if (modifiers.includes('camel')) {
|
||||||
if (arg.isStatic) {
|
if (arg.isStatic) {
|
||||||
arg.content = camelize(arg.content)
|
arg = extend({}, arg, { content: camelize(arg.content) })
|
||||||
} else {
|
} else {
|
||||||
camel = true
|
camel = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ const delegatedEvents = /*#__PURE__*/ makeMap(
|
||||||
export const transformVOn: DirectiveTransform = (dir, node, context) => {
|
export const transformVOn: DirectiveTransform = (dir, node, context) => {
|
||||||
let { arg, exp, loc, modifiers } = dir
|
let { arg, exp, loc, modifiers } = dir
|
||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
|
const isSlotOutlet = node.tag === 'slot'
|
||||||
|
|
||||||
if (!exp && !modifiers.length) {
|
if (!exp && !modifiers.length) {
|
||||||
context.options.onError(
|
context.options.onError(
|
||||||
|
@ -60,7 +61,7 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isComponent) {
|
if (isComponent || isSlotOutlet) {
|
||||||
const handler = exp || EMPTY_EXPRESSION
|
const handler = exp || EMPTY_EXPRESSION
|
||||||
return {
|
return {
|
||||||
key: arg,
|
key: arg,
|
||||||
|
|
Loading…
Reference in New Issue