From ba6167fcc98f114c16d3dd377212d77d4706ced2 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 8 May 2024 01:52:12 +0800 Subject: [PATCH 1/7] feat(compiler-vapor): static v-slot --- .../__snapshots__/compile.spec.ts.snap | 19 +++--- .../transformElement.spec.ts.snap | 38 +++++------ .../__snapshots__/vModel.spec.ts.snap | 12 ++-- .../__snapshots__/vSlot.spec.ts.snap | 56 ++++++++++++++++ .../transforms/transformElement.spec.ts | 4 +- .../__tests__/transforms/vSlot.spec.ts | 53 +++++++++++++++ packages/compiler-vapor/src/compile.ts | 2 + .../src/generators/component.ts | 16 ++++- packages/compiler-vapor/src/index.ts | 1 + packages/compiler-vapor/src/ir.ts | 11 ++- packages/compiler-vapor/src/transform.ts | 6 +- .../src/transforms/transformChildren.ts | 7 +- .../src/transforms/transformElement.ts | 2 + .../compiler-vapor/src/transforms/vSlot.ts | 67 +++++++++++++++++++ packages/compiler-vapor/src/utils.ts | 8 +++ 15 files changed, 261 insertions(+), 41 deletions(-) create mode 100644 packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap create mode 100644 packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts create mode 100644 packages/compiler-vapor/src/transforms/vSlot.ts diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index 1e5e4cf4f..9e401d0ba 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -29,15 +29,16 @@ const t0 = _template("
") 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 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 75195b027..2d3bc2f23 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -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 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index 5f44f54cf..62e0ece59 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -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 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap new file mode 100644 index 000000000..1bc370f03 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -0,0 +1,56 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler: transform slot > implicit default slot 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor'; +const t0 = _template("
") + +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("") + +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("
") + +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 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 2f5be196e..248241246 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -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', () => { diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts new file mode 100644 index 000000000..d9eb9d7bf --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -0,0 +1,53 @@ +import { + 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 { code } = compileWithSlots(`
`) + expect(code).toMatchSnapshot() + }) + + test('named slots w/ implicit default slot', () => { + const { code } = compileWithSlots( + ` + bar + `, + ) + expect(code).toMatchSnapshot() + }) + + test('nested slots', () => { + const { code } = compileWithSlots( + ` + + `, + ) + expect(code).toMatchSnapshot() + }) +}) diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index 25790d65c..7aa9659b1 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -29,6 +29,7 @@ import { transformVFor } from './transforms/vFor' import { transformComment } from './transforms/transformComment' import { transformSlotOutlet } from './transforms/transformSlotOutlet' import type { HackOptions } from './ir' +import { transformVSlot } from './transforms/vSlot' export { wrapTemplate } from './transforms/utils' @@ -108,6 +109,7 @@ export function getBaseTransformPreset( transformTemplateRef, transformText, transformElement, + transformVSlot, transformComment, transformChildren, ], diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 3256e514c..08e707363 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -22,8 +22,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, @@ -40,7 +40,9 @@ export function genCreateComponent( ...genCall( vaporHelper('createComponent'), tag, - rawProps || (isRoot ? 'null' : false), + rawProps || (oper.slots || isRoot ? 'null' : false), + oper.slots ? genSlots(oper, context) : isRoot ? 'null' : false, + isRoot && 'null', isRoot && 'true', ), ...genDirectivesForElement(oper.id, context), @@ -134,3 +136,13 @@ function genModelModifiers( const modifiersVal = genDirectiveModifiers(modelModifiers) return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] } + +function genSlots(oper: CreateComponentIRNode, context: CodegenContext) { + const slotList = Object.entries(oper.slots!) + return genMulti( + slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT, + ...slotList.map(([name, slot]) => { + return [name, ': ', ...genBlock(slot, context)] + }), + ) +} diff --git a/packages/compiler-vapor/src/index.ts b/packages/compiler-vapor/src/index.ts index e222fadee..6ee58bc54 100644 --- a/packages/compiler-vapor/src/index.ts +++ b/packages/compiler-vapor/src/index.ts @@ -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' diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index bba2f3cfc..3d8cd3601 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -199,12 +199,21 @@ export interface WithDirectiveIRNode extends BaseIRNode { builtin?: VaporHelper } +export interface ComponentSlotBlockIRNode extends BlockIRNode {} +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?: Record + dynamicSlots?: ComponentDynamicSlot[] resolve: boolean root: boolean diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 4399fbf90..48e8214fc 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -16,6 +16,7 @@ import { import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' import { type BlockIRNode, + type ComponentSlotBlockIRNode, DynamicFlag, type HackOptions, type IRDynamicInfo, @@ -77,6 +78,7 @@ export class TransformContext { comment: CommentNode[] = [] component: Set = this.ir.component + slots: Record | null = null private globalId = 0 @@ -90,11 +92,12 @@ export class TransformContext { } enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void { - const { block, template, dynamic, childrenTemplate } = this + const { block, template, dynamic, childrenTemplate, slots } = this this.block = ir this.dynamic = ir.dynamic this.template = '' this.childrenTemplate = [] + this.slots = null isVFor && this.inVFor++ return () => { // exit @@ -103,6 +106,7 @@ export class TransformContext { this.template = template this.dynamic = dynamic this.childrenTemplate = childrenTemplate + this.slots = slots isVFor && this.inVFor-- } } diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index da007fc1b..18a1835df 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -13,6 +13,9 @@ import { import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir' export const transformChildren: NodeTransform = (node, context) => { + const isComponent = + node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.COMPONENT + const isFragment = node.type === NodeTypes.ROOT || (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE) @@ -27,7 +30,7 @@ export const transformChildren: NodeTransform = (node, context) => { ) transformNode(childContext) - if (isFragment) { + if (isFragment || isComponent) { childContext.reference() childContext.registerTemplate() @@ -44,7 +47,7 @@ export const transformChildren: NodeTransform = (node, context) => { context.dynamic.children[i] = childContext.dynamic } - if (!isFragment) { + if (!(isFragment || isComponent)) { processDynamicChildren(context as TransformContext) } } diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index e45428af4..1dbc45982 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -104,7 +104,9 @@ function transformComponentElement( props: propsResult[0] ? propsResult[1] : [propsResult[1]], resolve, root, + slots: context.slots || undefined, }) + context.slots = null } function resolveSetupReference(name: string, context: TransformContext) { diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts new file mode 100644 index 000000000..006d3d37a --- /dev/null +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -0,0 +1,67 @@ +import { + type ElementNode, + ElementTypes, + NodeTypes, + 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 } from '../utils' + +// TODO dynamic slots +export const transformVSlot: NodeTransform = (node, context) => { + if (node.type !== NodeTypes.ELEMENT) return + + const { tagType, children } = node + const { parent } = context + let dir: VaporDirectiveNode | undefined + + 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 hasDefalutSlot = children.some( + n => !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), + ) + + const [block, onExit] = createSlotBlock( + node, + context as TransformContext, + ) + + const slots = (context.slots ||= {}) + + return () => { + onExit() + if (hasDefalutSlot) slots.default = block + if (Object.keys(slots).length) context.slots = slots + } + } else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) { + context.dynamic.flags |= DynamicFlag.NON_TEMPLATE + + const slots = context.slots! + + const [block, onExit] = createSlotBlock( + node, + context as TransformContext, + ) + + slots[dir.arg!.content] = block + return () => onExit() + } +} + +function createSlotBlock( + slotNode: ElementNode, + context: TransformContext, +): [BlockIRNode, () => void] { + const branch: BlockIRNode = newBlock(slotNode) + const exitBlock = context.enterBlock(branch) + return [branch, exitBlock] +} diff --git a/packages/compiler-vapor/src/utils.ts b/packages/compiler-vapor/src/utils.ts index 627978538..fdbd101a0 100644 --- a/packages/compiler-vapor/src/utils.ts +++ b/packages/compiler-vapor/src/utils.ts @@ -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 From 4526a7b56f25683bb5b85bc6761ef9f544dc5d23 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 8 May 2024 02:04:47 +0800 Subject: [PATCH 2/7] test: improve the test --- .../__tests__/transforms/vSlot.spec.ts | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index d9eb9d7bf..330872b74 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -1,4 +1,5 @@ import { + IRNodeTypes, transformChildren, transformElement, transformSlotOutlet, @@ -29,17 +30,63 @@ const compileWithSlots = makeCompile({ describe('compiler: transform slot', () => { test('implicit default slot', () => { - const { code } = compileWithSlots(`
`) + const { ir, code } = compileWithSlots(`
`) expect(code).toMatchSnapshot() + + expect(ir.template).toEqual(['
']) + 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 { code } = compileWithSlots( + const { ir, code } = compileWithSlots( ` bar `, ) expect(code).toMatchSnapshot() + + expect(ir.template).toEqual(['foo', 'bar', '']) + 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', () => { From 97a1251f933de540354e1dd4f18da48cbb0aba14 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Wed, 8 May 2024 16:47:20 +0800 Subject: [PATCH 3/7] feat(compiler-vapor): impl dynamic slot name --- .../__snapshots__/vSlot.spec.ts.snap | 14 +++++ .../__tests__/transforms/vSlot.spec.ts | 60 +++++++------------ .../src/generators/component.ts | 12 +++- packages/compiler-vapor/src/ir.ts | 7 ++- packages/compiler-vapor/src/transform.ts | 4 +- .../compiler-vapor/src/transforms/vSlot.ts | 16 +++-- 6 files changed, 65 insertions(+), 48 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 1bc370f03..d96e9eb97 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -1,5 +1,19 @@ // 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, { [_ctx.dynamicName]: () => { + const n0 = t0() + return n0 + } }, null, 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("
") diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index 330872b74..2db3e28ec 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -1,4 +1,6 @@ +import { createSimpleExpression } from '@vue/compiler-dom' import { + type CreateComponentIRNode, IRNodeTypes, transformChildren, transformElement, @@ -34,22 +36,10 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(ir.template).toEqual(['
']) - 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.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) + const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! + expect(slots.length).toBe(1) + expect(slots[0].name.content).toBe('default') expect(ir.block.returns).toEqual([1]) expect(ir.block.dynamic).toMatchObject({ children: [{ id: 1 }], @@ -65,28 +55,11 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(ir.template).toEqual(['foo', 'bar', '']) - 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 }], - }, - }, - }, - }, - ]) + expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) + const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! + expect(slots.length).toBe(2) + expect(slots[0].name.content).toBe('one') + expect(slots[1].name.content).toBe('default') }) test('nested slots', () => { @@ -97,4 +70,15 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() }) + + test('dynamic slots name', () => { + const { ir, code } = compileWithSlots(` + + `) + expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) + const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! + expect(slots.length).toBe(1) + expect(slots[0].name.isStatic).toBe(false) + expect(code).toMatchSnapshot() + }) }) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 08e707363..05afb5438 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -138,11 +138,17 @@ function genModelModifiers( } function genSlots(oper: CreateComponentIRNode, context: CodegenContext) { - const slotList = Object.entries(oper.slots!) + const slotList = oper.slots! return genMulti( slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT, - ...slotList.map(([name, slot]) => { - return [name, ': ', ...genBlock(slot, context)] + ...slotList.map(({ name, block }) => { + return [ + ...(name.isStatic + ? [name.content] + : ['[', ...genExpression(name, context), ']']), + ': ', + ...genBlock(block, context), + ] }), ) } diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 3d8cd3601..8d985c0e3 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -199,6 +199,11 @@ export interface WithDirectiveIRNode extends BaseIRNode { builtin?: VaporHelper } +export interface ComponentStaticSlot { + name: SimpleExpressionNode + block: ComponentSlotBlockIRNode +} + export interface ComponentSlotBlockIRNode extends BlockIRNode {} export interface ComponentDynamicSlot { name: SimpleExpressionNode @@ -212,7 +217,7 @@ export interface CreateComponentIRNode extends BaseIRNode { tag: string props: IRProps[] - slots?: Record + slots?: ComponentStaticSlot[] dynamicSlots?: ComponentDynamicSlot[] resolve: boolean diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 48e8214fc..1eceb8e4d 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -16,7 +16,7 @@ import { import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' import { type BlockIRNode, - type ComponentSlotBlockIRNode, + type ComponentStaticSlot, DynamicFlag, type HackOptions, type IRDynamicInfo, @@ -78,7 +78,7 @@ export class TransformContext { comment: CommentNode[] = [] component: Set = this.ir.component - slots: Record | null = null + slots: ComponentStaticSlot[] | null = null private globalId = 0 diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index 006d3d37a..9ea33a8d3 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -2,6 +2,7 @@ import { type ElementNode, ElementTypes, NodeTypes, + createSimpleExpression, isTemplateNode, isVSlot, } from '@vue/compiler-core' @@ -35,12 +36,16 @@ export const transformVSlot: NodeTransform = (node, context) => { context as TransformContext, ) - const slots = (context.slots ||= {}) + const slots = (context.slots ||= []) return () => { onExit() - if (hasDefalutSlot) slots.default = block - if (Object.keys(slots).length) context.slots = slots + if (hasDefalutSlot) + slots.push({ + name: createSimpleExpression('default', true), + block, + }) + if (slots.length) context.slots = slots } } else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) { context.dynamic.flags |= DynamicFlag.NON_TEMPLATE @@ -52,7 +57,10 @@ export const transformVSlot: NodeTransform = (node, context) => { context as TransformContext, ) - slots[dir.arg!.content] = block + slots.push({ + name: dir.arg!, + block, + }) return () => onExit() } } From c5ea21bc5a14e7e6449aeb5972a3412daaa59703 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 8 May 2024 22:36:18 +0800 Subject: [PATCH 4/7] refactor(compiler-vapor): impl dynamic slot name + add error detection --- .../__snapshots__/vSlot.spec.ts.snap | 11 +- .../__tests__/transforms/vSlot.spec.ts | 126 +++++++++++++++--- .../src/generators/component.ts | 50 +++++-- packages/compiler-vapor/src/ir.ts | 10 +- packages/compiler-vapor/src/transform.ts | 11 +- .../src/transforms/transformElement.ts | 2 + .../compiler-vapor/src/transforms/vSlot.ts | 77 ++++++++--- 7 files changed, 226 insertions(+), 61 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index d96e9eb97..ff692bbf6 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -6,10 +6,13 @@ const t0 = _template("foo") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n2 = _createComponent(_component_Comp, null, { [_ctx.dynamicName]: () => { - const n0 = t0() - return n0 - } }, null, true) + const n2 = _createComponent(_component_Comp, null, null, () => [{ + name: _ctx.name, + fn: () => { + const n0 = t0() + return n0 + } + }], true) return n2 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index 2db3e28ec..c98b75538 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -1,6 +1,5 @@ -import { createSimpleExpression } from '@vue/compiler-dom' +import { ErrorCodes, NodeTypes } from '@vue/compiler-core' import { - type CreateComponentIRNode, IRNodeTypes, transformChildren, transformElement, @@ -36,10 +35,22 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(ir.template).toEqual(['
']) - expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) - const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! - expect(slots.length).toBe(1) - expect(slots[0].name.content).toBe('default') + 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 }], @@ -55,11 +66,28 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(ir.template).toEqual(['foo', 'bar', '']) - expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) - const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! - expect(slots.length).toBe(2) - expect(slots[0].name.content).toBe('one') - expect(slots[1].name.content).toBe('default') + 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', () => { @@ -72,13 +100,75 @@ describe('compiler: transform slot', () => { }) test('dynamic slots name', () => { - const { ir, code } = compileWithSlots(` - - `) - expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE) - const slots = (ir.block.operation[0] as CreateComponentIRNode).slots! - expect(slots.length).toBe(1) - expect(slots[0].name.isStatic).toBe(false) + const { ir, code } = compileWithSlots( + ` + + `, + ) 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 = `bar` + 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 = `` + 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, + }, + }, + }) + }) }) }) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 05afb5438..8be5f67b3 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -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, @@ -31,7 +34,7 @@ export function genCreateComponent( const { vaporHelper } = context const tag = genTag() - const isRoot = oper.root + const { root: isRoot, slots, dynamicSlots } = oper const rawProps = genRawProps(oper.props, context) return [ @@ -40,9 +43,17 @@ export function genCreateComponent( ...genCall( vaporHelper('createComponent'), tag, - rawProps || (oper.slots || isRoot ? 'null' : false), - oper.slots ? genSlots(oper, context) : isRoot ? 'null' : false, - isRoot && 'null', + rawProps || (slots || dynamicSlots || isRoot ? 'null' : false), + slots + ? genSlots(slots, context) + : dynamicSlots || isRoot + ? 'null' + : false, + dynamicSlots + ? genDynamicSlots(dynamicSlots, context) + : isRoot + ? 'null' + : false, isRoot && 'true', ), ...genDirectivesForElement(oper.id, context), @@ -137,18 +148,29 @@ function genModelModifiers( return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] } -function genSlots(oper: CreateComponentIRNode, context: CodegenContext) { - const slotList = oper.slots! +function genSlots(slots: ComponentSlots, context: CodegenContext) { + const slotList = Object.entries(slots!) return genMulti( slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT, - ...slotList.map(({ name, block }) => { - return [ - ...(name.isStatic - ? [name.content] - : ['[', ...genExpression(name, context), ']']), - ': ', - ...genBlock(block, context), - ] + ...slotList.map(([name, slot]) => { + return [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 }) => { + return genMulti( + SEGMENTS_OBJECT_NEWLINE, + ['name: ', ...genExpression(name, context)], + ['fn: ', ...genBlock(fn, context)], + ) + }), + ) + return ['() => ', ...slotsExpr] +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 8d985c0e3..23e3114ec 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -199,12 +199,10 @@ export interface WithDirectiveIRNode extends BaseIRNode { builtin?: VaporHelper } -export interface ComponentStaticSlot { - name: SimpleExpressionNode - block: ComponentSlotBlockIRNode +export interface ComponentSlotBlockIRNode extends BlockIRNode { + // TODO slot props } - -export interface ComponentSlotBlockIRNode extends BlockIRNode {} +export type ComponentSlots = Record export interface ComponentDynamicSlot { name: SimpleExpressionNode fn: ComponentSlotBlockIRNode @@ -217,7 +215,7 @@ export interface CreateComponentIRNode extends BaseIRNode { tag: string props: IRProps[] - slots?: ComponentStaticSlot[] + slots?: ComponentSlots dynamicSlots?: ComponentDynamicSlot[] resolve: boolean diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 1eceb8e4d..363db3774 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -16,7 +16,8 @@ import { import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared' import { type BlockIRNode, - type ComponentStaticSlot, + type ComponentDynamicSlot, + type ComponentSlots, DynamicFlag, type HackOptions, type IRDynamicInfo, @@ -78,7 +79,8 @@ export class TransformContext { comment: CommentNode[] = [] component: Set = this.ir.component - slots: ComponentStaticSlot[] | null = null + slots: ComponentSlots | null = null + dynamicSlots: ComponentDynamicSlot[] | null = null private globalId = 0 @@ -92,12 +94,14 @@ export class TransformContext { } enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void { - const { block, template, dynamic, childrenTemplate, slots } = this + const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } = + this this.block = ir this.dynamic = ir.dynamic this.template = '' this.childrenTemplate = [] this.slots = null + this.dynamicSlots = null isVFor && this.inVFor++ return () => { // exit @@ -107,6 +111,7 @@ export class TransformContext { this.dynamic = dynamic this.childrenTemplate = childrenTemplate this.slots = slots + this.dynamicSlots = dynamicSlots isVFor && this.inVFor-- } } diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 1dbc45982..d2d3f07d9 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -105,8 +105,10 @@ function transformComponentElement( resolve, root, slots: context.slots || undefined, + dynamicSlots: context.dynamicSlots || undefined, }) context.slots = null + context.dynamicSlots = null } function resolveSetupReference(name: string, context: TransformContext) { diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index 9ea33a8d3..8d4554d9e 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -1,23 +1,25 @@ import { type ElementNode, ElementTypes, + ErrorCodes, NodeTypes, - createSimpleExpression, + 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 } from '../utils' +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 - let dir: VaporDirectiveNode | undefined const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length const isSlotTemplate = @@ -27,8 +29,10 @@ export const transformVSlot: NodeTransform = (node, context) => { parent.node.tagType === ElementTypes.COMPONENT if (isDefaultSlot) { - const hasDefalutSlot = children.some( - n => !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), + const defaultChildren = children.filter( + n => + isNonWhitespaceContent(node) && + !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), ) const [block, onExit] = createSlotBlock( @@ -36,31 +40,64 @@ export const transformVSlot: NodeTransform = (node, context) => { context as TransformContext, ) - const slots = (context.slots ||= []) + const slots = (context.slots ||= {}) + const dynamicSlots = (context.dynamicSlots ||= []) return () => { onExit() - if (hasDefalutSlot) - slots.push({ - name: createSimpleExpression('default', true), - block, - }) - if (slots.length) context.slots = slots + + 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, ) - slots.push({ - name: dir.arg!, - block, - }) + 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() } } @@ -73,3 +110,11 @@ function createSlotBlock( 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) +} From 958a9cae1d5eb0d7bd28745b4e52ba5942cd8ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 10 May 2024 21:30:52 +0800 Subject: [PATCH 5/7] refactor --- packages/compiler-vapor/src/compile.ts | 2 +- .../src/generators/component.ts | 28 ++++++++----------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index 7aa9659b1..25a27f23d 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -28,8 +28,8 @@ 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' import { transformVSlot } from './transforms/vSlot' +import type { HackOptions } from './ir' export { wrapTemplate } from './transforms/utils' diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 8be5f67b3..5b91a8446 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -34,7 +34,7 @@ export function genCreateComponent( const { vaporHelper } = context const tag = genTag() - const { root: isRoot, slots, dynamicSlots } = oper + const { root, slots, dynamicSlots } = oper const rawProps = genRawProps(oper.props, context) return [ @@ -43,18 +43,14 @@ export function genCreateComponent( ...genCall( vaporHelper('createComponent'), tag, - rawProps || (slots || dynamicSlots || isRoot ? 'null' : false), - slots - ? genSlots(slots, context) - : dynamicSlots || isRoot - ? 'null' - : false, + rawProps || (slots || dynamicSlots || root ? 'null' : false), + slots ? genSlots(slots, context) : dynamicSlots || root ? 'null' : false, dynamicSlots ? genDynamicSlots(dynamicSlots, context) - : isRoot + : root ? 'null' : false, - isRoot && 'true', + root && 'true', ), ...genDirectivesForElement(oper.id, context), ] @@ -149,12 +145,10 @@ function genModelModifiers( } function genSlots(slots: ComponentSlots, context: CodegenContext) { - const slotList = Object.entries(slots!) + const slotList = Object.entries(slots) return genMulti( slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT, - ...slotList.map(([name, slot]) => { - return [name, ': ', ...genBlock(slot, context)] - }), + ...slotList.map(([name, slot]) => [name, ': ', ...genBlock(slot, context)]), ) } @@ -164,13 +158,13 @@ function genDynamicSlots( ) { const slotsExpr = genMulti( dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY, - ...dynamicSlots.map(({ name, fn }) => { - return genMulti( + ...dynamicSlots.map(({ name, fn }) => + genMulti( SEGMENTS_OBJECT_NEWLINE, ['name: ', ...genExpression(name, context)], ['fn: ', ...genBlock(fn, context)], - ) - }), + ), + ), ) return ['() => ', ...slotsExpr] } From 2800df5f3f45fc90fdf1b3a8a55f865bebf5293c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sat, 11 May 2024 21:57:48 +0800 Subject: [PATCH 6/7] refactor: undefined --- packages/compiler-vapor/src/transform.ts | 8 ++++---- .../compiler-vapor/src/transforms/transformElement.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 363db3774..82f892758 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -79,8 +79,8 @@ export class TransformContext { comment: CommentNode[] = [] component: Set = this.ir.component - slots: ComponentSlots | null = null - dynamicSlots: ComponentDynamicSlot[] | null = null + slots?: ComponentSlots + dynamicSlots?: ComponentDynamicSlot[] private globalId = 0 @@ -100,8 +100,8 @@ export class TransformContext { this.dynamic = ir.dynamic this.template = '' this.childrenTemplate = [] - this.slots = null - this.dynamicSlots = null + this.slots = undefined + this.dynamicSlots = undefined isVFor && this.inVFor++ return () => { // exit diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index d2d3f07d9..c16ddd2e7 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -104,11 +104,11 @@ function transformComponentElement( props: propsResult[0] ? propsResult[1] : [propsResult[1]], resolve, root, - slots: context.slots || undefined, - dynamicSlots: context.dynamicSlots || undefined, + slots: context.slots, + dynamicSlots: context.dynamicSlots, }) - context.slots = null - context.dynamicSlots = null + context.slots = undefined + context.dynamicSlots = undefined } function resolveSetupReference(name: string, context: TransformContext) { From 58a5a03588573cc5d59386d2019704210e826320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sun, 12 May 2024 00:25:52 +0800 Subject: [PATCH 7/7] refactor --- packages/compiler-vapor/src/transform.ts | 2 +- .../src/transforms/transformChildren.ts | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 82f892758..893efce1f 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -85,7 +85,7 @@ export class TransformContext { private globalId = 0 constructor( - private ir: RootIRNode, + public ir: RootIRNode, public node: T, options: TransformOptions = {}, ) { diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index 18a1835df..68e63efea 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -13,12 +13,11 @@ import { import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir' export const transformChildren: NodeTransform = (node, context) => { - const isComponent = - node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.COMPONENT - 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 @@ -30,7 +29,7 @@ export const transformChildren: NodeTransform = (node, context) => { ) transformNode(childContext) - if (isFragment || isComponent) { + if (isFragment) { childContext.reference() childContext.registerTemplate() @@ -47,7 +46,7 @@ export const transformChildren: NodeTransform = (node, context) => { context.dynamic.children[i] = childContext.dynamic } - if (!(isFragment || isComponent)) { + if (!isFragment) { processDynamicChildren(context as TransformContext) } }