|
@@ -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
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- console.warn(`[Vue warn]: ${msg}`, ...args)
|
|
|
|
|
- // 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 {
|
|
} else {
|
|
|
- console.log(...formatTrace(trace))
|
|
|
|
|
|
|
+ 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)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ resumeTracking()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function getComponentTrace(): ComponentTraceStack {
|
|
function getComponentTrace(): ComponentTraceStack {
|
|
@@ -95,27 +92,18 @@ function getComponentTrace(): ComponentTraceStack {
|
|
|
return normalizedStack
|
|
return normalizedStack
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function formatTrace(trace: ComponentTraceStack): string[] {
|
|
|
|
|
- const logs: string[] = []
|
|
|
|
|
|
|
+function formatTrace(trace: ComponentTraceStack): any[] {
|
|
|
|
|
+ const logs: any[] = []
|
|
|
trace.forEach((entry, i) => {
|
|
trace.forEach((entry, i) => {
|
|
|
- const formatted = formatTraceEntry(entry, i)
|
|
|
|
|
- if (i === 0) {
|
|
|
|
|
- logs.push('at', ...formatted)
|
|
|
|
|
- } else {
|
|
|
|
|
- logs.push('\n', ...formatted)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
|
|
|
})
|
|
})
|
|
|
return logs
|
|
return logs
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function formatTraceEntry(
|
|
|
|
|
- { vnode, recurseCount }: TraceEntry,
|
|
|
|
|
- depth: number = 0
|
|
|
|
|
-): string[] {
|
|
|
|
|
- const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
|
|
|
|
|
|
|
+function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
|
|
|
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[] {
|
|
|
|
|
- const res: string[] = []
|
|
|
|
|
|
|
+function formatProps(props: Data): any[] {
|
|
|
|
|
+ const res: any[] = []
|
|
|
for (const key in props) {
|
|
for (const key in props) {
|
|
|
- const value = props[key]
|
|
|
|
|
- if (isString(value)) {
|
|
|
|
|
- res.push(`${key}=${JSON.stringify(value)}`)
|
|
|
|
|
- } else {
|
|
|
|
|
- res.push(`${key}=`, String(toRaw(value)))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ res.push(...formatProp(key, props[key]))
|
|
|
}
|
|
}
|
|
|
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]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|