feat: warning context
This commit is contained in:
parent
010e64c224
commit
fd018b83b5
@ -34,6 +34,7 @@ import { resolveProps } from './componentProps'
|
|||||||
import { resolveSlots } from './componentSlots'
|
import { resolveSlots } from './componentSlots'
|
||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from './patchFlags'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
@ -163,15 +164,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG,
|
isSVG,
|
||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
} else {
|
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
if (
|
|
||||||
__DEV__ &&
|
|
||||||
!(shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&
|
|
||||||
!(shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
|
|
||||||
) {
|
|
||||||
// TODO warn invalid node type
|
|
||||||
debugger
|
|
||||||
}
|
|
||||||
processComponent(
|
processComponent(
|
||||||
n1,
|
n1,
|
||||||
n2,
|
n2,
|
||||||
@ -181,8 +174,9 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG,
|
isSVG,
|
||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn('Invalid VNode type:', n2.type, `(${typeof n2.type})`)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,8 +486,8 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG
|
isSVG
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (__DEV__) {
|
||||||
// TODO warn missing or invalid target
|
warn('Invalid Portal target on mount:', target, `(${typeof target})`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// update content
|
// update content
|
||||||
@ -518,8 +512,8 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
move((children as VNode[])[i], nextTarget, null)
|
move((children as VNode[])[i], nextTarget, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (__DEV__) {
|
||||||
// TODO warn missing or invalid target
|
warn('Invalid Portal target on update:', target, `(${typeof target})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,6 +564,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
parentComponent
|
parentComponent
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
pushWarningContext(initialVNode)
|
||||||
|
}
|
||||||
|
|
||||||
// resolve props and slots for setup context
|
// resolve props and slots for setup context
|
||||||
const propsOptions = (initialVNode.type as any).props
|
const propsOptions = (initialVNode.type as any).props
|
||||||
resolveProps(instance, initialVNode.props, propsOptions)
|
resolveProps(instance, initialVNode.props, propsOptions)
|
||||||
@ -601,6 +599,11 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// This is triggered by mutation of component's own state (next: null)
|
// This is triggered by mutation of component's own state (next: null)
|
||||||
// OR parent calling processComponent (next: VNode)
|
// OR parent calling processComponent (next: VNode)
|
||||||
const { next } = instance
|
const { next } = instance
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
pushWarningContext(next || instance.vnode)
|
||||||
|
}
|
||||||
|
|
||||||
if (next !== null) {
|
if (next !== null) {
|
||||||
// update from parent
|
// update from parent
|
||||||
next.component = instance
|
next.component = instance
|
||||||
@ -646,8 +649,16 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (instance.u !== null) {
|
if (instance.u !== null) {
|
||||||
queuePostFlushCb(instance.u)
|
queuePostFlushCb(instance.u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
popWarningContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
popWarningContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchChildren(
|
function patchChildren(
|
||||||
@ -882,7 +893,13 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
for (i = s2; i <= e2; i++) {
|
for (i = s2; i <= e2; i++) {
|
||||||
const nextChild = (c2[i] = normalizeVNode(c2[i]))
|
const nextChild = (c2[i] = normalizeVNode(c2[i]))
|
||||||
if (nextChild.key != null) {
|
if (nextChild.key != null) {
|
||||||
// TODO warn duplicate keys
|
if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
|
||||||
|
warn(
|
||||||
|
`Duplicate keys found during update:`,
|
||||||
|
JSON.stringify(nextChild.key),
|
||||||
|
`Make sure keys are unique.`
|
||||||
|
)
|
||||||
|
}
|
||||||
keyToNewIndexMap.set(nextChild.key, i)
|
keyToNewIndexMap.set(nextChild.key, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1093,11 +1110,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
refs[ref] = value
|
refs[ref] = value
|
||||||
} else if (isRef(ref)) {
|
} else if (isRef(ref)) {
|
||||||
ref.value = value
|
ref.value = value
|
||||||
} else {
|
} else if (isFunction(ref)) {
|
||||||
if (__DEV__ && !isFunction(ref)) {
|
|
||||||
// TODO warn invalid ref type
|
|
||||||
}
|
|
||||||
ref(value, refs)
|
ref(value, refs)
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn('Invalid template ref type:', value, `(${typeof value})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ export const enum ShapeFlags {
|
|||||||
STATEFUL_COMPONENT = 1 << 2,
|
STATEFUL_COMPONENT = 1 << 2,
|
||||||
TEXT_CHILDREN = 1 << 3,
|
TEXT_CHILDREN = 1 << 3,
|
||||||
ARRAY_CHILDREN = 1 << 4,
|
ARRAY_CHILDREN = 1 << 4,
|
||||||
SLOTS_CHILDREN = 1 << 5
|
SLOTS_CHILDREN = 1 << 5,
|
||||||
|
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
|
||||||
}
|
}
|
||||||
|
|
||||||
// but the flags are also exported as an actual object for external use
|
// but the flags are also exported as an actual object for external use
|
||||||
@ -16,5 +17,6 @@ export const PublicShapeFlags = {
|
|||||||
STATEFUL_COMPONENT: ShapeFlags.STATEFUL_COMPONENT,
|
STATEFUL_COMPONENT: ShapeFlags.STATEFUL_COMPONENT,
|
||||||
TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN,
|
TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN,
|
||||||
ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN,
|
ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN,
|
||||||
SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN
|
SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN,
|
||||||
|
COMPONENT: ShapeFlags.COMPONENT
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,125 @@
|
|||||||
export function warn(...args: any[]) {
|
import { VNode } from './vnode'
|
||||||
// TODO
|
import { Data, ComponentInstance } from './component'
|
||||||
console.warn(...args)
|
import { isString } from '@vue/shared'
|
||||||
|
import { toRaw } from '@vue/reactivity'
|
||||||
|
|
||||||
|
let stack: VNode[] = []
|
||||||
|
|
||||||
|
type TraceEntry = {
|
||||||
|
vnode: VNode
|
||||||
|
recurseCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentTraceStack = TraceEntry[]
|
||||||
|
|
||||||
|
export function pushWarningContext(vnode: VNode) {
|
||||||
|
stack.push(vnode)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function popWarningContext() {
|
||||||
|
stack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warn(msg: string, ...args: any[]) {
|
||||||
|
// TODO app level warn handler
|
||||||
|
console.warn(`[Vue warn]: ${msg}`, ...args)
|
||||||
|
const trace = getComponentTrace()
|
||||||
|
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 {
|
||||||
|
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(): 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.
|
||||||
|
const normlaizedStack: ComponentTraceStack = []
|
||||||
|
|
||||||
|
while (currentVNode) {
|
||||||
|
const last = normlaizedStack[0]
|
||||||
|
if (last && last.vnode === currentVNode) {
|
||||||
|
last.recurseCount++
|
||||||
|
} else {
|
||||||
|
normlaizedStack.push({
|
||||||
|
vnode: currentVNode,
|
||||||
|
recurseCount: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const parentInstance: ComponentInstance | null = (currentVNode.component as ComponentInstance)
|
||||||
|
.parent
|
||||||
|
currentVNode = parentInstance && parentInstance.vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
return normlaizedStack
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
const rootLabel =
|
||||||
|
(vnode.component as ComponentInstance).parent == null ? `(Root)` : ``
|
||||||
|
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, '')
|
||||||
|
|
||||||
|
function formatComponentName(vnode: VNode, file?: string): string {
|
||||||
|
const Component = vnode.type as any
|
||||||
|
let name = Component.displayName || Component.name
|
||||||
|
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 {
|
||||||
|
res.push(`${key}=`, toRaw(value) as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user