test: test for app-level APIs
This commit is contained in:
parent
1e4535dc78
commit
98d1406214
@ -86,6 +86,7 @@ describe('reactivity/readonly', () => {
|
|||||||
observed.a = 2
|
observed.a = 2
|
||||||
expect(observed.a).toBe(1)
|
expect(observed.a).toBe(1)
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
|
expect(`target is readonly`).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger effects when unlocked', () => {
|
it('should trigger effects when unlocked', () => {
|
||||||
@ -178,9 +179,11 @@ describe('reactivity/readonly', () => {
|
|||||||
observed[0].a = 2
|
observed[0].a = 2
|
||||||
expect(observed[0].a).toBe(1)
|
expect(observed[0].a).toBe(1)
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
|
expect(`target is readonly`).toHaveBeenWarnedTimes(1)
|
||||||
observed[0] = { a: 2 }
|
observed[0] = { a: 2 }
|
||||||
expect(observed[0].a).toBe(1)
|
expect(observed[0].a).toBe(1)
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
|
expect(`target is readonly`).toHaveBeenWarnedTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger effects when unlocked', () => {
|
it('should trigger effects when unlocked', () => {
|
||||||
|
206
packages/runtime-core/__tests__/apiApp.spec.ts
Normal file
206
packages/runtime-core/__tests__/apiApp.spec.ts
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import {
|
||||||
|
createApp,
|
||||||
|
h,
|
||||||
|
nodeOps,
|
||||||
|
serializeInner,
|
||||||
|
mockWarn,
|
||||||
|
provide,
|
||||||
|
inject,
|
||||||
|
resolveComponent,
|
||||||
|
resolveDirective,
|
||||||
|
applyDirectives,
|
||||||
|
Plugin,
|
||||||
|
ref
|
||||||
|
} from '@vue/runtime-test'
|
||||||
|
|
||||||
|
describe('api: createApp', () => {
|
||||||
|
mockWarn()
|
||||||
|
|
||||||
|
test('mount', () => {
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
count: {
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root1 = nodeOps.createElement('div')
|
||||||
|
createApp().mount(Comp, root1)
|
||||||
|
expect(serializeInner(root1)).toBe(`0`)
|
||||||
|
|
||||||
|
// mount with props
|
||||||
|
const root2 = nodeOps.createElement('div')
|
||||||
|
const app2 = createApp()
|
||||||
|
app2.mount(Comp, root2, { count: 1 })
|
||||||
|
expect(serializeInner(root2)).toBe(`1`)
|
||||||
|
|
||||||
|
// remount warning
|
||||||
|
const root3 = nodeOps.createElement('div')
|
||||||
|
app2.mount(Comp, root3)
|
||||||
|
expect(serializeInner(root3)).toBe(``)
|
||||||
|
expect(`already been mounted`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('provide', () => {
|
||||||
|
const app = createApp()
|
||||||
|
app.provide('foo', 1)
|
||||||
|
app.provide('bar', 2)
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
setup() {
|
||||||
|
// test override
|
||||||
|
provide('foo', 3)
|
||||||
|
return () => h(Child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
const foo = inject('foo')
|
||||||
|
const bar = inject('bar')
|
||||||
|
return () => `${foo},${bar}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
app.mount(Root, root)
|
||||||
|
expect(serializeInner(root)).toBe(`3,2`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('component', () => {
|
||||||
|
const app = createApp()
|
||||||
|
app.component('FooBar', () => 'foobar!')
|
||||||
|
app.component('BarBaz', () => 'barbaz!')
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
// local override
|
||||||
|
components: {
|
||||||
|
BarBaz: () => 'barbaz-local!'
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
// resolve in setup
|
||||||
|
const FooBar = resolveComponent('foo-bar') as any
|
||||||
|
return () => {
|
||||||
|
// resolve in render
|
||||||
|
const BarBaz = resolveComponent('bar-baz') as any
|
||||||
|
return h('div', [h(FooBar), h(BarBaz)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
app.mount(Root, root)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>foobar!barbaz-local!</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('directive', () => {
|
||||||
|
const app = createApp()
|
||||||
|
|
||||||
|
const spy1 = jest.fn()
|
||||||
|
const spy2 = jest.fn()
|
||||||
|
const spy3 = jest.fn()
|
||||||
|
app.directive('FooBar', {
|
||||||
|
mounted: spy1
|
||||||
|
})
|
||||||
|
app.directive('BarBaz', {
|
||||||
|
mounted: spy2
|
||||||
|
})
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
// local override
|
||||||
|
directives: {
|
||||||
|
BarBaz: { mounted: spy3 }
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
// resolve in setup
|
||||||
|
const FooBar = resolveDirective('foo-bar') as any
|
||||||
|
return () => {
|
||||||
|
// resolve in render
|
||||||
|
const BarBaz = resolveDirective('bar-baz') as any
|
||||||
|
return applyDirectives(h('div'), [[FooBar], [BarBaz]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
app.mount(Root, root)
|
||||||
|
expect(spy1).toHaveBeenCalled()
|
||||||
|
expect(spy2).not.toHaveBeenCalled()
|
||||||
|
expect(spy3).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('use', () => {
|
||||||
|
const PluginA: Plugin = app => app.provide('foo', 1)
|
||||||
|
const PluginB: Plugin = {
|
||||||
|
install: app => app.provide('bar', 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = createApp()
|
||||||
|
app.use(PluginA)
|
||||||
|
app.use(PluginB)
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
setup() {
|
||||||
|
const foo = inject('foo')
|
||||||
|
const bar = inject('bar')
|
||||||
|
return () => `${foo},${bar}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
app.mount(Root, root)
|
||||||
|
expect(serializeInner(root)).toBe(`1,2`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('config.errorHandler', () => {
|
||||||
|
const app = createApp()
|
||||||
|
|
||||||
|
const error = new Error()
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
const handler = (app.config.errorHandler = jest.fn(
|
||||||
|
(err, instance, info) => {
|
||||||
|
expect(err).toBe(error)
|
||||||
|
expect((instance as any).count).toBe(count.value)
|
||||||
|
expect(info).toBe(`render function`)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
setup() {
|
||||||
|
const count = ref(0)
|
||||||
|
return {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mount(Root, nodeOps.createElement('div'))
|
||||||
|
expect(handler).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('config.warnHandler', () => {
|
||||||
|
const app = createApp()
|
||||||
|
|
||||||
|
const handler = (app.config.warnHandler = jest.fn(
|
||||||
|
(msg, instance, trace) => {}
|
||||||
|
))
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
setup() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mount(Root, nodeOps.createElement('div'))
|
||||||
|
expect(handler).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('mixin')
|
||||||
|
|
||||||
|
test.todo('config.optionsMergeStrategies')
|
||||||
|
})
|
@ -1,19 +0,0 @@
|
|||||||
describe('api: createApp', () => {
|
|
||||||
test('mount', () => {})
|
|
||||||
|
|
||||||
test('provide', () => {})
|
|
||||||
|
|
||||||
test('component', () => {})
|
|
||||||
|
|
||||||
test('directive', () => {})
|
|
||||||
|
|
||||||
test('use', () => {})
|
|
||||||
|
|
||||||
test.todo('mixin')
|
|
||||||
|
|
||||||
test('config.errorHandler', () => {})
|
|
||||||
|
|
||||||
test('config.warnHandler', () => {})
|
|
||||||
|
|
||||||
test.todo('config.optionsMergeStrategies')
|
|
||||||
})
|
|
@ -109,6 +109,7 @@ describe('directives', () => {
|
|||||||
render() {
|
render() {
|
||||||
_prevVnode = _vnode
|
_prevVnode = _vnode
|
||||||
_vnode = applyDirectives(h('div', count.value), [
|
_vnode = applyDirectives(h('div', count.value), [
|
||||||
|
[
|
||||||
{
|
{
|
||||||
beforeMount,
|
beforeMount,
|
||||||
mounted,
|
mounted,
|
||||||
@ -123,6 +124,7 @@ describe('directives', () => {
|
|||||||
'foo',
|
'foo',
|
||||||
// modifiers
|
// modifiers
|
||||||
{ ok: true }
|
{ ok: true }
|
||||||
|
]
|
||||||
])
|
])
|
||||||
return _vnode
|
return _vnode
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,12 @@ export interface AppConfig {
|
|||||||
performance: boolean
|
performance: boolean
|
||||||
errorHandler?: (
|
errorHandler?: (
|
||||||
err: Error,
|
err: Error,
|
||||||
instance: ComponentRenderProxy,
|
instance: ComponentRenderProxy | null,
|
||||||
info: string
|
info: string
|
||||||
) => void
|
) => void
|
||||||
warnHandler?: (
|
warnHandler?: (
|
||||||
msg: string,
|
msg: string,
|
||||||
instance: ComponentRenderProxy,
|
instance: ComponentRenderProxy | null,
|
||||||
trace: string
|
trace: string
|
||||||
) => void
|
) => void
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ export interface AppContext {
|
|||||||
|
|
||||||
type PluginInstallFunction = (app: App) => any
|
type PluginInstallFunction = (app: App) => any
|
||||||
|
|
||||||
type Plugin =
|
export type Plugin =
|
||||||
| PluginInstallFunction
|
| PluginInstallFunction
|
||||||
| {
|
| {
|
||||||
install: PluginInstallFunction
|
install: PluginInstallFunction
|
||||||
@ -82,6 +82,8 @@ export function createAppAPI(render: RootRenderFunction): () => App {
|
|||||||
return function createApp(): App {
|
return function createApp(): App {
|
||||||
const context = createAppContext()
|
const context = createAppContext()
|
||||||
|
|
||||||
|
let isMounted = false
|
||||||
|
|
||||||
const app: App = {
|
const app: App = {
|
||||||
get config() {
|
get config() {
|
||||||
return context.config
|
return context.config
|
||||||
@ -134,14 +136,20 @@ export function createAppAPI(render: RootRenderFunction): () => App {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mount(rootComponent, rootContainer, rootProps?: Data) {
|
mount(rootComponent, rootContainer, rootProps?: Data): any {
|
||||||
|
if (!isMounted) {
|
||||||
const vnode = createVNode(rootComponent, rootProps)
|
const vnode = createVNode(rootComponent, rootProps)
|
||||||
// store app context on the root VNode.
|
// store app context on the root VNode.
|
||||||
// this will be set on the root instance on initial mount.
|
// this will be set on the root instance on initial mount.
|
||||||
vnode.appContext = context
|
vnode.appContext = context
|
||||||
render(vnode, rootContainer)
|
render(vnode, rootContainer)
|
||||||
return (vnode.component as ComponentInstance)
|
isMounted = true
|
||||||
.renderProxy as ComponentRenderProxy
|
return (vnode.component as ComponentInstance).renderProxy
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`App has already been mounted. Create a new app instance instead.`
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
provide(key, value) {
|
provide(key, value) {
|
||||||
@ -164,15 +172,21 @@ export function resolveAsset(type: 'components' | 'directives', name: string) {
|
|||||||
if (instance) {
|
if (instance) {
|
||||||
let camelized
|
let camelized
|
||||||
let capitalized
|
let capitalized
|
||||||
|
let res
|
||||||
const local = (instance.type as any)[type]
|
const local = (instance.type as any)[type]
|
||||||
const global = instance.appContext[type]
|
if (local) {
|
||||||
const res =
|
res =
|
||||||
local[name] ||
|
local[name] ||
|
||||||
local[(camelized = camelize(name))] ||
|
local[(camelized = camelize(name))] ||
|
||||||
local[(capitalized = capitalize(name))] ||
|
local[(capitalized = capitalize(camelized))]
|
||||||
|
}
|
||||||
|
if (!res) {
|
||||||
|
const global = instance.appContext[type]
|
||||||
|
res =
|
||||||
global[name] ||
|
global[name] ||
|
||||||
global[camelized] ||
|
global[camelized || (camelized = camelize(name))] ||
|
||||||
global[capitalized]
|
global[capitalized || capitalize(camelized)]
|
||||||
|
}
|
||||||
if (__DEV__ && !res) {
|
if (__DEV__ && !res) {
|
||||||
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ export interface InjectionKey<T> extends Symbol {}
|
|||||||
export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
||||||
if (!currentInstance) {
|
if (!currentInstance) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
warn(`provide() is used without an active component instance.`)
|
warn(`provide() can only be used inside setup().`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let provides = currentInstance.provides
|
let provides = currentInstance.provides
|
||||||
@ -27,17 +27,16 @@ export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
|||||||
export function inject<T>(key: InjectionKey<T> | string): T | undefined
|
export function inject<T>(key: InjectionKey<T> | string): T | undefined
|
||||||
export function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
|
export function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
|
||||||
export function inject(key: InjectionKey<any> | string, defaultValue?: any) {
|
export function inject(key: InjectionKey<any> | string, defaultValue?: any) {
|
||||||
if (!currentInstance) {
|
if (currentInstance) {
|
||||||
// TODO warn
|
const provides = currentInstance.provides
|
||||||
} else {
|
if (key in provides) {
|
||||||
// TODO should also check for app-level provides
|
|
||||||
const provides = currentInstance.parent && currentInstance.provides
|
|
||||||
if (provides && key in provides) {
|
|
||||||
return provides[key as any] as any
|
return provides[key as any] as any
|
||||||
} else if (defaultValue !== undefined) {
|
} else if (defaultValue !== undefined) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
warn(`injection "${key}" not found.`)
|
warn(`injection "${key}" not found.`)
|
||||||
}
|
}
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(`inject() can only be used inside setup().`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ import {
|
|||||||
callWithErrorHandling,
|
callWithErrorHandling,
|
||||||
callWithAsyncErrorHandling
|
callWithAsyncErrorHandling
|
||||||
} from './errorHandling'
|
} from './errorHandling'
|
||||||
import { AppContext, createAppContext, resolveAsset } from './apiCreateApp'
|
import { AppContext, createAppContext, resolveAsset } from './apiApp'
|
||||||
|
import { Directive } from './directives'
|
||||||
|
|
||||||
export type Data = { [key: string]: unknown }
|
export type Data = { [key: string]: unknown }
|
||||||
|
|
||||||
@ -31,41 +32,42 @@ export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
|
|||||||
} & P &
|
} & P &
|
||||||
S
|
S
|
||||||
|
|
||||||
type SetupFunction<Props, RawBindings> = (
|
|
||||||
props: Props,
|
|
||||||
ctx: SetupContext
|
|
||||||
) => RawBindings | (() => VNodeChild)
|
|
||||||
|
|
||||||
type RenderFunction<Props = {}, RawBindings = {}> = <
|
type RenderFunction<Props = {}, RawBindings = {}> = <
|
||||||
Bindings extends UnwrapRef<RawBindings>
|
Bindings extends UnwrapRef<RawBindings>
|
||||||
>(
|
>(
|
||||||
this: ComponentRenderProxy<Props, Bindings>
|
this: ComponentRenderProxy<Props, Bindings>
|
||||||
) => VNodeChild
|
) => VNodeChild
|
||||||
|
|
||||||
interface ComponentOptionsWithoutProps<Props = Data, RawBindings = Data> {
|
interface ComponentOptionsBase<Props, RawBindings> {
|
||||||
props?: undefined
|
setup?: (
|
||||||
setup?: SetupFunction<Props, RawBindings>
|
props: Props,
|
||||||
|
ctx: SetupContext
|
||||||
|
) => RawBindings | (() => VNodeChild) | void
|
||||||
render?: RenderFunction<Props, RawBindings>
|
render?: RenderFunction<Props, RawBindings>
|
||||||
|
components?: Record<string, Component>
|
||||||
|
directives?: Record<string, Directive>
|
||||||
|
// TODO full 2.x options compat
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
|
||||||
|
extends ComponentOptionsBase<Props, RawBindings> {
|
||||||
|
props?: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentOptionsWithArrayProps<
|
interface ComponentOptionsWithArrayProps<
|
||||||
PropNames extends string = string,
|
PropNames extends string = string,
|
||||||
RawBindings = Data,
|
RawBindings = {},
|
||||||
Props = { [key in PropNames]?: unknown }
|
Props = { [key in PropNames]?: unknown }
|
||||||
> {
|
> extends ComponentOptionsBase<Props, RawBindings> {
|
||||||
props: PropNames[]
|
props: PropNames[]
|
||||||
setup?: SetupFunction<Props, RawBindings>
|
|
||||||
render?: RenderFunction<Props, RawBindings>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentOptionsWithProps<
|
interface ComponentOptionsWithProps<
|
||||||
PropsOptions = ComponentPropsOptions,
|
PropsOptions = ComponentPropsOptions,
|
||||||
RawBindings = Data,
|
RawBindings = {},
|
||||||
Props = ExtractPropTypes<PropsOptions>
|
Props = ExtractPropTypes<PropsOptions>
|
||||||
> {
|
> extends ComponentOptionsBase<Props, RawBindings> {
|
||||||
props: PropsOptions
|
props: PropsOptions
|
||||||
setup?: SetupFunction<Props, RawBindings>
|
|
||||||
render?: RenderFunction<Props, RawBindings>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentOptions =
|
export type ComponentOptions =
|
||||||
@ -105,7 +107,7 @@ interface SetupContext {
|
|||||||
emit: ((event: string, ...args: unknown[]) => void)
|
emit: ((event: string, ...args: unknown[]) => void)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentInstance<P = Data, S = Data> = {
|
export type ComponentInstance<P = {}, S = {}> = {
|
||||||
type: FunctionalComponent | ComponentOptions
|
type: FunctionalComponent | ComponentOptions
|
||||||
parent: ComponentInstance | null
|
parent: ComponentInstance | null
|
||||||
appContext: AppContext
|
appContext: AppContext
|
||||||
@ -193,12 +195,13 @@ export function createComponentInstance(
|
|||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parent: ComponentInstance | null
|
parent: ComponentInstance | null
|
||||||
): ComponentInstance {
|
): ComponentInstance {
|
||||||
|
// inherit parent app context - or - if root, adopt from root vnode
|
||||||
|
const appContext =
|
||||||
|
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
|
||||||
const instance = {
|
const instance = {
|
||||||
vnode,
|
vnode,
|
||||||
parent,
|
parent,
|
||||||
// inherit parent app context - or - if root, adopt from root vnode
|
appContext,
|
||||||
appContext:
|
|
||||||
(parent ? parent.appContext : vnode.appContext) || emptyAppContext,
|
|
||||||
type: vnode.type as any,
|
type: vnode.type as any,
|
||||||
root: null as any, // set later so it can point to itself
|
root: null as any, // set later so it can point to itself
|
||||||
next: null,
|
next: null,
|
||||||
@ -209,7 +212,7 @@ export function createComponentInstance(
|
|||||||
propsProxy: null,
|
propsProxy: null,
|
||||||
setupContext: null,
|
setupContext: null,
|
||||||
effects: null,
|
effects: null,
|
||||||
provides: parent ? parent.provides : {},
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||||
|
|
||||||
// setup context properties
|
// setup context properties
|
||||||
data: EMPTY_OBJ,
|
data: EMPTY_OBJ,
|
||||||
|
@ -5,11 +5,10 @@ const comp = resolveComponent('comp')
|
|||||||
const foo = resolveDirective('foo')
|
const foo = resolveDirective('foo')
|
||||||
const bar = resolveDirective('bar')
|
const bar = resolveDirective('bar')
|
||||||
|
|
||||||
return applyDirectives(
|
return applyDirectives(h(comp), [
|
||||||
h(comp),
|
|
||||||
[foo, this.x],
|
[foo, this.x],
|
||||||
[bar, this.y]
|
[bar, this.y]
|
||||||
)
|
])
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { VNode, cloneVNode } from './vnode'
|
import { VNode, cloneVNode } from './vnode'
|
||||||
@ -22,7 +21,7 @@ import {
|
|||||||
} from './component'
|
} from './component'
|
||||||
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
|
||||||
import { HostNode } from './createRenderer'
|
import { HostNode } from './createRenderer'
|
||||||
import { resolveAsset } from './apiCreateApp'
|
import { resolveAsset } from './apiApp'
|
||||||
|
|
||||||
export interface DirectiveBinding {
|
export interface DirectiveBinding {
|
||||||
instance: ComponentRenderProxy | null
|
instance: ComponentRenderProxy | null
|
||||||
@ -103,10 +102,7 @@ type DirectiveArguments = Array<
|
|||||||
| [Directive, any, string, DirectiveModifiers]
|
| [Directive, any, string, DirectiveModifiers]
|
||||||
>
|
>
|
||||||
|
|
||||||
export function applyDirectives(
|
export function applyDirectives(vnode: VNode, directives: DirectiveArguments) {
|
||||||
vnode: VNode,
|
|
||||||
...directives: DirectiveArguments
|
|
||||||
) {
|
|
||||||
const instance = currentRenderingInstance
|
const instance = currentRenderingInstance
|
||||||
if (instance !== null) {
|
if (instance !== null) {
|
||||||
vnode = cloneVNode(vnode)
|
vnode = cloneVNode(vnode)
|
||||||
|
@ -13,6 +13,8 @@ export const enum ErrorTypes {
|
|||||||
NATIVE_EVENT_HANDLER,
|
NATIVE_EVENT_HANDLER,
|
||||||
COMPONENT_EVENT_HANDLER,
|
COMPONENT_EVENT_HANDLER,
|
||||||
DIRECTIVE_HOOK,
|
DIRECTIVE_HOOK,
|
||||||
|
APP_ERROR_HANDLER,
|
||||||
|
APP_WARN_HANDLER,
|
||||||
SCHEDULER
|
SCHEDULER
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +40,8 @@ export const ErrorTypeStrings: Record<number | string, string> = {
|
|||||||
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||||
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
||||||
[ErrorTypes.DIRECTIVE_HOOK]: 'directive hook',
|
[ErrorTypes.DIRECTIVE_HOOK]: 'directive hook',
|
||||||
|
[ErrorTypes.APP_ERROR_HANDLER]: 'app errorHandler',
|
||||||
|
[ErrorTypes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||||
[ErrorTypes.SCHEDULER]:
|
[ErrorTypes.SCHEDULER]:
|
||||||
'scheduler flush. This may be a Vue internals bug. ' +
|
'scheduler flush. This may be a Vue internals bug. ' +
|
||||||
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue'
|
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue'
|
||||||
@ -81,25 +85,35 @@ export function handleError(
|
|||||||
type: AllErrorTypes
|
type: AllErrorTypes
|
||||||
) {
|
) {
|
||||||
const contextVNode = instance ? instance.vnode : null
|
const contextVNode = instance ? instance.vnode : null
|
||||||
let cur: ComponentInstance | null = instance && instance.parent
|
if (instance) {
|
||||||
|
let cur: ComponentInstance | null = instance.parent
|
||||||
|
// the exposed instance is the render proxy to keep it consistent with 2.x
|
||||||
|
const exposedInstance = instance.renderProxy
|
||||||
|
// in production the hook receives only the error code
|
||||||
|
const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
|
||||||
while (cur) {
|
while (cur) {
|
||||||
const errorCapturedHooks = cur.ec
|
const errorCapturedHooks = cur.ec
|
||||||
if (errorCapturedHooks !== null) {
|
if (errorCapturedHooks !== null) {
|
||||||
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
||||||
if (
|
if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) {
|
||||||
errorCapturedHooks[i](
|
|
||||||
err,
|
|
||||||
instance && instance.renderProxy,
|
|
||||||
// in production the hook receives only the error code
|
|
||||||
__DEV__ ? ErrorTypeStrings[type] : type
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cur = cur.parent
|
cur = cur.parent
|
||||||
}
|
}
|
||||||
|
// app-level handling
|
||||||
|
const appErrorHandler = instance.appContext.config.errorHandler
|
||||||
|
if (appErrorHandler) {
|
||||||
|
callWithErrorHandling(
|
||||||
|
appErrorHandler,
|
||||||
|
null,
|
||||||
|
ErrorTypes.APP_ERROR_HANDLER,
|
||||||
|
[err, exposedInstance, errorInfo]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
logError(err, type, contextVNode)
|
logError(err, type, contextVNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
|||||||
export { getCurrentInstance } from './component'
|
export { getCurrentInstance } from './component'
|
||||||
|
|
||||||
// For custom renderers
|
// For custom renderers
|
||||||
export { createAppAPI } from './apiCreateApp'
|
export { createAppAPI } from './apiApp'
|
||||||
export { createRenderer } from './createRenderer'
|
export { createRenderer } from './createRenderer'
|
||||||
export {
|
export {
|
||||||
handleError,
|
handleError,
|
||||||
@ -42,8 +42,8 @@ export { applyDirectives, resolveDirective } from './directives'
|
|||||||
|
|
||||||
// Types -----------------------------------------------------------------------
|
// Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
export { App } from './apiCreateApp'
|
export { App, AppConfig, AppContext, Plugin } from './apiApp'
|
||||||
export { VNode } from './vnode'
|
export { VNode, VNodeTypes } from './vnode'
|
||||||
export { FunctionalComponent, ComponentInstance } from './component'
|
export { FunctionalComponent, ComponentInstance } from './component'
|
||||||
export { RendererOptions } from './createRenderer'
|
export { RendererOptions } from './createRenderer'
|
||||||
export { Slot, Slots } from './componentSlots'
|
export { Slot, Slots } from './componentSlots'
|
||||||
|
@ -12,7 +12,7 @@ import { RawSlots } from './componentSlots'
|
|||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from './patchFlags'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
import { AppContext } from './apiCreateApp'
|
import { AppContext } from './apiApp'
|
||||||
|
|
||||||
export const Fragment = Symbol('Fragment')
|
export const Fragment = Symbol('Fragment')
|
||||||
export const Text = Symbol('Text')
|
export const Text = Symbol('Text')
|
||||||
|
@ -21,13 +21,24 @@ export function popWarningContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function warn(msg: string, ...args: any[]) {
|
export function warn(msg: string, ...args: any[]) {
|
||||||
// TODO app level warn handler
|
const instance = stack.length ? stack[stack.length - 1].component : null
|
||||||
|
const appWarnHandler = instance && instance.appContext.config.warnHandler
|
||||||
|
const trace = getComponentTrace()
|
||||||
|
|
||||||
|
if (appWarnHandler) {
|
||||||
|
appWarnHandler(
|
||||||
|
msg + args.join(''),
|
||||||
|
instance && instance.renderProxy,
|
||||||
|
formatTrace(trace).join('')
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
console.warn(`[Vue warn]: ${msg}`, ...args)
|
console.warn(`[Vue warn]: ${msg}`, ...args)
|
||||||
// avoid spamming console during tests
|
// avoid spamming console during tests
|
||||||
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const trace = getComponentTrace()
|
|
||||||
if (!trace.length) {
|
if (!trace.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -41,16 +52,7 @@ export function warn(msg: string, ...args: any[]) {
|
|||||||
console.log(...logs)
|
console.log(...logs)
|
||||||
console.groupEnd()
|
console.groupEnd()
|
||||||
} else {
|
} else {
|
||||||
const logs: string[] = []
|
console.log(...formatTrace(trace))
|
||||||
trace.forEach((entry, i) => {
|
|
||||||
const formatted = formatTraceEntry(entry, i)
|
|
||||||
if (i === 0) {
|
|
||||||
logs.push('at', ...formatted)
|
|
||||||
} else {
|
|
||||||
logs.push('\n', ...formatted)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log(...logs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +85,19 @@ function getComponentTrace(): ComponentTraceStack {
|
|||||||
return normlaizedStack
|
return normlaizedStack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTrace(trace: ComponentTraceStack): string[] {
|
||||||
|
const logs: string[] = []
|
||||||
|
trace.forEach((entry, i) => {
|
||||||
|
const formatted = formatTraceEntry(entry, i)
|
||||||
|
if (i === 0) {
|
||||||
|
logs.push('at', ...formatted)
|
||||||
|
} else {
|
||||||
|
logs.push('\n', ...formatted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
function formatTraceEntry(
|
function formatTraceEntry(
|
||||||
{ vnode, recurseCount }: TraceEntry,
|
{ vnode, recurseCount }: TraceEntry,
|
||||||
depth: number = 0
|
depth: number = 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user