feat: support casting plain element to component via is="vue:xxx"
In Vue 3's custom elements interop, we no longer process `is` usage on known native elements as component casting. (ref: https://v3.vuejs.org/guide/migration/custom-elements-interop.html) This introduced the need for `v-is`. However, since it is a directive, its value is considered a JavaScript expression. This makes it awkward to use (e.g. `v-is="'foo'"`) when majority of casting is non-dynamic, and also hinders static analysis when casting to built-in Vue components, e.g. transition-group. This commit adds the ability to cast a native element to a Vue component by simply adding a `vue:` prefix: ```html <button is="vue:my-button"></button> <ul is="vue:transition-group" tag="ul"></ul> ```
This commit is contained in:
parent
422b13e798
commit
af9e6999e1
@ -488,7 +488,12 @@ function parseTag(
|
|||||||
const options = context.options
|
const options = context.options
|
||||||
if (!context.inVPre && !options.isCustomElement(tag)) {
|
if (!context.inVPre && !options.isCustomElement(tag)) {
|
||||||
const hasVIs = props.some(
|
const hasVIs = props.some(
|
||||||
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
|
p =>
|
||||||
|
p.name === 'is' &&
|
||||||
|
// v-is="xxx" (TODO: deprecate)
|
||||||
|
(p.type === NodeTypes.DIRECTIVE ||
|
||||||
|
// is="vue:xxx"
|
||||||
|
(p.value && p.value.content.startsWith('vue:')))
|
||||||
)
|
)
|
||||||
if (options.isNativeTag && !hasVIs) {
|
if (options.isNativeTag && !hasVIs) {
|
||||||
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
||||||
|
@ -230,13 +230,19 @@ export function resolveComponentType(
|
|||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
ssr = false
|
ssr = false
|
||||||
) {
|
) {
|
||||||
const { tag } = node
|
let { tag } = node
|
||||||
|
|
||||||
// 1. dynamic component
|
// 1. dynamic component
|
||||||
const isProp = isComponentTag(tag)
|
const isExplicitDynamic = isComponentTag(tag)
|
||||||
? findProp(node, 'is')
|
const isProp =
|
||||||
: findDir(node, 'is')
|
findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
|
||||||
if (isProp) {
|
if (isProp) {
|
||||||
|
if (!isExplicitDynamic && isProp.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
// <button is="vue:xxx">
|
||||||
|
// if not <component>, only is value that starts with "vue:" will be
|
||||||
|
// treated as component by the parse phase and reach here.
|
||||||
|
tag = isProp.value!.content.slice(4)
|
||||||
|
} else {
|
||||||
const exp =
|
const exp =
|
||||||
isProp.type === NodeTypes.ATTRIBUTE
|
isProp.type === NodeTypes.ATTRIBUTE
|
||||||
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||||
@ -247,6 +253,7 @@ export function resolveComponentType(
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
||||||
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
@ -416,8 +423,11 @@ export function buildProps(
|
|||||||
isStatic = false
|
isStatic = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// skip :is on <component>
|
// skip is on <component>, or is="vue:xxx"
|
||||||
if (name === 'is' && isComponentTag(tag)) {
|
if (
|
||||||
|
name === 'is' &&
|
||||||
|
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user