]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: support casting plain element to component via is="vue:xxx"
authorEvan You <yyx990803@gmail.com>
Mon, 12 Apr 2021 17:07:59 +0000 (13:07 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 12 Apr 2021 17:08:07 +0000 (13:08 -0400)
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>
```

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

index 77e91935119d56a0c547c899b2e479e04a840445..3bcf4159d5f06717b7dcb021b8cb56f0441adae3 100644 (file)
@@ -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
index 113b03dd8564f83d20316f87e15bd36d7b18e507..334304adc1351795777bef88e370654398dc5cf9 100644 (file)
@@ -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
+        ])
+      }
     }
   }
 
@@ -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(