@tanstack/router-core 1.166.6 → 1.167.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/load-matches.cjs +10 -6
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.cjs +7 -3
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/path.cjs +1 -1
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/qss.cjs +1 -1
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +11 -2
- package/dist/cjs/router.cjs +22 -13
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +10 -1
- package/dist/cjs/utils.cjs +6 -3
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/load-matches.js +10 -6
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/new-process-route-tree.js +7 -3
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/path.js +1 -1
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/qss.js +1 -1
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/route.d.ts +11 -2
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +10 -1
- package/dist/esm/router.js +23 -14
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/utils.d.ts +2 -1
- package/dist/esm/utils.js +6 -3
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/load-matches.ts +24 -7
- package/src/new-process-route-tree.ts +8 -4
- package/src/path.ts +1 -1
- package/src/qss.ts +1 -1
- package/src/route.ts +88 -11
- package/src/router.ts +34 -14
- package/src/utils.ts +12 -3
package/dist/esm/utils.d.ts
CHANGED
|
@@ -63,13 +63,14 @@ export declare function last<T>(arr: ReadonlyArray<T>): T | undefined;
|
|
|
63
63
|
* Accepts either a literal value or a function of the previous value.
|
|
64
64
|
*/
|
|
65
65
|
export declare function functionalUpdate<TPrevious, TResult = TPrevious>(updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>, previous: TPrevious): TResult;
|
|
66
|
+
export declare const nullReplaceEqualDeep: typeof replaceEqualDeep;
|
|
66
67
|
/**
|
|
67
68
|
* This function returns `prev` if `_next` is deeply equal.
|
|
68
69
|
* If not, it will replace any deeply equal children of `b` with those of `a`.
|
|
69
70
|
* This can be used for structural sharing between immutable JSON values for example.
|
|
70
71
|
* Do not use this with signals
|
|
71
72
|
*/
|
|
72
|
-
export declare function replaceEqualDeep<T>(prev: any, _next: T, _depth?: number): T;
|
|
73
|
+
export declare function replaceEqualDeep<T>(prev: any, _next: T, _makeObj?: () => {}, _depth?: number): T;
|
|
73
74
|
export declare function isPlainObject(o: any): boolean;
|
|
74
75
|
/**
|
|
75
76
|
* Check if a value is a "plain" array (no extra enumerable keys).
|
package/dist/esm/utils.js
CHANGED
|
@@ -13,7 +13,9 @@ function functionalUpdate(updater, previous) {
|
|
|
13
13
|
}
|
|
14
14
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
15
15
|
const isEnumerable = Object.prototype.propertyIsEnumerable;
|
|
16
|
-
|
|
16
|
+
const createNull = () => /* @__PURE__ */ Object.create(null);
|
|
17
|
+
const nullReplaceEqualDeep = (prev, next) => replaceEqualDeep(prev, next, createNull);
|
|
18
|
+
function replaceEqualDeep(prev, _next, _makeObj = () => ({}), _depth = 0) {
|
|
17
19
|
if (isServer) {
|
|
18
20
|
return _next;
|
|
19
21
|
}
|
|
@@ -30,7 +32,7 @@ function replaceEqualDeep(prev, _next, _depth = 0) {
|
|
|
30
32
|
if (!nextItems) return next;
|
|
31
33
|
const prevSize = prevItems.length;
|
|
32
34
|
const nextSize = nextItems.length;
|
|
33
|
-
const copy = array ? new Array(nextSize) :
|
|
35
|
+
const copy = array ? new Array(nextSize) : _makeObj();
|
|
34
36
|
let equalItems = 0;
|
|
35
37
|
for (let i = 0; i < nextSize; i++) {
|
|
36
38
|
const key = array ? i : nextItems[i];
|
|
@@ -45,7 +47,7 @@ function replaceEqualDeep(prev, _next, _depth = 0) {
|
|
|
45
47
|
copy[key] = n;
|
|
46
48
|
continue;
|
|
47
49
|
}
|
|
48
|
-
const v = replaceEqualDeep(p, n, _depth + 1);
|
|
50
|
+
const v = replaceEqualDeep(p, n, _makeObj, _depth + 1);
|
|
49
51
|
copy[key] = v;
|
|
50
52
|
if (v === p) equalItems++;
|
|
51
53
|
}
|
|
@@ -259,6 +261,7 @@ export {
|
|
|
259
261
|
isPlainObject,
|
|
260
262
|
isPromise,
|
|
261
263
|
last,
|
|
264
|
+
nullReplaceEqualDeep,
|
|
262
265
|
replaceEqualDeep
|
|
263
266
|
};
|
|
264
267
|
//# sourceMappingURL=utils.js.map
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type Awaitable<T> = T | Promise<T>\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &\n keyof TRight extends never\n ? TRight\n : Omit<TRight, keyof TLeft & keyof TRight> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n }\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport type LooseReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\nexport type LooseAsyncReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn extends Promise<infer TReturn>\n ? TReturn\n : TReturn\n : never\n\n/**\n * Return the last element of an array.\n * Intended for non-empty arrays used within router internals.\n */\nexport function last<T>(arr: ReadonlyArray<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\n/**\n * Apply a value-or-updater to a previous value.\n * Accepts either a literal value or a function of the previous value.\n */\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nconst hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(prev: any, _next: T, _depth = 0): T {\n if (isServer) {\n return _next\n }\n if (prev === _next) {\n return prev\n }\n\n if (_depth > 500) return _next\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next\n\n const prevItems = array ? prev : getEnumerableOwnKeys(prev)\n if (!prevItems) return next\n const nextItems = array ? next : getEnumerableOwnKeys(next)\n if (!nextItems) return next\n const prevSize = prevItems.length\n const nextSize = nextItems.length\n const copy: any = array ? new Array(nextSize) : {}\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n const p = prev[key]\n const n = next[key]\n\n if (p === n) {\n copy[key] = p\n if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++\n continue\n }\n\n if (\n p === null ||\n n === null ||\n typeof p !== 'object' ||\n typeof n !== 'object'\n ) {\n copy[key] = n\n continue\n }\n\n const v = replaceEqualDeep(p, n, _depth + 1)\n copy[key] = v\n if (v === p) equalItems++\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n}\n\n/**\n * Equivalent to `Reflect.ownKeys`, but ensures that objects are \"clone-friendly\":\n * will return false if object has any non-enumerable properties.\n *\n * Optimized for the common case where objects have no symbol properties.\n */\nfunction getEnumerableOwnKeys(o: object) {\n const names = Object.getOwnPropertyNames(o)\n\n // Fast path: check all string property names are enumerable\n for (const name of names) {\n if (!isEnumerable.call(o, name)) return false\n }\n\n // Only check symbols if the object has any (most plain objects don't)\n const symbols = Object.getOwnPropertySymbols(o)\n\n // Fast path: no symbols, return names directly (avoids array allocation/concat)\n if (symbols.length === 0) return names\n\n // Slow path: has symbols, need to check and merge\n const keys: Array<string | symbol> = names\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) return false\n keys.push(symbol)\n }\n return keys\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\n/**\n * Check if a value is a \"plain\" array (no extra enumerable keys).\n */\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\n/**\n * Perform a deep equality check with options for partial comparison and\n * ignoring `undefined` values. Optimized for router state comparisons.\n */\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0, l = a.length; i < l; i++) {\n if (!deepEqual(a[i], b[i], opts)) return false\n }\n return true\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n\n if (opts?.partial) {\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n if (!deepEqual(a[k], b[k], opts)) return false\n }\n }\n return true\n }\n\n let aCount = 0\n if (!ignoreUndefined) {\n aCount = Object.keys(a).length\n } else {\n for (const k in a) {\n if (a[k] !== undefined) aCount++\n }\n }\n\n let bCount = 0\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n bCount++\n if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false\n }\n }\n\n return aCount === bCount\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\n/**\n * Create a promise with exposed resolve/reject and status fields.\n * Useful for coordinating async router lifecycle operations.\n */\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Heuristically detect dynamic import \"module not found\" errors\n * across major browsers for lazy route component handling.\n */\nexport function isModuleNotFoundError(error: any): boolean {\n // chrome: \"Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // firefox: \"error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // safari: \"Importing a module script failed.\"\n if (typeof error?.message !== 'string') return false\n return (\n error.message.startsWith('Failed to fetch dynamically imported module') ||\n error.message.startsWith('error loading dynamically imported module') ||\n error.message.startsWith('Importing a module script failed')\n )\n}\n\nexport function isPromise<T>(\n value: Promise<Awaited<T>> | T,\n): value is Promise<Awaited<T>> {\n return Boolean(\n value &&\n typeof value === 'object' &&\n typeof (value as Promise<T>).then === 'function',\n )\n}\n\nexport function findLast<T>(\n array: ReadonlyArray<T>,\n predicate: (item: T) => boolean,\n): T | undefined {\n for (let i = array.length - 1; i >= 0; i--) {\n const item = array[i]!\n if (predicate(item)) return item\n }\n return undefined\n}\n\n/**\n * Remove control characters that can cause open redirect vulnerabilities.\n * Characters like \\r (CR) and \\n (LF) can trick URL parsers into interpreting\n * paths like \"/\\r/evil.com\" as \"http://evil.com\".\n */\nfunction sanitizePathSegment(segment: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n return segment.replace(/[\\x00-\\x1f\\x7f]/g, '')\n}\n\nfunction decodeSegment(segment: string): string {\n let decoded: string\n try {\n decoded = decodeURI(segment)\n } catch {\n // if the decoding fails, try to decode the various parts leaving the malformed tags in place\n decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {\n try {\n return decodeURI(match)\n } catch {\n return match\n }\n })\n }\n return sanitizePathSegment(decoded)\n}\n\n/**\n * Default list of URL protocols to allow in links, redirects, and navigation.\n * Any absolute URL protocol not in this list is treated as dangerous by default.\n */\nexport const DEFAULT_PROTOCOL_ALLOWLIST = [\n // Standard web navigation\n 'http:',\n 'https:',\n\n // Common browser-safe actions\n 'mailto:',\n 'tel:',\n]\n\n/**\n * Check if a URL string uses a protocol that is not in the allowlist.\n * Returns true for blocked protocols like javascript:, blob:, data:, etc.\n *\n * The URL constructor correctly normalizes:\n * - Mixed case (JavaScript: → javascript:)\n * - Whitespace/control characters (java\\nscript: → javascript:)\n * - Leading whitespace\n *\n * For relative URLs (no protocol), returns false (safe).\n *\n * @param url - The URL string to check\n * @param allowlist - Set of protocols to allow\n * @returns true if the URL uses a protocol that is not allowed\n */\nexport function isDangerousProtocol(\n url: string,\n allowlist: Set<string>,\n): boolean {\n if (!url) return false\n\n try {\n // Use the URL constructor - it correctly normalizes protocols\n // per WHATWG URL spec, handling all bypass attempts automatically\n const parsed = new URL(url)\n return !allowlist.has(parsed.protocol)\n } catch {\n // URL constructor throws for relative URLs (no protocol)\n // These are safe - they can't execute scripts\n return false\n }\n}\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\nconst HTML_ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nconst HTML_ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\n/**\n * Escape HTML special characters in a string to prevent XSS attacks\n * when embedding strings in script tags during SSR.\n *\n * This is essential for preventing XSS vulnerabilities when user-controlled\n * content is embedded in inline scripts.\n */\nexport function escapeHtml(str: string): string {\n return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)\n}\n\nexport function decodePath(path: string) {\n if (!path) return { path, handledProtocolRelativeURL: false }\n\n // Fast path: most paths are already decoded and safe.\n // Only fall back to the slower scan/regex path when we see a '%' (encoded),\n // a backslash (explicitly handled), a control character, or a protocol-relative\n // prefix which needs collapsing.\n // eslint-disable-next-line no-control-regex\n if (!/[%\\\\\\x00-\\x1f\\x7f]/.test(path) && !path.startsWith('//')) {\n return { path, handledProtocolRelativeURL: false }\n }\n\n const re = /%25|%5C/gi\n let cursor = 0\n let result = ''\n let match\n while (null !== (match = re.exec(path))) {\n result += decodeSegment(path.slice(cursor, match.index)) + match[0]\n cursor = re.lastIndex\n }\n result = result + decodeSegment(cursor ? path.slice(cursor) : path)\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // After sanitizing control characters, paths like \"/\\r/evil.com\" become \"//evil.com\"\n // Collapse leading double slashes to a single slash\n let handledProtocolRelativeURL = false\n if (result.startsWith('//')) {\n handledProtocolRelativeURL = true\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return { path: result, handledProtocolRelativeURL }\n}\n\n/**\n * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.\n *\n * This function encodes:\n * - Whitespace characters (spaces → %20, tabs → %09, etc.)\n * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)\n *\n * It preserves:\n * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)\n * - ASCII special characters valid in URL paths (@, $, &, +, etc.)\n * - Forward slashes as path separators\n *\n * Used to generate proper href values for SSR without constructing URL objects.\n *\n * @example\n * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'\n * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'\n * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)\n */\nexport function encodePathLikeUrl(path: string): string {\n // Encode whitespace and non-ASCII characters that browsers encode in URLs\n\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n if (!/\\s|[^\\u0000-\\u007F]/.test(path)) return path\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n return path.replace(/\\s|[^\\u0000-\\u007F]/gu, encodeURIComponent)\n}\n\n/**\n * Builds the dev-mode CSS styles URL for route-scoped CSS collection.\n * Used by HeadContent components in all framework implementations to construct\n * the URL for the `/@tanstack-start/styles.css` endpoint.\n *\n * @param basepath - The router's basepath (may or may not have leading slash)\n * @param routeIds - Array of matched route IDs to include in the CSS collection\n * @returns The full URL path for the dev styles CSS endpoint\n */\nexport function buildDevStylesUrl(\n basepath: string,\n routeIds: Array<string>,\n): string {\n // Trim all leading and trailing slashes from basepath\n const trimmedBasepath = basepath.replace(/^\\/+|\\/+$/g, '')\n // Build normalized basepath: empty string for root, or '/path' for non-root\n const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`\n return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type Awaitable<T> = T | Promise<T>\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &\n keyof TRight extends never\n ? TRight\n : Omit<TRight, keyof TLeft & keyof TRight> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n }\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport type LooseReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\nexport type LooseAsyncReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn extends Promise<infer TReturn>\n ? TReturn\n : TReturn\n : never\n\n/**\n * Return the last element of an array.\n * Intended for non-empty arrays used within router internals.\n */\nexport function last<T>(arr: ReadonlyArray<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\n/**\n * Apply a value-or-updater to a previous value.\n * Accepts either a literal value or a function of the previous value.\n */\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nconst hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nconst createNull = () => Object.create(null)\nexport const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>\n replaceEqualDeep(prev, next, createNull)\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(\n prev: any,\n _next: T,\n _makeObj = () => ({}),\n _depth = 0,\n): T {\n if (isServer) {\n return _next\n }\n if (prev === _next) {\n return prev\n }\n\n if (_depth > 500) return _next\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next\n\n const prevItems = array ? prev : getEnumerableOwnKeys(prev)\n if (!prevItems) return next\n const nextItems = array ? next : getEnumerableOwnKeys(next)\n if (!nextItems) return next\n const prevSize = prevItems.length\n const nextSize = nextItems.length\n const copy: any = array ? new Array(nextSize) : _makeObj()\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n const p = prev[key]\n const n = next[key]\n\n if (p === n) {\n copy[key] = p\n if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++\n continue\n }\n\n if (\n p === null ||\n n === null ||\n typeof p !== 'object' ||\n typeof n !== 'object'\n ) {\n copy[key] = n\n continue\n }\n\n const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)\n copy[key] = v\n if (v === p) equalItems++\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n}\n\n/**\n * Equivalent to `Reflect.ownKeys`, but ensures that objects are \"clone-friendly\":\n * will return false if object has any non-enumerable properties.\n *\n * Optimized for the common case where objects have no symbol properties.\n */\nfunction getEnumerableOwnKeys(o: object) {\n const names = Object.getOwnPropertyNames(o)\n\n // Fast path: check all string property names are enumerable\n for (const name of names) {\n if (!isEnumerable.call(o, name)) return false\n }\n\n // Only check symbols if the object has any (most plain objects don't)\n const symbols = Object.getOwnPropertySymbols(o)\n\n // Fast path: no symbols, return names directly (avoids array allocation/concat)\n if (symbols.length === 0) return names\n\n // Slow path: has symbols, need to check and merge\n const keys: Array<string | symbol> = names\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) return false\n keys.push(symbol)\n }\n return keys\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\n/**\n * Check if a value is a \"plain\" array (no extra enumerable keys).\n */\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\n/**\n * Perform a deep equality check with options for partial comparison and\n * ignoring `undefined` values. Optimized for router state comparisons.\n */\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0, l = a.length; i < l; i++) {\n if (!deepEqual(a[i], b[i], opts)) return false\n }\n return true\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n\n if (opts?.partial) {\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n if (!deepEqual(a[k], b[k], opts)) return false\n }\n }\n return true\n }\n\n let aCount = 0\n if (!ignoreUndefined) {\n aCount = Object.keys(a).length\n } else {\n for (const k in a) {\n if (a[k] !== undefined) aCount++\n }\n }\n\n let bCount = 0\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n bCount++\n if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false\n }\n }\n\n return aCount === bCount\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\n/**\n * Create a promise with exposed resolve/reject and status fields.\n * Useful for coordinating async router lifecycle operations.\n */\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Heuristically detect dynamic import \"module not found\" errors\n * across major browsers for lazy route component handling.\n */\nexport function isModuleNotFoundError(error: any): boolean {\n // chrome: \"Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // firefox: \"error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // safari: \"Importing a module script failed.\"\n if (typeof error?.message !== 'string') return false\n return (\n error.message.startsWith('Failed to fetch dynamically imported module') ||\n error.message.startsWith('error loading dynamically imported module') ||\n error.message.startsWith('Importing a module script failed')\n )\n}\n\nexport function isPromise<T>(\n value: Promise<Awaited<T>> | T,\n): value is Promise<Awaited<T>> {\n return Boolean(\n value &&\n typeof value === 'object' &&\n typeof (value as Promise<T>).then === 'function',\n )\n}\n\nexport function findLast<T>(\n array: ReadonlyArray<T>,\n predicate: (item: T) => boolean,\n): T | undefined {\n for (let i = array.length - 1; i >= 0; i--) {\n const item = array[i]!\n if (predicate(item)) return item\n }\n return undefined\n}\n\n/**\n * Remove control characters that can cause open redirect vulnerabilities.\n * Characters like \\r (CR) and \\n (LF) can trick URL parsers into interpreting\n * paths like \"/\\r/evil.com\" as \"http://evil.com\".\n */\nfunction sanitizePathSegment(segment: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n return segment.replace(/[\\x00-\\x1f\\x7f]/g, '')\n}\n\nfunction decodeSegment(segment: string): string {\n let decoded: string\n try {\n decoded = decodeURI(segment)\n } catch {\n // if the decoding fails, try to decode the various parts leaving the malformed tags in place\n decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {\n try {\n return decodeURI(match)\n } catch {\n return match\n }\n })\n }\n return sanitizePathSegment(decoded)\n}\n\n/**\n * Default list of URL protocols to allow in links, redirects, and navigation.\n * Any absolute URL protocol not in this list is treated as dangerous by default.\n */\nexport const DEFAULT_PROTOCOL_ALLOWLIST = [\n // Standard web navigation\n 'http:',\n 'https:',\n\n // Common browser-safe actions\n 'mailto:',\n 'tel:',\n]\n\n/**\n * Check if a URL string uses a protocol that is not in the allowlist.\n * Returns true for blocked protocols like javascript:, blob:, data:, etc.\n *\n * The URL constructor correctly normalizes:\n * - Mixed case (JavaScript: → javascript:)\n * - Whitespace/control characters (java\\nscript: → javascript:)\n * - Leading whitespace\n *\n * For relative URLs (no protocol), returns false (safe).\n *\n * @param url - The URL string to check\n * @param allowlist - Set of protocols to allow\n * @returns true if the URL uses a protocol that is not allowed\n */\nexport function isDangerousProtocol(\n url: string,\n allowlist: Set<string>,\n): boolean {\n if (!url) return false\n\n try {\n // Use the URL constructor - it correctly normalizes protocols\n // per WHATWG URL spec, handling all bypass attempts automatically\n const parsed = new URL(url)\n return !allowlist.has(parsed.protocol)\n } catch {\n // URL constructor throws for relative URLs (no protocol)\n // These are safe - they can't execute scripts\n return false\n }\n}\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\nconst HTML_ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nconst HTML_ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\n/**\n * Escape HTML special characters in a string to prevent XSS attacks\n * when embedding strings in script tags during SSR.\n *\n * This is essential for preventing XSS vulnerabilities when user-controlled\n * content is embedded in inline scripts.\n */\nexport function escapeHtml(str: string): string {\n return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)\n}\n\nexport function decodePath(path: string) {\n if (!path) return { path, handledProtocolRelativeURL: false }\n\n // Fast path: most paths are already decoded and safe.\n // Only fall back to the slower scan/regex path when we see a '%' (encoded),\n // a backslash (explicitly handled), a control character, or a protocol-relative\n // prefix which needs collapsing.\n // eslint-disable-next-line no-control-regex\n if (!/[%\\\\\\x00-\\x1f\\x7f]/.test(path) && !path.startsWith('//')) {\n return { path, handledProtocolRelativeURL: false }\n }\n\n const re = /%25|%5C/gi\n let cursor = 0\n let result = ''\n let match\n while (null !== (match = re.exec(path))) {\n result += decodeSegment(path.slice(cursor, match.index)) + match[0]\n cursor = re.lastIndex\n }\n result = result + decodeSegment(cursor ? path.slice(cursor) : path)\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // After sanitizing control characters, paths like \"/\\r/evil.com\" become \"//evil.com\"\n // Collapse leading double slashes to a single slash\n let handledProtocolRelativeURL = false\n if (result.startsWith('//')) {\n handledProtocolRelativeURL = true\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return { path: result, handledProtocolRelativeURL }\n}\n\n/**\n * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.\n *\n * This function encodes:\n * - Whitespace characters (spaces → %20, tabs → %09, etc.)\n * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)\n *\n * It preserves:\n * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)\n * - ASCII special characters valid in URL paths (@, $, &, +, etc.)\n * - Forward slashes as path separators\n *\n * Used to generate proper href values for SSR without constructing URL objects.\n *\n * @example\n * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'\n * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'\n * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)\n */\nexport function encodePathLikeUrl(path: string): string {\n // Encode whitespace and non-ASCII characters that browsers encode in URLs\n\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n if (!/\\s|[^\\u0000-\\u007F]/.test(path)) return path\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n return path.replace(/\\s|[^\\u0000-\\u007F]/gu, encodeURIComponent)\n}\n\n/**\n * Builds the dev-mode CSS styles URL for route-scoped CSS collection.\n * Used by HeadContent components in all framework implementations to construct\n * the URL for the `/@tanstack-start/styles.css` endpoint.\n *\n * @param basepath - The router's basepath (may or may not have leading slash)\n * @param routeIds - Array of matched route IDs to include in the CSS collection\n * @returns The full URL path for the dev styles CSS endpoint\n */\nexport function buildDevStylesUrl(\n basepath: string,\n routeIds: Array<string>,\n): string {\n // Trim all leading and trailing slashes from basepath\n const trimmedBasepath = basepath.replace(/^\\/+|\\/+$/g, '')\n // Build normalized basepath: empty string for root, or '/path' for non-root\n const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`\n return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/load-matches.ts
CHANGED
|
@@ -657,11 +657,13 @@ const runLoader = async (
|
|
|
657
657
|
}
|
|
658
658
|
|
|
659
659
|
// Kick off the loader!
|
|
660
|
-
const
|
|
660
|
+
const routeLoader = route.options.loader
|
|
661
|
+
const loader =
|
|
662
|
+
typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
|
|
663
|
+
const loaderResult = loader?.(
|
|
661
664
|
getLoaderContext(inner, matchPromises, matchId, index, route),
|
|
662
665
|
)
|
|
663
|
-
const loaderResultIsPromise =
|
|
664
|
-
route.options.loader && isPromise(loaderResult)
|
|
666
|
+
const loaderResultIsPromise = !!loader && isPromise(loaderResult)
|
|
665
667
|
|
|
666
668
|
const willLoadSomething = !!(
|
|
667
669
|
loaderResultIsPromise ||
|
|
@@ -680,7 +682,7 @@ const runLoader = async (
|
|
|
680
682
|
}))
|
|
681
683
|
}
|
|
682
684
|
|
|
683
|
-
if (
|
|
685
|
+
if (loader) {
|
|
684
686
|
const loaderData = loaderResultIsPromise
|
|
685
687
|
? await loaderResult
|
|
686
688
|
: loaderResult
|
|
@@ -820,7 +822,11 @@ const loadRouteMatch = async (
|
|
|
820
822
|
(invalid || (shouldReload ?? staleMatchShouldReload))
|
|
821
823
|
if (preload && route.options.preload === false) {
|
|
822
824
|
// Do nothing
|
|
823
|
-
} else if (
|
|
825
|
+
} else if (
|
|
826
|
+
loaderShouldRunAsync &&
|
|
827
|
+
!inner.sync &&
|
|
828
|
+
shouldReloadInBackground
|
|
829
|
+
) {
|
|
824
830
|
loaderIsRunningAsync = true
|
|
825
831
|
;(async () => {
|
|
826
832
|
try {
|
|
@@ -835,7 +841,7 @@ const loadRouteMatch = async (
|
|
|
835
841
|
}
|
|
836
842
|
}
|
|
837
843
|
})()
|
|
838
|
-
} else if (status !== 'success' ||
|
|
844
|
+
} else if (status !== 'success' || loaderShouldRunAsync) {
|
|
839
845
|
await runLoader(inner, matchPromises, matchId, index, route)
|
|
840
846
|
} else {
|
|
841
847
|
syncMatchContext(inner, matchId, index)
|
|
@@ -846,6 +852,12 @@ const loadRouteMatch = async (
|
|
|
846
852
|
let loaderShouldRunAsync = false
|
|
847
853
|
let loaderIsRunningAsync = false
|
|
848
854
|
const route = inner.router.looseRoutesById[routeId]!
|
|
855
|
+
const routeLoader = route.options.loader
|
|
856
|
+
const shouldReloadInBackground =
|
|
857
|
+
((typeof routeLoader === 'function'
|
|
858
|
+
? undefined
|
|
859
|
+
: routeLoader?.staleReloadMode) ??
|
|
860
|
+
inner.router.options.defaultStaleReloadMode) !== 'blocking'
|
|
849
861
|
|
|
850
862
|
if (shouldSkipLoader(inner, matchId)) {
|
|
851
863
|
const match = inner.router.getMatch(matchId)
|
|
@@ -871,7 +883,12 @@ const loadRouteMatch = async (
|
|
|
871
883
|
// do not block if we already have stale data we can show
|
|
872
884
|
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
873
885
|
// and we don't want to swallow errors
|
|
874
|
-
if (
|
|
886
|
+
if (
|
|
887
|
+
prevMatch.status === 'success' &&
|
|
888
|
+
!inner.sync &&
|
|
889
|
+
!prevMatch.preload &&
|
|
890
|
+
shouldReloadInBackground
|
|
891
|
+
) {
|
|
875
892
|
return prevMatch
|
|
876
893
|
}
|
|
877
894
|
await prevMatch._nonReactive.loaderPromise
|
|
@@ -896,7 +896,7 @@ function extractParams<T extends RouteLike>(
|
|
|
896
896
|
): [rawParams: Record<string, string>, state: ParamExtractionState] {
|
|
897
897
|
const list = buildBranch(leaf.node)
|
|
898
898
|
let nodeParts: Array<string> | null = null
|
|
899
|
-
const rawParams: Record<string, string> =
|
|
899
|
+
const rawParams: Record<string, string> = Object.create(null)
|
|
900
900
|
/** which segment of the path we're currently processing */
|
|
901
901
|
let partIndex = leaf.extract?.part ?? 0
|
|
902
902
|
/** which node of the route tree branch we're currently processing */
|
|
@@ -1330,8 +1330,8 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1330
1330
|
sliceIndex += parts[i]!.length
|
|
1331
1331
|
}
|
|
1332
1332
|
const splat = sliceIndex === path.length ? '/' : path.slice(sliceIndex)
|
|
1333
|
-
bestFuzzy.rawParams ??=
|
|
1334
|
-
bestFuzzy.rawParams['**'] = decodeURIComponent(splat)
|
|
1333
|
+
bestFuzzy.rawParams ??= Object.create(null)
|
|
1334
|
+
bestFuzzy.rawParams!['**'] = decodeURIComponent(splat)
|
|
1335
1335
|
return bestFuzzy
|
|
1336
1336
|
}
|
|
1337
1337
|
|
|
@@ -1348,7 +1348,11 @@ function validateMatchParams<T extends RouteLike>(
|
|
|
1348
1348
|
frame.rawParams = rawParams
|
|
1349
1349
|
frame.extract = state
|
|
1350
1350
|
const parsed = frame.node.parse!(rawParams)
|
|
1351
|
-
frame.parsedParams = Object.assign(
|
|
1351
|
+
frame.parsedParams = Object.assign(
|
|
1352
|
+
Object.create(null),
|
|
1353
|
+
frame.parsedParams,
|
|
1354
|
+
parsed,
|
|
1355
|
+
)
|
|
1352
1356
|
return true
|
|
1353
1357
|
} catch {
|
|
1354
1358
|
return null
|
package/src/path.ts
CHANGED
|
@@ -279,7 +279,7 @@ export function interpolatePath({
|
|
|
279
279
|
// Tracking if any params are missing in the `params` object
|
|
280
280
|
// when interpolating the path
|
|
281
281
|
let isMissingParams = false
|
|
282
|
-
const usedParams: Record<string, unknown> =
|
|
282
|
+
const usedParams: Record<string, unknown> = Object.create(null)
|
|
283
283
|
|
|
284
284
|
if (!path || path === '/')
|
|
285
285
|
return { interpolatedPath: '/', usedParams, isMissingParams }
|
package/src/qss.ts
CHANGED
|
@@ -64,7 +64,7 @@ function toValue(str: unknown) {
|
|
|
64
64
|
export function decode(str: any): any {
|
|
65
65
|
const searchParams = new URLSearchParams(str)
|
|
66
66
|
|
|
67
|
-
const result: Record<string, unknown> =
|
|
67
|
+
const result: Record<string, unknown> = Object.create(null)
|
|
68
68
|
|
|
69
69
|
for (const [key, value] of searchParams.entries()) {
|
|
70
70
|
const previousValue = result[key]
|
package/src/route.ts
CHANGED
|
@@ -292,11 +292,44 @@ export type ResolveRouteContext<TRouteContextFn, TBeforeLoadFn> = Assign<
|
|
|
292
292
|
ContextAsyncReturnType<TBeforeLoadFn>
|
|
293
293
|
>
|
|
294
294
|
|
|
295
|
+
export type ResolveRouteLoaderFn<TLoaderFn> = TLoaderFn extends {
|
|
296
|
+
handler: infer THandler
|
|
297
|
+
}
|
|
298
|
+
? THandler
|
|
299
|
+
: TLoaderFn
|
|
300
|
+
|
|
301
|
+
export type RouteLoaderObject<
|
|
302
|
+
TRegister,
|
|
303
|
+
TParentRoute extends AnyRoute = AnyRoute,
|
|
304
|
+
TId extends string = string,
|
|
305
|
+
TParams = {},
|
|
306
|
+
TLoaderDeps = {},
|
|
307
|
+
TRouterContext = {},
|
|
308
|
+
TRouteContextFn = AnyContext,
|
|
309
|
+
TBeforeLoadFn = AnyContext,
|
|
310
|
+
TServerMiddlewares = unknown,
|
|
311
|
+
THandlers = undefined,
|
|
312
|
+
> = {
|
|
313
|
+
handler: RouteLoaderFn<
|
|
314
|
+
TRegister,
|
|
315
|
+
TParentRoute,
|
|
316
|
+
TId,
|
|
317
|
+
TParams,
|
|
318
|
+
TLoaderDeps,
|
|
319
|
+
TRouterContext,
|
|
320
|
+
TRouteContextFn,
|
|
321
|
+
TBeforeLoadFn,
|
|
322
|
+
TServerMiddlewares,
|
|
323
|
+
THandlers
|
|
324
|
+
>
|
|
325
|
+
staleReloadMode?: LoaderStaleReloadMode
|
|
326
|
+
}
|
|
327
|
+
|
|
295
328
|
export type ResolveLoaderData<TLoaderFn> = unknown extends TLoaderFn
|
|
296
329
|
? TLoaderFn
|
|
297
|
-
: LooseAsyncReturnType<TLoaderFn
|
|
330
|
+
: LooseAsyncReturnType<ResolveRouteLoaderFn<TLoaderFn>> extends never
|
|
298
331
|
? undefined
|
|
299
|
-
: LooseAsyncReturnType<TLoaderFn
|
|
332
|
+
: LooseAsyncReturnType<ResolveRouteLoaderFn<TLoaderFn>>
|
|
300
333
|
|
|
301
334
|
export type ResolveFullSearchSchema<
|
|
302
335
|
TParentRoute extends AnyRoute,
|
|
@@ -1010,8 +1043,7 @@ export interface FilebaseRouteOptionsInterface<
|
|
|
1010
1043
|
|
|
1011
1044
|
loader?: Constrain<
|
|
1012
1045
|
TLoaderFn,
|
|
1013
|
-
|
|
1014
|
-
ctx: LoaderFnContext<
|
|
1046
|
+
| RouteLoaderFn<
|
|
1015
1047
|
TRegister,
|
|
1016
1048
|
TParentRoute,
|
|
1017
1049
|
TId,
|
|
@@ -1022,13 +1054,19 @@ export interface FilebaseRouteOptionsInterface<
|
|
|
1022
1054
|
TBeforeLoadFn,
|
|
1023
1055
|
TServerMiddlewares,
|
|
1024
1056
|
THandlers
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1057
|
+
>
|
|
1058
|
+
| RouteLoaderObject<
|
|
1059
|
+
TRegister,
|
|
1060
|
+
TParentRoute,
|
|
1061
|
+
TId,
|
|
1062
|
+
TParams,
|
|
1063
|
+
TLoaderDeps,
|
|
1064
|
+
TRouterContext,
|
|
1065
|
+
TRouteContextFn,
|
|
1066
|
+
TBeforeLoadFn,
|
|
1067
|
+
TServerMiddlewares,
|
|
1068
|
+
THandlers
|
|
1069
|
+
>
|
|
1032
1070
|
>
|
|
1033
1071
|
}
|
|
1034
1072
|
|
|
@@ -1415,6 +1453,45 @@ export type RouteLoaderFn<
|
|
|
1415
1453
|
>,
|
|
1416
1454
|
) => any
|
|
1417
1455
|
|
|
1456
|
+
export type LoaderStaleReloadMode = 'background' | 'blocking'
|
|
1457
|
+
|
|
1458
|
+
export type RouteLoaderEntry<
|
|
1459
|
+
TRegister,
|
|
1460
|
+
TParentRoute extends AnyRoute = AnyRoute,
|
|
1461
|
+
TId extends string = string,
|
|
1462
|
+
TParams = {},
|
|
1463
|
+
TLoaderDeps = {},
|
|
1464
|
+
TRouterContext = {},
|
|
1465
|
+
TRouteContextFn = AnyContext,
|
|
1466
|
+
TBeforeLoadFn = AnyContext,
|
|
1467
|
+
TServerMiddlewares = unknown,
|
|
1468
|
+
THandlers = undefined,
|
|
1469
|
+
> =
|
|
1470
|
+
| RouteLoaderFn<
|
|
1471
|
+
TRegister,
|
|
1472
|
+
TParentRoute,
|
|
1473
|
+
TId,
|
|
1474
|
+
TParams,
|
|
1475
|
+
TLoaderDeps,
|
|
1476
|
+
TRouterContext,
|
|
1477
|
+
TRouteContextFn,
|
|
1478
|
+
TBeforeLoadFn,
|
|
1479
|
+
TServerMiddlewares,
|
|
1480
|
+
THandlers
|
|
1481
|
+
>
|
|
1482
|
+
| RouteLoaderObject<
|
|
1483
|
+
TRegister,
|
|
1484
|
+
TParentRoute,
|
|
1485
|
+
TId,
|
|
1486
|
+
TParams,
|
|
1487
|
+
TLoaderDeps,
|
|
1488
|
+
TRouterContext,
|
|
1489
|
+
TRouteContextFn,
|
|
1490
|
+
TBeforeLoadFn,
|
|
1491
|
+
TServerMiddlewares,
|
|
1492
|
+
THandlers
|
|
1493
|
+
>
|
|
1494
|
+
|
|
1418
1495
|
export interface LoaderFnContext<
|
|
1419
1496
|
in out TRegister = unknown,
|
|
1420
1497
|
in out TParentRoute extends AnyRoute = AnyRoute,
|
package/src/router.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
functionalUpdate,
|
|
13
13
|
isDangerousProtocol,
|
|
14
14
|
last,
|
|
15
|
+
nullReplaceEqualDeep,
|
|
15
16
|
replaceEqualDeep,
|
|
16
17
|
} from './utils'
|
|
17
18
|
import {
|
|
@@ -71,6 +72,7 @@ import type {
|
|
|
71
72
|
AnyContext,
|
|
72
73
|
AnyRoute,
|
|
73
74
|
AnyRouteWithContext,
|
|
75
|
+
LoaderStaleReloadMode,
|
|
74
76
|
MakeRemountDepsOptionsUnion,
|
|
75
77
|
RouteContextOptions,
|
|
76
78
|
RouteLike,
|
|
@@ -237,6 +239,15 @@ export interface RouterOptions<
|
|
|
237
239
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
|
|
238
240
|
*/
|
|
239
241
|
defaultStaleTime?: number
|
|
242
|
+
/**
|
|
243
|
+
* The default stale reload mode a route loader should use if no `loader.staleReloadMode` is provided.
|
|
244
|
+
*
|
|
245
|
+
* `'background'` preserves the current stale-while-revalidate behavior.
|
|
246
|
+
* `'blocking'` waits for stale loader reloads to complete before resolving navigation.
|
|
247
|
+
*
|
|
248
|
+
* @default 'background'
|
|
249
|
+
*/
|
|
250
|
+
defaultStaleReloadMode?: LoaderStaleReloadMode
|
|
240
251
|
/**
|
|
241
252
|
* The default `preloadStaleTime` a route should use if no preloadStaleTime is provided.
|
|
242
253
|
*
|
|
@@ -1295,7 +1306,7 @@ export class RouterCore<
|
|
|
1295
1306
|
pathname: decodePath(pathname).path,
|
|
1296
1307
|
external: false,
|
|
1297
1308
|
searchStr,
|
|
1298
|
-
search:
|
|
1309
|
+
search: nullReplaceEqualDeep(
|
|
1299
1310
|
previousLocation?.search,
|
|
1300
1311
|
parsedSearch,
|
|
1301
1312
|
) as any,
|
|
@@ -1324,7 +1335,10 @@ export class RouterCore<
|
|
|
1324
1335
|
pathname: decodePath(url.pathname).path,
|
|
1325
1336
|
external: !!this.rewrite && url.origin !== this.origin,
|
|
1326
1337
|
searchStr,
|
|
1327
|
-
search:
|
|
1338
|
+
search: nullReplaceEqualDeep(
|
|
1339
|
+
previousLocation?.search,
|
|
1340
|
+
parsedSearch,
|
|
1341
|
+
) as any,
|
|
1328
1342
|
hash: decodePath(url.hash.slice(1)).path,
|
|
1329
1343
|
state: replaceEqualDeep(previousLocation?.state, state),
|
|
1330
1344
|
}
|
|
@@ -1549,8 +1563,8 @@ export class RouterCore<
|
|
|
1549
1563
|
params: previousMatch?.params ?? routeParams,
|
|
1550
1564
|
_strictParams: strictParams,
|
|
1551
1565
|
search: previousMatch
|
|
1552
|
-
?
|
|
1553
|
-
:
|
|
1566
|
+
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1567
|
+
: nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
1554
1568
|
_strictSearch: strictMatchSearch,
|
|
1555
1569
|
}
|
|
1556
1570
|
} else {
|
|
@@ -1572,7 +1586,7 @@ export class RouterCore<
|
|
|
1572
1586
|
pathname: interpolatedPath,
|
|
1573
1587
|
updatedAt: Date.now(),
|
|
1574
1588
|
search: previousMatch
|
|
1575
|
-
?
|
|
1589
|
+
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1576
1590
|
: preMatchSearch,
|
|
1577
1591
|
_strictSearch: strictMatchSearch,
|
|
1578
1592
|
searchError: undefined,
|
|
@@ -1630,7 +1644,7 @@ export class RouterCore<
|
|
|
1630
1644
|
// Update the match's params
|
|
1631
1645
|
const previousMatch = previousMatchesByRouteId.get(match.routeId)
|
|
1632
1646
|
match.params = previousMatch
|
|
1633
|
-
?
|
|
1647
|
+
? nullReplaceEqualDeep(previousMatch.params, routeParams)
|
|
1634
1648
|
: routeParams
|
|
1635
1649
|
|
|
1636
1650
|
if (!existingMatch) {
|
|
@@ -1729,7 +1743,10 @@ export class RouterCore<
|
|
|
1729
1743
|
params = lastStateMatch.params
|
|
1730
1744
|
} else {
|
|
1731
1745
|
// Parse params through the route chain
|
|
1732
|
-
const strictParams: Record<string, unknown> =
|
|
1746
|
+
const strictParams: Record<string, unknown> = Object.assign(
|
|
1747
|
+
Object.create(null),
|
|
1748
|
+
routeParams,
|
|
1749
|
+
)
|
|
1733
1750
|
for (const route of matchedRoutes) {
|
|
1734
1751
|
try {
|
|
1735
1752
|
extractStrictParams(
|
|
@@ -1836,7 +1853,10 @@ export class RouterCore<
|
|
|
1836
1853
|
// From search should always use the current location
|
|
1837
1854
|
const fromSearch = lightweightResult.search
|
|
1838
1855
|
// Same with params. It can't hurt to provide as many as possible
|
|
1839
|
-
const fromParams =
|
|
1856
|
+
const fromParams = Object.assign(
|
|
1857
|
+
Object.create(null),
|
|
1858
|
+
lightweightResult.params,
|
|
1859
|
+
)
|
|
1840
1860
|
|
|
1841
1861
|
// Resolve the next to
|
|
1842
1862
|
// ensure this includes the basePath if set
|
|
@@ -1847,7 +1867,7 @@ export class RouterCore<
|
|
|
1847
1867
|
// Resolve the next params
|
|
1848
1868
|
const nextParams =
|
|
1849
1869
|
dest.params === false || dest.params === null
|
|
1850
|
-
?
|
|
1870
|
+
? Object.create(null)
|
|
1851
1871
|
: (dest.params ?? true) === true
|
|
1852
1872
|
? fromParams
|
|
1853
1873
|
: Object.assign(
|
|
@@ -1933,7 +1953,7 @@ export class RouterCore<
|
|
|
1933
1953
|
})
|
|
1934
1954
|
|
|
1935
1955
|
// Replace the equal deep
|
|
1936
|
-
nextSearch =
|
|
1956
|
+
nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch)
|
|
1937
1957
|
|
|
1938
1958
|
// Stringify the next search
|
|
1939
1959
|
const searchStr = this.options.stringifySearch(nextSearch)
|
|
@@ -2013,7 +2033,7 @@ export class RouterCore<
|
|
|
2013
2033
|
let maskedNext = maskedDest ? build(maskedDest) : undefined
|
|
2014
2034
|
|
|
2015
2035
|
if (!maskedNext) {
|
|
2016
|
-
const params =
|
|
2036
|
+
const params = Object.create(null)
|
|
2017
2037
|
|
|
2018
2038
|
if (this.options.routeMasks) {
|
|
2019
2039
|
const match = findFlatMatch<RouteMask<TRouteTree>>(
|
|
@@ -2032,7 +2052,7 @@ export class RouterCore<
|
|
|
2032
2052
|
// Otherwise, use the matched params or the provided params value
|
|
2033
2053
|
const nextParams =
|
|
2034
2054
|
maskParams === false || maskParams === null
|
|
2035
|
-
?
|
|
2055
|
+
? Object.create(null)
|
|
2036
2056
|
: (maskParams ?? true) === true
|
|
2037
2057
|
? params
|
|
2038
2058
|
: Object.assign(params, functionalUpdate(maskParams, params))
|
|
@@ -3013,7 +3033,7 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3013
3033
|
routesById: Record<string, TRouteLike>
|
|
3014
3034
|
processedTree: ProcessedTree<any, any, any>
|
|
3015
3035
|
}) {
|
|
3016
|
-
const routeParams: Record<string, string> =
|
|
3036
|
+
const routeParams: Record<string, string> = Object.create(null)
|
|
3017
3037
|
const trimmedPath = trimPathRight(pathname)
|
|
3018
3038
|
|
|
3019
3039
|
let foundRoute: TRouteLike | undefined = undefined
|
|
@@ -3022,7 +3042,7 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3022
3042
|
if (match) {
|
|
3023
3043
|
foundRoute = match.route
|
|
3024
3044
|
Object.assign(routeParams, match.rawParams) // Copy params, because they're cached
|
|
3025
|
-
parsedParams = Object.assign(
|
|
3045
|
+
parsedParams = Object.assign(Object.create(null), match.parsedParams)
|
|
3026
3046
|
}
|
|
3027
3047
|
|
|
3028
3048
|
const matchedRoutes = match?.branch || [routesById[rootRouteId]!]
|
package/src/utils.ts
CHANGED
|
@@ -215,13 +215,22 @@ export function functionalUpdate<TPrevious, TResult = TPrevious>(
|
|
|
215
215
|
const hasOwn = Object.prototype.hasOwnProperty
|
|
216
216
|
const isEnumerable = Object.prototype.propertyIsEnumerable
|
|
217
217
|
|
|
218
|
+
const createNull = () => Object.create(null)
|
|
219
|
+
export const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>
|
|
220
|
+
replaceEqualDeep(prev, next, createNull)
|
|
221
|
+
|
|
218
222
|
/**
|
|
219
223
|
* This function returns `prev` if `_next` is deeply equal.
|
|
220
224
|
* If not, it will replace any deeply equal children of `b` with those of `a`.
|
|
221
225
|
* This can be used for structural sharing between immutable JSON values for example.
|
|
222
226
|
* Do not use this with signals
|
|
223
227
|
*/
|
|
224
|
-
export function replaceEqualDeep<T>(
|
|
228
|
+
export function replaceEqualDeep<T>(
|
|
229
|
+
prev: any,
|
|
230
|
+
_next: T,
|
|
231
|
+
_makeObj = () => ({}),
|
|
232
|
+
_depth = 0,
|
|
233
|
+
): T {
|
|
225
234
|
if (isServer) {
|
|
226
235
|
return _next
|
|
227
236
|
}
|
|
@@ -243,7 +252,7 @@ export function replaceEqualDeep<T>(prev: any, _next: T, _depth = 0): T {
|
|
|
243
252
|
if (!nextItems) return next
|
|
244
253
|
const prevSize = prevItems.length
|
|
245
254
|
const nextSize = nextItems.length
|
|
246
|
-
const copy: any = array ? new Array(nextSize) :
|
|
255
|
+
const copy: any = array ? new Array(nextSize) : _makeObj()
|
|
247
256
|
|
|
248
257
|
let equalItems = 0
|
|
249
258
|
|
|
@@ -268,7 +277,7 @@ export function replaceEqualDeep<T>(prev: any, _next: T, _depth = 0): T {
|
|
|
268
277
|
continue
|
|
269
278
|
}
|
|
270
279
|
|
|
271
|
-
const v = replaceEqualDeep(p, n, _depth + 1)
|
|
280
|
+
const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)
|
|
272
281
|
copy[key] = v
|
|
273
282
|
if (v === p) equalItems++
|
|
274
283
|
}
|