Skip to content

Commit

Permalink
feat: support casting plain element to component via is="vue:xxx"
Browse files Browse the repository at this point in the history
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>
```
  • Loading branch information
yyx990803 committed Apr 12, 2021
1 parent 422b13e commit af9e699
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 15 deletions.
7 changes: 6 additions & 1 deletion packages/compiler-core/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,12 @@ function parseTag(
const options = context.options
if (!context.inVPre && !options.isCustomElement(tag)) {
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(tag)) tagType = ElementTypes.COMPONENT
Expand Down
38 changes: 24 additions & 14 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,21 +230,28 @@ export function resolveComponentType(
context: TransformContext,
ssr = false
) {
const { tag } = node
let { tag } = node

// 1. dynamic component
const isProp = isComponentTag(tag)
? findProp(node, 'is')
: findDir(node, 'is')
const isExplicitDynamic = isComponentTag(tag)
const isProp =
findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
if (isProp) {
const exp =
isProp.type === NodeTypes.ATTRIBUTE
? isProp.value && createSimpleExpression(isProp.value.content, true)
: isProp.exp
if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
exp
])
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 =
isProp.type === NodeTypes.ATTRIBUTE
? isProp.value && createSimpleExpression(isProp.value.content, true)
: isProp.exp
if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
exp
])
}
}
}

Expand Down Expand Up @@ -416,8 +423,11 @@ export function buildProps(
isStatic = false
}
}
// skip :is on <component>
if (name === 'is' && isComponentTag(tag)) {
// skip is on <component>, or is="vue:xxx"
if (
name === 'is' &&
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
) {
continue
}
properties.push(
Expand Down

0 comments on commit af9e699

Please sign in to comment.