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()
+ })
+ })
})
-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'
export interface AppConfig {
devtools: boolean
performance: boolean
+ readonly isNativeTag?: (tag: string) => boolean
errorHandler?: (
err: Error,
instance: ComponentPublicInstance | null,
config: {
devtools: true,
performance: false,
+ isNativeTag: NO,
errorHandler: undefined,
warnHandler: undefined
},
},
component(name: string, component?: Component): any {
+ if (__DEV__) {
+ validateComponentName(name, context.config)
+ }
if (!component) {
return context.components[name]
} else {
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 {
capitalize,
NOOP,
isArray,
- isObject
+ isObject,
+ NO
} from '@vue/shared'
import { SuspenseBoundary } from './suspense'
import {
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
import { createRenderer } from '@vue/runtime-core'
+import { isHTMLTag, isSVGTag } from '@vue/shared'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
...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 {