feat: improve warning component trace
This commit is contained in:
parent
d4cd3fb352
commit
2507ad2b44
@ -1,9 +1,17 @@
|
|||||||
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
||||||
import { EMPTY_OBJ } from './utils'
|
import { EMPTY_OBJ } from './utils'
|
||||||
import { VNode } from './vdom'
|
import { VNode } from './vdom'
|
||||||
|
import { Data } from './componentOptions'
|
||||||
|
|
||||||
let stack: VNode[] = []
|
let stack: VNode[] = []
|
||||||
|
|
||||||
|
type TraceEntry = {
|
||||||
|
type: VNode
|
||||||
|
recurseCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentTraceStack = TraceEntry[]
|
||||||
|
|
||||||
export function pushWarningContext(vnode: VNode) {
|
export function pushWarningContext(vnode: VNode) {
|
||||||
stack.push(vnode)
|
stack.push(vnode)
|
||||||
}
|
}
|
||||||
@ -13,30 +21,50 @@ export function popWarningContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function warn(msg: string, ...args: any[]) {
|
export function warn(msg: string, ...args: any[]) {
|
||||||
// TODO warn handler?
|
// TODO warn handler
|
||||||
console.warn(`[Vue warn]: ${msg}${getComponentTrace()}`, ...args)
|
console.warn(`[Vue warn]: ${msg}`, ...args)
|
||||||
|
const trace = getComponentTrace()
|
||||||
|
if (console.groupCollapsed) {
|
||||||
|
trace.forEach((entry, i) => {
|
||||||
|
const formatted = formatTraceEntry(entry, i)
|
||||||
|
if (i === 0) {
|
||||||
|
console.groupCollapsed('at', ...formatted)
|
||||||
|
} else {
|
||||||
|
console.log(...formatted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.groupEnd()
|
||||||
|
} else {
|
||||||
|
const logs: string[] = []
|
||||||
|
trace.forEach((entry, i) => {
|
||||||
|
const formatted = formatTraceEntry(entry, i)
|
||||||
|
if (i === 0) {
|
||||||
|
logs.push('at', ...formatted)
|
||||||
|
} else {
|
||||||
|
logs.push('\n', ...formatted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(...logs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComponentTrace(): string {
|
function getComponentTrace(): ComponentTraceStack {
|
||||||
let current: VNode | null | undefined = stack[stack.length - 1]
|
let current: VNode | null | undefined = stack[stack.length - 1]
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return ''
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can't just use the stack because it will be incomplete during updates
|
// 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
|
// that did not start from the root. Re-construct the parent chain using
|
||||||
// contextVNode information.
|
// contextVNode information.
|
||||||
const normlaizedStack: Array<{
|
const normlaizedStack: ComponentTraceStack = []
|
||||||
type: VNode
|
|
||||||
recurseCount: number
|
|
||||||
}> = []
|
|
||||||
|
|
||||||
while (current) {
|
while (current) {
|
||||||
const last = normlaizedStack[0]
|
const last = normlaizedStack[0]
|
||||||
if (last && last.type === current) {
|
if (last && last.type === current) {
|
||||||
last.recurseCount++
|
last.recurseCount++
|
||||||
} else {
|
} else {
|
||||||
normlaizedStack.unshift({
|
normlaizedStack.push({
|
||||||
type: current,
|
type: current,
|
||||||
recurseCount: 0
|
recurseCount: 0
|
||||||
})
|
})
|
||||||
@ -44,35 +72,33 @@ function getComponentTrace(): string {
|
|||||||
current = current.contextVNode
|
current = current.contextVNode
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return normlaizedStack
|
||||||
`\nat ` +
|
}
|
||||||
normlaizedStack
|
|
||||||
.map(({ type, recurseCount }, i) => {
|
function formatTraceEntry(
|
||||||
const padding = i === 0 ? '' : ' '.repeat(i + 1)
|
{ type, 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)` : ``
|
||||||
return (
|
const open = padding + `<${formatComponentName(type.tag as ComponentType)}`
|
||||||
padding + formatComponentName(type.tag as ComponentType) + postfix
|
const close = `>` + postfix
|
||||||
)
|
return type.data ? [open, ...formatProps(type.data), close] : [open + close]
|
||||||
})
|
|
||||||
.join('\n')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const classifyRE = /(?:^|[-_])(\w)/g
|
const classifyRE = /(?:^|[-_])(\w)/g
|
||||||
const classify = (str: string): string =>
|
const classify = (str: string): string =>
|
||||||
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
||||||
|
|
||||||
function formatComponentName(c: ComponentType, includeFile?: boolean): string {
|
function formatComponentName(c: ComponentType, file?: string): string {
|
||||||
let name: string
|
let name: string
|
||||||
let file: string | null = null
|
|
||||||
|
|
||||||
if (c.prototype && c.prototype.render) {
|
if (c.prototype && c.prototype.render) {
|
||||||
// stateful
|
// stateful
|
||||||
const cc = c as ComponentClass
|
const cc = c as ComponentClass
|
||||||
const options = cc.options || EMPTY_OBJ
|
const options = cc.options || EMPTY_OBJ
|
||||||
name = options.displayName || cc.name
|
name = options.displayName || cc.name
|
||||||
file = options.__file
|
|
||||||
} else {
|
} else {
|
||||||
// functional
|
// functional
|
||||||
const fc = c as FunctionalComponent
|
const fc = c as FunctionalComponent
|
||||||
@ -86,6 +112,18 @@ function formatComponentName(c: ComponentType, includeFile?: boolean): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePostfix = file && includeFile !== false ? ` at ${file}` : ''
|
return classify(name)
|
||||||
return `<${classify(name)}>` + filePostfix
|
}
|
||||||
|
|
||||||
|
function formatProps(props: Data) {
|
||||||
|
const res = []
|
||||||
|
for (const key in props) {
|
||||||
|
const value = props[key]
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
res.push(`${key}=${JSON.stringify(value)}`)
|
||||||
|
} else {
|
||||||
|
res.push(`${key}=`, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user