]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: runtime component name validation (#217)
author月迷津渡 <CodeDaraW@gmail.com>
Mon, 14 Oct 2019 19:36:30 +0000 (03:36 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 14 Oct 2019 19:36:29 +0000 (15:36 -0400)
packages/runtime-core/__tests__/apiApp.spec.ts
packages/runtime-core/src/apiApp.ts
packages/runtime-core/src/component.ts
packages/runtime-dom/src/index.ts

index 64899eba15af9b1acb089bd547a7a2c5325f5f20..a559f5ed4eef1f5f7544640ede128611ebf65744 100644 (file)
@@ -286,4 +286,85 @@ describe('api: createApp', () => {
     app.mount(Root, nodeOps.createElement('div'))
     expect(handler).toHaveBeenCalledTimes(1)
   })
+
+  describe('config.isNativeTag', () => {
+    const isNativeTag = jest.fn(tag => tag === 'div')
+
+    test('Component.name', () => {
+      const app = createApp()
+      Object.defineProperty(app.config, 'isNativeTag', {
+        value: isNativeTag,
+        writable: false
+      })
+
+      const Root = {
+        name: 'div',
+        setup() {
+          return {
+            count: ref(0)
+          }
+        },
+        render() {
+          return null
+        }
+      }
+
+      app.mount(Root, nodeOps.createElement('div'))
+      expect(
+        `Do not use built-in or reserved HTML elements as component id: div`
+      ).toHaveBeenWarned()
+    })
+
+    test('Component.components', () => {
+      const app = createApp()
+      Object.defineProperty(app.config, 'isNativeTag', {
+        value: isNativeTag,
+        writable: false
+      })
+
+      const Root = {
+        components: {
+          div: () => 'div'
+        },
+        setup() {
+          return {
+            count: ref(0)
+          }
+        },
+        render() {
+          return null
+        }
+      }
+
+      app.mount(Root, nodeOps.createElement('div'))
+      expect(
+        `Do not use built-in or reserved HTML elements as component id: div`
+      ).toHaveBeenWarned()
+    })
+
+    test('register using app.component', () => {
+      const app = createApp()
+      Object.defineProperty(app.config, 'isNativeTag', {
+        value: isNativeTag,
+        writable: false
+      })
+
+      const Root = {
+        setup() {
+          return {
+            count: ref(0)
+          }
+        },
+        render() {
+          return null
+        }
+      }
+
+      app.component('div', () => 'div')
+      app.mount(Root, nodeOps.createElement('div'))
+      expect(
+        `Do not use built-in or reserved HTML elements as component id: div`
+      ).toHaveBeenWarned()
+    })
+  })
 })
index b80c502cf524e7a2e133965ac2f386338a4625fd..d7f393bc8d9820bb78a365c4a5631424ef3fa30b 100644 (file)
@@ -1,10 +1,10 @@
-import { Component, Data } from './component'
+import { Component, Data, validateComponentName } from './component'
 import { ComponentOptions } from './apiOptions'
 import { ComponentPublicInstance } from './componentProxy'
 import { Directive } from './directives'
 import { RootRenderFunction } from './createRenderer'
 import { InjectionKey } from './apiInject'
-import { isFunction } from '@vue/shared'
+import { isFunction, NO } from '@vue/shared'
 import { warn } from './warning'
 import { createVNode } from './vnode'
 
@@ -27,6 +27,7 @@ export interface App<HostElement = any> {
 export interface AppConfig {
   devtools: boolean
   performance: boolean
+  readonly isNativeTag?: (tag: string) => boolean
   errorHandler?: (
     err: Error,
     instance: ComponentPublicInstance | null,
@@ -60,6 +61,7 @@ export function createAppContext(): AppContext {
     config: {
       devtools: true,
       performance: false,
+      isNativeTag: NO,
       errorHandler: undefined,
       warnHandler: undefined
     },
@@ -111,6 +113,9 @@ export function createAppAPI<HostNode, HostElement>(
       },
 
       component(name: string, component?: Component): any {
+        if (__DEV__) {
+          validateComponentName(name, context.config)
+        }
         if (!component) {
           return context.components[name]
         } else {
index d9e1e9b9652bfa95b0d14eca5df40910d5c5e53f..72c3fdac3f5ecde37c419d4c59dd39a242c54b72 100644 (file)
@@ -12,7 +12,7 @@ import {
   callWithErrorHandling,
   callWithAsyncErrorHandling
 } from './errorHandling'
-import { AppContext, createAppContext } from './apiApp'
+import { AppContext, createAppContext, AppConfig } from './apiApp'
 import { Directive } from './directives'
 import { applyOptions, ComponentOptions } from './apiOptions'
 import {
@@ -21,7 +21,8 @@ import {
   capitalize,
   NOOP,
   isArray,
-  isObject
+  isObject,
+  NO
 } from '@vue/shared'
 import { SuspenseBoundary } from './suspense'
 import {
@@ -223,11 +224,37 @@ export const setCurrentInstance = (
   currentInstance = instance
 }
 
+const BuiltInTagSet = new Set(['slot', 'component'])
+const isBuiltInTag = (tag: string) => BuiltInTagSet.has(tag)
+
+export function validateComponentName(name: string, config: AppConfig) {
+  const appIsNativeTag = config.isNativeTag || NO
+  if (isBuiltInTag(name) || appIsNativeTag(name)) {
+    warn(
+      'Do not use built-in or reserved HTML elements as component id: ' + name
+    )
+  }
+}
+
 export function setupStatefulComponent(
   instance: ComponentInternalInstance,
   parentSuspense: SuspenseBoundary | null
 ) {
   const Component = instance.type as ComponentOptions
+
+  if (__DEV__) {
+    if (Component.name) {
+      validateComponentName(Component.name, instance.appContext.config)
+    }
+    if (Component.components) {
+      const names = Object.keys(Component.components)
+      for (let i = 0; i < names.length; i++) {
+        const name = names[i]
+        validateComponentName(name, instance.appContext.config)
+      }
+    }
+  }
+
   // 1. create render proxy
   instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers)
   // 2. create props proxy
index ac293a0a3f002896a56c82ae30bc5b30825d89de..35167655ea3641a0fe672a5a68c8d0327f22dc9d 100644 (file)
@@ -1,4 +1,5 @@
 import { createRenderer } from '@vue/runtime-core'
+import { isHTMLTag, isSVGTag } from '@vue/shared'
 import { nodeOps } from './nodeOps'
 import { patchProp } from './patchProp'
 
@@ -7,7 +8,19 @@ const { render, createApp } = createRenderer<Node, Element>({
   ...nodeOps
 })
 
-export { render, createApp }
+const wrappedCreateApp = () => {
+  const app = createApp()
+  // inject `isNativeTag` dev only
+  Object.defineProperty(app.config, 'isNativeTag', {
+    value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
+    writable: false
+  })
+  return app
+}
+
+const exportedCreateApp = __DEV__ ? wrappedCreateApp : createApp
+
+export { render, exportedCreateApp as createApp }
 
 // DOM-only runtime helpers
 export {