diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 79cafc4205c..ce03b4f6af0 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -60,12 +60,13 @@ export function renderComponentRoot( } = instance let result + let fallthroughAttrs const prev = setCurrentRenderingInstance(instance) if (__DEV__) { accessedAttrs = false } + try { - let fallthroughAttrs if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { // withProxy is a proxy with a different `has` trap only for // runtime-compiled render functions using `with` block. @@ -110,127 +111,127 @@ export function renderComponentRoot( ? attrs : getFunctionalFallthrough(attrs) } + } catch (err) { + blockStack.length = 0 + handleError(err, instance, ErrorCodes.RENDER_FUNCTION) + result = createVNode(Comment) + } - // attr merging - // in dev mode, comments are preserved, and it's possible for a template - // to have comments along side the root element which makes it a fragment - let root = result - let setRoot: ((root: VNode) => void) | undefined = undefined - if ( - __DEV__ && - result.patchFlag > 0 && - result.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT - ) { - ;[root, setRoot] = getChildRoot(result) - } + // attr merging + // in dev mode, comments are preserved, and it's possible for a template + // to have comments along side the root element which makes it a fragment + let root = result + let setRoot: ((root: VNode) => void) | undefined = undefined + if ( + __DEV__ && + result.patchFlag > 0 && + result.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT + ) { + ;[root, setRoot] = getChildRoot(result) + } - if (fallthroughAttrs && inheritAttrs !== false) { - const keys = Object.keys(fallthroughAttrs) - const { shapeFlag } = root - if (keys.length) { - if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT)) { - if (propsOptions && keys.some(isModelListener)) { - // If a v-model listener (onUpdate:xxx) has a corresponding declared - // prop, it indicates this component expects to handle v-model and - // it should not fallthrough. - // related: #1543, #1643, #1989 - fallthroughAttrs = filterModelListeners( - fallthroughAttrs, - propsOptions - ) - } - root = cloneVNode(root, fallthroughAttrs) - } else if (__DEV__ && !accessedAttrs && root.type !== Comment) { - const allAttrs = Object.keys(attrs) - const eventAttrs: string[] = [] - const extraAttrs: string[] = [] - for (let i = 0, l = allAttrs.length; i < l; i++) { - const key = allAttrs[i] - if (isOn(key)) { - // ignore v-model handlers when they fail to fallthrough - if (!isModelListener(key)) { - // remove `on`, lowercase first letter to reflect event casing - // accurately - eventAttrs.push(key[2].toLowerCase() + key.slice(3)) - } - } else { - extraAttrs.push(key) + if (fallthroughAttrs && inheritAttrs !== false) { + const keys = Object.keys(fallthroughAttrs) + const { shapeFlag } = root + if (keys.length) { + if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT)) { + if (propsOptions && keys.some(isModelListener)) { + // If a v-model listener (onUpdate:xxx) has a corresponding declared + // prop, it indicates this component expects to handle v-model and + // it should not fallthrough. + // related: #1543, #1643, #1989 + fallthroughAttrs = filterModelListeners( + fallthroughAttrs, + propsOptions + ) + } + root = cloneVNode(root, fallthroughAttrs) + } else if (__DEV__ && !accessedAttrs && root.type !== Comment) { + const allAttrs = Object.keys(attrs) + const eventAttrs: string[] = [] + const extraAttrs: string[] = [] + for (let i = 0, l = allAttrs.length; i < l; i++) { + const key = allAttrs[i] + if (isOn(key)) { + // ignore v-model handlers when they fail to fallthrough + if (!isModelListener(key)) { + // remove `on`, lowercase first letter to reflect event casing + // accurately + eventAttrs.push(key[2].toLowerCase() + key.slice(3)) } - } - if (extraAttrs.length) { - warn( - `Extraneous non-props attributes (` + - `${extraAttrs.join(', ')}) ` + - `were passed to component but could not be automatically inherited ` + - `because component renders fragment or text root nodes.` - ) - } - if (eventAttrs.length) { - warn( - `Extraneous non-emits event listeners (` + - `${eventAttrs.join(', ')}) ` + - `were passed to component but could not be automatically inherited ` + - `because component renders fragment or text root nodes. ` + - `If the listener is intended to be a component custom event listener only, ` + - `declare it using the "emits" option.` - ) + } else { + extraAttrs.push(key) } } - } - } - - if ( - __COMPAT__ && - isCompatEnabled(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, instance) && - vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT && - root.shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT) - ) { - const { class: cls, style } = vnode.props || {} - if (cls || style) { - if (__DEV__ && inheritAttrs === false) { - warnDeprecation( - DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, - instance, - getComponentName(instance.type) + if (extraAttrs.length) { + warn( + `Extraneous non-props attributes (` + + `${extraAttrs.join(', ')}) ` + + `were passed to component but could not be automatically inherited ` + + `because component renders fragment or text root nodes.` + ) + } + if (eventAttrs.length) { + warn( + `Extraneous non-emits event listeners (` + + `${eventAttrs.join(', ')}) ` + + `were passed to component but could not be automatically inherited ` + + `because component renders fragment or text root nodes. ` + + `If the listener is intended to be a component custom event listener only, ` + + `declare it using the "emits" option.` ) } - root = cloneVNode(root, { - class: cls, - style: style - }) } } + } - // inherit directives - if (vnode.dirs) { - if (__DEV__ && !isElementRoot(root)) { - warn( - `Runtime directive used on component with non-element root node. ` + - `The directives will not function as intended.` + if ( + __COMPAT__ && + isCompatEnabled(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, instance) && + vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT && + root.shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT) + ) { + const { class: cls, style } = vnode.props || {} + if (cls || style) { + if (__DEV__ && inheritAttrs === false) { + warnDeprecation( + DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, + instance, + getComponentName(instance.type) ) } - root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs - } - // inherit transition data - if (vnode.transition) { - if (__DEV__ && !isElementRoot(root)) { - warn( - `Component inside renders non-element root node ` + - `that cannot be animated.` - ) - } - root.transition = vnode.transition + root = cloneVNode(root, { + class: cls, + style: style + }) } + } - if (__DEV__ && setRoot) { - setRoot(root) - } else { - result = root + // inherit directives + if (vnode.dirs) { + if (__DEV__ && !isElementRoot(root)) { + warn( + `Runtime directive used on component with non-element root node. ` + + `The directives will not function as intended.` + ) } - } catch (err) { - blockStack.length = 0 - handleError(err, instance, ErrorCodes.RENDER_FUNCTION) - result = createVNode(Comment) + root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs + } + // inherit transition data + if (vnode.transition) { + if (__DEV__ && !isElementRoot(root)) { + warn( + `Component inside renders non-element root node ` + + `that cannot be animated.` + ) + } + root.transition = vnode.transition + } + + if (__DEV__ && setRoot) { + setRoot(root) + } else { + result = root } setCurrentRenderingInstance(prev) diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 333125d578d..ccf7ac70b41 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -1,5 +1,5 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling' -import { isArray } from '@vue/shared' +import { isArray, NOOP } from '@vue/shared' import { ComponentInternalInstance, getComponentName } from './component' import { warn } from './warning' @@ -128,10 +128,7 @@ function queueCb( if (!isArray(cb)) { if ( !activeQueue || - !activeQueue.includes( - cb, - cb.allowRecurse ? index + 1 : index - ) + !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index) ) { pendingQueue.push(cb) } @@ -241,11 +238,20 @@ function flushJobs(seen?: CountMap) { // its update can be skipped. queue.sort((a, b) => getId(a) - getId(b)) + // conditional usage of checkRecursiveUpdate must be determined out of + // try ... catch block since Rollup by default de-optimizes treeshaking + // inside try-catch. This can leave all warning code unshaked. Although + // they would get eventually shaken by a minifier like terser, some minifiers + // would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610) + const check = __DEV__ + ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job) + : NOOP + try { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex] if (job && job.active !== false) { - if (__DEV__ && checkRecursiveUpdates(seen!, job)) { + if (__DEV__ && check(job)) { continue } // console.log(`running:`, job.id)