1// Guarded Vue runtime â detects/suppresses infinite watcher loops in production. See patches/vue+3.5.26.md 2 3export * from './vue.esm-browser.prod.js'; 4 5import { 6 getCurrentInstance, 7 watch as _watch, 8 watchEffect as _watchEffect, 9} from './vue.esm-browser.prod.js'; 10 11let globalEnabled = true; 12 13const tickCounters = new Map(); 14let resetScheduled = false; 15 16function scheduleCounterReset() { 17 if (!resetScheduled) { 18 resetScheduled = true; 19 queueMicrotask(() => { 20 tickCounters.clear(); 21 resetScheduled = false; 22 }); 23 } 24} 25 26function incrementAndGet(id) { 27 scheduleCounterReset(); 28 const count = (tickCounters.get(id) || 0) + 1; 29 tickCounters.set(id, count); 30 return count; 31} 32 33function resolveComponentName() { 34 try { 35 const instance = getCurrentInstance(); 36 return ( 37 instance?.type?.__name ?? 38 instance?.type?.name ?? 39 undefined 40 ); 41 } catch { 42 return undefined; 43 } 44} 45 46let globalOnLoop; 47 48function defaultOnLoop(info) { 49 console.error( 50 `[WatcherLoopGuard] Watcher loop detected (>${info.limit} calls/tick)\n` + 51 ` Component: ${info.componentName ?? '<unknown>'}\n` + 52 ` Source: ${info.source}` + 53 (info.watchValues 54 ? `\n New value: ${JSON.stringify(info.watchValues.newValue)}\n Old value: ${JSON.stringify(info.watchValues.oldValue)}` 55 : ''), 56 '\n' + info.stack, 57 ); 58} 59 60function createGuard(sourceDesc, limit, onLoop) { 61 const id = Symbol('watcher-guard'); 62 const componentName = resolveComponentName(); 63 64 return (watchValues) => { 65 if (!globalEnabled) return true; 66 67 const count = incrementAndGet(id); 68 69 if (count > limit) { 70 if (count === limit + 1) { 71 const info = { 72 count, 73 limit, 74 componentName, 75 source: sourceDesc, 76 stack: new Error('WatcherLoopGuard').stack || '', 77 watchValues, 78 }; 79 onLoop(info); 80 if (globalOnLoop) globalOnLoop(info); 81 } 82 return false; 83 } 84 85 return true; 86 }; 87} 88 89function createGuardedWatch(config) { 90 const limit = config?.limit ?? 100; 91 const onLoop = config?.onLoop ?? defaultOnLoop; 92 93 return function guardedWatch(source, callback, options) { 94 const sourceDesc = 95 typeof source === 'function' 96 ? `source: ${source.toString().slice(0, 100)}` 97 : `callback: ${callback.toString().slice(0, 100)}`; 98 const check = createGuard(sourceDesc, limit, onLoop); 99 100 const wrappedCallback = (...args) => { 101 if (!check({ newValue: args[0], oldValue: args[1] })) return; 102 return callback(...args); 103 }; 104 105 return _watch(source, wrappedCallback, options); 106 }; 107} 108 109function createGuardedWatchEffect(config) { 110 const limit = config?.limit ?? 100; 111 const onLoop = config?.onLoop ?? defaultOnLoop; 112 113 return (effect, options) => { 114 const check = createGuard(effect.toString().slice(0, 200), limit, onLoop); 115 116 return _watchEffect((onCleanup) => { 117 if (!check()) return; 118 effect(onCleanup); 119 }, options); 120 }; 121} 122 123const _cfg = typeof window !== 'undefined' && window.INITIAL_DATA?.watcherLoopGuard; 124 125if (_cfg && _cfg.enabled === false) { 126 globalEnabled = false; 127} 128 129const _limit = _cfg?.limit; 130 131export const watch = createGuardedWatch(_limit ? { limit: _limit } : {}); 132export const watchEffect = createGuardedWatchEffect(_limit ? { limit: _limit } : {}); 133 134if (typeof window !== 'undefined') { 135 window.__watcherLoopGuard = { 136 setEnabled: (v) => { 137 globalEnabled = !!v; 138 }, 139 setOnLoop: (fn) => { 140 globalOnLoop = typeof fn === 'function' ? fn : undefined; 141 }, 142 }; 143}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.