UNPKG

vue-next

Version:

## Status: Pre-Alpha.

1,359 lines (1,339 loc) 111 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var reactivity = require('@vue/reactivity'); require('@vue/compiler-dom'); // Patch flags are optimization hints generated by the compiler. // when a block with dynamicChildren is encountered during diff, the algorithm // enters "optimized mode". In this mode, we know that the vdom is produced by // a render function generated by the compiler, so the algorithm only needs to // handle updates explicitly marked by these patch flags. // runtime object for public consumption const PublicPatchFlags = { TEXT: 1 /* TEXT */, CLASS: 2 /* CLASS */, STYLE: 4 /* STYLE */, PROPS: 8 /* PROPS */, NEED_PATCH: 32 /* NEED_PATCH */, FULL_PROPS: 16 /* FULL_PROPS */, KEYED_FRAGMENT: 64 /* KEYED_FRAGMENT */, UNKEYED_FRAGMENT: 128 /* UNKEYED_FRAGMENT */, DYNAMIC_SLOTS: 256 /* DYNAMIC_SLOTS */, BAIL: -1 /* BAIL */ }; const EMPTY_OBJ = {}; const EMPTY_ARR = []; const NOOP = () => { }; /** * Always return false. */ const NO = () => false; const extend = (a, b) => { for (const key in b) { a[key] = b[key]; } return a; }; const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); const isArray = Array.isArray; const isFunction = (val) => typeof val === 'function'; const isString = (val) => typeof val === 'string'; const isObject = (val) => val !== null && typeof val === 'object'; function isPromise(val) { return isObject(val) && isFunction(val.then) && isFunction(val.catch); } const objectToString = Object.prototype.toString; const toTypeString = (value) => objectToString.call(value); const isPlainObject = (val) => toTypeString(val) === '[object Object]'; const isReservedProp = (key) => key === 'key' || key === 'ref' || key.startsWith(`onVnode`); const camelizeRE = /-(\w)/g; const camelize = (str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); }; const hyphenateRE = /\B([A-Z])/g; const hyphenate = (str) => { return str.replace(hyphenateRE, '-$1').toLowerCase(); }; const capitalize = (str) => { return str.charAt(0).toUpperCase() + str.slice(1); }; // compare whether a value has changed, accounting for NaN. const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); // implementation, close to no-op function createComponent(options) { return isFunction(options) ? { setup: options } : options; } const stack = []; function warn(msg, ...args) { const instance = stack.length ? stack[stack.length - 1].component : null; const appWarnHandler = instance && instance.appContext.config.warnHandler; const trace = getComponentTrace(); if (appWarnHandler) { appWarnHandler(msg + args.join(''), instance && instance.renderProxy, formatTrace(trace).join('')); return; } console.warn(`[Vue warn]: ${msg}`, ...args); // avoid spamming console during tests if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { return; } if (!trace.length) { return; } if (trace.length > 1 && console.groupCollapsed) { console.groupCollapsed('at', ...formatTraceEntry(trace[0])); const logs = []; trace.slice(1).forEach((entry, i) => { if (i !== 0) logs.push('\n'); logs.push(...formatTraceEntry(entry, i + 1)); }); console.log(...logs); console.groupEnd(); } else { console.log(...formatTrace(trace)); } } function getComponentTrace() { let currentVNode = 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 normalizedStack = []; while (currentVNode) { const last = normalizedStack[0]; if (last && last.vnode === currentVNode) { last.recurseCount++; } else { normalizedStack.push({ vnode: currentVNode, recurseCount: 0 }); } const parentInstance = currentVNode.component .parent; currentVNode = parentInstance && parentInstance.vnode; } return normalizedStack; } function formatTrace(trace) { const logs = []; trace.forEach((entry, i) => { const formatted = formatTraceEntry(entry, i); if (i === 0) { logs.push('at', ...formatted); } else { logs.push('\n', ...formatted); } }); return logs; } function formatTraceEntry({ vnode, recurseCount }, depth = 0) { 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.parent == null ? `(Root)` : ``; return vnode.props ? [open, ...formatProps(vnode.props), close, rootLabel] : [open + close, rootLabel]; } const classifyRE = /(?:^|[-_])(\w)/g; const classify = (str) => str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, ''); function formatComponentName(vnode, file) { const Component = vnode.type; let name = isFunction(Component) ? 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) { const res = []; for (const key in props) { const value = props[key]; if (isString(value)) { res.push(`${key}=${JSON.stringify(value)}`); } else { res.push(`${key}=`, String(reactivity.toRaw(value))); } } return res; } function callWithErrorHandling(fn, instance, type, args) { let res; try { res = args ? fn(...args) : fn(); } catch (err) { handleError(err, instance, type); } return res; } function callWithAsyncErrorHandling(fn, instance, type, args) { if (isFunction(fn)) { const res = callWithErrorHandling(fn, instance, type, args); if (res != null && !res._isVue && isPromise(res)) { res.catch((err) => { handleError(err, instance, type); }); } return res; } for (let i = 0; i < fn.length; i++) { callWithAsyncErrorHandling(fn[i], instance, type, args); } } function handleError(err, instance, type) { const contextVNode = instance ? instance.vnode : null; if (instance) { let cur = instance.parent; // the exposed instance is the render proxy to keep it consistent with 2.x const exposedInstance = instance.renderProxy; // in production the hook receives only the error code const errorInfo = type; while (cur) { const errorCapturedHooks = cur.ec; if (errorCapturedHooks !== null) { for (let i = 0; i < errorCapturedHooks.length; i++) { if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) { return; } } } cur = cur.parent; } // app-level handling const appErrorHandler = instance.appContext.config.errorHandler; if (appErrorHandler) { callWithErrorHandling(appErrorHandler, null, 8 /* APP_ERROR_HANDLER */, [err, exposedInstance, errorInfo]); return; } } logError(err); } function logError(err, type, contextVNode) { // default behavior is crash in prod & test, recover in dev. { throw err; } } const queue = []; const postFlushCbs = []; const p = Promise.resolve(); let isFlushing = false; function nextTick(fn) { return fn ? p.then(fn) : p; } function queueJob(job) { if (!queue.includes(job)) { queue.push(job); if (!isFlushing) { nextTick(flushJobs); } } } function queuePostFlushCb(cb) { if (!isArray(cb)) { postFlushCbs.push(cb); } else { postFlushCbs.push(...cb); } if (!isFlushing) { nextTick(flushJobs); } } const dedupe = (cbs) => [...new Set(cbs)]; function flushPostFlushCbs() { if (postFlushCbs.length) { const cbs = dedupe(postFlushCbs); postFlushCbs.length = 0; for (let i = 0; i < cbs.length; i++) { cbs[i](); } } } function flushJobs(seenJobs) { isFlushing = true; let job; while ((job = queue.shift())) { callWithErrorHandling(job, null, 10 /* SCHEDULER */); } flushPostFlushCbs(); isFlushing = false; // some postFlushCb queued jobs! // keep flushing until it drains. if (queue.length) { flushJobs(); } } const Fragment = Symbol( undefined); const Portal = Symbol( undefined); const Suspense = Symbol( undefined); const Text = Symbol( undefined); const Comment = 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: // // function render() { // return (openBlock(),createBlock('div', null, [...])) // } // // disableTracking is true when creating a fragment block, since a fragment // always diffs its children. function openBlock(disableTracking) { blockStack.push((currentBlock = disableTracking ? null : [])); } // Whether we should be tracking dynamic child nodes inside a block. // Only tracks when this value is > 0 // We are not using a simple boolean because this value may need to be // incremented/decremented by nested usage of v-once (see below) let shouldTrack = 1; // Block tracking sometimes needs to be disabled, for example during the // creation of a tree that needs to be cached by v-once. The compiler generates // code like this: // _cache[1] || ( // setBlockTracking(-1), // _cache[1] = createVNode(...), // setBlockTracking(1), // _cache[1] // ) function setBlockTracking(value) { shouldTrack += value; } // 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. function createBlock(type, props, children, patchFlag, dynamicProps) { // avoid a block with patchFlag tracking itself shouldTrack--; const vnode = createVNode(type, props, children, patchFlag, dynamicProps); shouldTrack++; // save current block children on the block vnode vnode.dynamicChildren = currentBlock || EMPTY_ARR; // close block blockStack.pop(); currentBlock = blockStack[blockStack.length - 1] || null; // a block is always going to be patched, so track it as a child of its // parent block if (currentBlock !== null) { currentBlock.push(vnode); } return vnode; } function isVNode(value) { return value ? value._isVNode === true : false; } function createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null) { // class & style normalization. if (props !== null) { // for reactive or proxy objects, we need to clone it to enable mutation. if (reactivity.isReactive(props) || SetupProxySymbol in props) { props = extend({}, props); } let { class: klass, style } = props; if (klass != null && !isString(klass)) { props.class = normalizeClass(klass); } if (style != null) { // reactive state objects need to be cloned since they are likely to be // mutated if (reactivity.isReactive(style) && !isArray(style)) { style = extend({}, style); } props.style = normalizeStyle(style); } } // encode the vnode type information into a bitmap const shapeFlag = isString(type) ? 1 /* ELEMENT */ : isObject(type) ? 4 /* STATEFUL_COMPONENT */ : isFunction(type) ? 2 /* FUNCTIONAL_COMPONENT */ : 0; const vnode = { _isVNode: true, type, props, key: (props !== null && props.key) || null, ref: (props !== null && props.ref) || null, children: null, component: null, suspense: null, el: null, anchor: null, target: null, shapeFlag, patchFlag, dynamicProps, dynamicChildren: null, appContext: null }; normalizeChildren(vnode, children); // 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. if (shouldTrack > 0 && currentBlock !== null && (patchFlag > 0 || shapeFlag & 4 /* STATEFUL_COMPONENT */ || shapeFlag & 2 /* FUNCTIONAL_COMPONENT */)) { currentBlock.push(vnode); } return vnode; } function cloneVNode(vnode, extraProps) { return { _isVNode: true, type: vnode.type, props: extraProps ? vnode.props ? mergeProps(vnode.props, extraProps) : extraProps : vnode.props, key: vnode.key, ref: vnode.ref, children: vnode.children, target: vnode.target, shapeFlag: vnode.shapeFlag, patchFlag: vnode.patchFlag, dynamicProps: vnode.dynamicProps, dynamicChildren: vnode.dynamicChildren, appContext: vnode.appContext, // these should be set to null since they should only be present on // mounted VNodes. If they are somehow not null, this means we have // encountered an already-mounted vnode being used again. component: null, suspense: null, el: null, anchor: null }; } function createTextVNode(text = ' ', flag = 0) { return createVNode(Text, null, text, flag); } function createCommentVNode(text = '', // when used as the v-else branch, the comment node must be created as a // block to ensure correct updates. asBlock = false) { return asBlock ? createBlock(Comment, null, text) : createVNode(Comment, null, text); } function normalizeVNode(child) { if (child == null) { // 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 { // primitive types return createVNode(Text, null, child + ''); } } function normalizeChildren(vnode, children) { let type = 0; if (children == null) { children = null; } else if (isArray(children)) { type = 16 /* ARRAY_CHILDREN */; } else if (typeof children === 'object') { type = 32 /* SLOTS_CHILDREN */; } else if (isFunction(children)) { children = { default: children }; type = 32 /* SLOTS_CHILDREN */; } else { children = isString(children) ? children : children + ''; type = 8 /* TEXT_CHILDREN */; } vnode.children = children; vnode.shapeFlag |= type; } function normalizeStyle(value) { if (isArray(value)) { const res = {}; for (let i = 0; i < value.length; i++) { const normalized = normalizeStyle(value[i]); if (normalized) { for (const key in normalized) { res[key] = normalized[key]; } } } return res; } else if (isObject(value)) { return value; } } function normalizeClass(value) { let res = ''; if (isString(value)) { res = value; } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { res += normalizeClass(value[i]) + ' '; } } else if (isObject(value)) { for (const name in value) { if (value[name]) { res += name + ' '; } } } return res.trim(); } const handlersRE = /^on|^vnode/; function mergeProps(...args) { const ret = {}; extend(ret, args[0]); for (let i = 1; i < args.length; i++) { const toMerge = args[i]; for (const key in toMerge) { if (key === 'class') { ret.class = normalizeClass([ret.class, toMerge.class]); } else if (key === 'style') { ret.style = normalizeStyle([ret.style, toMerge.style]); } else if (handlersRE.test(key)) { // on*, vnode* const existing = ret[key]; ret[key] = existing ? [].concat(existing, toMerge[key]) : toMerge[key]; } else { ret[key] = toMerge[key]; } } } return ret; } function injectHook(type, hook, target) { if (target) { (target[type] || (target[type] = [])).push((...args) => { if (target.isUnmounted) { return; } // disable tracking inside all lifecycle hooks // since they can potentially be called inside effects. reactivity.pauseTracking(); // Set currentInstance during hook invocation. // This assumes the hook does not synchronously trigger other hooks, which // can only be false when the user does something really funky. setCurrentInstance(target); const res = callWithAsyncErrorHandling(hook, target, type, args); setCurrentInstance(null); reactivity.resumeTracking(); return res; }); } } const createHook = (lifecycle) => (hook, target = currentInstance) => injectHook(lifecycle, hook, target); const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */); const onMounted = createHook("m" /* MOUNTED */); const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */); const onUpdated = createHook("u" /* UPDATED */); const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */); const onUnmounted = createHook("um" /* UNMOUNTED */); const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */); const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */); const onErrorCaptured = createHook("ec" /* ERROR_CAPTURED */); // mark the current rendering instance for asset resolution (e.g. // resolveComponent, resolveDirective) during render let currentRenderingInstance = null; // 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 renderComponentRoot(instance) { const { type: Component, vnode, renderProxy, props, slots, attrs, emit } = instance; let result; currentRenderingInstance = instance; try { if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) { result = normalizeVNode(instance.render.call(renderProxy)); } else { // functional const render = Component; result = normalizeVNode(render.length > 1 ? render(props, { attrs, slots, emit }) : render(props, null /* we know it doesn't need it */)); } // attr merging if (Component.props != null && Component.inheritAttrs !== false && attrs !== EMPTY_OBJ && Object.keys(attrs).length) { if (result.shapeFlag & 1 /* ELEMENT */ || result.shapeFlag & 6 /* COMPONENT */) { result = cloneVNode(result, attrs); } else if (false && !accessedAttrs) { warn(`Extraneous non-props attributes (${Object.keys(attrs).join(',')}) ` + `were passed to component but could not be automatically inhertied ` + `because component renders fragment or text root nodes.`); } } } catch (err) { handleError(err, instance, 1 /* RENDER_FUNCTION */); result = createVNode(Comment); } currentRenderingInstance = null; return result; } function shouldUpdateComponent(prevVNode, nextVNode, optimized) { const { props: prevProps, children: prevChildren } = prevVNode; const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; if (patchFlag > 0) { if (patchFlag & 256 /* DYNAMIC_SLOTS */) { // slot content that references values that might have changed, // e.g. in a v-for return true; } if (patchFlag & 16 /* FULL_PROPS */) { // presence of this flag indicates props are always non-null return hasPropsChanged(prevProps, nextProps); } 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]) { return true; } } } } else if (!optimized) { // this path is only taken by manually written render functions // so presence of any children leads to a forced update if (prevChildren != null || nextChildren != null) { return true; } if (prevProps === nextProps) { return false; } if (prevProps === null) { return nextProps !== null; } if (nextProps === null) { return true; } return hasPropsChanged(prevProps, nextProps); } return false; } function hasPropsChanged(prevProps, nextProps) { 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]) { return true; } } return false; } // resolve raw VNode data. // - filter out reserved keys (key, ref, slots) // - extract class and style into $attrs (to be merged onto child // component root) // - for the rest: // - if has declared props: put declared ones in `props`, the rest in `attrs` // - else: everything goes in `props`. function resolveProps(instance, rawProps, _options) { const hasDeclaredProps = _options != null; const options = normalizePropsOptions(_options); if (!rawProps && !hasDeclaredProps) { return; } const props = {}; let attrs = void 0; // update the instance propsProxy (passed to setup()) to trigger potential // changes const propsProxy = instance.propsProxy; const setProp = propsProxy ? (key, val) => { props[key] = val; propsProxy[key] = val; } : (key, val) => { props[key] = val; }; // allow mutation of propsProxy (which is readonly by default) reactivity.unlock(); if (rawProps != null) { for (const key in rawProps) { // key, ref are reserved if (isReservedProp(key)) continue; // prop option names are camelized during normalization, so to support // kebab -> camel conversion here we need to camelize the key. const camelKey = camelize(key); if (hasDeclaredProps && !hasOwn(options, camelKey)) { (attrs || (attrs = {}))[key] = rawProps[key]; } else { setProp(camelKey, rawProps[key]); } } } // set default values, cast booleans & run validators if (hasDeclaredProps) { for (const key in options) { let opt = options[key]; if (opt == null) continue; const isAbsent = !hasOwn(props, key); const hasDefault = hasOwn(opt, 'default'); const currentValue = props[key]; // default values if (hasDefault && currentValue === undefined) { const defaultValue = opt.default; setProp(key, isFunction(defaultValue) ? defaultValue() : defaultValue); } // boolean casting if (opt["1" /* shouldCast */]) { if (isAbsent && !hasDefault) { setProp(key, false); } else if (opt["2" /* shouldCastTrue */] && (currentValue === '' || currentValue === hyphenate(key))) { setProp(key, true); } } } } else { // if component has no declared props, $attrs === $props attrs = props; } // in case of dynamic props, check if we need to delete keys from // the props proxy const { patchFlag } = instance.vnode; if (propsProxy !== null && (patchFlag === 0 || patchFlag & 16 /* FULL_PROPS */)) { const rawInitialProps = reactivity.toRaw(propsProxy); for (const key in rawInitialProps) { if (!hasOwn(props, key)) { delete propsProxy[key]; } } } // lock readonly reactivity.lock(); instance.props = props; instance.attrs = options ? attrs || EMPTY_OBJ : instance.props; } const normalizationMap = new WeakMap(); function normalizePropsOptions(raw) { if (!raw) { return null; } if (normalizationMap.has(raw)) { return normalizationMap.get(raw); } const normalized = {}; normalizationMap.set(raw, normalized); if (isArray(raw)) { for (let i = 0; i < raw.length; i++) { const normalizedKey = camelize(raw[i]); if (normalizedKey[0] !== '$') { normalized[normalizedKey] = EMPTY_OBJ; } } } else { for (const key in raw) { const normalizedKey = camelize(key); if (normalizedKey[0] !== '$') { const opt = raw[key]; const prop = (normalized[normalizedKey] = isArray(opt) || isFunction(opt) ? { type: opt } : opt); if (prop != null) { const booleanIndex = getTypeIndex(Boolean, prop.type); const stringIndex = getTypeIndex(String, prop.type); prop["1" /* shouldCast */] = booleanIndex > -1; prop["2" /* shouldCastTrue */] = booleanIndex < stringIndex; } } } } return normalized; } // use function string name to check type constructors // so that it works across vms / iframes. function getType(ctor) { const match = ctor && ctor.toString().match(/^\s*function (\w+)/); return match ? match[1] : ''; } function isSameType(a, b) { return getType(a) === getType(b); } function getTypeIndex(type, expectedTypes) { if (isArray(expectedTypes)) { for (let i = 0, len = expectedTypes.length; i < len; i++) { if (isSameType(expectedTypes[i], type)) { return i; } } } else if (isObject(expectedTypes)) { return isSameType(expectedTypes, type) ? 0 : -1; } return -1; } const normalizeSlotValue = (value) => isArray(value) ? value.map(normalizeVNode) : [normalizeVNode(value)]; const normalizeSlot = (key, rawSlot) => (props) => { return normalizeSlotValue(rawSlot(props)); }; function resolveSlots(instance, children) { let slots; if (instance.vnode.shapeFlag & 32 /* SLOTS_CHILDREN */) { const rawSlots = children; if (rawSlots._compiled) { // pre-normalized slots object generated by compiler slots = children; } else { slots = {}; for (const key in rawSlots) { const value = rawSlots[key]; if (isFunction(value)) { slots[key] = normalizeSlot(key, value); } else if (value != null) { const normalized = normalizeSlotValue(value); slots[key] = () => normalized; } } } } else if (children !== null) { const normalized = normalizeSlotValue(children); slots = { default: () => normalized }; } if (slots !== void 0) { instance.slots = slots; } } /** Runtime helper for applying directives to a vnode. Example usage: const comp = resolveComponent('comp') const foo = resolveDirective('foo') const bar = resolveDirective('bar') return withDirectives(h(comp), [ [foo, this.x], [bar, this.y] ]) */ const valueCache = new WeakMap(); function applyDirective(props, instance, directive, value, arg, modifiers = EMPTY_OBJ) { let valueCacheForDir = valueCache.get(directive); if (!valueCacheForDir) { valueCacheForDir = new WeakMap(); valueCache.set(directive, valueCacheForDir); } if (isFunction(directive)) { directive = { mounted: directive, updated: directive }; } for (const key in directive) { const hook = directive[key]; const hookKey = `onVnode` + key[0].toUpperCase() + key.slice(1); const vnodeHook = (vnode, prevVNode) => { let oldValue; if (prevVNode != null) { oldValue = valueCacheForDir.get(prevVNode); valueCacheForDir.delete(prevVNode); } valueCacheForDir.set(vnode, value); hook(vnode.el, { instance: instance.renderProxy, value, oldValue, arg, modifiers }, vnode, prevVNode); }; const existing = props[hookKey]; props[hookKey] = existing ? [].concat(existing, vnodeHook) : vnodeHook; } } function withDirectives(vnode, directives) { const instance = currentRenderingInstance; if (instance !== null) { vnode.props = vnode.props || {}; for (let i = 0; i < directives.length; i++) { const [dir, value, arg, modifiers] = directives[i]; applyDirective(vnode.props, instance, dir, value, arg, modifiers); } } return vnode; } function invokeDirectiveHook(hook, instance, vnode, prevVNode = null) { callWithAsyncErrorHandling(hook, instance, 7 /* DIRECTIVE_HOOK */, [ vnode, prevVNode ]); } function createAppContext() { return { config: { devtools: true, performance: false, isNativeTag: NO, isCustomElement: NO, errorHandler: undefined, warnHandler: undefined }, mixins: [], components: {}, directives: {}, provides: {} }; } function createAppAPI(render) { return function createApp() { const context = createAppContext(); let isMounted = false; const app = { get config() { return context.config; }, set config(v) { }, use(plugin) { if (isFunction(plugin)) { plugin(app); } else if (isFunction(plugin.install)) { plugin.install(app); } return app; }, mixin(mixin) { context.mixins.push(mixin); return app; }, component(name, component) { if (!component) { return context.components[name]; } else { context.components[name] = component; return app; } }, directive(name, directive) { if (!directive) { return context.directives[name]; } else { context.directives[name] = directive; return app; } }, mount(rootComponent, rootContainer, rootProps) { if (!isMounted) { const vnode = createVNode(rootComponent, rootProps); // store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context; render(vnode, rootContainer); isMounted = true; return vnode.component.renderProxy; } }, provide(key, value) { // TypeScript doesn't allow symbols as index type // https://github.com/Microsoft/TypeScript/issues/24587 context.provides[key] = value; } }; return app; }; } function createSuspenseBoundary(vnode, parent, parentComponent, container, hiddenContainer, anchor, isSVG, optimized) { return { vnode, parent, parentComponent, isSVG, optimized, container, hiddenContainer, anchor, deps: 0, subTree: null, fallbackTree: null, isResolved: false, isUnmounted: false, effects: [] }; } function normalizeSuspenseChildren(vnode) { const { shapeFlag, children } = vnode; if (shapeFlag & PublicShapeFlags.SLOTS_CHILDREN) { const { default: d, fallback } = children; return { content: normalizeVNode(isFunction(d) ? d() : d), fallback: normalizeVNode(isFunction(fallback) ? fallback() : fallback) }; } else { return { content: normalizeVNode(children), fallback: normalizeVNode(null) }; } } const prodEffectOptions = { scheduler: queueJob }; function isSameType$1(n1, n2) { return n1.type === n2.type && n1.key === n2.key; } function invokeHooks(hooks, arg) { for (let i = 0; i < hooks.length; i++) { hooks[i](arg); } } function queuePostRenderEffect(fn, suspense) { if (suspense !== null && !suspense.isResolved) { if (isArray(fn)) { suspense.effects.push(...fn); } else { suspense.effects.push(fn); } } else { queuePostFlushCb(fn); } } /** * The createRenderer function accepts two generic arguments: * HostNode and HostElement, corresponding to Node and Element types in the * host environment. For example, for runtime-dom, HostNode would be the DOM * `Node` interface and HostElement would be the DOM `Element` interface. * * Custom renderers can pass in the platform specific types like this: * * ``` js * const { render, createApp } = createRenderer<Node, Element>({ * patchProp, * ...nodeOps * }) * ``` */ function createRenderer(options) { const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, querySelector: hostQuerySelector } = options; function patch(n1, // null means this is a mount n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) { // patching & not same type, unmount old tree if (n1 != null && !isSameType$1(n1, n2)) { anchor = getNextHostNode(n1); unmount(n1, parentComponent, parentSuspense, true); n1 = null; } const { type, shapeFlag } = n2; switch (type) { case Text: processText(n1, n2, container, anchor); break; case Comment: processCommentNode(n1, n2, container, anchor); break; case Fragment: processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); break; case Portal: processPortal(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); break; case Suspense: { processSuspense(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); } break; default: if (shapeFlag & 1 /* ELEMENT */) { processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); } else if (shapeFlag & 6 /* COMPONENT */) { processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); } } } function processText(n1, n2, container, anchor) { if (n1 == null) { hostInsert((n2.el = hostCreateText(n2.children)), container, anchor); } else { const el = (n2.el = n1.el); if (n2.children !== n1.children) { hostSetText(el, n2.children); } } } function processCommentNode(n1, n2, container, anchor) { if (n1 == null) { hostInsert((n2.el = hostCreateComment(n2.children || '')), container, anchor); } else { // there's no support for dynamic comments n2.el = n1.el; } } function processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { if (n1 == null) { mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); } else { patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized); } if (n2.ref !== null && parentComponent !== null) { setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el); } } function mountElement(vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { const tag = vnode.type; isSVG = isSVG || tag === 'svg'; const el = (vnode.el = hostCreateElement(tag, isSVG)); const { props, shapeFlag } = vnode; if (props != null) { for (const key in props) { if (isReservedProp(key)) continue; hostPatchProp(el, key, props[key], null, isSVG); } if (props.onVnodeBeforeMount != null) { invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode); } } if (shapeFlag & 8 /* TEXT_CHILDREN */) { hostSetElementText(el, vnode.children); } else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG, optimized || vnode.dynamicChildren !== null); } hostInsert(el, container, anchor); if (props != null && props.onVnodeMounted != null) { queuePostRenderEffect(() => { invokeDirectiveHook(props.onVnodeMounted, parentComponent, vnode); }, parentSuspense); } } function mountChildren(children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0) { for (let i = start; i < children.length; i++) { const child = optimized ? children[i] : (children[i] = normalizeVNode(children[i])); patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized); } } function patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized) { const el = (n2.el = n1.el); const { patchFlag, dynamicChildren } = n2; const oldProps = (n1 && n1.props) || EMPTY_OBJ; const newProps = n2.props || EMPTY_OBJ; if (newProps.onVnodeBeforeUpdate != null) { invokeDirectiveHook(newProps.onVnodeBeforeUpdate, parentComponent, n2, n1); } if (patchFlag > 0) { // the presence of a patchFlag means this element's render code was // generated by the compiler and can take the fast path. // in this path old node and new node are guaranteed to have the same shape // (i.e. at the exact same position in the source template) if (patchFlag & 16 /* FULL_PROPS */) { // element props contain dynamic keys, full diff needed patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG); } else { // class // this flag is matched when the element has dynamic class bindings. if (patchFlag & 2 /* CLASS */) { if (oldProps.class !== newProps.class) { hostPatchProp(el, 'class', newProps.class, null, isSVG); } } // style // this flag is matched when the element has dynamic style bindings if (patchFlag & 4 /* STYLE */) { hostPatchProp(el, 'style', newProps.style, oldProps.style, isSVG); } // props // This flag is matched when the element has dynamic prop/attr bindings // other than class and style. The keys of dynamic prop/attrs are saved for // faster iteration. // Note dynamic keys like :[foo]="bar" will cause this optimization to // bail out and go through a full diff because we need to unset the old key if (patchFlag & 8 /* PROPS */) { // if the flag is present then dynamicProps must be non-null const propsToUpdate = n2.dynamicProps; for (let i = 0; i < propsToUpdate.length; i++) { const key = propsToUpdate[i]; const prev = oldProps[key]; const next = newProps[key]; if (prev !== next) { hostPatchProp(el, key, next, prev, isSVG, n1.children, parentComponent, parentSuspense, unmountChildren); } } } } // text // This flag is matched when the element has only dynamic text children. // this flag is terminal (i.e. skips children diffing). if (patchFlag & 1 /* TEXT */) { if (n1.children !== n2.children) { hostSetElementText(el, n2.children); } return; // terminal } } else if (!optimized && dynamicChildren == null) { // unoptimized, full diff patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG); } if (dynamicChildren != null) { patchBlockChildren(n1.dynamicChildren, dynamicChildren, el, parentComponent, parentSuspense, isSVG); } else if (!optimized) { // full diff patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG); } if (newProps.onVnodeUpdated != null) { queuePostRenderEffect(() => { invokeDirectiveHook(newProps.onVnodeUpdated, parentComponent, n2, n1); }, parentSuspense); } } // The fast path for blocks. function patchBlockChildren(oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) { for (let i = 0; i < newChildren.length; i++) { const oldVNode = oldChildren[i]; patch(oldVNode, newChildren[i], // in the case of a Fragment, we need to provide the actual parent // of the Fragment itself so it can move its children. In other cases, // the parent container is not actually used so we just pass the // block element here to avoid a DOM parentNode call. oldVNode.type === Fragment ? hostParentNode(oldVNode.el) : fallbackContainer, null, parentComponent, parentSuspense, isSVG, true); } } function patchProps(el, vnode, oldProps, newProps, parentComponent, parentSuspense, isSVG) { if (oldProps !== newProps) { for (const key in newProps) { if (isReservedProp(key)) continue; const next = newProps[key]; const prev = oldProps[key]; if (next !== prev) { hostPatchProp(el, key, next, prev, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren); } } if (oldProps !== EMPTY_OBJ) { for (const key in oldProps) { if (isReservedProp(key)) continue; if (!(key in newProps)) { hostPatchProp(el, key, null, null, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren); } } } } } function processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateComment('')); const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateComment('')); if (n1 == null) { hostInsert(fragmentStartAnchor, container, anchor); hostInsert(fragmentEndAnchor, container, anchor); // a fragment can only have array children // since they are either generated by the compiler, or implicitly created // from arrays. mountChildren(n2.children, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized); } else { patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized); } } function processPortal(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) { const targetSelector = n2.props && n2.props.target; const { patchFlag, shapeFlag, children } = n2; if (n1 == null) { const target = (n2.target = isString(targetSelector) ? hostQuerySelector(targetSelector) : targetSelector); if (target != null) { if (shapeFlag & 8 /* TEXT_CHILDREN */) { hostSetElementText(target, children); } else if (shapeFlag & 16 /* ARRAY_CHILDREN */) { mountChildren(children, target, null, parentComponent, parentSuspense, isSVG, optimized); } } } else { // update content const target = (n2.target = n1.target); if (patchFlag === 1 /* TEXT */) { hostSetElementText(target, children); }