one 1.9.8 → 1.9.9

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.
Files changed (94) hide show
  1. package/dist/cjs/useLoader.cjs +34 -2
  2. package/dist/cjs/useLoader.js +34 -3
  3. package/dist/cjs/useLoader.js.map +2 -2
  4. package/dist/cjs/useLoader.native.js +40 -2
  5. package/dist/cjs/useLoader.native.js.map +1 -1
  6. package/dist/cjs/useMatches.cjs +10 -0
  7. package/dist/cjs/useMatches.js +8 -0
  8. package/dist/cjs/useMatches.js.map +1 -1
  9. package/dist/cjs/useMatches.native.js +28 -0
  10. package/dist/cjs/useMatches.native.js.map +1 -1
  11. package/dist/cjs/utils/cleanUrl.cjs +1 -1
  12. package/dist/cjs/utils/cleanUrl.js +1 -1
  13. package/dist/cjs/utils/cleanUrl.js.map +1 -1
  14. package/dist/cjs/utils/cleanUrl.native.js +1 -1
  15. package/dist/cjs/utils/cleanUrl.native.js.map +1 -1
  16. package/dist/cjs/vite/constants.cjs +7 -3
  17. package/dist/cjs/vite/constants.js +7 -2
  18. package/dist/cjs/vite/constants.js.map +1 -1
  19. package/dist/cjs/vite/constants.native.js +7 -3
  20. package/dist/cjs/vite/constants.native.js.map +1 -1
  21. package/dist/cjs/vite/plugins/clientTreeShakePlugin.cjs +12 -3
  22. package/dist/cjs/vite/plugins/clientTreeShakePlugin.js +12 -3
  23. package/dist/cjs/vite/plugins/clientTreeShakePlugin.js.map +1 -1
  24. package/dist/cjs/vite/plugins/clientTreeShakePlugin.native.js +11 -3
  25. package/dist/cjs/vite/plugins/clientTreeShakePlugin.native.js.map +1 -1
  26. package/dist/cjs/vite/plugins/clientTreeShakePlugin.test.cjs +81 -0
  27. package/dist/cjs/vite/plugins/clientTreeShakePlugin.test.js +87 -0
  28. package/dist/cjs/vite/plugins/clientTreeShakePlugin.test.js.map +1 -1
  29. package/dist/cjs/vite/plugins/clientTreeShakePlugin.test.native.js +87 -0
  30. package/dist/cjs/vite/plugins/clientTreeShakePlugin.test.native.js.map +1 -1
  31. package/dist/cjs/vite/replaceLoader.cjs +4 -2
  32. package/dist/cjs/vite/replaceLoader.js +4 -3
  33. package/dist/cjs/vite/replaceLoader.js.map +1 -1
  34. package/dist/cjs/vite/replaceLoader.native.js +3 -1
  35. package/dist/cjs/vite/replaceLoader.native.js.map +1 -1
  36. package/dist/esm/useLoader.js +34 -2
  37. package/dist/esm/useLoader.js.map +2 -2
  38. package/dist/esm/useLoader.mjs +34 -3
  39. package/dist/esm/useLoader.mjs.map +1 -1
  40. package/dist/esm/useLoader.native.js +40 -3
  41. package/dist/esm/useLoader.native.js.map +1 -1
  42. package/dist/esm/useMatches.js +8 -0
  43. package/dist/esm/useMatches.js.map +1 -1
  44. package/dist/esm/useMatches.mjs +8 -1
  45. package/dist/esm/useMatches.mjs.map +1 -1
  46. package/dist/esm/useMatches.native.js +26 -1
  47. package/dist/esm/useMatches.native.js.map +1 -1
  48. package/dist/esm/utils/cleanUrl.js +1 -1
  49. package/dist/esm/utils/cleanUrl.js.map +1 -1
  50. package/dist/esm/utils/cleanUrl.mjs +1 -1
  51. package/dist/esm/utils/cleanUrl.mjs.map +1 -1
  52. package/dist/esm/utils/cleanUrl.native.js +1 -1
  53. package/dist/esm/utils/cleanUrl.native.js.map +1 -1
  54. package/dist/esm/vite/constants.js +7 -2
  55. package/dist/esm/vite/constants.js.map +1 -1
  56. package/dist/esm/vite/constants.mjs +6 -3
  57. package/dist/esm/vite/constants.mjs.map +1 -1
  58. package/dist/esm/vite/constants.native.js +6 -3
  59. package/dist/esm/vite/constants.native.js.map +1 -1
  60. package/dist/esm/vite/plugins/clientTreeShakePlugin.js +13 -4
  61. package/dist/esm/vite/plugins/clientTreeShakePlugin.js.map +1 -1
  62. package/dist/esm/vite/plugins/clientTreeShakePlugin.mjs +13 -4
  63. package/dist/esm/vite/plugins/clientTreeShakePlugin.mjs.map +1 -1
  64. package/dist/esm/vite/plugins/clientTreeShakePlugin.native.js +12 -4
  65. package/dist/esm/vite/plugins/clientTreeShakePlugin.native.js.map +1 -1
  66. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.js +87 -0
  67. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.js.map +1 -1
  68. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.mjs +81 -0
  69. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.mjs.map +1 -1
  70. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.native.js +87 -0
  71. package/dist/esm/vite/plugins/clientTreeShakePlugin.test.native.js.map +1 -1
  72. package/dist/esm/vite/replaceLoader.js +4 -3
  73. package/dist/esm/vite/replaceLoader.js.map +1 -1
  74. package/dist/esm/vite/replaceLoader.mjs +4 -2
  75. package/dist/esm/vite/replaceLoader.mjs.map +1 -1
  76. package/dist/esm/vite/replaceLoader.native.js +3 -1
  77. package/dist/esm/vite/replaceLoader.native.js.map +1 -1
  78. package/package.json +9 -9
  79. package/src/useLoader.ts +69 -0
  80. package/src/useMatches.ts +13 -2
  81. package/src/utils/cleanUrl.ts +1 -1
  82. package/src/vite/constants.ts +4 -0
  83. package/src/vite/plugins/clientTreeShakePlugin.test.ts +121 -0
  84. package/src/vite/plugins/clientTreeShakePlugin.ts +13 -3
  85. package/src/vite/replaceLoader.ts +23 -9
  86. package/types/useLoader.d.ts +10 -0
  87. package/types/useLoader.d.ts.map +1 -1
  88. package/types/useMatches.d.ts +7 -0
  89. package/types/useMatches.d.ts.map +1 -1
  90. package/types/vite/constants.d.ts +1 -0
  91. package/types/vite/constants.d.ts.map +1 -1
  92. package/types/vite/plugins/clientTreeShakePlugin.d.ts +1 -1
  93. package/types/vite/plugins/clientTreeShakePlugin.d.ts.map +1 -1
  94. package/types/vite/replaceLoader.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "one",
3
- "version": "1.9.8",
3
+ "version": "1.9.9",
4
4
  "license": "BSD-3-Clause",
5
5
  "sideEffects": [
6
6
  "setup.mjs",
@@ -154,17 +154,17 @@
154
154
  "@react-navigation/routers": "~7.5.1",
155
155
  "@swc/core": "^1.14.0",
156
156
  "@ungap/structured-clone": "^1.2.0",
157
- "@vxrn/color-scheme": "1.9.8",
158
- "@vxrn/compiler": "1.9.8",
159
- "@vxrn/resolve": "1.9.8",
160
- "@vxrn/tslib-lite": "1.9.8",
161
- "@vxrn/use-isomorphic-layout-effect": "1.9.8",
162
- "@vxrn/vite-plugin-metro": "1.9.8",
157
+ "@vxrn/color-scheme": "1.9.9",
158
+ "@vxrn/compiler": "1.9.9",
159
+ "@vxrn/resolve": "1.9.9",
160
+ "@vxrn/tslib-lite": "1.9.9",
161
+ "@vxrn/use-isomorphic-layout-effect": "1.9.9",
162
+ "@vxrn/vite-plugin-metro": "1.9.9",
163
163
  "babel-dead-code-elimination": "1.0.10",
164
164
  "babel-plugin-module-resolver": "^5.0.2",
165
165
  "citty": "^0.1.6",
166
166
  "core-js": "^3.38.1",
167
- "create-vxrn": "1.9.8",
167
+ "create-vxrn": "1.9.9",
168
168
  "escape-string-regexp": "^5.0.0",
169
169
  "expo-linking": "~8.0.8",
170
170
  "expo-modules-core": "~3.0.24",
@@ -189,7 +189,7 @@
189
189
  "use-latest-callback": "^0.2.3",
190
190
  "vite": "^7.1.12",
191
191
  "vite-tsconfig-paths": "^6.0.5",
192
- "vxrn": "1.9.8",
192
+ "vxrn": "1.9.9",
193
193
  "ws": "^8.18.0",
194
194
  "xxhashjs": "^0.2.2"
195
195
  },
package/src/useLoader.ts CHANGED
@@ -4,6 +4,7 @@ import { useParams, usePathname } from './hooks'
4
4
  import { findNearestNotFoundRoute, setNotFoundState } from './notFoundState'
5
5
  import { router } from './router/imperative-api'
6
6
  import { preloadedLoaderData, preloadingLoader, routeNode } from './router/router'
7
+ import { subscribeToClientMatches, getClientMatchesSnapshot, updateMatchLoaderData } from './useMatches'
7
8
  import { getLoaderPath } from './utils/cleanUrl'
8
9
  import { dynamicImport } from './utils/dynamicImport'
9
10
  import { weakKey } from './utils/weakKey'
@@ -192,6 +193,14 @@ export async function refetchLoader(pathname: string): Promise<void> {
192
193
  hasLoadedOnce: true,
193
194
  })
194
195
 
196
+ // also sync to the page match in clientMatches so components using the
197
+ // match-based data path (useLoader with routeId stub) see the update
198
+ const currentMatches = getClientMatchesSnapshot()
199
+ const pageMatch = currentMatches[currentMatches.length - 1]
200
+ if (pageMatch && pageMatch.pathname === pathname) {
201
+ updateMatchLoaderData(pageMatch.routeId, result)
202
+ }
203
+
195
204
  recordLoaderTiming?.({
196
205
  path: pathname,
197
206
  startTime,
@@ -225,6 +234,28 @@ if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
225
234
  ;(window as any).__oneRefetchLoader = refetchLoader
226
235
  }
227
236
 
237
+ /**
238
+ * Refetch the loader data for a specific match (identified by routeId) by fetching
239
+ * the current page's loader JS and updating only that match entry in clientMatches.
240
+ *
241
+ * Note: the server's loader JS endpoint runs the *page* loader, so for layout
242
+ * routeIds this fetches fresh page data and stores it on the layout match. A
243
+ * dedicated per-layout refetch endpoint would be needed to truly re-run a layout
244
+ * loader in isolation; that can be added in a follow-up.
245
+ */
246
+ export async function refetchMatchLoader(routeId: string, currentPath: string): Promise<void> {
247
+ const cacheBust = `${Date.now()}`
248
+ const loaderJSUrl = getLoaderPath(currentPath, true, cacheBust)
249
+
250
+ const module = await dynamicImport(loaderJSUrl)?.catch(() => null)
251
+ if (!module?.loader) return
252
+
253
+ const result = await module.loader()
254
+ if (result?.__oneRedirect || result?.__oneError) return
255
+
256
+ updateMatchLoaderData(routeId, result)
257
+ }
258
+
228
259
  /**
229
260
  * Access loader data with full state control including refetch capability.
230
261
  * Use this when you need loading state or refetch; use `useLoader` for just data.
@@ -281,11 +312,26 @@ export function useLoaderState<
281
312
  return { data: serverData, refetch: async () => {}, state: 'idle' } as any
282
313
  }
283
314
 
315
+ // detect if the loader stub returns a routeId string (set by clientTreeShakePlugin)
316
+ // this enables looking up data from the matches array (needed for layout loaders)
317
+ const matchRouteId = loader ? (() => {
318
+ const result = loader()
319
+ return typeof result === 'string' && result.startsWith('./') ? result : null
320
+ })() : null
321
+
322
+ // subscribe to clientMatches changes so refetch triggers re-render
323
+ const clientMatches = useSyncExternalStore(
324
+ subscribeToClientMatches,
325
+ getClientMatchesSnapshot,
326
+ getClientMatchesSnapshot
327
+ )
328
+
284
329
  // preloaded data from SSR/SSG - only use if server context path matches current path
285
330
  const serverContextPath = loaderPropsFromServerContext?.path
286
331
  const preloadedData =
287
332
  serverContextPath === currentPath ? loaderDataFromServerContext : undefined
288
333
 
334
+ // all hooks must be called unconditionally to satisfy React's rules of hooks
289
335
  const loaderStateEntry = useSyncExternalStore(
290
336
  subscribe,
291
337
  () => getLoaderState(currentPath, preloadedData),
@@ -294,6 +340,29 @@ export function useLoaderState<
294
340
 
295
341
  const refetch = useCallback(() => refetchLoader(currentPath), [currentPath])
296
342
 
343
+ // if the loader returns a routeId, look up data from matches instead of loaderState
344
+ // only use this path when the match has data (layouts always have data preserved from SSR;
345
+ // pages might have null loaderData during client navigation if preloading hasn't completed,
346
+ // so they fall through to the regular loaderState path which handles suspension/loading)
347
+ if (matchRouteId) {
348
+ const match = clientMatches.find((m) => m.routeId === matchRouteId)
349
+ if (match && match.loaderData != null) {
350
+ return {
351
+ data: match.loaderData,
352
+ // refetch updates both loaderState (for useLoaderState() consumers without a loader)
353
+ // and the match entry (for this component and useMatches consumers)
354
+ refetch: async () => {
355
+ await refetchLoader(currentPath)
356
+ const fresh = loaderState[currentPath]
357
+ if (fresh?.data != null) {
358
+ updateMatchLoaderData(matchRouteId, fresh.data)
359
+ }
360
+ },
361
+ state: loaderStateEntry.state,
362
+ } as any
363
+ }
364
+ }
365
+
297
366
  // no loader, just return state/refetch for the path
298
367
  if (!loader) {
299
368
  return {
package/src/useMatches.ts CHANGED
@@ -11,12 +11,12 @@ export type RouteMatch = One.RouteMatch
11
11
  let clientMatches: RouteMatch[] = []
12
12
  const clientMatchesListeners = new Set<() => void>()
13
13
 
14
- function subscribeToClientMatches(callback: () => void) {
14
+ export function subscribeToClientMatches(callback: () => void) {
15
15
  clientMatchesListeners.add(callback)
16
16
  return () => clientMatchesListeners.delete(callback)
17
17
  }
18
18
 
19
- function getClientMatchesSnapshot() {
19
+ export function getClientMatchesSnapshot() {
20
20
  return clientMatches
21
21
  }
22
22
 
@@ -32,6 +32,17 @@ export function setClientMatches(matches: RouteMatch[]) {
32
32
  }
33
33
  }
34
34
 
35
+ /**
36
+ * Update the loaderData for a single match by routeId, leaving all other matches intact.
37
+ * @internal
38
+ */
39
+ export function updateMatchLoaderData(routeId: string, loaderData: unknown) {
40
+ clientMatches = clientMatches.map((m) => (m.routeId === routeId ? { ...m, loaderData } : m))
41
+ for (const listener of clientMatchesListeners) {
42
+ listener()
43
+ }
44
+ }
45
+
35
46
  /**
36
47
  * Returns an array of all matched routes from root to the current page.
37
48
  * Each match contains the route's loader data, params, and route ID.
@@ -47,7 +47,7 @@ export function getPathFromLoaderPath(loaderPath: string) {
47
47
  loaderPath
48
48
  .replace(LOADER_JS_POSTFIX_REGEX, '')
49
49
  .replace(/^(\/_one)?\/assets/, '')
50
- .replace(/_refetch_\d+_/, '')
50
+ .replace(/_refetch_\d+_?/, '')
51
51
  // decode: __ → _ (escaped underscore), _ → / (path separator)
52
52
  .replace(/__|_/g, (match) => (match === '__' ? '_' : '/'))
53
53
  )
@@ -1,5 +1,9 @@
1
1
  export const EMPTY_LOADER_STRING = `export function loader() {return "__vxrn__loader__"};`
2
2
 
3
+ export function makeLoaderRouteIdStub(routeId: string): string {
4
+ return `export function loader() {return ${JSON.stringify(routeId)}};`
5
+ }
6
+
3
7
  export const LoaderDataCache = {}
4
8
 
5
9
  export const SERVER_CONTEXT_POST_RENDER_STRING = `_one_post_render_data_`
@@ -123,6 +123,127 @@ export default function Page() {
123
123
  expect(result).toBeUndefined()
124
124
  })
125
125
 
126
+ it('should embed routeId in loader stub when root is provided', async () => {
127
+ const code = `
128
+ import { serverOnlyModule } from 'server-only-pkg'
129
+ import { useLoader } from 'one'
130
+
131
+ export async function loader() {
132
+ return serverOnlyModule()
133
+ }
134
+
135
+ export default function Layout() {
136
+ const data = useLoader(loader)
137
+ return data
138
+ }
139
+ `
140
+ const result = await transformTreeShakeClient(code, '/project/app/_layout.tsx', '/project')
141
+ expect(result).toBeDefined()
142
+ // routeId should be relative to app/ directory to match route contextKey format
143
+ expect(result!.code).toContain('export function loader() {return "./_layout.tsx"}')
144
+ expect(result!.code).not.toContain('__vxrn__loader__')
145
+ })
146
+
147
+ it('should embed routeId with render mode suffix in loader stub', async () => {
148
+ const code = `
149
+ import { serverOnlyModule } from 'server-only-pkg'
150
+ import { useLoader } from 'one'
151
+
152
+ export async function loader() {
153
+ return serverOnlyModule()
154
+ }
155
+
156
+ export default function Layout() {
157
+ const data = useLoader(loader)
158
+ return data
159
+ }
160
+ `
161
+ const result = await transformTreeShakeClient(code, '/project/app/user+ssr.tsx', '/project')
162
+ expect(result).toBeDefined()
163
+ expect(result!.code).toContain('export function loader() {return "./user+ssr.tsx"}')
164
+ })
165
+
166
+ it('should fall back to __vxrn__loader__ stub when no root provided', async () => {
167
+ const code = `
168
+ import { serverOnlyModule } from 'server-only-pkg'
169
+ import { useLoader } from 'one'
170
+
171
+ export async function loader() {
172
+ return serverOnlyModule()
173
+ }
174
+
175
+ export default function Page() {
176
+ const data = useLoader(loader)
177
+ return data
178
+ }
179
+ `
180
+ const result = await transformTreeShakeClient(code, '/app/index.tsx')
181
+ expect(result).toBeDefined()
182
+ expect(result!.code).toContain('__vxrn__loader__')
183
+ })
184
+
185
+ it('should not transform route file when loader is imported (no inline declaration)', async () => {
186
+ // importing a loader from another file is not a supported pattern
187
+ // the plugin only handles inline loader declarations
188
+ const code = `
189
+ import { loader } from './loaders/my-loader'
190
+ import { useLoader } from 'one'
191
+
192
+ export { loader }
193
+
194
+ export default function Layout() {
195
+ const data = useLoader(loader)
196
+ return data
197
+ }
198
+ `
199
+ const result = await transformTreeShakeClient(code, '/project/app/_layout.tsx', '/project')
200
+ expect(result).toBeUndefined()
201
+ })
202
+
203
+ it('should tree-shake server imports from source file where loader is defined', async () => {
204
+ // when loader is defined in a separate file, that file gets its own transform pass
205
+ // server-only imports should be removed, loader replaced with routeId stub
206
+ const sourceCode = `
207
+ import { db } from 'server-only-db'
208
+
209
+ export async function loader() {
210
+ return db.query('SELECT * FROM users')
211
+ }
212
+
213
+ export function helperUsedByClient() {
214
+ return 'hello'
215
+ }
216
+ `
217
+ const result = await transformTreeShakeClient(
218
+ sourceCode,
219
+ '/project/app/loaders/my-loader.ts',
220
+ '/project'
221
+ )
222
+ expect(result).toBeDefined()
223
+ // server-only import removed
224
+ expect(result!.code).not.toContain('server-only-db')
225
+ // client-safe export preserved
226
+ expect(result!.code).toContain('helperUsedByClient')
227
+ // loader replaced with stub (routeId relative to app/ dir)
228
+ expect(result!.code).toContain(
229
+ 'export function loader() {return "./loaders/my-loader.ts"}'
230
+ )
231
+ })
232
+
233
+ it('should not transform re-export from source syntax', async () => {
234
+ // importing/re-exporting a loader from another file is not a supported pattern
235
+ const code = `
236
+ export { loader } from './loaders/shared-loader'
237
+ import { useLoader } from 'one'
238
+
239
+ export default function Page() {
240
+ return 'hello'
241
+ }
242
+ `
243
+ const result = await transformTreeShakeClient(code, '/project/app/page.tsx', '/project')
244
+ expect(result).toBeUndefined()
245
+ })
246
+
126
247
  it('should preserve type-only imports during tree shaking', async () => {
127
248
  const code = `
128
249
  import type { SomeType } from 'types-pkg'
@@ -8,7 +8,7 @@ import {
8
8
  findReferencedIdentifiers,
9
9
  } from 'babel-dead-code-elimination'
10
10
  import type { Plugin } from 'vite'
11
- import { EMPTY_LOADER_STRING } from '../constants'
11
+ import { EMPTY_LOADER_STRING, makeLoaderRouteIdStub } from '../constants'
12
12
 
13
13
  const traverse = (BabelTraverse['default'] || BabelTraverse) as typeof BabelTraverse
14
14
  const generate = (BabelGenerate['default'] ||
@@ -74,7 +74,7 @@ export const clientTreeShakePlugin = (): Plugin => {
74
74
  return
75
75
  }
76
76
 
77
- const out = await transformTreeShakeClient(code, id)
77
+ const out = await transformTreeShakeClient(code, id, process.cwd())
78
78
 
79
79
  return out
80
80
  },
@@ -82,7 +82,7 @@ export const clientTreeShakePlugin = (): Plugin => {
82
82
  } satisfies Plugin
83
83
  }
84
84
 
85
- export async function transformTreeShakeClient(code: string, id: string) {
85
+ export async function transformTreeShakeClient(code: string, id: string, root?: string) {
86
86
  if (!/generateStaticParams|loader/.test(code)) {
87
87
  return
88
88
  }
@@ -123,6 +123,9 @@ export async function transformTreeShakeClient(code: string, id: string) {
123
123
  generateStaticParams: false,
124
124
  }
125
125
 
126
+ // note: only handles inline declarations (export function loader / export const loader)
127
+ // importing a loader from another file (export { loader } from './other') is not supported
128
+ // and will break client-side tree shaking. loaders must be defined in the route file.
126
129
  try {
127
130
  traverse(ast, {
128
131
  ExportNamedDeclaration(path) {
@@ -188,6 +191,13 @@ export async function transformTreeShakeClient(code: string, id: string) {
188
191
  removedFunctions
189
192
  .map((key) => {
190
193
  if (key === 'loader') {
194
+ if (root) {
195
+ // compute routeId relative to the app/ directory to match route contextKey format
196
+ // contextKeys are like "./_layout.tsx", "./matches-test/page1+ssg.tsx"
197
+ const fromRoot = relative(root, id).replace(/\\/g, '/')
198
+ const routeId = './' + fromRoot.replace(/^app\//, '')
199
+ return makeLoaderRouteIdStub(routeId)
200
+ }
191
201
  return EMPTY_LOADER_STRING
192
202
  }
193
203
 
@@ -1,3 +1,8 @@
1
+ // matches the routeId stub return value in both minified and non-minified code:
2
+ // non-minified: return "./loader-refetch/index.tsx"
3
+ // minified: return"./loader-refetch/index.tsx"
4
+ const routeIdReturnRegex = /return\s*"\.\/[^"]+"/
5
+
1
6
  export function replaceLoader({
2
7
  code,
3
8
  loaderData,
@@ -6,18 +11,27 @@ export function replaceLoader({
6
11
  loaderData: object
7
12
  }) {
8
13
  const stringifiedData = JSON.stringify(loaderData)
14
+ const safeData = stringifiedData.replace(/\$/g, '$$$$')
9
15
 
10
16
  const out = (() => {
11
- if (!code.includes('__vxrn__loader__')) {
12
- return code + `\nexport const loader = () => (${stringifiedData})`
17
+ // old-style placeholder stub
18
+ if (code.includes('__vxrn__loader__')) {
19
+ return code.replace(
20
+ /["']__vxrn__loader__['"]/,
21
+ // make sure this ' ' is added in front,
22
+ // minifiers will do `return"something"
23
+ // but if its null then it becomes returnnull
24
+ ' ' + safeData
25
+ )
26
+ }
27
+
28
+ // new-style routeId stub from clientTreeShakePlugin
29
+ // works with both minified (return"./path") and non-minified (return "./path") code
30
+ if (routeIdReturnRegex.test(code)) {
31
+ return code.replace(routeIdReturnRegex, 'return ' + safeData)
13
32
  }
14
- return code.replace(
15
- /["']__vxrn__loader__['"]/,
16
- // make sure this ' ' is added in front,
17
- // minifiers will do `return"something"
18
- // but if its null then it becomes returnnull
19
- ' ' + stringifiedData.replace(/\$/g, '$$$$')
20
- )
33
+
34
+ return code + `\nexport const loader = () => (${stringifiedData})`
21
35
  })()
22
36
 
23
37
  return out
@@ -21,6 +21,16 @@ export declare function getLoaderTimingHistory(): LoaderTimingEntry[];
21
21
  * ```
22
22
  */
23
23
  export declare function refetchLoader(pathname: string): Promise<void>;
24
+ /**
25
+ * Refetch the loader data for a specific match (identified by routeId) by fetching
26
+ * the current page's loader JS and updating only that match entry in clientMatches.
27
+ *
28
+ * Note: the server's loader JS endpoint runs the *page* loader, so for layout
29
+ * routeIds this fetches fresh page data and stores it on the layout match. A
30
+ * dedicated per-layout refetch endpoint would be needed to truly re-run a layout
31
+ * loader in isolation; that can be added in a follow-up.
32
+ */
33
+ export declare function refetchMatchLoader(routeId: string, currentPath: string): Promise<void>;
24
34
  /**
25
35
  * Access loader data with full state control including refetch capability.
26
36
  * Use this when you need loading state or refetch; use `useLoader` for just data.
@@ -1 +1 @@
1
- {"version":3,"file":"useLoader.d.ts","sourceRoot":"","sources":["../src/useLoader.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CAC1C,CAAA;AAuCD,wBAAgB,sBAAsB,IAAI,iBAAiB,EAAE,CAE5D;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwGnE;AAOD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,SAAS,QAAQ,GAAG,GAAG,EAC7B,QAAQ,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,EAExE,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,SAAS,SAAS,GACvB;IAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,QAAQ,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAA;IAClE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,CA+TJ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,QAAQ,EACvB,QAAQ,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,EACxE,MAAM,EAAE,MAAM,GAAG,QAAQ,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAG9E;AAKD,wBAAgB,gBAAgB,SAG/B"}
1
+ {"version":3,"file":"useLoader.d.ts","sourceRoot":"","sources":["../src/useLoader.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CAC1C,CAAA;AAuCD,wBAAgB,sBAAsB,IAAI,iBAAiB,EAAE,CAE5D;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgHnE;AAOD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,SAAS,QAAQ,GAAG,GAAG,EAC7B,QAAQ,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,EAExE,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,SAAS,SAAS,GACvB;IAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,QAAQ,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAA;IAClE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,CAqWJ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,QAAQ,EACvB,QAAQ,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,EACxE,MAAM,EAAE,MAAM,GAAG,QAAQ,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAG9E;AAKD,wBAAgB,gBAAgB,SAG/B"}
@@ -3,12 +3,19 @@ import type { One } from './vite/types';
3
3
  * Re-export for convenience
4
4
  */
5
5
  export type RouteMatch = One.RouteMatch;
6
+ export declare function subscribeToClientMatches(callback: () => void): () => boolean;
7
+ export declare function getClientMatchesSnapshot(): One.RouteMatch[];
6
8
  /**
7
9
  * Update the client-side matches store.
8
10
  * Called after navigation to update the matches with new loader data.
9
11
  * @internal
10
12
  */
11
13
  export declare function setClientMatches(matches: RouteMatch[]): void;
14
+ /**
15
+ * Update the loaderData for a single match by routeId, leaving all other matches intact.
16
+ * @internal
17
+ */
18
+ export declare function updateMatchLoaderData(routeId: string, loaderData: unknown): void;
12
19
  /**
13
20
  * Returns an array of all matched routes from root to the current page.
14
21
  * Each match contains the route's loader data, params, and route ID.
@@ -1 +1 @@
1
- {"version":3,"file":"useMatches.d.ts","sourceRoot":"","sources":["../src/useMatches.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAA;AAevC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,QAKrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,UAAU,IAAI,UAAU,EAAE,CAoBzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,UAAU,GAAG,SAAS,CAGrD"}
1
+ {"version":3,"file":"useMatches.d.ts","sourceRoot":"","sources":["../src/useMatches.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAA;AAMvC,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,IAAI,iBAG5D;AAED,wBAAgB,wBAAwB,qBAEvC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,QAKrD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,QAKzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,UAAU,IAAI,UAAU,EAAE,CAoBzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,UAAU,GAAG,SAAS,CAGrD"}
@@ -1,4 +1,5 @@
1
1
  export declare const EMPTY_LOADER_STRING = "export function loader() {return \"__vxrn__loader__\"};";
2
+ export declare function makeLoaderRouteIdStub(routeId: string): string;
2
3
  export declare const LoaderDataCache: {};
3
4
  export declare const SERVER_CONTEXT_POST_RENDER_STRING = "_one_post_render_data_";
4
5
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/vite/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,4DAA0D,CAAA;AAE1F,eAAO,MAAM,eAAe,IAAK,CAAA;AAEjC,eAAO,MAAM,iCAAiC,2BAA2B,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/vite/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,4DAA0D,CAAA;AAE1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,eAAO,MAAM,eAAe,IAAK,CAAA;AAEjC,eAAO,MAAM,iCAAiC,2BAA2B,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import type { Plugin } from 'vite';
2
2
  export declare const clientTreeShakePlugin: () => Plugin;
3
- export declare function transformTreeShakeClient(code: string, id: string): Promise<{
3
+ export declare function transformTreeShakeClient(code: string, id: string, root?: string): Promise<{
4
4
  code: string;
5
5
  map: {
6
6
  version: number;
@@ -1 +1 @@
1
- {"version":3,"file":"clientTreeShakePlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/clientTreeShakePlugin.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA4ClC,eAAO,MAAM,qBAAqB,QAAO,MA6BxC,CAAA;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;eAmItE"}
1
+ {"version":3,"file":"clientTreeShakePlugin.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/clientTreeShakePlugin.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA4ClC,eAAO,MAAM,qBAAqB,QAAO,MA6BxC,CAAA;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;;;;;;;;;;;eA6IrF"}
@@ -1 +1 @@
1
- {"version":3,"file":"replaceLoader.d.ts","sourceRoot":"","sources":["../../src/vite/replaceLoader.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAU,GACX,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB,UAiBA"}
1
+ {"version":3,"file":"replaceLoader.d.ts","sourceRoot":"","sources":["../../src/vite/replaceLoader.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAU,GACX,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB,UA0BA"}