From: 三咲智子 Kevin Deng Date: Wed, 13 Nov 2024 06:56:39 +0000 (+0800) Subject: feat(vapor): dynamic component X-Git-Tag: v3.6.0-alpha.1~16^2~289 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5f92ff8ca23dee4672677f8e1ca6aeacbea9f9b4;p=thirdparty%2Fvuejs%2Fcore.git feat(vapor): dynamic component --- 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 63c6e2d287..8e4509e196 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -213,6 +213,54 @@ export function render(_ctx) { }" `; +exports[`compiler: element transform > dynamic component > capitalized version w/ static binding 1`] = ` +"import { resolveDynamicComponent as _resolveDynamicComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const n0 = _createComponent(_resolveDynamicComponent("foo"), null, null, true) + return n0 +}" +`; + +exports[`compiler: element transform > dynamic component > dynamic binding 1`] = ` +"import { resolveDynamicComponent as _resolveDynamicComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const n0 = _createComponent(_resolveDynamicComponent(_ctx.foo), null, null, true) + return n0 +}" +`; + +exports[`compiler: element transform > dynamic component > dynamic binding shorthand 1`] = ` +"import { resolveDynamicComponent as _resolveDynamicComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const n0 = _createComponent(_resolveDynamicComponent(_ctx.is), null, null, true) + return n0 +}" +`; + +exports[`compiler: element transform > dynamic component > normal component with is prop 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const _component_custom_input = _resolveComponent("custom-input") + const n0 = _createComponent(_component_custom_input, [ + { is: () => ("foo") } + ], null, true) + return n0 +}" +`; + +exports[`compiler: element transform > dynamic component > static binding 1`] = ` +"import { resolveDynamicComponent as _resolveDynamicComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const n0 = _createComponent(_resolveDynamicComponent("foo"), null, null, true) + return n0 +}" +`; + exports[`compiler: element transform > empty template 1`] = ` " export function render(_ctx) { diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 746ac44cc6..dacfab9825 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -423,6 +423,117 @@ describe('compiler: element transform', () => { }) }) + describe('dynamic component', () => { + test('static binding', () => { + const { code, ir, vaporHelpers } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + expect(vaporHelpers).toContain('resolveDynamicComponent') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'component', + asset: true, + root: true, + props: [[]], + dynamic: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo', + isStatic: true, + }, + }, + ]) + }) + + test('capitalized version w/ static binding', () => { + const { code, ir, vaporHelpers } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + expect(vaporHelpers).toContain('resolveDynamicComponent') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Component', + asset: true, + root: true, + props: [[]], + dynamic: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo', + isStatic: true, + }, + }, + ]) + }) + + test('dynamic binding', () => { + const { code, ir, vaporHelpers } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + expect(vaporHelpers).toContain('resolveDynamicComponent') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'component', + asset: true, + root: true, + props: [[]], + dynamic: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo', + isStatic: false, + }, + }, + ]) + }) + + test('dynamic binding shorthand', () => { + const { code, ir, vaporHelpers } = + compileWithElementTransform(``) + expect(code).toMatchSnapshot() + expect(vaporHelpers).toContain('resolveDynamicComponent') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'component', + asset: true, + root: true, + props: [[]], + dynamic: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'is', + isStatic: false, + }, + }, + ]) + }) + + // #3934 + test('normal component with is prop', () => { + const { code, ir, vaporHelpers } = compileWithElementTransform( + ``, + { + isNativeTag: () => false, + }, + ) + expect(code).toMatchSnapshot() + expect(vaporHelpers).toContain('resolveComponent') + expect(vaporHelpers).not.toContain('resolveDynamicComponent') + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'custom-input', + asset: true, + root: true, + props: [[{ key: { content: 'is' }, values: [{ content: 'foo' }] }]], + }, + ]) + }) + }) + test('static props', () => { const { code, ir } = compileWithElementTransform( `
`, diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 8b2534b9fb..a0df807902 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -65,7 +65,12 @@ export function genCreateComponent( ] function genTag() { - if (oper.asset) { + if (oper.dynamic) { + return genCall( + vaporHelper('resolveDynamicComponent'), + genExpression(oper.dynamic, context), + ) + } else if (oper.asset) { return toValidAssetId(oper.tag, 'component') } else { return genExpression( diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 8e10ab0a7c..0b0b87fc4b 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -194,6 +194,7 @@ export interface CreateComponentIRNode extends BaseIRNode { asset: boolean root: boolean once: boolean + dynamic?: SimpleExpressionNode } export interface DeclareOldRefIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 60d7f60234..1af0ff8725 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -1,13 +1,16 @@ import { isValidHTMLNesting } from '../html-nesting' import { type AttributeNode, + type ComponentNode, type ElementNode, ElementTypes, ErrorCodes, NodeTypes, + type PlainElementNode, type SimpleExpressionNode, createCompilerError, createSimpleExpression, + isStaticArgOf, } from '@vue/compiler-dom' import { camelize, @@ -33,6 +36,7 @@ import { type VaporDirectiveNode, } from '../ir' import { EMPTY_EXPRESSION } from './utils' +import { findProp } from '../utils' export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap( // the leading comma is intentional so empty string "" is also included @@ -51,46 +55,56 @@ export const transformElement: NodeTransform = (node, context) => { ) return - const { tag, tagType } = node - const isComponent = tagType === ElementTypes.COMPONENT + const isComponent = node.tagType === ElementTypes.COMPONENT + const isDynamicComponent = isComponentTag(node.tag) const propsResult = buildProps( node, context as TransformContext, isComponent, + isDynamicComponent, ) ;(isComponent ? transformComponentElement : transformNativeElement)( - tag, + node as any, propsResult, context as TransformContext, + isDynamicComponent, ) } } function transformComponentElement( - tag: string, + node: ComponentNode, propsResult: PropsResult, context: TransformContext, + isDynamicComponent: boolean, ) { - let asset = true + const dynamicComponent = isDynamicComponent + ? resolveDynamicComponent(node) + : undefined - const fromSetup = resolveSetupReference(tag, context) - if (fromSetup) { - tag = fromSetup - asset = false - } + let { tag } = node + let asset = true - const dotIndex = tag.indexOf('.') - if (dotIndex > 0) { - const ns = resolveSetupReference(tag.slice(0, dotIndex), context) - if (ns) { - tag = ns + tag.slice(dotIndex) + if (!dynamicComponent) { + const fromSetup = resolveSetupReference(tag, context) + if (fromSetup) { + tag = fromSetup asset = false } - } - if (asset) { - context.component.add(tag) + const dotIndex = tag.indexOf('.') + if (dotIndex > 0) { + const ns = resolveSetupReference(tag.slice(0, dotIndex), context) + if (ns) { + tag = ns + tag.slice(dotIndex) + asset = false + } + } + + if (asset) { + context.component.add(tag) + } } context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT @@ -106,10 +120,28 @@ function transformComponentElement( root, slots: [...context.slots], once: context.inVOnce, + dynamic: dynamicComponent, }) context.slots = [] } +function resolveDynamicComponent(node: ComponentNode) { + const isProp = findProp(node, 'is', false, true /* allow empty */) + if (!isProp) return + + if (isProp.type === NodeTypes.ATTRIBUTE) { + return isProp.value && createSimpleExpression(isProp.value.content, true) + } else { + return ( + isProp.exp || + // #10469 handle :is shorthand + extend(createSimpleExpression(`is`, false, isProp.arg!.loc), { + ast: null, + }) + ) + } +} + function resolveSetupReference(name: string, context: TransformContext) { const bindings = context.options.bindingMetadata if (!bindings || bindings.__isScriptSetup === false) { @@ -128,10 +160,11 @@ function resolveSetupReference(name: string, context: TransformContext) { } function transformNativeElement( - tag: string, + node: PlainElementNode, propsResult: PropsResult, context: TransformContext, ) { + const { tag } = node const { scopeId } = context.options let template = '' @@ -189,6 +222,7 @@ export function buildProps( node: ElementNode, context: TransformContext, isComponent: boolean, + isDynamicComponent: boolean, ): PropsResult { const props = node.props as (VaporDirectiveNode | AttributeNode)[] if (props.length === 0) return [false, []] @@ -252,6 +286,18 @@ export function buildProps( } } + // exclude `is` prop for + if ( + (isDynamicComponent && + prop.type === NodeTypes.ATTRIBUTE && + prop.name === 'is') || + (prop.type === NodeTypes.DIRECTIVE && + prop.name === 'bind' && + isStaticArgOf(prop.arg, 'is')) + ) { + continue + } + const result = transformProp(prop, node, context) if (result) { dynamicExpr.push(result.key, result.value) @@ -362,3 +408,7 @@ function mergePropValues(existing: IRProp, incoming: IRProp) { const newValues = incoming.values existing.values.push(...newValues) } + +function isComponentTag(tag: string) { + return tag === 'component' || tag === 'Component' +} diff --git a/packages/runtime-vapor/src/helpers/resolveAssets.ts b/packages/runtime-vapor/src/helpers/resolveAssets.ts index 06bd31f57f..b3281b5542 100644 --- a/packages/runtime-vapor/src/helpers/resolveAssets.ts +++ b/packages/runtime-vapor/src/helpers/resolveAssets.ts @@ -1,4 +1,4 @@ -import { camelize, capitalize } from '@vue/shared' +import { camelize, capitalize, isString } from '@vue/shared' import { type Directive, warn } from '..' import { type Component, currentInstance } from '../component' import { getComponentName } from '../component' @@ -79,3 +79,16 @@ function resolve(registry: Record | undefined, name: string) { registry[capitalize(camelize(name))]) ) } + +/** + * @private + */ +export function resolveDynamicComponent( + component: string | Component, +): string | Component { + if (isString(component)) { + return resolveAsset(COMPONENTS, component, false) || component + } else { + return component + } +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index b7b7592f59..0a652b2081 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -131,7 +131,11 @@ export { createFor, createForSlots } from './apiCreateFor' export { createComponent } from './apiCreateComponent' export { createSelector } from './apiCreateSelector' -export { resolveComponent, resolveDirective } from './helpers/resolveAssets' +export { + resolveComponent, + resolveDirective, + resolveDynamicComponent, +} from './helpers/resolveAssets' export { toHandlers } from './helpers/toHandlers' export { withDestructure } from './destructure'