]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: dynamic component fallback to native element
authorEvan You <yyx990803@gmail.com>
Mon, 23 Mar 2020 18:47:04 +0000 (14:47 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 23 Mar 2020 18:47:04 +0000 (14:47 -0400)
fix #870

packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/helpers/resolveAssets.ts
packages/runtime-core/src/vnode.ts

index 85800feff69823ea95ed7a64dc5643931ed76f21..43ba48b9a49b35b7d0fa86481faab3f3164f739b 100644 (file)
@@ -798,9 +798,18 @@ describe('compiler: element transform', () => {
   describe('dynamic component', () => {
     test('static binding', () => {
       const { node, root } = parseWithBind(`<component is="foo" />`)
-      expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
+      expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
       expect(node).toMatchObject({
-        tag: '_component_foo'
+        tag: {
+          callee: RESOLVE_DYNAMIC_COMPONENT,
+          arguments: [
+            {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: 'foo',
+              isStatic: true
+            }
+          ]
+        }
       })
     })
 
@@ -813,7 +822,8 @@ describe('compiler: element transform', () => {
           arguments: [
             {
               type: NodeTypes.SIMPLE_EXPRESSION,
-              content: 'foo'
+              content: 'foo',
+              isStatic: false
             }
           ]
         }
index 3123d8ecb7721cfdb73c4f0d2028413158b5cf50..155c1dcffb37f1fb765e12276488f8447f6edba7 100644 (file)
@@ -204,19 +204,13 @@ export function resolveComponentType(
   // 1. dynamic component
   const isProp = node.tag === 'component' && findProp(node, 'is')
   if (isProp) {
-    // static <component is="foo" />
-    if (isProp.type === NodeTypes.ATTRIBUTE) {
-      const isType = isProp.value && isProp.value.content
-      if (isType) {
-        context.helper(RESOLVE_COMPONENT)
-        context.components.add(isType)
-        return toValidAssetId(isType, `component`)
-      }
-    }
-    // dynamic <component :is="asdf" />
-    else if (isProp.exp) {
+    const exp =
+      isProp.type === NodeTypes.ATTRIBUTE
+        ? isProp.value && createSimpleExpression(isProp.value.content, true)
+        : isProp.exp
+    if (exp) {
       return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
-        isProp.exp
+        exp
       ])
     }
   }
index 8abae35d321ee5d6658eb8fabb1c624d220a0a78..c0275dc93ba9c524b3a7e489b30e64ee0ce7e99e 100644 (file)
@@ -20,13 +20,11 @@ describe('ssr: components', () => {
   test('dynamic component', () => {
     expect(compile(`<component is="foo" prop="b" />`).code)
       .toMatchInlineSnapshot(`
-      "const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
+      "const { resolveDynamicComponent: _resolveDynamicComponent, withCtx: _withCtx } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
       return function ssrRender(_ctx, _push, _parent) {
-        const _component_foo = _resolveComponent(\\"foo\\")
-
-        _push(_ssrRenderComponent(_component_foo, { prop: \\"b\\" }, null, _parent))
+        _push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), { prop: \\"b\\" }, null, _parent))
       }"
     `)
 
index 6cac9b248acfc5719c259a26c0dd708cc5687ac3..0e56ba9ff9e57c819969c009c571210028498aad 100644 (file)
@@ -6,11 +6,14 @@ import {
   Component,
   Directive,
   resolveDynamicComponent,
-  h
+  h,
+  serializeInner
 } from '@vue/runtime-test'
 import { mockWarn } from '@vue/shared'
 
 describe('resolveAssets', () => {
+  mockWarn()
+
   test('should work', () => {
     const FooBar = () => null
     const BarBaz = { mounted: () => null }
@@ -63,8 +66,6 @@ describe('resolveAssets', () => {
   })
 
   describe('warning', () => {
-    mockWarn()
-
     test('used outside render() or setup()', () => {
       resolveComponent('foo')
       expect(
@@ -128,5 +129,22 @@ describe('resolveAssets', () => {
       expect(bar).toBe(dynamicComponents.bar)
       expect(baz).toBe(dynamicComponents.baz)
     })
+
+    test('resolve dynamic component should fallback to plain element without warning', () => {
+      const Root = {
+        setup() {
+          return () => {
+            return h(resolveDynamicComponent('div') as string, null, {
+              default: () => 'hello'
+            })
+          }
+        }
+      }
+
+      const app = createApp(Root)
+      const root = nodeOps.createElement('div')
+      app.mount(root)
+      expect(serializeInner(root)).toBe('<div>hello</div>')
+    })
   })
 })
index bb84aa495de8590099355344d665bc5aba350ba4..7c11c2ab9c7b5e7434bcfe9ed0622fd6461f8fc7 100644 (file)
@@ -106,7 +106,7 @@ describe('vnode', () => {
       const vnode = createVNode('p', null, ['foo'])
       expect(vnode.children).toMatchObject(['foo'])
       expect(vnode.shapeFlag).toBe(
-        ShapeFlags.ELEMENT + ShapeFlags.ARRAY_CHILDREN
+        ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN
       )
     })
 
@@ -114,7 +114,7 @@ describe('vnode', () => {
       const vnode = createVNode('p', null, { foo: 'foo' })
       expect(vnode.children).toMatchObject({ foo: 'foo' })
       expect(vnode.shapeFlag).toBe(
-        ShapeFlags.ELEMENT + ShapeFlags.SLOTS_CHILDREN
+        ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
       )
     })
 
@@ -122,7 +122,7 @@ describe('vnode', () => {
       const vnode = createVNode('p', null, nop)
       expect(vnode.children).toMatchObject({ default: nop })
       expect(vnode.shapeFlag).toBe(
-        ShapeFlags.ELEMENT + ShapeFlags.SLOTS_CHILDREN
+        ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
       )
     })
 
@@ -130,7 +130,19 @@ describe('vnode', () => {
       const vnode = createVNode('p', null, 'foo')
       expect(vnode.children).toBe('foo')
       expect(vnode.shapeFlag).toBe(
-        ShapeFlags.ELEMENT + ShapeFlags.TEXT_CHILDREN
+        ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
+      )
+    })
+
+    test('element with slots', () => {
+      const children = [createVNode('span', null, 'hello')]
+      const vnode = createVNode('div', null, {
+        default: () => children
+      })
+
+      expect(vnode.children).toBe(children)
+      expect(vnode.shapeFlag).toBe(
+        ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN
       )
     })
   })
index 9c3dee57ff2293b9ce566cd59703981e9be59ed1..f0c575592528a5aa7e9b3bb39bf1853da902fed7 100644 (file)
@@ -24,10 +24,14 @@ export function resolveComponent(name: string): Component | undefined {
 
 export function resolveDynamicComponent(
   component: unknown
-): Component | undefined {
+): Component | string | undefined {
   if (!component) return
   if (isString(component)) {
-    return resolveAsset(COMPONENTS, component, currentRenderingInstance)
+    return (
+      resolveAsset(COMPONENTS, component, currentRenderingInstance, false) ||
+      // fallback to plain element
+      component
+    )
   } else if (isFunction(component) || isObject(component)) {
     return component
   }
@@ -41,7 +45,8 @@ export function resolveDirective(name: string): Directive | undefined {
 function resolveAsset(
   type: typeof COMPONENTS,
   name: string,
-  instance?: ComponentInternalInstance | null
+  instance?: ComponentInternalInstance | null,
+  warnMissing?: boolean
 ): Component | undefined
 // overload 2: directives
 function resolveAsset(
@@ -54,7 +59,8 @@ function resolveAsset(
   type: typeof COMPONENTS | typeof DIRECTIVES,
   name: string,
   instance: ComponentInternalInstance | null = currentRenderingInstance ||
-    currentInstance
+    currentInstance,
+  warnMissing = true
 ) {
   if (instance) {
     let camelized, capitalized
@@ -75,7 +81,8 @@ function resolveAsset(
         res = self
       }
     }
-    if (__DEV__ && !res) {
+    if (__DEV__ && warnMissing && !res) {
+      debugger
       warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
     }
     return res
index 29b1fb0dde93e3870342b32bf70916f2c322f247..78599e63c440c37ffe9f26777235f9beb3493b74 100644 (file)
@@ -397,9 +397,16 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
   } else if (isArray(children)) {
     type = ShapeFlags.ARRAY_CHILDREN
   } else if (typeof children === 'object') {
-    type = ShapeFlags.SLOTS_CHILDREN
-    if (!(children as RawSlots)._) {
-      ;(children as RawSlots)._ctx = currentRenderingInstance
+    // in case <component :is="x"> resolves to native element, the vnode call
+    // will receive slots object.
+    if (vnode.shapeFlag & ShapeFlags.ELEMENT && (children as any).default) {
+      normalizeChildren(vnode, (children as any).default())
+      return
+    } else {
+      type = ShapeFlags.SLOTS_CHILDREN
+      if (!(children as RawSlots)._) {
+        ;(children as RawSlots)._ctx = currentRenderingInstance
+      }
     }
   } else if (isFunction(children)) {
     children = { default: children, _ctx: currentRenderingInstance }