vue-svg-loader
Version:
Use SVG files as Vue Components
1,241 lines (1,230 loc) • 250 kB
JavaScript
import { pauseTracking, resetTracking, isRef, toRaw, isProxy, shallowReactive, trigger, effect, stop, isReactive, reactive, shallowReadonly, track, proxyRefs, computed as computed$1, ref } from '@vue/reactivity';
export { customRef, isProxy, isReactive, isReadonly, isRef, markRaw, proxyRefs, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, triggerRef, unref } from '@vue/reactivity';
import { isString, isFunction, isPromise, isArray, extend, isModelListener, isOn, camelize, capitalize, EMPTY_ARR, normalizeClass, isObject, normalizeStyle, EMPTY_OBJ, hyphenate, hasOwn, def, isReservedProp, toRawType, makeMap, remove, invokeArrayFns, NO, getGlobalThis, NOOP, hasChanged, isGloballyWhitelisted } from '@vue/shared';
export { camelize, capitalize, toDisplayString } from '@vue/shared';
const stack = [];
function pushWarningContext(vnode) {
stack.push(vnode);
}
function popWarningContext() {
stack.pop();
}
function warn(msg, ...args) {
// avoid props formatting or warn handler tracking deps that might be mutated
// during patch, leading to infinite recursion.
pauseTracking();
const instance = stack.length ? stack[stack.length - 1].component : null;
const appWarnHandler = instance && instance.appContext.config.warnHandler;
const trace = getComponentTrace();
if (appWarnHandler) {
callWithErrorHandling(appWarnHandler, instance, 11 /* APP_WARN_HANDLER */, [
msg + args.join(''),
instance && instance.proxy,
trace
.map(({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`)
.join('\n'),
trace
]);
}
else {
const warnArgs = [`[Vue warn]: ${msg}`, ...args];
/* istanbul ignore if */
if (trace.length &&
// avoid spamming console during tests
!false) {
warnArgs.push(`\n`, ...formatTrace(trace));
}
console.warn(...warnArgs);
}
resetTracking();
}
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 && currentVNode.component.parent;
currentVNode = parentInstance && parentInstance.vnode;
}
return normalizedStack;
}
/* istanbul ignore next */
function formatTrace(trace) {
const logs = [];
trace.forEach((entry, i) => {
logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry));
});
return logs;
}
function formatTraceEntry({ vnode, recurseCount }) {
const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``;
const isRoot = vnode.component ? vnode.component.parent == null : false;
const open = ` at <${formatComponentName(vnode.component, vnode.type, isRoot)}`;
const close = `>` + postfix;
return vnode.props
? [open, ...formatProps(vnode.props), close]
: [open + close];
}
/* istanbul ignore next */
function formatProps(props) {
const res = [];
const keys = Object.keys(props);
keys.slice(0, 3).forEach(key => {
res.push(...formatProp(key, props[key]));
});
if (keys.length > 3) {
res.push(` ...`);
}
return res;
}
/* istanbul ignore next */
function formatProp(key, value, raw) {
if (isString(value)) {
value = JSON.stringify(value);
return raw ? value : [`${key}=${value}`];
}
else if (typeof value === 'number' ||
typeof value === 'boolean' ||
value == null) {
return raw ? value : [`${key}=${value}`];
}
else if (isRef(value)) {
value = formatProp(key, toRaw(value.value), true);
return raw ? value : [`${key}=Ref<`, value, `>`];
}
else if (isFunction(value)) {
return [`${key}=fn${value.name ? `<${value.name}>` : ``}`];
}
else {
value = toRaw(value);
return raw ? value : [`${key}=`, value];
}
}
const ErrorTypeStrings = {
["bc" /* BEFORE_CREATE */]: 'beforeCreate hook',
["c" /* CREATED */]: 'created hook',
["bm" /* BEFORE_MOUNT */]: 'beforeMount hook',
["m" /* MOUNTED */]: 'mounted hook',
["bu" /* BEFORE_UPDATE */]: 'beforeUpdate hook',
["u" /* UPDATED */]: 'updated',
["bum" /* BEFORE_UNMOUNT */]: 'beforeUnmount hook',
["um" /* UNMOUNTED */]: 'unmounted hook',
["a" /* ACTIVATED */]: 'activated hook',
["da" /* DEACTIVATED */]: 'deactivated hook',
["ec" /* ERROR_CAPTURED */]: 'errorCaptured hook',
["rtc" /* RENDER_TRACKED */]: 'renderTracked hook',
["rtg" /* RENDER_TRIGGERED */]: 'renderTriggered hook',
[0 /* SETUP_FUNCTION */]: 'setup function',
[1 /* RENDER_FUNCTION */]: 'render function',
[2 /* WATCH_GETTER */]: 'watcher getter',
[3 /* WATCH_CALLBACK */]: 'watcher callback',
[4 /* WATCH_CLEANUP */]: 'watcher cleanup function',
[5 /* NATIVE_EVENT_HANDLER */]: 'native event handler',
[6 /* COMPONENT_EVENT_HANDLER */]: 'component event handler',
[7 /* VNODE_HOOK */]: 'vnode hook',
[8 /* DIRECTIVE_HOOK */]: 'directive hook',
[9 /* TRANSITION_HOOK */]: 'transition hook',
[10 /* APP_ERROR_HANDLER */]: 'app errorHandler',
[11 /* APP_WARN_HANDLER */]: 'app warnHandler',
[12 /* FUNCTION_REF */]: 'ref function',
[13 /* ASYNC_COMPONENT_LOADER */]: 'async component loader',
[14 /* SCHEDULER */]: 'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next'
};
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 && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type);
});
}
return res;
}
const values = [];
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
}
return values;
}
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.proxy;
// in production the hook receives only the error code
const errorInfo = (process.env.NODE_ENV !== 'production') ? ErrorTypeStrings[type] : type;
while (cur) {
const errorCapturedHooks = cur.ec;
if (errorCapturedHooks) {
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, 10 /* APP_ERROR_HANDLER */, [err, exposedInstance, errorInfo]);
return;
}
}
logError(err, type, contextVNode);
}
function logError(err, type, contextVNode) {
if ((process.env.NODE_ENV !== 'production')) {
const info = ErrorTypeStrings[type];
if (contextVNode) {
pushWarningContext(contextVNode);
}
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`);
if (contextVNode) {
popWarningContext();
}
// crash in dev so it's more noticeable
throw err;
}
else {
// recover in prod to reduce the impact on end-user
console.error(err);
}
}
let isFlushing = false;
let isFlushPending = false;
const queue = [];
let flushIndex = 0;
const pendingPreFlushCbs = [];
let activePreFlushCbs = null;
let preFlushIndex = 0;
const pendingPostFlushCbs = [];
let activePostFlushCbs = null;
let postFlushIndex = 0;
const resolvedPromise = Promise.resolve();
let currentFlushPromise = null;
let currentPreFlushParentJob = null;
const RECURSION_LIMIT = 100;
function nextTick(fn) {
const p = currentFlushPromise || resolvedPromise;
return fn ? p.then(fn) : p;
}
function queueJob(job) {
// the dedupe search uses the startIndex argument of Array.includes()
// by default the search index includes the current job that is being run
// so it cannot recursively trigger itself again.
// if the job is a watch() callback, the search will start with a +1 index to
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
if ((!queue.length ||
!queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
job !== currentPreFlushParentJob) {
queue.push(job);
queueFlush();
}
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
function invalidateJob(job) {
const i = queue.indexOf(job);
if (i > -1) {
queue[i] = null;
}
}
function queueCb(cb, activeQueue, pendingQueue, index) {
if (!isArray(cb)) {
if (!activeQueue ||
!activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)) {
pendingQueue.push(cb);
}
}
else {
// if cb is an array, it is a component lifecycle hook which can only be
// triggered by a job, which is already deduped in the main queue, so
// we can skip duplicate check here to improve perf
pendingQueue.push(...cb);
}
queueFlush();
}
function queuePreFlushCb(cb) {
queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex);
}
function queuePostFlushCb(cb) {
queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex);
}
function flushPreFlushCbs(seen, parentJob = null) {
if (pendingPreFlushCbs.length) {
currentPreFlushParentJob = parentJob;
activePreFlushCbs = [...new Set(pendingPreFlushCbs)];
pendingPreFlushCbs.length = 0;
if ((process.env.NODE_ENV !== 'production')) {
seen = seen || new Map();
}
for (preFlushIndex = 0; preFlushIndex < activePreFlushCbs.length; preFlushIndex++) {
if ((process.env.NODE_ENV !== 'production')) {
checkRecursiveUpdates(seen, activePreFlushCbs[preFlushIndex]);
}
activePreFlushCbs[preFlushIndex]();
}
activePreFlushCbs = null;
preFlushIndex = 0;
currentPreFlushParentJob = null;
// recursively flush until it drains
flushPreFlushCbs(seen, parentJob);
}
}
function flushPostFlushCbs(seen) {
if (pendingPostFlushCbs.length) {
activePostFlushCbs = [...new Set(pendingPostFlushCbs)];
pendingPostFlushCbs.length = 0;
if ((process.env.NODE_ENV !== 'production')) {
seen = seen || new Map();
}
activePostFlushCbs.sort((a, b) => getId(a) - getId(b));
for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {
if ((process.env.NODE_ENV !== 'production')) {
checkRecursiveUpdates(seen, activePostFlushCbs[postFlushIndex]);
}
activePostFlushCbs[postFlushIndex]();
}
activePostFlushCbs = null;
postFlushIndex = 0;
}
}
const getId = (job) => job.id == null ? Infinity : job.id;
function flushJobs(seen) {
isFlushPending = false;
isFlushing = true;
if ((process.env.NODE_ENV !== 'production')) {
seen = seen || new Map();
}
flushPreFlushCbs(seen);
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smaller
// 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++) {
const job = queue[flushIndex];
if (job) {
if ((process.env.NODE_ENV !== 'production')) {
checkRecursiveUpdates(seen, job);
}
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
}
finally {
flushIndex = 0;
queue.length = 0;
flushPostFlushCbs(seen);
isFlushing = false;
currentFlushPromise = null;
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
function checkRecursiveUpdates(seen, fn) {
if (!seen.has(fn)) {
seen.set(fn, 1);
}
else {
const count = seen.get(fn);
if (count > RECURSION_LIMIT) {
throw new Error(`Maximum recursive updates exceeded. ` +
`This means you have a reactive effect that is mutating its own ` +
`dependencies and thus recursively triggering itself. Possible sources ` +
`include component template, render function, updated hook or ` +
`watcher source function.`);
}
else {
seen.set(fn, count + 1);
}
}
}
let isHmrUpdating = false;
const hmrDirtyComponents = new Set();
// Expose the HMR runtime on the global object
// This makes it entirely tree-shakable without polluting the exports and makes
// it easier to be used in toolings like vue-loader
// Note: for a component to be eligible for HMR it also needs the __hmrId option
// to be set so that its instances can be registered / removed.
if ((process.env.NODE_ENV !== 'production')) {
const globalObject = typeof global !== 'undefined'
? global
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: {};
globalObject.__VUE_HMR_RUNTIME__ = {
createRecord: tryWrap(createRecord),
rerender: tryWrap(rerender),
reload: tryWrap(reload)
};
}
const map = new Map();
function registerHMR(instance) {
const id = instance.type.__hmrId;
let record = map.get(id);
if (!record) {
createRecord(id);
record = map.get(id);
}
record.add(instance);
}
function unregisterHMR(instance) {
map.get(instance.type.__hmrId).delete(instance);
}
function createRecord(id) {
if (map.has(id)) {
return false;
}
map.set(id, new Set());
return true;
}
function rerender(id, newRender) {
const record = map.get(id);
if (!record)
return;
// Array.from creates a snapshot which avoids the set being mutated during
// updates
Array.from(record).forEach(instance => {
if (newRender) {
instance.render = newRender;
}
instance.renderCache = [];
// this flag forces child components with slot content to update
isHmrUpdating = true;
instance.update();
isHmrUpdating = false;
});
}
function reload(id, newComp) {
const record = map.get(id);
if (!record)
return;
// Array.from creates a snapshot which avoids the set being mutated during
// updates
Array.from(record).forEach(instance => {
const comp = instance.type;
if (!hmrDirtyComponents.has(comp)) {
// 1. Update existing comp definition to match new one
extend(comp, newComp);
for (const key in comp) {
if (!(key in newComp)) {
delete comp[key];
}
}
// 2. Mark component dirty. This forces the renderer to replace the component
// on patch.
hmrDirtyComponents.add(comp);
// 3. Make sure to unmark the component after the reload.
queuePostFlushCb(() => {
hmrDirtyComponents.delete(comp);
});
}
if (instance.parent) {
// 4. Force the parent instance to re-render. This will cause all updated
// components to be unmounted and re-mounted. Queue the update so that we
// don't end up forcing the same parent to re-render multiple times.
queueJob(instance.parent.update);
}
else if (instance.appContext.reload) {
// root instance mounted via createApp() has a reload method
instance.appContext.reload();
}
else if (typeof window !== 'undefined') {
// root instance inside tree created via raw render(). Force reload.
window.location.reload();
}
else {
console.warn('[HMR] Root or manually mounted instance modified. Full reload required.');
}
});
}
function tryWrap(fn) {
return (id, arg) => {
try {
return fn(id, arg);
}
catch (e) {
console.error(e);
console.warn(`[HMR] Something went wrong during Vue component hot-reload. ` +
`Full reload required.`);
}
};
}
// 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.
let accessedAttrs = false;
function markAttrsAccessed() {
accessedAttrs = true;
}
function renderComponentRoot(instance) {
const { type: Component, parent, vnode, proxy, withProxy, props, slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
let result;
currentRenderingInstance = instance;
if ((process.env.NODE_ENV !== 'production')) {
accessedAttrs = false;
}
try {
let fallthroughAttrs;
if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
// withProxy is a proxy with a different `has` trap only for
// runtime-compiled render functions using `with` block.
const proxyToUse = withProxy || proxy;
result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
fallthroughAttrs = attrs;
}
else {
// functional
const render = Component;
// in dev, mark attrs accessed if optional props (attrs === props)
if ((process.env.NODE_ENV !== 'production') && attrs === props) {
markAttrsAccessed();
}
result = normalizeVNode(render.length > 1
? render(props, (process.env.NODE_ENV !== 'production')
? {
get attrs() {
markAttrsAccessed();
return attrs;
},
slots,
emit
}
: { attrs, slots, emit })
: render(props, null /* we know it doesn't need it */));
fallthroughAttrs = Component.props
? attrs
: getFunctionalFallthrough(attrs);
}
// attr merging
// in dev mode, comments are preserved, and it's possible for a template
// to have comments along side the root element which makes it a fragment
let root = result;
let setRoot = undefined;
if ((process.env.NODE_ENV !== 'production')) {
;
[root, setRoot] = getChildRoot(result);
}
if (Component.inheritAttrs !== false && fallthroughAttrs) {
const keys = Object.keys(fallthroughAttrs);
const { shapeFlag } = root;
if (keys.length) {
if (shapeFlag & 1 /* ELEMENT */ ||
shapeFlag & 6 /* COMPONENT */) {
if (shapeFlag & 1 /* ELEMENT */ && keys.some(isModelListener)) {
// #1643, #1543
// component v-model listeners should only fallthrough for component
// HOCs
fallthroughAttrs = filterModelListeners(fallthroughAttrs);
}
root = cloneVNode(root, fallthroughAttrs);
}
else if ((process.env.NODE_ENV !== 'production') && !accessedAttrs && root.type !== Comment) {
const allAttrs = Object.keys(attrs);
const eventAttrs = [];
const extraAttrs = [];
for (let i = 0, l = allAttrs.length; i < l; i++) {
const key = allAttrs[i];
if (isOn(key)) {
// ignore v-model handlers when they fail to fallthrough
if (!isModelListener(key)) {
// remove `on`, lowercase first letter to reflect event casing
// accurately
eventAttrs.push(key[2].toLowerCase() + key.slice(3));
}
}
else {
extraAttrs.push(key);
}
}
if (extraAttrs.length) {
warn(`Extraneous non-props attributes (` +
`${extraAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes.`);
}
if (eventAttrs.length) {
warn(`Extraneous non-emits event listeners (` +
`${eventAttrs.join(', ')}) ` +
`were passed to component but could not be automatically inherited ` +
`because component renders fragment or text root nodes. ` +
`If the listener is intended to be a component custom event listener only, ` +
`declare it using the "emits" option.`);
}
}
}
}
// inherit scopeId
const scopeId = vnode.scopeId;
// vite#536: if subtree root is created from parent slot if would already
// have the correct scopeId, in this case adding the scopeId will cause
// it to be removed if the original slot vnode is reused.
const needScopeId = scopeId && root.scopeId !== scopeId;
const treeOwnerId = parent && parent.type.__scopeId;
const slotScopeId = treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null;
if (needScopeId || slotScopeId) {
const extras = {};
if (needScopeId)
extras[scopeId] = '';
if (slotScopeId)
extras[slotScopeId] = '';
root = cloneVNode(root, extras);
}
// inherit directives
if (vnode.dirs) {
if ((process.env.NODE_ENV !== 'production') && !isElementRoot(root)) {
warn(`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`);
}
root.dirs = vnode.dirs;
}
// inherit transition data
if (vnode.transition) {
if ((process.env.NODE_ENV !== 'production') && !isElementRoot(root)) {
warn(`Component inside <Transition> renders non-element root node ` +
`that cannot be animated.`);
}
root.transition = vnode.transition;
}
if ((process.env.NODE_ENV !== 'production') && setRoot) {
setRoot(root);
}
else {
result = root;
}
}
catch (err) {
handleError(err, instance, 1 /* RENDER_FUNCTION */);
result = createVNode(Comment);
}
currentRenderingInstance = null;
return result;
}
/**
* dev only
*/
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) {
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);
}
};
return [normalizeVNode(childRoot), setRoot];
};
const getFunctionalFallthrough = (attrs) => {
let res;
for (const key in attrs) {
if (key === 'class' || key === 'style' || isOn(key)) {
(res || (res = {}))[key] = attrs[key];
}
}
return res;
};
const filterModelListeners = (attrs) => {
const res = {};
for (const key in attrs) {
if (!isModelListener(key)) {
res[key] = attrs[key];
}
}
return res;
};
const isElementRoot = (vnode) => {
return (vnode.shapeFlag & 6 /* COMPONENT */ ||
vnode.shapeFlag & 1 /* ELEMENT */ ||
vnode.type === Comment // potential v-if branch switch
);
};
function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
const { props: prevProps, children: prevChildren } = prevVNode;
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;
// Parent component's render function was hot-updated. Since this may have
// caused the child component's slots content to have changed, we need to
// force the child to update as well.
if ((process.env.NODE_ENV !== 'production') && (prevChildren || nextChildren) && isHmrUpdating) {
return true;
}
// force child update for runtime directive or transition on component vnode.
if (nextVNode.dirs || nextVNode.transition) {
return true;
}
if (optimized && patchFlag > 0) {
if (patchFlag & 1024 /* DYNAMIC_SLOTS */) {
// slot content that references values that might have changed,
// e.g. in a v-for
return true;
}
if (patchFlag & 16 /* FULL_PROPS */) {
if (!prevProps) {
return !!nextProps;
}
// 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 {
// this path is only taken by manually written render functions
// so presence of any children leads to a forced update
if (prevChildren || nextChildren) {
if (!nextChildren || !nextChildren.$stable) {
return true;
}
}
if (prevProps === nextProps) {
return false;
}
if (!prevProps) {
return !!nextProps;
}
if (!nextProps) {
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;
}
function updateHOCHostEl({ vnode, parent }, el // HostNode
) {
while (parent && parent.subTree === vnode) {
(vnode = parent.vnode).el = el;
parent = parent.parent;
}
}
const isSuspense = (type) => type.__isSuspense;
// Suspense exposes a component-like API, and is treated like a component
// in the compiler, but internally it's a special built-in type that hooks
// directly into the renderer.
const SuspenseImpl = {
// In order to make Suspense tree-shakable, we need to avoid importing it
// directly in the renderer. The renderer checks for the __isSuspense flag
// on a vnode's type and calls the `process` method, passing in renderer
// internals.
__isSuspense: true,
process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized,
// platform-specific impl passed from renderer
rendererInternals) {
if (n1 == null) {
mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, rendererInternals);
}
else {
patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, optimized, rendererInternals);
}
},
hydrate: hydrateSuspense
};
// Force-casted public typing for h and TSX props inference
const Suspense = ( SuspenseImpl
);
function mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, rendererInternals) {
const { p: patch, o: { createElement } } = rendererInternals;
const hiddenContainer = createElement('div');
const suspense = (n2.suspense = createSuspenseBoundary(n2, parentSuspense, parentComponent, container, hiddenContainer, anchor, isSVG, optimized, rendererInternals));
// start mounting the content subtree in an off-dom container
patch(null, suspense.subTree, hiddenContainer, null, parentComponent, suspense, isSVG, optimized);
// now check if we have encountered any async deps
if (suspense.deps > 0) {
// mount the fallback tree
patch(null, suspense.fallbackTree, container, anchor, parentComponent, null, // fallback tree will not have suspense context
isSVG, optimized);
n2.el = suspense.fallbackTree.el;
}
else {
// Suspense has no async deps. Just resolve.
suspense.resolve();
}
}
function patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, optimized, { p: patch }) {
const suspense = (n2.suspense = n1.suspense);
suspense.vnode = n2;
const { content, fallback } = normalizeSuspenseChildren(n2);
const oldSubTree = suspense.subTree;
const oldFallbackTree = suspense.fallbackTree;
if (!suspense.isResolved) {
patch(oldSubTree, content, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, optimized);
if (suspense.deps > 0) {
// still pending. patch the fallback tree.
patch(oldFallbackTree, fallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context
isSVG, optimized);
n2.el = fallback.el;
}
// If deps somehow becomes 0 after the patch it means the patch caused an
// async dep component to unmount and removed its dep. It will cause the
// suspense to resolve and we don't need to do anything here.
}
else {
// just normal patch inner content as a fragment
patch(oldSubTree, content, container, anchor, parentComponent, suspense, isSVG, optimized);
n2.el = content.el;
}
suspense.subTree = content;
suspense.fallbackTree = fallback;
}
let hasWarned = false;
function createSuspenseBoundary(vnode, parent, parentComponent, container, hiddenContainer, anchor, isSVG, optimized, rendererInternals, isHydrating = false) {
/* istanbul ignore if */
if ((process.env.NODE_ENV !== 'production') && !false && !hasWarned) {
hasWarned = true;
// @ts-ignore `console.info` cannot be null error
console[console.info ? 'info' : 'log'](`<Suspense> is an experimental feature and its API will likely change.`);
}
const { p: patch, m: move, um: unmount, n: next, o: { parentNode } } = rendererInternals;
const getCurrentTree = () => suspense.isResolved || suspense.isHydrating
? suspense.subTree
: suspense.fallbackTree;
const { content, fallback } = normalizeSuspenseChildren(vnode);
const suspense = {
vnode,
parent,
parentComponent,
isSVG,
optimized,
container,
hiddenContainer,
anchor,
deps: 0,
subTree: content,
fallbackTree: fallback,
isHydrating,
isResolved: false,
isUnmounted: false,
effects: [],
resolve() {
if ((process.env.NODE_ENV !== 'production')) {
if (suspense.isResolved) {
throw new Error(`resolveSuspense() is called on an already resolved suspense boundary.`);
}
if (suspense.isUnmounted) {
throw new Error(`resolveSuspense() is called on an already unmounted suspense boundary.`);
}
}
const { vnode, subTree, fallbackTree, effects, parentComponent, container } = suspense;
if (suspense.isHydrating) {
suspense.isHydrating = false;
}
else {
// this is initial anchor on mount
let { anchor } = suspense;
// unmount fallback tree
if (fallbackTree.el) {
// if the fallback tree was mounted, it may have been moved
// as part of a parent suspense. get the latest anchor for insertion
anchor = next(fallbackTree);
unmount(fallbackTree, parentComponent, suspense, true);
}
// move content from off-dom container to actual container
move(subTree, container, anchor, 0 /* ENTER */);
}
const el = (vnode.el = subTree.el);
// suspense as the root node of a component...
if (parentComponent && parentComponent.subTree === vnode) {
parentComponent.vnode.el = el;
updateHOCHostEl(parentComponent, el);
}
// check if there is a pending parent suspense
let parent = suspense.parent;
let hasUnresolvedAncestor = false;
while (parent) {
if (!parent.isResolved) {
// found a pending parent suspense, merge buffered post jobs
// into that parent
parent.effects.push(...effects);
hasUnresolvedAncestor = true;
break;
}
parent = parent.parent;
}
// no pending parent suspense, flush all jobs
if (!hasUnresolvedAncestor) {
queuePostFlushCb(effects);
}
suspense.isResolved = true;
suspense.effects = [];
// invoke @resolve event
const onResolve = vnode.props && vnode.props.onResolve;
if (isFunction(onResolve)) {
onResolve();
}
},
recede() {
suspense.isResolved = false;
const { vnode, subTree, fallbackTree, parentComponent, container, hiddenContainer, isSVG, optimized } = suspense;
// move content tree back to the off-dom container
const anchor = next(subTree);
move(subTree, hiddenContainer, null, 1 /* LEAVE */);
// remount the fallback tree
patch(null, fallbackTree, container, anchor, parentComponent, null, // fallback tree will not have suspense context
isSVG, optimized);
const el = (vnode.el = fallbackTree.el);
// suspense as the root node of a component...
if (parentComponent && parentComponent.subTree === vnode) {
parentComponent.vnode.el = el;
updateHOCHostEl(parentComponent, el);
}
// invoke @recede event
const onRecede = vnode.props && vnode.props.onRecede;
if (isFunction(onRecede)) {
onRecede();
}
},
move(container, anchor, type) {
move(getCurrentTree(), container, anchor, type);
suspense.container = container;
},
next() {
return next(getCurrentTree());
},
registerDep(instance, setupRenderEffect) {
// suspense is already resolved, need to recede.
// use queueJob so it's handled synchronously after patching the current
// suspense tree
if (suspense.isResolved) {
queueJob(() => {
suspense.recede();
});
}
const hydratedEl = instance.vnode.el;
suspense.deps++;
instance
.asyncDep.catch(err => {
handleError(err, instance, 0 /* SETUP_FUNCTION */);
})
.then(asyncSetupResult => {
// retry when the setup() promise resolves.
// component may have been unmounted before resolve.
if (instance.isUnmounted || suspense.isUnmounted) {
return;
}
suspense.deps--;
// retry from this component
instance.asyncResolved = true;
const { vnode } = instance;
if ((process.env.NODE_ENV !== 'production')) {
pushWarningContext(vnode);
}
handleSetupResult(instance, asyncSetupResult);
if (hydratedEl) {
// vnode may have been replaced if an update happened before the
// async dep is resolved.
vnode.el = hydratedEl;
}
setupRenderEffect(instance, vnode,
// component may have been moved before resolve.
// if this is not a hydration, instance.subTree will be the comment
// placeholder.
hydratedEl
? parentNode(hydratedEl)
: parentNode(instance.subTree.el),
// anchor will not be used if this is hydration, so only need to
// consider the comment placeholder case.
hydratedEl ? null : next(instance.subTree), suspense, isSVG, optimized);
updateHOCHostEl(instance, vnode.el);
if ((process.env.NODE_ENV !== 'production')) {
popWarningContext();
}
if (suspense.deps === 0) {
suspense.resolve();
}
});
},
unmount(parentSuspense, doRemove) {
suspense.isUnmounted = true;
unmount(suspense.subTree, parentComponent, parentSuspense, doRemove);
if (!suspense.isResolved) {
unmount(suspense.fallbackTree, parentComponent, parentSuspense, doRemove);
}
}
};
return suspense;
}
function hydrateSuspense(node, vnode, parentComponent, parentSuspense, isSVG, optimized, rendererInternals, hydrateNode) {
/* eslint-disable no-restricted-globals */
const suspense = (vnode.suspense = createSuspenseBoundary(vnode, parentSuspense, parentComponent, node.parentNode, document.createElement('div'), null, isSVG, optimized, rendererInternals, true /* hydrating */));
// there are two possible scenarios for server-rendered suspense:
// - success: ssr content should be fully resolved
// - failure: ssr content should be the fallback branch.
// however, on the client we don't really know if it has failed or not
// attempt to hydrate the DOM assuming it has succeeded, but we still
// need to construct a suspense boundary first
const result = hydrateNode(node, suspense.subTree, parentComponent, suspense, optimized);
if (suspense.deps === 0) {
suspense.resolve();
}
return result;
/* eslint-enable no-restricted-globals */
}
function normalizeSuspenseChildren(vnode) {
const { shapeFlag, children } = vnode;
if (shapeFlag & 32 /* 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)
};
}
}
function queueEffectWithSuspense(fn, suspense) {
if (suspense && !suspense.isResolved) {
if (isArray(fn)) {
suspense.effects.push(...fn);
}
else {
suspense.effects.push(fn);
}
}
else {
queuePostFlushCb(fn);
}
}
let isRenderingCompiledSlot = 0;
/**
* Compiler runtime helper for rendering `<slot/>`
* @private
*/
function renderSlot(slots, name, props = {},
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback) {
let slot = slots[name];
if ((process.env.NODE_ENV !== 'production') && slot && slot.length > 1) {
warn(`SSR-optimized slot function detected in a non-SSR-optimized render ` +
`function. You need to mark this component with $dynamic-slots in the ` +
`parent template.`);
slot = () => [];
}
// a compiled slot disables block tracking by default to avoid manual
// invocation interfering with template-based block tracking, but in
// `renderSlot` we can be sure that it's template-based so we can force
// enable it.
isRenderingCompiledSlot++;
const rendered = (openBlock(),
createBlock(Fragment, { key: props.key }, slot ? slot(props) : fallback ? fallback() : [], slots._ === 1 /* STABLE */
? 64 /* STABLE_FRAGMENT */
: -2 /* BAIL */));
isRenderingCompiledSlot--;
return rendered;
}
/**
* Wrap a slot function to memoize current rendering instance
* @private
*/
function withCtx(fn, ctx = currentRenderingInstance) {
if (!ctx)
return fn;
return function renderFnWithContext() {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we need to push a null block to
// avoid that. This isn't necessary if rendering a compiled `<slot>`.
if (!isRenderingCompiledSlot) {
openBlock(true /* null block that disables tracking */);
}
const owner = currentRenderingInstance;
setCurrentRenderingInstance(ctx);
const res = fn.apply(null, arguments);
setCurrentRenderingInstance(owner);
if (!isRenderingCompiledSlot) {
closeBlock();
}
return res;
};
}
// SFC scoped style ID management.
let currentScopeId = null;
const scopeIdStack = [];
/**
* @private
*/
function pushScopeId(id) {
scopeIdStack.push((currentScopeId = id));
}
/**
* @private
*/
function popScopeId() {
scopeIdStack.pop();
currentScopeId = scopeIdStack[scopeIdStack.length - 1] || null;
}
/**
* @private
*/
function withScopeId(id) {
return ((fn) => withCtx(function () {
pushScopeId(id);
const res = fn.apply(this, arguments);
popScopeId();
return res;
}));
}
const isTeleport = (type) => type.__isTeleport;
const isTeleportDisabled = (props) => props && (props.disabled || props.disabled === '');
const resolveTarget = (props, select) => {
const targetSelector = props && props.to;
if (isString(targetSelector)) {
if (!select) {
(process.env.NODE_ENV !== 'production') &&
warn(`Current renderer does not support string target for Teleports. ` +
`(missing querySelector renderer option)`);
return null;
}
else {
const target = select(targetSelector);
if (!target) {
(process.env.NODE_ENV !== 'production') &&
warn(`Failed to locate Teleport target with selector "${targetSelector}". ` +
`Note the target element must exist before the component is mounted - ` +
`i.e. the target cannot be rendered by the component itself, and ` +
`ideally should be outside of the entire Vue component tree.`);
}
return target;
}
}
else {
if ((process.env.NODE_ENV !== 'production') && !targetSelector) {
warn(`Invalid Teleport target: ${targetSelector}`);
}
return targetSelector;
}
};
const TeleportImpl = {
__isTeleport: true,
process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals) {
const { mc: mountChildren, pc: patchChildren, pbc: patchBlockChildren, o: { insert, querySelector, createText, createComment } } = internals;
const disabled = isTeleportDisabled(n2.props);
const { shapeFlag, children } = n2;
if (n1 == null) {
// insert anchors in the main view
const placeholder = (n2.el = (process.env.NODE_ENV !== 'production')
? createComment('teleport start')
: createText(''));
const mainAnchor = (n2.anchor = (process.env.NODE_ENV !== 'production')
? createComment('teleport end')
: createText(''));
insert(placeholder, container, anchor);
insert(mainAnchor, container, anchor);
const target = (n2.target = resolveTarget(n2.props, querySelector));
const targetAnchor = (n2.targetAnchor = createText(''));
if (target) {
insert(targetAnchor, target);
}
else if ((process.env.NODE_ENV !== 'production')) {
warn('Invalid Teleport target on mount:', target, `(${typeof target})`);
}
const mount = (container, anchor) => {
// Teleport *always* has Array children. This is enforced in both the
// compiler and vnode children normalization.
if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
mountChildren(children, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
};
if (disabled) {
mount(container, mainAnchor);
}
else if (target) {
mount(target, targetAnchor);
}
}
else {
// update content
n2.el = n1.el;
const mainAnchor = (n2.anchor = n1.anchor);
const target = (n2.target = n1.target);
const targetAnchor = (n2.targetAnchor = n1.targetAnchor);
const wasDisabled = isTeleportDisabled(n1.props);