vue3-yuanma/packages/runtime-core/src/warning.ts

154 lines
4.1 KiB
TypeScript
Raw Normal View History

2019-08-30 22:36:30 +08:00
import { VNode } from './vnode'
2019-10-08 21:26:09 +08:00
import { Data, ComponentInternalInstance, Component } from './component'
import { isString, isFunction } from '@vue/shared'
2019-08-30 22:36:30 +08:00
import { toRaw } from '@vue/reactivity'
import { callWithErrorHandling, ErrorCodes } from './errorHandling'
2019-08-30 22:36:30 +08:00
2019-10-08 21:26:09 +08:00
type ComponentVNode = VNode & {
type: Component
}
const stack: VNode[] = []
2019-08-30 22:36:30 +08:00
type TraceEntry = {
2019-10-08 21:26:09 +08:00
vnode: ComponentVNode
2019-08-30 22:36:30 +08:00
recurseCount: number
}
type ComponentTraceStack = TraceEntry[]
2019-10-08 21:26:09 +08:00
export function pushWarningContext(vnode: ComponentVNode) {
2019-08-30 22:36:30 +08:00
stack.push(vnode)
}
export function popWarningContext() {
stack.pop()
}
export function warn(msg: string, ...args: any[]) {
2019-09-04 06:11:04 +08:00
const instance = stack.length ? stack[stack.length - 1].component : null
const appWarnHandler = instance && instance.appContext.config.warnHandler
const trace = getComponentTrace()
if (appWarnHandler) {
callWithErrorHandling(
appWarnHandler,
instance,
ErrorCodes.APP_WARN_HANDLER,
[
msg + args.join(''),
instance && instance.renderProxy,
formatTrace(trace).join('')
]
2019-09-04 06:11:04 +08:00
)
return
}
2019-08-30 22:36:30 +08:00
console.warn(`[Vue warn]: ${msg}`, ...args)
2019-09-03 00:09:29 +08:00
// avoid spamming console during tests
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
2019-08-30 22:36:30 +08:00
return
}
2019-09-03 00:09:29 +08:00
if (!trace.length) {
return
}
2019-08-30 22:36:30 +08:00
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 {
2019-09-04 06:11:04 +08:00
console.log(...formatTrace(trace))
2019-08-30 22:36:30 +08:00
}
}
function getComponentTrace(): ComponentTraceStack {
let currentVNode: VNode | null = stack[stack.length - 1]
if (!currentVNode) {
return []
}
// we can't just use the stack because it will be incomplete during updates
// that did not start from the root. Re-construct the parent chain using
// instance parent pointers.
2019-10-05 22:48:54 +08:00
const normalizedStack: ComponentTraceStack = []
2019-08-30 22:36:30 +08:00
while (currentVNode) {
2019-10-05 22:48:54 +08:00
const last = normalizedStack[0]
2019-08-30 22:36:30 +08:00
if (last && last.vnode === currentVNode) {
last.recurseCount++
} else {
2019-10-05 22:48:54 +08:00
normalizedStack.push({
2019-08-30 22:36:30 +08:00
vnode: currentVNode,
recurseCount: 0
})
}
2019-10-05 22:09:34 +08:00
const parentInstance: ComponentInternalInstance | null = currentVNode.component!
2019-08-30 22:36:30 +08:00
.parent
currentVNode = parentInstance && parentInstance.vnode
}
2019-10-05 22:48:54 +08:00
return normalizedStack
2019-08-30 22:36:30 +08:00
}
2019-09-04 06:11:04 +08:00
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
}
2019-08-30 22:36:30 +08:00
function formatTraceEntry(
{ vnode, recurseCount }: TraceEntry,
depth: number = 0
): string[] {
const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
const open = padding + `<${formatComponentName(vnode)}`
const close = `>` + postfix
2019-10-05 22:09:34 +08:00
const rootLabel = vnode.component!.parent == null ? `(Root)` : ``
2019-08-30 22:36:30 +08:00
return vnode.props
? [open, ...formatProps(vnode.props), close, rootLabel]
: [open + close, rootLabel]
}
const classifyRE = /(?:^|[-_])(\w)/g
const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
2019-10-08 21:26:09 +08:00
function formatComponentName(vnode: ComponentVNode, file?: string): string {
const Component = vnode.type
let name = isFunction(Component) ? Component.displayName : Component.name
2019-08-30 22:36:30 +08:00
if (!name && file) {
const match = file.match(/([^/\\]+)\.vue$/)
if (match) {
name = match[1]
}
}
return name ? classify(name) : 'AnonymousComponent'
}
function formatProps(props: Data): string[] {
const res: string[] = []
for (const key in props) {
const value = props[key]
if (isString(value)) {
res.push(`${key}=${JSON.stringify(value)}`)
} else {
2019-10-08 21:26:09 +08:00
res.push(`${key}=`, String(toRaw(value)))
2019-08-30 22:36:30 +08:00
}
}
return res
2019-05-28 18:06:00 +08:00
}