diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
index bf3510a052d..f93e1131b6d 100644
--- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
@@ -99,6 +99,40 @@ describe('compiler: element transform', () => {
expect(node.tag).toBe(`$setup["Example"]`)
})
+ test('resolve component from setup bindings & component', () => {
+ const { root, node } = parseWithElementTransform(``, {
+ bindingMetadata: {
+ search: BindingTypes.SETUP_CONST,
+ },
+ isNativeTag: (tag: string) => tag !== 'search',
+ })
+ expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
+ expect(node.tag).toBe(`_resolveLateAddedTag("search", 'setupState')`)
+
+ const { root: root2, node: node2 } = parseWithElementTransform(
+ ``,
+ {
+ bindingMetadata: {
+ search: BindingTypes.SETUP_LET,
+ },
+ isNativeTag: (tag: string) => tag !== 'search',
+ },
+ )
+ expect(root2.helpers).not.toContain(RESOLVE_COMPONENT)
+ expect(node2.tag).toBe(`_resolveLateAddedTag("search", 'setupState')`)
+ })
+
+ test('resolve component from props', () => {
+ const { root, node } = parseWithElementTransform(``, {
+ bindingMetadata: {
+ search: BindingTypes.PROPS,
+ },
+ isNativeTag: (tag: string) => tag !== 'search',
+ })
+ expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
+ expect(node.tag).toBe(`_unref(_resolveLateAddedTag("search", 'props'))`)
+ })
+
test('resolve component from setup bindings (inline)', () => {
const { root, node } = parseWithElementTransform(``, {
inline: true,
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index bae13372a98..26c86f1175c 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -9,6 +9,7 @@ import {
OPEN_BLOCK,
type RENDER_LIST,
type RENDER_SLOT,
+ RESOLVE_LATE_ADDED_TAG,
WITH_DIRECTIVES,
type WITH_MEMO,
} from './runtimeHelpers'
@@ -875,6 +876,10 @@ export function getVNodeBlockHelper(
return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
}
+export function getResolveLateAddedTagHelper(): typeof RESOLVE_LATE_ADDED_TAG {
+ return RESOLVE_LATE_ADDED_TAG
+}
+
export function convertToBlock(
node: VNodeCall,
{ helper, removeHelper, inSSR }: TransformContext,
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 99020bcf1ae..ed3feb1341f 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -24,6 +24,7 @@ import {
type TemplateLiteral,
type TextNode,
type VNodeCall,
+ getResolveLateAddedTagHelper,
getVNodeBlockHelper,
getVNodeHelper,
locStub,
@@ -336,6 +337,8 @@ export function generate(
if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
// binding optimization args
args.push('$props', '$setup', '$data', '$options')
+ // Add helper 'getResolveLateAddedTagHelper' for $setup
+ context.helper(getResolveLateAddedTagHelper())
}
const signature =
!__BROWSER__ && options.isTS
diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts
index 7cf3757b249..21d4de02de8 100644
--- a/packages/compiler-core/src/runtimeHelpers.ts
+++ b/packages/compiler-core/src/runtimeHelpers.ts
@@ -26,6 +26,9 @@ export const CREATE_STATIC: unique symbol = Symbol(
export const RESOLVE_COMPONENT: unique symbol = Symbol(
__DEV__ ? `resolveComponent` : ``,
)
+export const RESOLVE_LATE_ADDED_TAG: unique symbol = Symbol(
+ __DEV__ ? `resolveLateAddedTag` : ``,
+)
export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol(
__DEV__ ? `resolveDynamicComponent` : ``,
)
@@ -98,6 +101,7 @@ export const helperNameMap: Record = {
[CREATE_TEXT]: `createTextVNode`,
[CREATE_STATIC]: `createStaticVNode`,
[RESOLVE_COMPONENT]: `resolveComponent`,
+ [RESOLVE_LATE_ADDED_TAG]: `resolveLateAddedTag`,
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
[RESOLVE_FILTER]: `resolveFilter`,
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 1dca0c514c1..4bb62909c66 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -21,12 +21,14 @@ import {
createObjectProperty,
createSimpleExpression,
createVNodeCall,
+ getResolveLateAddedTagHelper,
} from '../ast'
import {
PatchFlags,
camelize,
capitalize,
isBuiltInDirective,
+ isLateTag,
isObject,
isOn,
isReservedProp,
@@ -85,7 +87,6 @@ export const transformElement: NodeTransform = (node, context) => {
) {
return
}
-
const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
@@ -344,10 +345,13 @@ function resolveSetupReference(name: string, context: TransformContext) {
checkType(BindingTypes.SETUP_REACTIVE_CONST) ||
checkType(BindingTypes.LITERAL_CONST)
if (fromConst) {
+ const helper = context.helperString
return context.inline
? // in inline mode, const setup bindings (e.g. imports) can be used as-is
fromConst
- : `$setup[${JSON.stringify(fromConst)}]`
+ : isLateTag(fromConst)
+ ? `${helper(getResolveLateAddedTagHelper())}(${JSON.stringify(fromConst)}, 'setupState')`
+ : `$setup[${JSON.stringify(fromConst)}]`
}
const fromMaybeRef =
@@ -355,17 +359,25 @@ function resolveSetupReference(name: string, context: TransformContext) {
checkType(BindingTypes.SETUP_REF) ||
checkType(BindingTypes.SETUP_MAYBE_REF)
if (fromMaybeRef) {
+ const helper = context.helperString
return context.inline
? // setup scope bindings that may be refs need to be unrefed
`${context.helperString(UNREF)}(${fromMaybeRef})`
- : `$setup[${JSON.stringify(fromMaybeRef)}]`
+ : isLateTag(fromMaybeRef)
+ ? `${helper(getResolveLateAddedTagHelper())}(${JSON.stringify(fromMaybeRef)}, 'setupState')`
+ : `$setup[${JSON.stringify(fromMaybeRef)}]`
}
const fromProps = checkType(BindingTypes.PROPS)
if (fromProps) {
- return `${context.helperString(UNREF)}(${
- context.inline ? '__props' : '$props'
- }[${JSON.stringify(fromProps)}])`
+ const helper = context.helperString
+ const fromPropsStr = JSON.stringify(fromProps)
+ let propsCode = context.inline
+ ? `__props[${fromPropsStr}]`
+ : isLateTag(fromProps)
+ ? `${helper(getResolveLateAddedTagHelper())}(${fromPropsStr}, 'props')`
+ : `$props[${fromPropsStr}]`
+ return `${helper(UNREF)}(${propsCode})`
}
}
diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts
index 910fab33424..57a74d4e72c 100644
--- a/packages/runtime-core/src/helpers/resolveAssets.ts
+++ b/packages/runtime-core/src/helpers/resolveAssets.ts
@@ -7,7 +7,7 @@ import {
} from '../component'
import { currentRenderingInstance } from '../componentRenderContext'
import type { Directive } from '../directives'
-import { camelize, capitalize, isString } from '@vue/shared'
+import { camelize, capitalize, isLateTag, isString } from '@vue/shared'
import { warn } from '../warning'
import type { VNodeTypes } from '../vnode'
@@ -118,12 +118,21 @@ function resolveAsset(
return Component
}
- if (__DEV__ && warnMissing && !res) {
- const extra =
- type === COMPONENTS
- ? `\nIf this is a native custom element, make sure to exclude it from ` +
+ if (
+ __DEV__ &&
+ warnMissing &&
+ ((!res && !isLateTag(name)) || (res && isLateTag(name)))
+ ) {
+ let extra = ''
+ if (type === COMPONENTS) {
+ if (isLateTag(name)) {
+ extra = `\nplease do not use built-in tag names as component names.`
+ } else {
+ extra =
+ `\nIf this is a native custom element, make sure to exclude it from ` +
`component resolution via compilerOptions.isCustomElement.`
- : ``
+ }
+ }
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
}
@@ -144,3 +153,27 @@ function resolve(registry: Record | undefined, name: string) {
registry[capitalize(camelize(name))])
)
}
+
+/**
+ * @private
+ */
+export function resolveLateAddedTag(
+ name: string,
+ key: 'setupState' | 'props',
+): unknown {
+ if (!currentRenderingInstance || !currentRenderingInstance[key]) return name
+ const data = currentRenderingInstance[key]
+ const value = data[name]
+ // Only the render function for the value is parsed as a component
+ // and a warning is reported
+ if (
+ __DEV__ &&
+ value &&
+ (value as ComponentInternalInstance).render &&
+ isLateTag(name as string)
+ ) {
+ const extra = `\nplease do not use built-in tag names as component names.`
+ warn(`Failed to resolve component: ${name},${extra}`)
+ }
+ return value
+}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 1ed6f21df77..42683fd4abc 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -144,6 +144,7 @@ export {
resolveComponent,
resolveDirective,
resolveDynamicComponent,
+ resolveLateAddedTag,
} from './helpers/resolveAssets'
// For integration with runtime compiler
export { registerRuntimeCompiler, isRuntimeOnly } from './component'
diff --git a/packages/shared/src/domTagConfig.ts b/packages/shared/src/domTagConfig.ts
index 7f9d198e569..da23b36e3dc 100644
--- a/packages/shared/src/domTagConfig.ts
+++ b/packages/shared/src/domTagConfig.ts
@@ -14,6 +14,8 @@ const HTML_TAGS =
'option,output,progress,select,textarea,details,dialog,menu,' +
'summary,template,blockquote,iframe,tfoot'
+const LATE_ADDED_TAGS = 'search'
+
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element
const SVG_TAGS =
'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
@@ -62,3 +64,6 @@ export const isMathMLTag: (key: string) => boolean =
*/
export const isVoidTag: (key: string) => boolean =
/*@__PURE__*/ makeMap(VOID_TAGS)
+
+export const isLateTag: (key: string) => boolean =
+ /*#__PURE__*/ makeMap(LATE_ADDED_TAGS)