import { resolveSlots } from './componentSlots'
import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags'
+import { pushWarningContext, popWarningContext, warn } from './warning'
const prodEffectOptions = {
scheduler: queueJob
isSVG,
optimized
)
- } else {
- if (
- __DEV__ &&
- !(shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&
- !(shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
- ) {
- // TODO warn invalid node type
- debugger
- }
+ } else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
isSVG,
optimized
)
+ } else if (__DEV__) {
+ warn('Invalid VNode type:', n2.type, `(${typeof n2.type})`)
}
- break
}
}
isSVG
)
}
- } else {
- // TODO warn missing or invalid target
+ } else if (__DEV__) {
+ warn('Invalid Portal target on mount:', target, `(${typeof target})`)
}
} else {
// update content
move((children as VNode[])[i], nextTarget, null)
}
}
- } else {
- // TODO warn missing or invalid target
+ } else if (__DEV__) {
+ warn('Invalid Portal target on update:', target, `(${typeof target})`)
}
}
}
parentComponent
))
+ if (__DEV__) {
+ pushWarningContext(initialVNode)
+ }
+
// resolve props and slots for setup context
const propsOptions = (initialVNode.type as any).props
resolveProps(instance, initialVNode.props, propsOptions)
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
const { next } = instance
+
+ if (__DEV__) {
+ pushWarningContext(next || instance.vnode)
+ }
+
if (next !== null) {
// update from parent
next.component = instance
if (instance.u !== null) {
queuePostFlushCb(instance.u)
}
+
+ if (__DEV__) {
+ popWarningContext()
+ }
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
+
+ if (__DEV__) {
+ popWarningContext()
+ }
}
function patchChildren(
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = normalizeVNode(c2[i]))
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)
}
}
refs[ref] = value
} else if (isRef(ref)) {
ref.value = value
- } else {
- if (__DEV__ && !isFunction(ref)) {
- // TODO warn invalid ref type
- }
+ } else if (isFunction(ref)) {
ref(value, refs)
+ } else if (__DEV__) {
+ warn('Invalid template ref type:', value, `(${typeof value})`)
}
}
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
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
STATEFUL_COMPONENT: ShapeFlags.STATEFUL_COMPONENT,
TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN,
ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN,
- SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN
+ SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN,
+ COMPONENT: ShapeFlags.COMPONENT
}
-export function warn(...args: any[]) {
- // TODO
- console.warn(...args)
+import { VNode } from './vnode'
+import { Data, ComponentInstance } from './component'
+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
}