Skip to content

Commit af9e699

Browse files
committed
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> ```
1 parent 422b13e commit af9e699

File tree

2 files changed

+30
-15
lines changed

2 files changed

+30
-15
lines changed

packages/compiler-core/src/parse.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,12 @@ function parseTag(
488488
const options = context.options
489489
if (!context.inVPre && !options.isCustomElement(tag)) {
490490
const hasVIs = props.some(
491-
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
491+
p =>
492+
p.name === 'is' &&
493+
// v-is="xxx" (TODO: deprecate)
494+
(p.type === NodeTypes.DIRECTIVE ||
495+
// is="vue:xxx"
496+
(p.value && p.value.content.startsWith('vue:')))
492497
)
493498
if (options.isNativeTag && !hasVIs) {
494499
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT

packages/compiler-core/src/transforms/transformElement.ts

+24-14
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,28 @@ export function resolveComponentType(
230230
context: TransformContext,
231231
ssr = false
232232
) {
233-
const { tag } = node
233+
let { tag } = node
234234

235235
// 1. dynamic component
236-
const isProp = isComponentTag(tag)
237-
? findProp(node, 'is')
238-
: findDir(node, 'is')
236+
const isExplicitDynamic = isComponentTag(tag)
237+
const isProp =
238+
findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
239239
if (isProp) {
240-
const exp =
241-
isProp.type === NodeTypes.ATTRIBUTE
242-
? isProp.value && createSimpleExpression(isProp.value.content, true)
243-
: isProp.exp
244-
if (exp) {
245-
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
246-
exp
247-
])
240+
if (!isExplicitDynamic && isProp.type === NodeTypes.ATTRIBUTE) {
241+
// <button is="vue:xxx">
242+
// if not <component>, only is value that starts with "vue:" will be
243+
// treated as component by the parse phase and reach here.
244+
tag = isProp.value!.content.slice(4)
245+
} else {
246+
const exp =
247+
isProp.type === NodeTypes.ATTRIBUTE
248+
? isProp.value && createSimpleExpression(isProp.value.content, true)
249+
: isProp.exp
250+
if (exp) {
251+
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
252+
exp
253+
])
254+
}
248255
}
249256
}
250257

@@ -416,8 +423,11 @@ export function buildProps(
416423
isStatic = false
417424
}
418425
}
419-
// skip :is on <component>
420-
if (name === 'is' && isComponentTag(tag)) {
426+
// skip is on <component>, or is="vue:xxx"
427+
if (
428+
name === 'is' &&
429+
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
430+
) {
421431
continue
422432
}
423433
properties.push(

0 commit comments

Comments
 (0)