refactor: improve warning output

This commit is contained in:
Evan You 2019-11-02 23:21:02 -04:00
parent f3c1fa75f6
commit 25af8dc73b

View File

@ -1,7 +1,7 @@
import { VNode } from './vnode' import { VNode } from './vnode'
import { Data, ComponentInternalInstance, Component } from './component' import { Data, ComponentInternalInstance, Component } from './component'
import { isString, isFunction } from '@vue/shared' import { isString, isFunction } from '@vue/shared'
import { toRaw } from '@vue/reactivity' import { toRaw, isRef, pauseTracking, resumeTracking } from '@vue/reactivity'
import { callWithErrorHandling, ErrorCodes } from './errorHandling' import { callWithErrorHandling, ErrorCodes } from './errorHandling'
type ComponentVNode = VNode & { type ComponentVNode = VNode & {
@ -26,6 +26,10 @@ export function popWarningContext() {
} }
export function warn(msg: string, ...args: any[]) { export function warn(msg: string, ...args: any[]) {
// avoid props formatting or warn handler tracking deps that might be mutated
// during patch, leading to infinite recursion.
pauseTracking()
const instance = stack.length ? stack[stack.length - 1].component : null const instance = stack.length ? stack[stack.length - 1].component : null
const appWarnHandler = instance && instance.appContext.config.warnHandler const appWarnHandler = instance && instance.appContext.config.warnHandler
const trace = getComponentTrace() const trace = getComponentTrace()
@ -38,32 +42,25 @@ export function warn(msg: string, ...args: any[]) {
[ [
msg + args.join(''), msg + args.join(''),
instance && instance.renderProxy, instance && instance.renderProxy,
formatTrace(trace).join('') trace
.map(({ vnode }) => `at <${formatComponentName(vnode)}>`)
.join('\n'),
trace
] ]
) )
return } else {
const warnArgs = [`[Vue warn]: ${msg}`, ...args]
if (
trace.length &&
// avoid spamming console during tests
(typeof process === 'undefined' || process.env.NODE_ENV !== 'test')
) {
warnArgs.push(`\n`, ...formatTrace(trace))
}
console.warn(...warnArgs)
} }
console.warn(`[Vue warn]: ${msg}`, ...args) resumeTracking()
// avoid spamming console during tests
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
return
}
if (!trace.length) {
return
}
if (trace.length > 1 && console.groupCollapsed) {
console.groupCollapsed('at', ...formatTraceEntry(trace[0]))
const logs: string[] = []
trace.slice(1).forEach((entry, i) => {
if (i !== 0) logs.push('\n')
logs.push(...formatTraceEntry(entry, i + 1))
})
console.log(...logs)
console.groupEnd()
} else {
console.log(...formatTrace(trace))
}
} }
function getComponentTrace(): ComponentTraceStack { function getComponentTrace(): ComponentTraceStack {
@ -95,27 +92,18 @@ function getComponentTrace(): ComponentTraceStack {
return normalizedStack return normalizedStack
} }
function formatTrace(trace: ComponentTraceStack): string[] { function formatTrace(trace: ComponentTraceStack): any[] {
const logs: string[] = [] const logs: any[] = []
trace.forEach((entry, i) => { trace.forEach((entry, i) => {
const formatted = formatTraceEntry(entry, i) logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
if (i === 0) {
logs.push('at', ...formatted)
} else {
logs.push('\n', ...formatted)
}
}) })
return logs return logs
} }
function formatTraceEntry( function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
{ vnode, recurseCount }: TraceEntry,
depth: number = 0
): string[] {
const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
const postfix = const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : `` recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
const open = padding + `<${formatComponentName(vnode)}` const open = ` at <${formatComponentName(vnode)}`
const close = `>` + postfix const close = `>` + postfix
const rootLabel = vnode.component!.parent == null ? `(Root)` : `` const rootLabel = vnode.component!.parent == null ? `(Root)` : ``
return vnode.props return vnode.props
@ -129,25 +117,39 @@ const classify = (str: string): string =>
function formatComponentName(vnode: ComponentVNode, file?: string): string { function formatComponentName(vnode: ComponentVNode, file?: string): string {
const Component = vnode.type const Component = vnode.type
let name = isFunction(Component) ? Component.displayName : Component.name let name = isFunction(Component)
? Component.displayName || Component.name
: Component.name
if (!name && file) { if (!name && file) {
const match = file.match(/([^/\\]+)\.vue$/) const match = file.match(/([^/\\]+)\.vue$/)
if (match) { if (match) {
name = match[1] name = match[1]
} }
} }
return name ? classify(name) : 'AnonymousComponent' return name ? classify(name) : 'Anonymous'
} }
function formatProps(props: Data): string[] { function formatProps(props: Data): any[] {
const res: string[] = [] const res: any[] = []
for (const key in props) { for (const key in props) {
const value = props[key] res.push(...formatProp(key, props[key]))
if (isString(value)) {
res.push(`${key}=${JSON.stringify(value)}`)
} else {
res.push(`${key}=`, String(toRaw(value)))
}
} }
return res return res
} }
function formatProp(key: string, value: unknown): any[]
function formatProp(key: string, value: unknown, raw: true): any
function formatProp(key: string, value: unknown, raw?: boolean): any {
if (isString(value)) {
value = JSON.stringify(value)
return raw ? value : [`${key}=${value}`]
} else if (typeof value === 'number' || value == null) {
return raw ? value : [`${key}=${value}`]
} else if (isRef(value)) {
value = formatProp(key, toRaw(value.value), true)
return raw ? value : [`${key}=Ref<`, value, `>`]
} else {
value = toRaw(value)
return raw ? value : [`${key}=`, value]
}
}