feat: runtime component name validation (#217)
This commit is contained in:
parent
fd209f5a66
commit
66023a8886
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user