diff --git a/.gitignore b/.gitignore index 570edfc..8ebfa13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ coverage node_modules examples/legacy/index.js +examples/global/index.js examples/compsable/index.js .DS_Store lib diff --git a/README.md b/README.md index 2b1bfa2..39499c4 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,15 @@ the below example that `examples/composable/App.vue` have `i18n` custom block: diff --git a/e2e/example.test.js b/e2e/example.test.js index 0e781ec..ee53666 100644 --- a/e2e/example.test.js +++ b/e2e/example.test.js @@ -1,4 +1,4 @@ -;['composable', 'legacy'].forEach(pattern => { +;['composable', 'legacy', 'globe'].forEach(pattern => { describe(`${pattern}`, () => { beforeAll(async () => { await page.goto(`http://localhost:8080/${pattern}/`) diff --git a/examples/composable/App.vue b/examples/composable/App.vue index 6e0130f..250aac2 100644 --- a/examples/composable/App.vue +++ b/examples/composable/App.vue @@ -14,9 +14,11 @@ import { useI18n } from 'vue-i18n' export default { name: 'App', setup() { - return { ...useI18n({ + const { t, locale } = useI18n({ inheritLocale: true - }) } + }) + + return { t, locale } } } diff --git a/examples/composable/index.js b/examples/composable/index.js index fb7ea53..284459e 100644 --- a/examples/composable/index.js +++ b/examples/composable/index.js @@ -147,7 +147,7 @@ const toDisplayString = (val) => { : String(val); }; const replacer = (_key, val) => { - if (val instanceof Map) { + if (isMap(val)) { return { [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => { entries[`${key} =>`] = val; @@ -155,7 +155,7 @@ const replacer = (_key, val) => { }, {}) }; } - else if (val instanceof Set) { + else if (isSet(val)) { return { [`Set(${val.size})`]: [...val.values()] }; @@ -166,7 +166,7 @@ const replacer = (_key, val) => { return val; }; const EMPTY_OBJ = {}; -const EMPTY_ARR = []; +const EMPTY_ARR = []; const NOOP = () => { }; /** * Always return false. @@ -185,6 +185,8 @@ const remove = (arr, el) => { const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); const isArray = Array.isArray; +const isMap = (val) => toTypeString(val) === '[object Map]'; +const isSet = (val) => toTypeString(val) === '[object Set]'; const isDate = (val) => val instanceof Date; const isFunction = (val) => typeof val === 'function'; const isString = (val) => typeof val === 'string'; @@ -196,11 +198,17 @@ const isPromise = (val) => { const objectToString = Object.prototype.toString; const toTypeString = (value) => objectToString.call(value); const toRawType = (value) => { + // extract "RawType" from strings like "[object RawType]" return toTypeString(value).slice(8, -1); }; const isPlainObject = (val) => toTypeString(val) === '[object Object]'; -const isIntegerKey = (key) => isString(key) && key[0] !== '-' && '' + parseInt(key, 10) === key; -const isReservedProp = /*#__PURE__*/ makeMap('key,ref,' + +const isIntegerKey = (key) => isString(key) && + key !== 'NaN' && + key[0] !== '-' && + '' + parseInt(key, 10) === key; +const isReservedProp = /*#__PURE__*/ makeMap( +// the leading comma is intentional so empty string "" is also included +',key,ref,' + 'onVnodeBeforeMount,onVnodeMounted,' + 'onVnodeBeforeUpdate,onVnodeUpdated,' + 'onVnodeBeforeUnmount,onVnodeUnmounted'); @@ -222,15 +230,15 @@ const hyphenateRE = /\B([A-Z])/g; /** * @private */ -const hyphenate = cacheStringFunction((str) => { - return str.replace(hyphenateRE, '-$1').toLowerCase(); -}); +const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase()); /** * @private */ -const capitalize = cacheStringFunction((str) => { - return str.charAt(0).toUpperCase() + str.slice(1); -}); +const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1)); +/** + * @private + */ +const toHandlerKey = cacheStringFunction((str) => (str ? `on${capitalize(str)}` : ``)); // compare whether a value has changed, accounting for NaN. const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); const invokeArrayFns = (fns, arg) => { @@ -245,6 +253,10 @@ const def = (obj, key, value) => { value }); }; +const toNumber = (val) => { + const n = parseFloat(val); + return isNaN(n) ? val : n; +}; let _globalThis; const getGlobalThis = () => { return (_globalThis || @@ -309,6 +321,7 @@ function createReactiveEffect(fn, options) { } }; effect.id = uid++; + effect.allowRecurse = !!options.allowRecurse; effect._isEffect = true; effect.active = true; effect.raw = fn; @@ -366,7 +379,7 @@ function trigger(target, type, key, newValue, oldValue, oldTarget) { const add = (effectsToAdd) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { - if (effect !== activeEffect) { + if (effect !== activeEffect || effect.allowRecurse) { effects.add(effect); } }); @@ -390,15 +403,32 @@ function trigger(target, type, key, newValue, oldValue, oldTarget) { add(depsMap.get(key)); } // also run for iteration key on ADD | DELETE | Map.SET - const shouldTriggerIteration = (type === "add" /* ADD */ && - (!isArray(target) || isIntegerKey(key))) || - (type === "delete" /* DELETE */ && !isArray(target)); - if (shouldTriggerIteration || - (type === "set" /* SET */ && target instanceof Map)) { - add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY)); - } - if (shouldTriggerIteration && target instanceof Map) { - add(depsMap.get(MAP_KEY_ITERATE_KEY)); + switch (type) { + case "add" /* ADD */: + if (!isArray(target)) { + add(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + add(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + else if (isIntegerKey(key)) { + // new index added to array -> length changes + add(depsMap.get('length')); + } + break; + case "delete" /* DELETE */: + if (!isArray(target)) { + add(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + add(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + break; + case "set" /* SET */: + if (isMap(target)) { + add(depsMap.get(ITERATE_KEY)); + } + break; } } const run = (effect) => { @@ -421,22 +451,32 @@ const readonlyGet = /*#__PURE__*/ createGetter(true); const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true); const arrayInstrumentations = {}; ['includes', 'indexOf', 'lastIndexOf'].forEach(key => { + const method = Array.prototype[key]; arrayInstrumentations[key] = function (...args) { const arr = toRaw(this); for (let i = 0, l = this.length; i < l; i++) { track(arr, "get" /* GET */, i + ''); } // we run the method using the original args first (which may be reactive) - const res = arr[key](...args); + const res = method.apply(arr, args); if (res === -1 || res === false) { // if that didn't work, run it again using raw values. - return arr[key](...args.map(toRaw)); + return method.apply(arr, args.map(toRaw)); } else { return res; } }; }); +['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => { + const method = Array.prototype[key]; + arrayInstrumentations[key] = function (...args) { + pauseTracking(); + const res = method.apply(this, args); + resetTracking(); + return res; + }; +}); function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { if (key === "__v_isReactive" /* IS_REACTIVE */) { @@ -450,12 +490,11 @@ function createGetter(isReadonly = false, shallow = false) { return target; } const targetIsArray = isArray(target); - if (targetIsArray && hasOwn(arrayInstrumentations, key)) { + if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver); } const res = Reflect.get(target, key, receiver); - const keyIsSymbol = isSymbol(key); - if (keyIsSymbol + if (isSymbol(key) ? builtInSymbols.has(key) : key === `__proto__` || key === `__v_isRef`) { return res; @@ -525,7 +564,7 @@ function has(target, key) { return result; } function ownKeys(target) { - track(target, "iterate" /* ITERATE */, ITERATE_KEY); + track(target, "iterate" /* ITERATE */, isArray(target) ? 'length' : ITERATE_KEY); return Reflect.ownKeys(target); } const mutableHandlers = { @@ -600,34 +639,34 @@ function add(value) { const target = toRaw(this); const proto = getProto(target); const hadKey = proto.has.call(target, value); - const result = proto.add.call(target, value); + target.add(value); if (!hadKey) { trigger(target, "add" /* ADD */, value, value); } - return result; + return this; } function set$1(key, value) { value = toRaw(value); const target = toRaw(this); - const { has, get, set } = getProto(target); + const { has, get } = getProto(target); let hadKey = has.call(target, key); if (!hadKey) { key = toRaw(key); hadKey = has.call(target, key); } const oldValue = get.call(target, key); - const result = set.call(target, key, value); + target.set(key, value); if (!hadKey) { trigger(target, "add" /* ADD */, key, value); } else if (hasChanged(value, oldValue)) { trigger(target, "set" /* SET */, key, value); } - return result; + return this; } function deleteEntry(key) { const target = toRaw(this); - const { has, get, delete: del } = getProto(target); + const { has, get } = getProto(target); let hadKey = has.call(target, key); if (!hadKey) { key = toRaw(key); @@ -635,7 +674,7 @@ function deleteEntry(key) { } const oldValue = get ? get.call(target, key) : undefined; // forward the operation before queueing reactions - const result = del.call(target, key); + const result = target.delete(key); if (hadKey) { trigger(target, "delete" /* DELETE */, key, undefined); } @@ -645,7 +684,7 @@ function clear() { const target = toRaw(this); const hadItems = target.size !== 0; // forward the operation before queueing reactions - const result = getProto(target).clear.call(target); + const result = target.clear(); if (hadItems) { trigger(target, "clear" /* CLEAR */, undefined, undefined); } @@ -670,9 +709,9 @@ function createIterableMethod(method, isReadonly, isShallow) { return function (...args) { const target = this["__v_raw" /* RAW */]; const rawTarget = toRaw(target); - const isMap = rawTarget instanceof Map; - const isPair = method === 'entries' || (method === Symbol.iterator && isMap); - const isKeyOnly = method === 'keys' && isMap; + const targetIsMap = isMap(rawTarget); + const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap); + const isKeyOnly = method === 'keys' && targetIsMap; const innerIterator = target[method](...args); const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive; !isReadonly && @@ -811,12 +850,18 @@ function reactive(target) { } return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers); } -// Return a reactive-copy of the original object, where only the root level -// properties are reactive, and does NOT unwrap refs nor recursively convert -// returned properties. +/** + * Return a shallowly-reactive copy of the original object, where only the root + * level properties are reactive. It also does not auto-unwrap refs (even at the + * root level). + */ function shallowReactive(target) { return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers); } +/** + * Creates a readonly copy of the original object. Note the returned copy is not + * made reactive, but `readonly` can be called on an already reactive object. + */ function readonly(target) { return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers); } @@ -914,6 +959,24 @@ function proxyRefs(objectWithRefs) { ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); } +class ObjectRefImpl { + constructor(_object, _key) { + this._object = _object; + this._key = _key; + this.__v_isRef = true; + } + get value() { + return this._object[this._key]; + } + set value(newVal) { + this._object[this._key] = newVal; + } +} +function toRef(object, key) { + return isRef(object[key]) + ? object[key] + : new ObjectRefImpl(object, key); +} class ComputedRefImpl { constructor(getter, _setter, isReadonly) { @@ -1090,7 +1153,7 @@ function callWithAsyncErrorHandling(fn, instance, type, args) { } return values; } -function handleError(err, instance, type) { +function handleError(err, instance, type, throwInDev = true) { const contextVNode = instance ? instance.vnode : null; if (instance) { let cur = instance.parent; @@ -1102,7 +1165,7 @@ function handleError(err, instance, type) { const errorCapturedHooks = cur.ec; if (errorCapturedHooks) { for (let i = 0; i < errorCapturedHooks.length; i++) { - if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) { + if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { return; } } @@ -1116,9 +1179,9 @@ function handleError(err, instance, type) { return; } } - logError(err); + logError(err, type, contextVNode, throwInDev); } -function logError(err, type, contextVNode) { +function logError(err, type, contextVNode, throwInDev = true) { { // recover in prod to reduce the impact on end-user console.error(err); @@ -1141,7 +1204,7 @@ let currentPreFlushParentJob = null; const RECURSION_LIMIT = 100; function nextTick(fn) { const p = currentFlushPromise || resolvedPromise; - return fn ? p.then(fn) : p; + return fn ? p.then(this ? fn.bind(this) : fn) : p; } function queueJob(job) { // the dedupe search uses the startIndex argument of Array.includes() @@ -1166,7 +1229,7 @@ function queueFlush() { function invalidateJob(job) { const i = queue.indexOf(job); if (i > -1) { - queue[i] = null; + queue.splice(i, 1); } } function queueCb(cb, activeQueue, pendingQueue, index) { @@ -1235,8 +1298,6 @@ function flushJobs(seen) { // priority number) // 2. If a component is unmounted during a parent component's update, // its update can be skipped. - // Jobs can never be null before flush starts, since they are only invalidated - // during execution of another flushed job. queue.sort((a, b) => getId(a) - getId(b)); try { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { @@ -1279,15 +1340,106 @@ function checkRecursiveUpdates(seen, fn) { } } -// mark the current rendering instance for asset resolution (e.g. -// resolveComponent, resolveDirective) during render +function emit(instance, event, ...rawArgs) { + const props = instance.vnode.props || EMPTY_OBJ; + let args = rawArgs; + const isModelListener = event.startsWith('update:'); + // for v-model update:xxx events, apply modifiers on args + const modelArg = isModelListener && event.slice(7); + if (modelArg && modelArg in props) { + const modifiersKey = `${modelArg === 'modelValue' ? 'model' : modelArg}Modifiers`; + const { number, trim } = props[modifiersKey] || EMPTY_OBJ; + if (trim) { + args = rawArgs.map(a => a.trim()); + } + else if (number) { + args = rawArgs.map(toNumber); + } + } + if ( __VUE_PROD_DEVTOOLS__) ; + // convert handler name to camelCase. See issue #2249 + let handlerName = toHandlerKey(camelize(event)); + let handler = props[handlerName]; + // for v-model update:xxx events, also trigger kebab-case equivalent + // for props passed via kebab-case + if (!handler && isModelListener) { + handlerName = toHandlerKey(hyphenate(event)); + handler = props[handlerName]; + } + if (handler) { + callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args); + } + const onceHandler = props[handlerName + `Once`]; + if (onceHandler) { + if (!instance.emitted) { + (instance.emitted = {})[handlerName] = true; + } + else if (instance.emitted[handlerName]) { + return; + } + callWithAsyncErrorHandling(onceHandler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args); + } +} +function normalizeEmitsOptions(comp, appContext, asMixin = false) { + if (!appContext.deopt && comp.__emits !== undefined) { + return comp.__emits; + } + const raw = comp.emits; + let normalized = {}; + // apply mixin/extends props + let hasExtends = false; + if (__VUE_OPTIONS_API__ && !isFunction(comp)) { + const extendEmits = (raw) => { + hasExtends = true; + extend(normalized, normalizeEmitsOptions(raw, appContext, true)); + }; + if (!asMixin && appContext.mixins.length) { + appContext.mixins.forEach(extendEmits); + } + if (comp.extends) { + extendEmits(comp.extends); + } + if (comp.mixins) { + comp.mixins.forEach(extendEmits); + } + } + if (!raw && !hasExtends) { + return (comp.__emits = null); + } + if (isArray(raw)) { + raw.forEach(key => (normalized[key] = null)); + } + else { + extend(normalized, raw); + } + return (comp.__emits = normalized); +} +// Check if an incoming prop key is a declared emit event listener. +// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are +// both considered matched listeners. +function isEmitListener(options, key) { + if (!options || !isOn(key)) { + return false; + } + key = key.slice(2).replace(/Once$/, ''); + return (hasOwn(options, key[0].toLowerCase() + key.slice(1)) || + hasOwn(options, hyphenate(key)) || + hasOwn(options, key)); +} + +/** + * mark the current rendering instance for asset resolution (e.g. + * resolveComponent, resolveDirective) during render + */ let currentRenderingInstance = null; function setCurrentRenderingInstance(instance) { currentRenderingInstance = instance; } -// dev only flag to track whether $attrs was used during render. -// If $attrs was used during render then the warning for failed attrs -// fallthrough can be suppressed. +/** + * dev only flag to track whether $attrs was used during render. + * If $attrs was used during render then the warning for failed attrs + * fallthrough can be suppressed. + */ let accessedAttrs = false; function markAttrsAccessed() { accessedAttrs = true; @@ -1331,7 +1483,7 @@ function renderComponentRoot(instance) { // to have comments along side the root element which makes it a fragment let root = result; let setRoot = undefined; - if (("production" !== 'production')) ; + if (("production" !== 'production') && result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) ; if (Component.inheritAttrs !== false && fallthroughAttrs) { const keys = Object.keys(fallthroughAttrs); const { shapeFlag } = root; @@ -1353,7 +1505,7 @@ function renderComponentRoot(instance) { // inherit directives if (vnode.dirs) { if (("production" !== 'production') && !isElementRoot(root)) ; - root.dirs = vnode.dirs; + root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs; } // inherit transition data if (vnode.transition) { @@ -1374,35 +1526,54 @@ function renderComponentRoot(instance) { } /** * dev only + * In dev mode, template root level comments are rendered, which turns the + * template into a fragment root, but we need to locate the single element + * root for attrs and scope id processing. */ const getChildRoot = (vnode) => { - if (vnode.type !== Fragment) { - return [vnode, undefined]; - } const rawChildren = vnode.children; const dynamicChildren = vnode.dynamicChildren; - const children = rawChildren.filter(child => { - return !(isVNode(child) && - child.type === Comment && - child.children !== 'v-if'); - }); - if (children.length !== 1) { + const childRoot = filterSingleRoot(rawChildren); + if (!childRoot) { return [vnode, undefined]; } - const childRoot = children[0]; const index = rawChildren.indexOf(childRoot); const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1; const setRoot = (updatedRoot) => { rawChildren[index] = updatedRoot; - if (dynamicIndex > -1) { - dynamicChildren[dynamicIndex] = updatedRoot; - } - else if (dynamicChildren && updatedRoot.patchFlag > 0) { - dynamicChildren.push(updatedRoot); + if (dynamicChildren) { + if (dynamicIndex > -1) { + dynamicChildren[dynamicIndex] = updatedRoot; + } + else if (updatedRoot.patchFlag > 0) { + vnode.dynamicChildren = [...dynamicChildren, updatedRoot]; + } } }; return [normalizeVNode(childRoot), setRoot]; }; +function filterSingleRoot(children) { + let singleRoot; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (isVNode(child)) { + // ignore user comment + if (child.type !== Comment || child.children === 'v-if') { + if (singleRoot) { + // has more than 1 non-comment child, return now + return; + } + else { + singleRoot = child; + } + } + } + else { + return; + } + } + return singleRoot; +} const getFunctionalFallthrough = (attrs) => { let res; for (const key in attrs) { @@ -1428,13 +1599,14 @@ const isElementRoot = (vnode) => { ); }; function shouldUpdateComponent(prevVNode, nextVNode, optimized) { - const { props: prevProps, children: prevChildren } = prevVNode; + const { props: prevProps, children: prevChildren, component } = prevVNode; const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; + const emits = component.emitsOptions; // force child update for runtime directive or transition on component vnode. if (nextVNode.dirs || nextVNode.transition) { return true; } - if (optimized && patchFlag > 0) { + if (optimized && patchFlag >= 0) { if (patchFlag & 1024 /* DYNAMIC_SLOTS */) { // slot content that references values that might have changed, // e.g. in a v-for @@ -1445,13 +1617,14 @@ function shouldUpdateComponent(prevVNode, nextVNode, optimized) { return !!nextProps; } // presence of this flag indicates props are always non-null - return hasPropsChanged(prevProps, nextProps); + return hasPropsChanged(prevProps, nextProps, emits); } else if (patchFlag & 8 /* PROPS */) { const dynamicProps = nextVNode.dynamicProps; for (let i = 0; i < dynamicProps.length; i++) { const key = dynamicProps[i]; - if (nextProps[key] !== prevProps[key]) { + if (nextProps[key] !== prevProps[key] && + !isEmitListener(emits, key)) { return true; } } @@ -1474,18 +1647,19 @@ function shouldUpdateComponent(prevVNode, nextVNode, optimized) { if (!nextProps) { return true; } - return hasPropsChanged(prevProps, nextProps); + return hasPropsChanged(prevProps, nextProps, emits); } return false; } -function hasPropsChanged(prevProps, nextProps) { +function hasPropsChanged(prevProps, nextProps, emitsOptions) { const nextKeys = Object.keys(nextProps); if (nextKeys.length !== Object.keys(prevProps).length) { return true; } for (let i = 0; i < nextKeys.length; i++) { const key = nextKeys[i]; - if (nextProps[key] !== prevProps[key]) { + if (nextProps[key] !== prevProps[key] && + !isEmitListener(emitsOptions, key)) { return true; } } @@ -1500,8 +1674,35 @@ function updateHOCHostEl({ vnode, parent }, el // HostNode } const isSuspense = (type) => type.__isSuspense; +function normalizeSuspenseChildren(vnode) { + const { shapeFlag, children } = vnode; + let content; + let fallback; + if (shapeFlag & 32 /* SLOTS_CHILDREN */) { + content = normalizeSuspenseSlot(children.default); + fallback = normalizeSuspenseSlot(children.fallback); + } + else { + content = normalizeSuspenseSlot(children); + fallback = normalizeVNode(null); + } + return { + content, + fallback + }; +} +function normalizeSuspenseSlot(s) { + if (isFunction(s)) { + s = s(); + } + if (isArray(s)) { + const singleChild = filterSingleRoot(s); + s = singleChild; + } + return normalizeVNode(s); +} function queueEffectWithSuspense(fn, suspense) { - if (suspense && !suspense.isResolved) { + if (suspense && suspense.pendingBranch) { if (isArray(fn)) { suspense.effects.push(...fn); } @@ -1512,7 +1713,7 @@ function queueEffectWithSuspense(fn, suspense) { else { queuePostFlushCb(fn); } -} +} let isRenderingCompiledSlot = 0; const setCompiledSlotRendering = (n) => (isRenderingCompiledSlot += n); @@ -1547,515 +1748,124 @@ function withCtx(fn, ctx = currentRenderingInstance) { // SFC scoped style ID management. let currentScopeId = null; -const isTeleport = (type) => type.__isTeleport; -const NULL_DYNAMIC_COMPONENT = Symbol(); - -const Fragment = Symbol( undefined); -const Text = Symbol( undefined); -const Comment = Symbol( undefined); -const Static = Symbol( undefined); -// Since v-if and v-for are the two possible ways node structure can dynamically -// change, once we consider v-if branches and each v-for fragment a block, we -// can divide a template into nested blocks, and within each block the node -// structure would be stable. This allows us to skip most children diffing -// and only worry about the dynamic nodes (indicated by patch flags). -const blockStack = []; -let currentBlock = null; -/** - * Open a block. - * This must be called before `createBlock`. It cannot be part of `createBlock` - * because the children of the block are evaluated before `createBlock` itself - * is called. The generated code typically looks like this: - * - * ```js - * function render() { - * return (openBlock(),createBlock('div', null, [...])) - * } - * ``` - * disableTracking is true when creating a v-for fragment block, since a v-for - * fragment always diffs its children. - * - * @private - */ -function openBlock(disableTracking = false) { - blockStack.push((currentBlock = disableTracking ? null : [])); -} -function closeBlock() { - blockStack.pop(); - currentBlock = blockStack[blockStack.length - 1] || null; -} -/** - * Create a block root vnode. Takes the same exact arguments as `createVNode`. - * A block root keeps track of dynamic nodes within the block in the - * `dynamicChildren` array. - * - * @private - */ -function createBlock(type, props, children, patchFlag, dynamicProps) { - const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true /* isBlock: prevent a block from tracking itself */); - // save current block children on the block vnode - vnode.dynamicChildren = currentBlock || EMPTY_ARR; - // close block - closeBlock(); - // a block is always going to be patched, so track it as a child of its - // parent block - if ( currentBlock) { - currentBlock.push(vnode); - } - return vnode; -} -function isVNode(value) { - return value ? value.__v_isVNode === true : false; -} -function isSameVNodeType(n1, n2) { - return n1.type === n2.type && n1.key === n2.key; -} -const InternalObjectKey = `__vInternal`; -const normalizeKey = ({ key }) => key != null ? key : null; -const normalizeRef = ({ ref }) => { - return (ref != null - ? isArray(ref) - ? ref - : [currentRenderingInstance, ref] - : null); -}; -const createVNode = ( _createVNode); -function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) { - if (!type || type === NULL_DYNAMIC_COMPONENT) { - type = Comment; - } - if (isVNode(type)) { - const cloned = cloneVNode(type, props); - if (children) { - normalizeChildren(cloned, children); - } - return cloned; - } - // class component normalization. - if (isFunction(type) && '__vccOpts' in type) { - type = type.__vccOpts; +function initProps(instance, rawProps, isStateful, // result of bitwise flag comparison +isSSR = false) { + const props = {}; + const attrs = {}; + def(attrs, InternalObjectKey, 1); + setFullProps(instance, rawProps, props, attrs); + if (isStateful) { + // stateful + instance.props = isSSR ? props : shallowReactive(props); } - // class & style normalization. - if (props) { - // for reactive or proxy objects, we need to clone it to enable mutation. - if (isProxy(props) || InternalObjectKey in props) { - props = extend({}, props); - } - let { class: klass, style } = props; - if (klass && !isString(klass)) { - props.class = normalizeClass(klass); + else { + if (!instance.type.props) { + // functional w/ optional props, props === attrs + instance.props = attrs; } - if (isObject(style)) { - // reactive state objects need to be cloned since they are likely to be - // mutated - if (isProxy(style) && !isArray(style)) { - style = extend({}, style); - } - props.style = normalizeStyle(style); + else { + // functional w/ declared props + instance.props = props; } } - // encode the vnode type information into a bitmap - const shapeFlag = isString(type) - ? 1 /* ELEMENT */ - : isSuspense(type) - ? 128 /* SUSPENSE */ - : isTeleport(type) - ? 64 /* TELEPORT */ - : isObject(type) - ? 4 /* STATEFUL_COMPONENT */ - : isFunction(type) - ? 2 /* FUNCTIONAL_COMPONENT */ - : 0; - const vnode = { - __v_isVNode: true, - ["__v_skip" /* SKIP */]: true, - type, - props, - key: props && normalizeKey(props), - ref: props && normalizeRef(props), - scopeId: currentScopeId, - children: null, - component: null, - suspense: null, - dirs: null, - transition: null, - el: null, - anchor: null, - target: null, - targetAnchor: null, - staticCount: 0, - shapeFlag, - patchFlag, - dynamicProps, - dynamicChildren: null, - appContext: null - }; - normalizeChildren(vnode, children); - if ( - // avoid a block node from tracking itself - !isBlockNode && - // has current parent block - currentBlock && - // presence of a patch flag indicates this node needs patching on updates. - // component nodes also should always be patched, because even if the - // component doesn't need to update, it needs to persist the instance on to - // the next vnode so that it can be properly unmounted later. - (patchFlag > 0 || shapeFlag & 6 /* COMPONENT */) && - // the EVENTS flag is only for hydration and if it is the only flag, the - // vnode should not be considered dynamic due to handler caching. - patchFlag !== 32 /* HYDRATE_EVENTS */) { - currentBlock.push(vnode); - } - return vnode; -} -function cloneVNode(vnode, extraProps) { - // This is intentionally NOT using spread or extend to avoid the runtime - // key enumeration cost. - const { props, patchFlag } = vnode; - const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props; - return { - __v_isVNode: true, - ["__v_skip" /* SKIP */]: true, - type: vnode.type, - props: mergedProps, - key: mergedProps && normalizeKey(mergedProps), - ref: extraProps && extraProps.ref ? normalizeRef(extraProps) : vnode.ref, - scopeId: vnode.scopeId, - children: vnode.children, - target: vnode.target, - targetAnchor: vnode.targetAnchor, - staticCount: vnode.staticCount, - shapeFlag: vnode.shapeFlag, - // if the vnode is cloned with extra props, we can no longer assume its - // existing patch flag to be reliable and need to add the FULL_PROPS flag. - // note: perserve flag for fragments since they use the flag for children - // fast paths only. - patchFlag: extraProps && vnode.type !== Fragment - ? patchFlag === -1 // hoisted node - ? 16 /* FULL_PROPS */ - : patchFlag | 16 /* FULL_PROPS */ - : patchFlag, - dynamicProps: vnode.dynamicProps, - dynamicChildren: vnode.dynamicChildren, - appContext: vnode.appContext, - dirs: vnode.dirs, - transition: vnode.transition, - // These should technically only be non-null on mounted VNodes. However, - // they *should* be copied for kept-alive vnodes. So we just always copy - // them since them being non-null during a mount doesn't affect the logic as - // they will simply be overwritten. - component: vnode.component, - suspense: vnode.suspense, - el: vnode.el, - anchor: vnode.anchor - }; -} -/** - * @private - */ -function createTextVNode(text = ' ', flag = 0) { - return createVNode(Text, null, text, flag); -} -function normalizeVNode(child) { - if (child == null || typeof child === 'boolean') { - // empty placeholder - return createVNode(Comment); - } - else if (isArray(child)) { - // fragment - return createVNode(Fragment, null, child); - } - else if (typeof child === 'object') { - // already vnode, this should be the most common since compiled templates - // always produce all-vnode children arrays - return child.el === null ? child : cloneVNode(child); - } - else { - // strings and numbers - return createVNode(Text, null, String(child)); - } -} -// optimized normalization for template-compiled render fns -function cloneIfMounted(child) { - return child.el === null ? child : cloneVNode(child); + instance.attrs = attrs; } -function normalizeChildren(vnode, children) { - let type = 0; - const { shapeFlag } = vnode; - if (children == null) { - children = null; - } - else if (isArray(children)) { - type = 16 /* ARRAY_CHILDREN */; - } - else if (typeof children === 'object') { - if (shapeFlag & 1 /* ELEMENT */ || shapeFlag & 64 /* TELEPORT */) { - // Normalize slot to plain children for plain element and Teleport - const slot = children.default; - if (slot) { - // _c marker is added by withCtx() indicating this is a compiled slot - slot._c && setCompiledSlotRendering(1); - normalizeChildren(vnode, slot()); - slot._c && setCompiledSlotRendering(-1); - } - return; - } - else { - type = 32 /* SLOTS_CHILDREN */; - const slotFlag = children._; - if (!slotFlag && !(InternalObjectKey in children)) { - children._ctx = currentRenderingInstance; - } - else if (slotFlag === 3 /* FORWARDED */ && currentRenderingInstance) { - // a child component receives forwarded slots from the parent. - // its slot type is determined by its parent's slot type. - if (currentRenderingInstance.vnode.patchFlag & 1024 /* DYNAMIC_SLOTS */) { - children._ = 2 /* DYNAMIC */; - vnode.patchFlag |= 1024 /* DYNAMIC_SLOTS */; +function updateProps(instance, rawProps, rawPrevProps, optimized) { + const { props, attrs, vnode: { patchFlag } } = instance; + const rawCurrentProps = toRaw(props); + const [options] = instance.propsOptions; + if ( + // always force full diff in dev + // - #1942 if hmr is enabled with sfc component + // - vite#872 non-sfc component used by sfc component + + (optimized || patchFlag > 0) && + !(patchFlag & 16 /* FULL_PROPS */)) { + if (patchFlag & 8 /* PROPS */) { + // Compiler-generated props & no keys change, just set the updated + // the props. + const propsToUpdate = instance.vnode.dynamicProps; + for (let i = 0; i < propsToUpdate.length; i++) { + const key = propsToUpdate[i]; + // PROPS flag guarantees rawProps to be non-null + const value = rawProps[key]; + if (options) { + // attr / props separation was done on init and will be consistent + // in this code path, so just check if attrs have it. + if (hasOwn(attrs, key)) { + attrs[key] = value; + } + else { + const camelizedKey = camelize(key); + props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value, instance); + } } else { - children._ = 1 /* STABLE */; + attrs[key] = value; } } } } - else if (isFunction(children)) { - children = { default: children, _ctx: currentRenderingInstance }; - type = 32 /* SLOTS_CHILDREN */; - } else { - children = String(children); - // force teleport children to array so it can be moved around - if (shapeFlag & 64 /* TELEPORT */) { - type = 16 /* ARRAY_CHILDREN */; - children = [createTextVNode(children)]; + // full props update. + setFullProps(instance, rawProps, props, attrs); + // in case of dynamic props, check if we need to delete keys from + // the props object + let kebabKey; + for (const key in rawCurrentProps) { + if (!rawProps || + // for camelCase + (!hasOwn(rawProps, key) && + // it's possible the original props was passed in as kebab-case + // and converted to camelCase (#955) + ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))) { + if (options) { + if (rawPrevProps && + // for camelCase + (rawPrevProps[key] !== undefined || + // for kebab-case + rawPrevProps[kebabKey] !== undefined)) { + props[key] = resolvePropValue(options, rawProps || EMPTY_OBJ, key, undefined, instance); + } + } + else { + delete props[key]; + } + } } - else { - type = 8 /* TEXT_CHILDREN */; + // in the case of functional component w/o props declaration, props and + // attrs point to the same object so it should already have been updated. + if (attrs !== rawCurrentProps) { + for (const key in attrs) { + if (!rawProps || !hasOwn(rawProps, key)) { + delete attrs[key]; + } + } } } - vnode.children = children; - vnode.shapeFlag |= type; + // trigger updates for $attrs in case it's used in component slots + trigger(instance, "set" /* SET */, '$attrs'); } -function mergeProps(...args) { - const ret = extend({}, args[0]); - for (let i = 1; i < args.length; i++) { - const toMerge = args[i]; - for (const key in toMerge) { - if (key === 'class') { - if (ret.class !== toMerge.class) { - ret.class = normalizeClass([ret.class, toMerge.class]); - } +function setFullProps(instance, rawProps, props, attrs) { + const [options, needCastKeys] = instance.propsOptions; + if (rawProps) { + for (const key in rawProps) { + const value = rawProps[key]; + // key, ref are reserved and never passed down + if (isReservedProp(key)) { + continue; } - else if (key === 'style') { - ret.style = normalizeStyle([ret.style, toMerge.style]); + // prop option names are camelized during normalization, so to support + // kebab -> camel conversion here we need to camelize the key. + let camelKey; + if (options && hasOwn(options, (camelKey = camelize(key)))) { + props[camelKey] = value; } - else if (isOn(key)) { - const existing = ret[key]; - const incoming = toMerge[key]; - if (existing !== incoming) { - ret[key] = existing - ? [].concat(existing, toMerge[key]) - : incoming; - } - } - else { - ret[key] = toMerge[key]; - } - } - } - return ret; -} - -function emit(instance, event, ...args) { - const props = instance.vnode.props || EMPTY_OBJ; - if ( __VUE_PROD_DEVTOOLS__) ; - let handlerName = `on${capitalize(event)}`; - let handler = props[handlerName]; - // for v-model update:xxx events, also trigger kebab-case equivalent - // for props passed via kebab-case - if (!handler && event.startsWith('update:')) { - handlerName = `on${capitalize(hyphenate(event))}`; - handler = props[handlerName]; - } - if (!handler) { - handler = props[handlerName + `Once`]; - if (!instance.emitted) { - (instance.emitted = {})[handlerName] = true; - } - else if (instance.emitted[handlerName]) { - return; - } - } - if (handler) { - callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args); - } -} -function normalizeEmitsOptions(comp, appContext, asMixin = false) { - const appId = appContext.app ? appContext.app._uid : -1; - const cache = comp.__emits || (comp.__emits = {}); - const cached = cache[appId]; - if (cached !== undefined) { - return cached; - } - const raw = comp.emits; - let normalized = {}; - // apply mixin/extends props - let hasExtends = false; - if (__VUE_OPTIONS_API__ && !isFunction(comp)) { - const extendEmits = (raw) => { - hasExtends = true; - extend(normalized, normalizeEmitsOptions(raw, appContext, true)); - }; - if (!asMixin && appContext.mixins.length) { - appContext.mixins.forEach(extendEmits); - } - if (comp.extends) { - extendEmits(comp.extends); - } - if (comp.mixins) { - comp.mixins.forEach(extendEmits); - } - } - if (!raw && !hasExtends) { - return (cache[appId] = null); - } - if (isArray(raw)) { - raw.forEach(key => (normalized[key] = null)); - } - else { - extend(normalized, raw); - } - return (cache[appId] = normalized); -} -// Check if an incoming prop key is a declared emit event listener. -// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are -// both considered matched listeners. -function isEmitListener(options, key) { - if (!options || !isOn(key)) { - return false; - } - key = key.replace(/Once$/, ''); - return (hasOwn(options, key[2].toLowerCase() + key.slice(3)) || - hasOwn(options, key.slice(2))); -} - -function initProps(instance, rawProps, isStateful, // result of bitwise flag comparison -isSSR = false) { - const props = {}; - const attrs = {}; - def(attrs, InternalObjectKey, 1); - setFullProps(instance, rawProps, props, attrs); - if (isStateful) { - // stateful - instance.props = isSSR ? props : shallowReactive(props); - } - else { - if (!instance.type.props) { - // functional w/ optional props, props === attrs - instance.props = attrs; - } - else { - // functional w/ declared props - instance.props = props; - } - } - instance.attrs = attrs; -} -function updateProps(instance, rawProps, rawPrevProps, optimized) { - const { props, attrs, vnode: { patchFlag } } = instance; - const rawCurrentProps = toRaw(props); - const [options] = instance.propsOptions; - if ( - // always force full diff if hmr is enabled - - (optimized || patchFlag > 0) && - !(patchFlag & 16 /* FULL_PROPS */)) { - if (patchFlag & 8 /* PROPS */) { - // Compiler-generated props & no keys change, just set the updated - // the props. - const propsToUpdate = instance.vnode.dynamicProps; - for (let i = 0; i < propsToUpdate.length; i++) { - const key = propsToUpdate[i]; - // PROPS flag guarantees rawProps to be non-null - const value = rawProps[key]; - if (options) { - // attr / props separation was done on init and will be consistent - // in this code path, so just check if attrs have it. - if (hasOwn(attrs, key)) { - attrs[key] = value; - } - else { - const camelizedKey = camelize(key); - props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value); - } - } - else { - attrs[key] = value; - } - } - } - } - else { - // full props update. - setFullProps(instance, rawProps, props, attrs); - // in case of dynamic props, check if we need to delete keys from - // the props object - let kebabKey; - for (const key in rawCurrentProps) { - if (!rawProps || - // for camelCase - (!hasOwn(rawProps, key) && - // it's possible the original props was passed in as kebab-case - // and converted to camelCase (#955) - ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))) { - if (options) { - if (rawPrevProps && - // for camelCase - (rawPrevProps[key] !== undefined || - // for kebab-case - rawPrevProps[kebabKey] !== undefined)) { - props[key] = resolvePropValue(options, rawProps || EMPTY_OBJ, key, undefined); - } - } - else { - delete props[key]; - } - } - } - // in the case of functional component w/o props declaration, props and - // attrs point to the same object so it should already have been updated. - if (attrs !== rawCurrentProps) { - for (const key in attrs) { - if (!rawProps || !hasOwn(rawProps, key)) { - delete attrs[key]; - } - } - } - } - // trigger updates for $attrs in case it's used in component slots - trigger(instance, "set" /* SET */, '$attrs'); -} -function setFullProps(instance, rawProps, props, attrs) { - const [options, needCastKeys] = instance.propsOptions; - if (rawProps) { - for (const key in rawProps) { - const value = rawProps[key]; - // key, ref are reserved and never passed down - if (isReservedProp(key)) { - continue; - } - // prop option names are camelized during normalization, so to support - // kebab -> camel conversion here we need to camelize the key. - let camelKey; - if (options && hasOwn(options, (camelKey = camelize(key)))) { - props[camelKey] = value; - } - else if (!isEmitListener(instance.emitsOptions, key)) { - // Any non-declared (either as a prop or an emitted event) props are put - // into a separate `attrs` object for spreading. Make sure to preserve - // original key casing - attrs[key] = value; + else if (!isEmitListener(instance.emitsOptions, key)) { + // Any non-declared (either as a prop or an emitted event) props are put + // into a separate `attrs` object for spreading. Make sure to preserve + // original key casing + attrs[key] = value; } } } @@ -2063,21 +1873,25 @@ function setFullProps(instance, rawProps, props, attrs) { const rawCurrentProps = toRaw(props); for (let i = 0; i < needCastKeys.length; i++) { const key = needCastKeys[i]; - props[key] = resolvePropValue(options, rawCurrentProps, key, rawCurrentProps[key]); + props[key] = resolvePropValue(options, rawCurrentProps, key, rawCurrentProps[key], instance); } } } -function resolvePropValue(options, props, key, value) { +function resolvePropValue(options, props, key, value, instance) { const opt = options[key]; if (opt != null) { const hasDefault = hasOwn(opt, 'default'); // default values if (hasDefault && value === undefined) { const defaultValue = opt.default; - value = - opt.type !== Function && isFunction(defaultValue) - ? defaultValue(props) - : defaultValue; + if (opt.type !== Function && isFunction(defaultValue)) { + setCurrentInstance(instance); + value = defaultValue(props); + setCurrentInstance(null); + } + else { + value = defaultValue; + } } // boolean casting if (opt[0 /* shouldCast */]) { @@ -2093,11 +1907,8 @@ function resolvePropValue(options, props, key, value) { return value; } function normalizePropsOptions(comp, appContext, asMixin = false) { - const appId = appContext.app ? appContext.app._uid : -1; - const cache = comp.__props || (comp.__props = {}); - const cached = cache[appId]; - if (cached) { - return cached; + if (!appContext.deopt && comp.__props) { + return comp.__props; } const raw = comp.props; const normalized = {}; @@ -2123,7 +1934,7 @@ function normalizePropsOptions(comp, appContext, asMixin = false) { } } if (!raw && !hasExtends) { - return (cache[appId] = EMPTY_ARR); + return (comp.__props = EMPTY_ARR); } if (isArray(raw)) { for (let i = 0; i < raw.length; i++) { @@ -2154,7 +1965,13 @@ function normalizePropsOptions(comp, appContext, asMixin = false) { } } } - return (cache[appId] = [normalized, needCastKeys]); + return (comp.__props = [normalized, needCastKeys]); +} +function validatePropName(key) { + if (key[0] !== '$') { + return true; + } + return false; } // use function string name to check type constructors // so that it works across vms / iframes. @@ -2178,15 +1995,6 @@ function getTypeIndex(type, expectedTypes) { } return -1; } -/** - * dev only - */ -function validatePropName(key) { - if (key[0] !== '$') { - return true; - } - return false; -} function injectHook(type, hook, target = currentInstance, prepend = false) { if (target) { @@ -2234,47 +2042,221 @@ const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */); const onErrorCaptured = (hook, target = currentInstance) => { injectHook("ec" /* ERROR_CAPTURED */, hook, target); }; - -const isKeepAlive = (vnode) => vnode.type.__isKeepAlive; -function onActivated(hook, target) { - registerKeepAliveHook(hook, "a" /* ACTIVATED */, target); -} -function onDeactivated(hook, target) { - registerKeepAliveHook(hook, "da" /* DEACTIVATED */, target); +// initial value for watchers to trigger on undefined initial values +const INITIAL_WATCHER_VALUE = {}; +// implementation +function watch(source, cb, options) { + return doWatch(source, cb, options); } -function registerKeepAliveHook(hook, type, target = currentInstance) { - // cache the deactivate branch check wrapper for injected hooks so the same - // hook can be properly deduped by the scheduler. "__wdc" stands for "with - // deactivation check". - const wrappedHook = hook.__wdc || - (hook.__wdc = () => { - // only fire the hook if the target instance is NOT in a deactivated branch. - let current = target; - while (current) { - if (current.isDeactivated) { - return; - } - current = current.parent; +function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { + let getter; + let forceTrigger = false; + if (isRef(source)) { + getter = () => source.value; + forceTrigger = !!source._shallow; + } + else if (isReactive(source)) { + getter = () => source; + deep = true; + } + else if (isArray(source)) { + getter = () => source.map(s => { + if (isRef(s)) { + return s.value; } - hook(); - }); - injectHook(type, wrappedHook, target); - // In addition to registering it on the target instance, we walk up the parent - // chain and register it on all ancestor instances that are keep-alive roots. - // This avoids the need to walk the entire component tree when invoking these - // hooks, and more importantly, avoids the need to track child components in - // arrays. - if (target) { - let current = target.parent; - while (current && current.parent) { - if (isKeepAlive(current.parent.vnode)) { - injectToKeepAliveRoot(wrappedHook, type, target, current); + else if (isReactive(s)) { + return traverse(s); } - current = current.parent; - } - } -} -function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { + else if (isFunction(s)) { + return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */); + } + else ; + }); + } + else if (isFunction(source)) { + if (cb) { + // getter with cb + getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */); + } + else { + // no cb -> simple effect + getter = () => { + if (instance && instance.isUnmounted) { + return; + } + if (cleanup) { + cleanup(); + } + return callWithErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onInvalidate]); + }; + } + } + else { + getter = NOOP; + } + if (cb && deep) { + const baseGetter = getter; + getter = () => traverse(baseGetter()); + } + let cleanup; + const onInvalidate = (fn) => { + cleanup = runner.options.onStop = () => { + callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */); + }; + }; + let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE; + const job = () => { + if (!runner.active) { + return; + } + if (cb) { + // watch(source, cb) + const newValue = runner(); + if (deep || forceTrigger || hasChanged(newValue, oldValue)) { + // cleanup before running cb again + if (cleanup) { + cleanup(); + } + callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [ + newValue, + // pass undefined as the old value when it's changed for the first time + oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, + onInvalidate + ]); + oldValue = newValue; + } + } + else { + // watchEffect + runner(); + } + }; + // important: mark the job as a watcher callback so that scheduler knows + // it is allowed to self-trigger (#1727) + job.allowRecurse = !!cb; + let scheduler; + if (flush === 'sync') { + scheduler = job; + } + else if (flush === 'post') { + scheduler = () => queuePostRenderEffect(job, instance && instance.suspense); + } + else { + // default: 'pre' + scheduler = () => { + if (!instance || instance.isMounted) { + queuePreFlushCb(job); + } + else { + // with 'pre' option, the first call must happen before + // the component is mounted so it is called synchronously. + job(); + } + }; + } + const runner = effect(getter, { + lazy: true, + onTrack, + onTrigger, + scheduler + }); + recordInstanceBoundEffect(runner, instance); + // initial run + if (cb) { + if (immediate) { + job(); + } + else { + oldValue = runner(); + } + } + else if (flush === 'post') { + queuePostRenderEffect(runner, instance && instance.suspense); + } + else { + runner(); + } + return () => { + stop(runner); + if (instance) { + remove(instance.effects, runner); + } + }; +} +// this.$watch +function instanceWatch(source, cb, options) { + const publicThis = this.proxy; + const getter = isString(source) + ? () => publicThis[source] + : source.bind(publicThis); + return doWatch(getter, cb.bind(publicThis), options, this); +} +function traverse(value, seen = new Set()) { + if (!isObject(value) || seen.has(value)) { + return value; + } + seen.add(value); + if (isRef(value)) { + traverse(value.value, seen); + } + else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + traverse(value[i], seen); + } + } + else if (isSet(value) || isMap(value)) { + value.forEach((v) => { + traverse(v, seen); + }); + } + else { + for (const key in value) { + traverse(value[key], seen); + } + } + return value; +} + +const isKeepAlive = (vnode) => vnode.type.__isKeepAlive; +function onActivated(hook, target) { + registerKeepAliveHook(hook, "a" /* ACTIVATED */, target); +} +function onDeactivated(hook, target) { + registerKeepAliveHook(hook, "da" /* DEACTIVATED */, target); +} +function registerKeepAliveHook(hook, type, target = currentInstance) { + // cache the deactivate branch check wrapper for injected hooks so the same + // hook can be properly deduped by the scheduler. "__wdc" stands for "with + // deactivation check". + const wrappedHook = hook.__wdc || + (hook.__wdc = () => { + // only fire the hook if the target instance is NOT in a deactivated branch. + let current = target; + while (current) { + if (current.isDeactivated) { + return; + } + current = current.parent; + } + hook(); + }); + injectHook(type, wrappedHook, target); + // In addition to registering it on the target instance, we walk up the parent + // chain and register it on all ancestor instances that are keep-alive roots. + // This avoids the need to walk the entire component tree when invoking these + // hooks, and more importantly, avoids the need to track child components in + // arrays. + if (target) { + let current = target.parent; + while (current && current.parent) { + if (isKeepAlive(current.parent.vnode)) { + injectToKeepAliveRoot(wrappedHook, type, target, current); + } + current = current.parent; + } + } +} +function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { // injectHook wraps the original for error handling, so make sure to remove // the wrapped version. const injected = injectHook(type, hook, keepAliveRoot, true /* prepend */); @@ -2473,6 +2455,11 @@ function createAppAPI(render, hydrate) { if (__VUE_OPTIONS_API__) { if (!context.mixins.includes(mixin)) { context.mixins.push(mixin); + // global mixin with props/emits de-optimizes props/emits + // normalization caching. + if (mixin.props || mixin.emits) { + context.deopt = true; + } } } return app; @@ -2543,26 +2530,34 @@ function initFeatureFlags() { } } +const isAsyncWrapper = (i) => !!i.type.__asyncLoader; + const prodEffectOptions = { - scheduler: queueJob + scheduler: queueJob, + // #1801, #2043 component render effects should allow recursive updates + allowRecurse: true }; const queuePostRenderEffect = queueEffectWithSuspense ; -const setRef = (rawRef, oldRawRef, parentComponent, parentSuspense, vnode) => { +const setRef = (rawRef, oldRawRef, parentSuspense, vnode) => { + if (isArray(rawRef)) { + rawRef.forEach((r, i) => setRef(r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode)); + return; + } let value; - if (!vnode) { + if (!vnode || isAsyncWrapper(vnode)) { value = null; } else { if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) { - value = vnode.component.proxy; + value = vnode.component.exposed || vnode.component.proxy; } else { value = vnode.el; } } - const [owner, ref] = rawRef; - const oldRef = oldRawRef && oldRawRef[1]; + const { i: owner, r: ref } = rawRef; + const oldRef = oldRawRef && oldRawRef.r; const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs; const setupState = owner.setupState; // unset old ref @@ -2608,10 +2603,7 @@ const setRef = (rawRef, oldRawRef, parentComponent, parentSuspense, vnode) => { } } else if (isFunction(ref)) { - callWithErrorHandling(ref, parentComponent, 12 /* FUNCTION_REF */, [ - value, - refs - ]); + callWithErrorHandling(ref, owner, 12 /* FUNCTION_REF */, [value, refs]); } else ; }; @@ -2686,7 +2678,7 @@ function baseCreateRenderer(options, createHydrationFns) { } // set ref if (ref != null && parentComponent) { - setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2); + setRef(ref, n1 && n1.ref, parentSuspense, n2); } }; const processText = (n1, n2, container, anchor) => { @@ -2712,6 +2704,24 @@ function baseCreateRenderer(options, createHydrationFns) { const mountStaticNode = (n2, container, anchor, isSVG) => { [n2.el, n2.anchor] = hostInsertStaticContent(n2.children, container, anchor, isSVG); }; + const moveStaticNode = ({ el, anchor }, container, nextSibling) => { + let next; + while (el && el !== anchor) { + next = hostNextSibling(el); + hostInsert(el, container, nextSibling); + el = next; + } + hostInsert(anchor, container, nextSibling); + }; + const removeStaticNode = ({ el, anchor }) => { + let next; + while (el && el !== anchor) { + next = hostNextSibling(el); + hostRemove(el); + el = next; + } + hostRemove(anchor); + }; const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => { isSVG = isSVG || n2.type === 'svg'; if (n1 == null) { @@ -2761,30 +2771,6 @@ function baseCreateRenderer(options, createHydrationFns) { } // scopeId setScopeId(el, scopeId, vnode, parentComponent); - // if (scopeId) { - // hostSetScopeId(el, scopeId) - // } - // if (parentComponent) { - // const treeOwnerId = parentComponent.type.__scopeId - // // vnode's own scopeId and the current patched component's scopeId is - // // different - this is a slot content node. - // if (treeOwnerId && treeOwnerId !== scopeId) { - // hostSetScopeId(el, treeOwnerId + '-s') - // } - // const parentScopeId = - // vnode === parentComponent.subTree && parentComponent.vnode.scopeId - // if (parentScopeId) { - // hostSetScopeId(el, parentScopeId) - // if (parentComponent.parent) { - // const treeOwnerId = parentComponent.parent.type.__scopeId - // // vnode's own scopeId and the current patched component's scopeId is - // // different - this is a slot content node. - // if (treeOwnerId && treeOwnerId !== parentScopeId) { - // hostSetScopeId(el, treeOwnerId + '-s') - // } - // } - // } - // } } if ( __VUE_PROD_DEVTOOLS__) { Object.defineProperty(el, '__vnode', { @@ -2801,7 +2787,7 @@ function baseCreateRenderer(options, createHydrationFns) { } // #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved // #1689 For inside suspense + suspense resolved case, just call it - const needCallTransitionHooks = (!parentSuspense || (parentSuspense && parentSuspense.isResolved)) && + const needCallTransitionHooks = (!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) && transition && !transition.persisted; if (needCallTransitionHooks) { @@ -2829,7 +2815,8 @@ function baseCreateRenderer(options, createHydrationFns) { if (treeOwnerId && treeOwnerId !== scopeId) { hostSetScopeId(el, treeOwnerId + '-s'); } - if (vnode === parentComponent.subTree) { + let subTree = parentComponent.subTree; + if (vnode === subTree) { setScopeId(el, parentComponent.vnode.scopeId, parentComponent.vnode, parentComponent.parent); } } @@ -2952,6 +2939,7 @@ function baseCreateRenderer(options, createHydrationFns) { const patchProps = (el, vnode, oldProps, newProps, parentComponent, parentSuspense, isSVG) => { if (oldProps !== newProps) { for (const key in newProps) { + // empty string is not valid prop if (isReservedProp(key)) continue; const next = newProps[key]; @@ -2992,6 +2980,15 @@ function baseCreateRenderer(options, createHydrationFns) { // a stable fragment (template root or