]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: warning context
authorEvan You <yyx990803@gmail.com>
Fri, 30 Aug 2019 14:36:30 +0000 (10:36 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 30 Aug 2019 14:36:30 +0000 (10:36 -0400)
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/shapeFlags.ts
packages/runtime-core/src/warning.ts

index 0552a8381e68308f5ab67635d7276e93c65e6009..91221e5500dfba5d6a3b70feb4453b7160adfde1 100644 (file)
@@ -34,6 +34,7 @@ import { resolveProps } from './componentProps'
 import { resolveSlots } from './componentSlots'
 import { PatchFlags } from './patchFlags'
 import { ShapeFlags } from './shapeFlags'
+import { pushWarningContext, popWarningContext, warn } from './warning'
 
 const prodEffectOptions = {
   scheduler: queueJob
@@ -163,15 +164,7 @@ export function createRenderer(options: RendererOptions) {
             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,
@@ -181,8 +174,9 @@ export function createRenderer(options: RendererOptions) {
             isSVG,
             optimized
           )
+        } else if (__DEV__) {
+          warn('Invalid VNode type:', n2.type, `(${typeof n2.type})`)
         }
-        break
     }
   }
 
@@ -492,8 +486,8 @@ export function createRenderer(options: RendererOptions) {
             isSVG
           )
         }
-      } else {
-        // TODO warn missing or invalid target
+      } else if (__DEV__) {
+        warn('Invalid Portal target on mount:', target, `(${typeof target})`)
       }
     } else {
       // update content
@@ -518,8 +512,8 @@ export function createRenderer(options: RendererOptions) {
               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})`)
         }
       }
     }
@@ -570,6 +564,10 @@ export function createRenderer(options: RendererOptions) {
       parentComponent
     ))
 
+    if (__DEV__) {
+      pushWarningContext(initialVNode)
+    }
+
     // resolve props and slots for setup context
     const propsOptions = (initialVNode.type as any).props
     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)
         // OR parent calling processComponent (next: VNode)
         const { next } = instance
+
+        if (__DEV__) {
+          pushWarningContext(next || instance.vnode)
+        }
+
         if (next !== null) {
           // update from parent
           next.component = instance
@@ -646,8 +649,16 @@ export function createRenderer(options: RendererOptions) {
         if (instance.u !== null) {
           queuePostFlushCb(instance.u)
         }
+
+        if (__DEV__) {
+          popWarningContext()
+        }
       }
     }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
+
+    if (__DEV__) {
+      popWarningContext()
+    }
   }
 
   function patchChildren(
@@ -882,7 +893,13 @@ export function createRenderer(options: RendererOptions) {
       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)
         }
       }
@@ -1093,11 +1110,10 @@ export function createRenderer(options: RendererOptions) {
       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})`)
     }
   }
 
index 173aa9054ca89789a08712aec7ead4934bdd42c8..7d00837b40ee04159795b5f20ea8525dabf3b8ba 100644 (file)
@@ -6,7 +6,8 @@ export const enum ShapeFlags {
   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
@@ -16,5 +17,6 @@ export const PublicShapeFlags = {
   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
 }
index 3553a408606d68b8912cf23cc79603ef189d2282..257a46f67a127969edfa37e8d02646a90010201f 100644 (file)
@@ -1,4 +1,125 @@
-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
 }