@tanstack/router-core 0.0.1-beta.10 → 0.0.1-beta.13

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.10",
4
+ "version": "0.0.1-beta.13",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -33,6 +33,7 @@
33
33
  "build/**",
34
34
  "src"
35
35
  ],
36
+ "sideEffects": false,
36
37
  "peerDependencies": {
37
38
  "react": ">=16",
38
39
  "react-dom": ">=16"
package/src/frameworks.ts CHANGED
@@ -3,8 +3,7 @@ export interface FrameworkGenerics {
3
3
  // and are extended by framework adapters, but cannot be
4
4
  // pre-defined as constraints:
5
5
  //
6
- // Element: any
7
- // SyncOrAsyncElement?: any
6
+ // Component: any
8
7
  }
9
8
 
10
9
  export type GetFrameworkGeneric<U> = U extends keyof FrameworkGenerics
package/src/index.ts CHANGED
@@ -7,7 +7,6 @@ export {
7
7
  export { default as invariant } from 'tiny-invariant'
8
8
 
9
9
  export * from './frameworks'
10
- export * from './index'
11
10
  export * from './link'
12
11
  export * from './path'
13
12
  export * from './qss'
@@ -108,18 +108,12 @@ export type RouteOptions<
108
108
  // Filter functions that can manipulate search params *after* they are passed to links and navigate
109
109
  // calls that match this route.
110
110
  postSearchFilters?: SearchFilter<TFullSearchSchema>[]
111
- // The duration to wait during `loader` execution before showing the `pendingElement`
112
- pendingMs?: number
113
- // _If the `pendingElement` is shown_, the minimum duration for which it will be visible.
114
- pendingMinMs?: number
115
- // The content to be rendered when the route is matched. If no element is provided, defaults to `<Outlet />`
116
- element?: GetFrameworkGeneric<'SyncOrAsyncElement'> // , NoInfer<TLoaderData>>
117
- // The content to be rendered when `loader` encounters an error
118
- errorElement?: GetFrameworkGeneric<'SyncOrAsyncElement'> // , NoInfer<TLoaderData>>
119
- // The content to be rendered when rendering encounters an error
120
- catchElement?: GetFrameworkGeneric<'SyncOrAsyncElement'> // , NoInfer<TLoaderData>>
121
- // The content to be rendered when the duration of `loader` execution surpasses the `pendingMs` duration
122
- pendingElement?: GetFrameworkGeneric<'SyncOrAsyncElement'> //, NoInfer<TLoaderData>>
111
+ // The content to be rendered when the route is matched. If no component is provided, defaults to `<Outlet />`
112
+ component?: GetFrameworkGeneric<'Component'> // , NoInfer<TLoaderData>>
113
+ // The content to be rendered when the route encounters an error
114
+ errorComponent?: GetFrameworkGeneric<'Component'> // , NoInfer<TLoaderData>>
115
+ // If supported by your framework, the content to be rendered as the fallback content until the route is ready to render
116
+ pendingComponent?: GetFrameworkGeneric<'Component'> //, NoInfer<TLoaderData>>
123
117
  // An asynchronous function responsible for preparing or fetching data for the route before it is rendered
124
118
  loader?: LoaderFn<TRouteLoaderData, TFullSearchSchema, TAllParams>
125
119
  // The max age to consider loader data fresh (not-stale) for this route in milliseconds from the time of fetch
@@ -132,7 +126,7 @@ export type RouteOptions<
132
126
  // might invalidate the route's data.
133
127
  action?: ActionFn<TActionPayload, TActionResponse>
134
128
  // Set this to true to rethrow errors up the component tree to either the nearest error boundary or
135
- // route with error element, whichever comes first.
129
+ // route with error component, whichever comes first.
136
130
  useErrorBoundary?: boolean
137
131
  // This function is called
138
132
  // when moving from an inactive state to an active one. Likewise, when moving from
package/src/routeMatch.ts CHANGED
@@ -28,20 +28,14 @@ export interface RouteMatch<
28
28
  loaderData: TRouteInfo['loaderData']
29
29
  routeLoaderData: TRouteInfo['routeLoaderData']
30
30
  isFetching: boolean
31
- isPending: boolean
32
31
  invalidAt: number
33
32
  __: {
34
- element?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
35
- errorElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
36
- catchElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
37
- pendingElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
33
+ component?: GetFrameworkGeneric<'Component'> // , TRouteInfo['loaderData']>
34
+ errorComponent?: GetFrameworkGeneric<'Component'> // , TRouteInfo['loaderData']>
35
+ pendingComponent?: GetFrameworkGeneric<'Component'> // , TRouteInfo['loaderData']>
38
36
  loadPromise?: Promise<void>
39
- loaderDataPromise?: Promise<void>
40
- elementsPromise?: Promise<void>
37
+ componentsPromise?: Promise<void>
41
38
  dataPromise?: Promise<void>
42
- pendingTimeout?: Timeout
43
- pendingMinTimeout?: Timeout
44
- pendingMinPromise?: Promise<void>
45
39
  onExit?:
46
40
  | void
47
41
  | ((matchContext: {
@@ -53,28 +47,24 @@ export interface RouteMatch<
53
47
  // setParentMatch: (parentMatch: RouteMatch) => void
54
48
  // addChildMatch: (childMatch: RouteMatch) => void
55
49
  validate: () => void
56
- startPending: () => void
57
- cancelPending: () => void
58
50
  notify: () => void
59
51
  resolve: () => void
60
52
  }
61
53
  cancel: () => void
62
54
  load: (
63
- loaderOpts?: { withPending?: boolean } & (
55
+ loaderOpts?:
64
56
  | { preload: true; maxAge: number; gcMaxAge: number }
65
- | { preload?: false; maxAge?: never; gcMaxAge?: never }
66
- ),
57
+ | { preload?: false; maxAge?: never; gcMaxAge?: never },
67
58
  ) => Promise<TRouteInfo['routeLoaderData']>
68
59
  fetch: (opts?: { maxAge?: number }) => Promise<TRouteInfo['routeLoaderData']>
69
60
  invalidate: () => void
70
61
  hasLoaders: () => boolean
71
62
  }
72
63
 
73
- const elementTypes = [
74
- 'element',
75
- 'errorElement',
76
- 'catchElement',
77
- 'pendingElement',
64
+ const componentTypes = [
65
+ 'component',
66
+ 'errorComponent',
67
+ 'pendingComponent',
78
68
  ] as const
79
69
 
80
70
  export function createRouteMatch<
@@ -99,7 +89,6 @@ export function createRouteMatch<
99
89
  status: 'idle',
100
90
  routeLoaderData: {} as TRouteInfo['routeLoaderData'],
101
91
  loaderData: {} as TRouteInfo['loaderData'],
102
- isPending: false,
103
92
  isFetching: false,
104
93
  isInvalid: false,
105
94
  invalidAt: Infinity,
@@ -116,37 +105,6 @@ export function createRouteMatch<
116
105
  routeMatch.__.resolve()
117
106
  routeMatch.router.notify()
118
107
  },
119
- startPending: () => {
120
- const pendingMs =
121
- routeMatch.options.pendingMs ?? router.options.defaultPendingMs
122
- const pendingMinMs =
123
- routeMatch.options.pendingMinMs ?? router.options.defaultPendingMinMs
124
-
125
- if (
126
- routeMatch.__.pendingTimeout ||
127
- routeMatch.status !== 'loading' ||
128
- typeof pendingMs === 'undefined'
129
- ) {
130
- return
131
- }
132
-
133
- routeMatch.__.pendingTimeout = setTimeout(() => {
134
- routeMatch.isPending = true
135
- routeMatch.__.resolve()
136
- if (typeof pendingMinMs !== 'undefined') {
137
- routeMatch.__.pendingMinPromise = new Promise(
138
- (r) =>
139
- (routeMatch.__.pendingMinTimeout = setTimeout(r, pendingMinMs)),
140
- )
141
- }
142
- }, pendingMs)
143
- },
144
- cancelPending: () => {
145
- routeMatch.isPending = false
146
- clearTimeout(routeMatch.__.pendingTimeout)
147
- clearTimeout(routeMatch.__.pendingMinTimeout)
148
- delete routeMatch.__.pendingMinPromise
149
- },
150
108
  validate: () => {
151
109
  // Validate the search params and stabilize them
152
110
  const parentSearch =
@@ -177,11 +135,11 @@ export function createRouteMatch<
177
135
  ...nextSearch,
178
136
  })
179
137
 
180
- elementTypes.map(async (type) => {
181
- const routeElement = routeMatch.options[type]
138
+ componentTypes.map(async (type) => {
139
+ const component = routeMatch.options[type]
182
140
 
183
141
  if (typeof routeMatch.__[type] !== 'function') {
184
- routeMatch.__[type] = routeElement
142
+ routeMatch.__[type] = component
185
143
  }
186
144
  })
187
145
  } catch (err: any) {
@@ -199,7 +157,6 @@ export function createRouteMatch<
199
157
  },
200
158
  cancel: () => {
201
159
  routeMatch.__.abortController?.abort()
202
- routeMatch.__.cancelPending()
203
160
  },
204
161
  invalidate: () => {
205
162
  routeMatch.isInvalid = true
@@ -207,7 +164,7 @@ export function createRouteMatch<
207
164
  hasLoaders: () => {
208
165
  return !!(
209
166
  route.options.loader ||
210
- elementTypes.some((d) => typeof route.options[d] === 'function')
167
+ componentTypes.some((d) => route.options[d]?.preload)
211
168
  )
212
169
  },
213
170
  load: async (loaderOpts) => {
@@ -262,102 +219,87 @@ export function createRouteMatch<
262
219
  routeMatch.isFetching = true
263
220
  routeMatch.__.resolve = resolve as () => void
264
221
 
265
- routeMatch.__.loaderDataPromise = (async () => {
266
- // Load the elements and data in parallel
267
-
268
- routeMatch.__.elementsPromise = (async () => {
269
- // then run all element and data loaders in parallel
270
- // For each element type, potentially load it asynchronously
271
-
272
- await Promise.all(
273
- elementTypes.map(async (type) => {
274
- const routeElement = routeMatch.options[type]
275
-
276
- if (typeof routeMatch.__[type] === 'function') {
277
- routeMatch.__[type] = await router.options.createElement!(
278
- routeElement,
279
- )
280
- }
281
- }),
282
- )
283
- })()
222
+ routeMatch.__.componentsPromise = (async () => {
223
+ // then run all component and data loaders in parallel
224
+ // For each component type, potentially load it asynchronously
284
225
 
285
- routeMatch.__.dataPromise = Promise.resolve().then(async () => {
286
- try {
287
- if (routeMatch.options.loader) {
288
- const data = await routeMatch.options.loader({
289
- params: routeMatch.params,
290
- search: routeMatch.routeSearch,
291
- signal: routeMatch.__.abortController.signal,
292
- })
293
- if (id !== routeMatch.__.latestId) {
294
- return routeMatch.__.loadPromise
295
- }
226
+ await Promise.all(
227
+ componentTypes.map(async (type) => {
228
+ const component = routeMatch.options[type]
296
229
 
297
- routeMatch.routeLoaderData = replaceEqualDeep(
298
- routeMatch.routeLoaderData,
299
- data,
230
+ if (routeMatch.__[type]?.preload) {
231
+ routeMatch.__[type] = await router.options.loadComponent!(
232
+ component,
300
233
  )
301
234
  }
235
+ }),
236
+ )
237
+ })()
302
238
 
303
- routeMatch.error = undefined
304
- routeMatch.status = 'success'
305
- routeMatch.updatedAt = Date.now()
306
- routeMatch.invalidAt =
307
- routeMatch.updatedAt +
308
- (opts?.maxAge ??
309
- routeMatch.options.loaderMaxAge ??
310
- router.options.defaultLoaderMaxAge ??
311
- 0)
312
- } catch (err) {
239
+ routeMatch.__.dataPromise = Promise.resolve().then(async () => {
240
+ try {
241
+ if (routeMatch.options.loader) {
242
+ const data = await routeMatch.options.loader({
243
+ params: routeMatch.params,
244
+ search: routeMatch.routeSearch,
245
+ signal: routeMatch.__.abortController.signal,
246
+ })
313
247
  if (id !== routeMatch.__.latestId) {
314
248
  return routeMatch.__.loadPromise
315
249
  }
316
250
 
317
- if (process.env.NODE_ENV !== 'production') {
318
- console.error(err)
319
- }
320
- routeMatch.error = err
321
- routeMatch.status = 'error'
322
- routeMatch.updatedAt = Date.now()
251
+ routeMatch.routeLoaderData = replaceEqualDeep(
252
+ routeMatch.routeLoaderData,
253
+ data,
254
+ )
323
255
  }
324
- })
325
256
 
326
- try {
327
- await Promise.all([
328
- routeMatch.__.elementsPromise,
329
- routeMatch.__.dataPromise,
330
- ])
257
+ routeMatch.error = undefined
258
+ routeMatch.status = 'success'
259
+ routeMatch.updatedAt = Date.now()
260
+ routeMatch.invalidAt =
261
+ routeMatch.updatedAt +
262
+ (opts?.maxAge ??
263
+ routeMatch.options.loaderMaxAge ??
264
+ router.options.defaultLoaderMaxAge ??
265
+ 0)
266
+ } catch (err) {
331
267
  if (id !== routeMatch.__.latestId) {
332
268
  return routeMatch.__.loadPromise
333
269
  }
334
270
 
335
- if (routeMatch.__.pendingMinPromise) {
336
- await routeMatch.__.pendingMinPromise
337
- delete routeMatch.__.pendingMinPromise
271
+ if (process.env.NODE_ENV !== 'production') {
272
+ console.error(err)
338
273
  }
339
- } finally {
340
- if (id !== routeMatch.__.latestId) {
341
- return routeMatch.__.loadPromise
342
- }
343
- routeMatch.__.cancelPending()
344
- routeMatch.isPending = false
345
- routeMatch.isFetching = false
346
- routeMatch.__.notify()
274
+ routeMatch.error = err
275
+ routeMatch.status = 'error'
276
+ routeMatch.updatedAt = Date.now()
347
277
  }
348
- })()
278
+ })
349
279
 
350
- await routeMatch.__.loaderDataPromise
351
-
352
- if (id !== routeMatch.__.latestId) {
353
- return routeMatch.__.loadPromise
280
+ try {
281
+ await Promise.all([
282
+ routeMatch.__.componentsPromise,
283
+ routeMatch.__.dataPromise,
284
+ ])
285
+ if (id !== routeMatch.__.latestId) {
286
+ return routeMatch.__.loadPromise
287
+ }
288
+ } finally {
289
+ if (id !== routeMatch.__.latestId) {
290
+ return routeMatch.__.loadPromise
291
+ }
292
+ routeMatch.isFetching = false
293
+ routeMatch.__.notify()
354
294
  }
355
-
356
- delete routeMatch.__.loaderDataPromise
357
295
  })
358
296
 
359
297
  await routeMatch.__.loadPromise
360
298
 
299
+ if (id !== routeMatch.__.latestId) {
300
+ return routeMatch.__.loadPromise
301
+ }
302
+
361
303
  delete routeMatch.__.loadPromise
362
304
  },
363
305
  }
package/src/router.ts CHANGED
@@ -91,12 +91,9 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
91
91
  defaultPreloadGcMaxAge?: number
92
92
  defaultPreloadDelay?: number
93
93
  useErrorBoundary?: boolean
94
- defaultElement?: GetFrameworkGeneric<'Element'>
95
- defaultErrorElement?: GetFrameworkGeneric<'Element'>
96
- defaultCatchElement?: GetFrameworkGeneric<'Element'>
97
- defaultPendingElement?: GetFrameworkGeneric<'Element'>
98
- defaultPendingMs?: number
99
- defaultPendingMinMs?: number
94
+ defaultComponent?: GetFrameworkGeneric<'Component'>
95
+ defaultErrorComponent?: GetFrameworkGeneric<'Component'>
96
+ defaultPendingComponent?: GetFrameworkGeneric<'Component'>
100
97
  defaultLoaderMaxAge?: number
101
98
  defaultLoaderGcMaxAge?: number
102
99
  caseSensitive?: boolean
@@ -104,9 +101,12 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
104
101
  basepath?: string
105
102
  createRouter?: (router: Router<any, any>) => void
106
103
  createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
107
- createElement?: (
108
- element: GetFrameworkGeneric<'SyncOrAsyncElement'>,
109
- ) => Promise<GetFrameworkGeneric<'Element'>>
104
+ loadComponent?: (
105
+ component: GetFrameworkGeneric<'Component'>,
106
+ ) => Promise<GetFrameworkGeneric<'Component'>>
107
+ // renderComponent?: (
108
+ // component: GetFrameworkGeneric<'Component'>,
109
+ // ) => GetFrameworkGeneric<'Element'>
110
110
  }
111
111
 
112
112
  export interface Action<
@@ -294,10 +294,9 @@ export interface Router<
294
294
  ) => RouteMatch[]
295
295
  loadMatches: (
296
296
  resolvedMatches: RouteMatch[],
297
- loaderOpts?: { withPending?: boolean } & (
297
+ loaderOpts?:
298
298
  | { preload: true; maxAge: number; gcMaxAge: number }
299
- | { preload?: false; maxAge?: never; gcMaxAge?: never }
300
- ),
299
+ | { preload?: false; maxAge?: never; gcMaxAge?: never },
301
300
  ) => Promise<void>
302
301
  invalidateRoute: (opts: MatchLocation) => void
303
302
  reload: () => Promise<void>
@@ -555,21 +554,28 @@ export function createRouter<
555
554
  strictParseParams: true,
556
555
  })
557
556
 
558
- router.state = {
559
- ...router.state,
560
- pending: {
557
+ if (typeof document !== 'undefined') {
558
+ router.state = {
559
+ ...router.state,
560
+ pending: {
561
+ matches: matches,
562
+ location: router.location,
563
+ },
564
+ status: 'loading',
565
+ }
566
+ } else {
567
+ router.state = {
568
+ ...router.state,
561
569
  matches: matches,
562
570
  location: router.location,
563
- },
564
- status: 'loading',
571
+ status: 'loading',
572
+ }
565
573
  }
566
574
 
567
575
  router.notify()
568
576
 
569
577
  // Load the matches
570
- await router.loadMatches(matches, {
571
- withPending: true,
572
- })
578
+ await router.loadMatches(matches)
573
579
 
574
580
  if (router.startedLoadingAt !== id) {
575
581
  // Ignore side-effects of match loading
@@ -589,6 +595,10 @@ export function createRouter<
589
595
  }
590
596
  })
591
597
 
598
+ const entering = matches.filter((d) => {
599
+ return !previousMatches.find((dd) => dd.matchId === d.matchId)
600
+ })
601
+
592
602
  const now = Date.now()
593
603
 
594
604
  exiting.forEach((d) => {
@@ -597,12 +607,6 @@ export function createRouter<
597
607
  search: d.routeSearch,
598
608
  })
599
609
 
600
- // // Clear actions
601
- // if (d.action) {
602
- // d.action.current = undefined
603
- // d.action.submissions = []
604
- // }
605
-
606
610
  // Clear idle error states when match leaves
607
611
  if (d.status === 'error' && !d.isFetching) {
608
612
  d.status = 'idle'
@@ -627,10 +631,6 @@ export function createRouter<
627
631
  })
628
632
  })
629
633
 
630
- const entering = matches.filter((d) => {
631
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
632
- })
633
-
634
634
  entering.forEach((d) => {
635
635
  d.__.onExit = d.options.onMatch?.({
636
636
  params: d.params,
@@ -639,8 +639,6 @@ export function createRouter<
639
639
  delete router.matchCache[d.matchId]
640
640
  })
641
641
 
642
- // router.notify()
643
-
644
642
  if (router.startedLoadingAt !== id) {
645
643
  // Ignore side-effects of match loading
646
644
  return
@@ -829,14 +827,8 @@ export function createRouter<
829
827
  match.__.validate()
830
828
  match.load(loaderOpts)
831
829
 
832
- if (match.status === 'loading') {
833
- // If requested, start the pending timers
834
- if (loaderOpts?.withPending) match.__.startPending()
835
- }
836
-
837
830
  if (match.__.loadPromise) {
838
831
  // Wait for the first sign of activity from the match
839
- // This might be completion, error, or a pending state
840
832
  await match.__.loadPromise
841
833
  }
842
834
  })