@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1
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/{reactContext.js → localisedUrls/config.js} +14 -10
- package/dist/cjs/localisedUrls/index.js +95 -0
- package/dist/cjs/localisedUrls/normalise.js +87 -0
- package/dist/cjs/localisedUrls/pathname.js +68 -0
- package/dist/cjs/localisedUrls/patterns.js +214 -0
- package/dist/cjs/localisedUrls/redirect.js +104 -0
- package/dist/cjs/localisedUrls/resolve.js +78 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +92 -0
- package/dist/cjs/localisedUrls/routes.js +217 -0
- package/dist/cjs/localisedUrls/types.js +18 -0
- package/dist/cjs/rendererHead.js +18 -5
- package/dist/cjs/request-policy/policyCore.js +1 -1
- package/dist/cjs/request-policy/transport.js +2 -2
- package/dist/cjs/routerState.js +85 -77
- package/dist/cjs/routerStatePlugin.js +63 -9
- package/dist/esm/localisedUrls/config.mjs +11 -0
- package/dist/esm/localisedUrls/index.mjs +8 -0
- package/dist/esm/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/localisedUrls/patterns.mjs +152 -0
- package/dist/esm/localisedUrls/redirect.mjs +57 -0
- package/dist/esm/localisedUrls/resolve.mjs +37 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +54 -0
- package/dist/esm/localisedUrls/routes.mjs +176 -0
- package/dist/esm/localisedUrls/types.mjs +0 -0
- package/dist/esm/rendererHead.mjs +14 -5
- package/dist/esm/request-policy/policyCore.mjs +1 -1
- package/dist/esm/request-policy/transport.mjs +2 -2
- package/dist/esm/routerState.mjs +81 -73
- package/dist/esm/routerStatePlugin.mjs +62 -11
- package/dist/esm-node/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/localisedUrls/index.mjs +9 -0
- package/dist/esm-node/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/localisedUrls/patterns.mjs +153 -0
- package/dist/esm-node/localisedUrls/redirect.mjs +58 -0
- package/dist/esm-node/localisedUrls/resolve.mjs +38 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +55 -0
- package/dist/esm-node/localisedUrls/routes.mjs +177 -0
- package/dist/esm-node/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/rendererHead.mjs +14 -5
- package/dist/esm-node/request-policy/policyCore.mjs +1 -1
- package/dist/esm-node/request-policy/transport.mjs +2 -2
- package/dist/esm-node/routerState.mjs +81 -73
- package/dist/esm-node/routerStatePlugin.mjs +62 -11
- package/dist/types/helmetCompat.d.ts +1 -1
- package/dist/types/localisedUrls/config.d.ts +9 -0
- package/dist/types/localisedUrls/index.d.ts +10 -0
- package/dist/types/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/localisedUrls/patterns.d.ts +11 -0
- package/dist/types/localisedUrls/redirect.d.ts +19 -0
- package/dist/types/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +22 -0
- package/dist/types/localisedUrls/routes.d.ts +3 -0
- package/dist/types/localisedUrls/types.d.ts +14 -0
- package/dist/types/rendererHead.d.ts +5 -3
- package/dist/types/request-policy/types.d.ts +1 -1
- package/dist/types/routeHydration.d.ts +2 -2
- package/dist/types/routerProvider.d.ts +1 -3
- package/dist/types/routerState.d.ts +11 -6
- package/dist/types/routerStatePlugin.d.ts +12 -0
- package/dist/types/routerStateTypes.d.ts +47 -17
- package/package.json +42 -44
- package/src/helmetCompat.ts +1 -1
- package/src/localisedUrls/config.ts +18 -0
- package/src/localisedUrls/index.ts +25 -0
- package/src/localisedUrls/normalise.ts +62 -0
- package/src/localisedUrls/pathname.ts +60 -0
- package/src/localisedUrls/patterns.ts +283 -0
- package/src/localisedUrls/redirect.ts +110 -0
- package/src/localisedUrls/resolve.ts +106 -0
- package/src/localisedUrls/routeIdentity.ts +83 -0
- package/src/localisedUrls/routes.ts +451 -0
- package/src/localisedUrls/types.ts +18 -0
- package/src/rendererHead.ts +25 -8
- package/src/request-policy/policyCore.ts +1 -1
- package/src/request-policy/transport.ts +2 -2
- package/src/request-policy/types.ts +1 -1
- package/src/routeHydration.ts +2 -2
- package/src/routerProvider.ts +1 -1
- package/src/routerState.ts +127 -115
- package/src/routerStatePlugin.ts +106 -10
- package/src/routerStateTypes.ts +47 -17
- package/dist/cjs/buildIdentity.js +0 -73
- package/dist/esm/buildIdentity.mjs +0 -35
- package/dist/esm/reactContext.mjs +0 -7
- package/dist/esm-node/buildIdentity.mjs +0 -36
- package/dist/esm-node/reactContext.mjs +0 -8
- package/dist/types/buildIdentity.d.ts +0 -37
- package/dist/types/reactContext.d.ts +0 -6
- package/rslib.config.mts +0 -19
- package/rstest.config.mts +0 -38
- package/src/buildIdentity.ts +0 -61
- package/src/reactContext.ts +0 -16
- package/tests/buildIdentity.test.ts +0 -51
- package/tests/contextExtensions.test.ts +0 -27
- package/tests/headRuntime.test.tsx +0 -229
- package/tests/rendererHead.test.ts +0 -187
- package/tests/request-policy/browser-policy.test.ts +0 -108
- package/tests/request-policy/client-isolation.test.ts +0 -109
- package/tests/request-policy/node-policy.test.ts +0 -447
- package/tests/request-policy/policyCore.test.ts +0 -129
- package/tests/request-policy/requestContext.test.ts +0 -66
- package/tests/request-policy/requestFactory.outbound-contract.test.ts +0 -290
- package/tests/request-policy/traceparent.test.ts +0 -52
- package/tests/request-policy/transport-retry.test.ts +0 -367
- package/tests/routeHydration.test.ts +0 -180
- package/tests/routerPrefetchPolicy.test.ts +0 -102
- package/tests/routerProvider.test.ts +0 -29
- package/tests/routerState.test.ts +0 -78
- package/tests/routerStatePlugin.test.ts +0 -63
- package/tests/runtimeContextProjection.test.ts +0 -83
- package/tests/safeFailure.test.ts +0 -136
- package/tests/shellStream.test.ts +0 -122
- package/tests/workspaceEvents.test.ts +0 -221
- package/tsconfig.json +0 -10
package/src/routerState.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { createRuntimeContextExtension } from './contextExtensions';
|
|
|
3
3
|
import type {
|
|
4
4
|
InternalRouterRuntimeState,
|
|
5
5
|
InternalRouterServerSnapshot,
|
|
6
|
-
RouterRouteMatchSnapshot,
|
|
7
6
|
RouterServerPrepareResult,
|
|
8
7
|
} from './routerStateTypes';
|
|
9
8
|
|
|
@@ -13,6 +12,9 @@ export type {
|
|
|
13
12
|
InternalRouterServerSnapshot,
|
|
14
13
|
RouterFramework,
|
|
15
14
|
RouterLifecyclePhase,
|
|
15
|
+
RouterLinkTarget,
|
|
16
|
+
RouterNavigationCapability,
|
|
17
|
+
RouterNavigationSnapshot,
|
|
16
18
|
RouterRouteMatchSnapshot,
|
|
17
19
|
RouterServerPrepareResult,
|
|
18
20
|
} from './routerStateTypes';
|
|
@@ -50,112 +52,138 @@ export function getRouterServerSnapshot(
|
|
|
50
52
|
return routerServerSnapshotExtension.get(runtimeContext);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
function toHydrationScripts(state: {
|
|
56
|
-
hydrationScript?: string;
|
|
57
|
-
hydrationScripts?: string[];
|
|
58
|
-
}) {
|
|
59
|
-
if (state.hydrationScripts?.length) {
|
|
60
|
-
return state.hydrationScripts;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return state.hydrationScript ? [state.hydrationScript] : undefined;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function getMatchedRouteIdsFromMatches(matches?: RouterRouteMatchSnapshot[]) {
|
|
67
|
-
const routeIds = matches
|
|
68
|
-
?.map(match => match.assetRouteId ?? match.routeId)
|
|
69
|
-
.filter((routeId): routeId is string => typeof routeId === 'string');
|
|
70
|
-
|
|
71
|
-
return routeIds?.length ? routeIds : undefined;
|
|
72
|
-
}
|
|
73
|
-
|
|
55
|
+
/** Capture prepared metadata once; live router updates cannot rewrite it. */
|
|
74
56
|
export function createRouterServerSnapshot(
|
|
75
|
-
|
|
57
|
+
snapshot: InternalRouterServerSnapshot,
|
|
76
58
|
): InternalRouterServerSnapshot {
|
|
77
|
-
const
|
|
59
|
+
const matches = snapshot.matches?.map(match =>
|
|
60
|
+
Object.freeze({
|
|
61
|
+
...match,
|
|
62
|
+
...(match.params ? { params: Object.freeze({ ...match.params }) } : {}),
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
78
65
|
const matchedRouteIds =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
...
|
|
83
|
-
...(hydrationScripts
|
|
66
|
+
snapshot.matchedRouteIds ??
|
|
67
|
+
matches?.map(match => match.assetRouteId ?? match.routeId);
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
...snapshot,
|
|
70
|
+
...(snapshot.hydrationScripts
|
|
71
|
+
? { hydrationScripts: Object.freeze([...snapshot.hydrationScripts]) }
|
|
72
|
+
: {}),
|
|
73
|
+
...(matchedRouteIds
|
|
74
|
+
? { matchedRouteIds: Object.freeze([...matchedRouteIds]) }
|
|
75
|
+
: {}),
|
|
76
|
+
...(matches ? { matches: Object.freeze(matches) } : {}),
|
|
77
|
+
...(snapshot.errors
|
|
78
|
+
? { errors: Object.freeze({ ...snapshot.errors }) }
|
|
79
|
+
: {}),
|
|
80
|
+
...(snapshot.routerData
|
|
84
81
|
? {
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
routerData: Object.freeze({
|
|
83
|
+
...snapshot.routerData,
|
|
84
|
+
...(snapshot.routerData.loaderData
|
|
85
|
+
? {
|
|
86
|
+
loaderData: Object.freeze({
|
|
87
|
+
...snapshot.routerData.loaderData,
|
|
88
|
+
}),
|
|
89
|
+
}
|
|
90
|
+
: {}),
|
|
91
|
+
...(snapshot.routerData.errors
|
|
92
|
+
? { errors: Object.freeze({ ...snapshot.routerData.errors }) }
|
|
93
|
+
: {}),
|
|
94
|
+
}),
|
|
87
95
|
}
|
|
88
96
|
: {}),
|
|
89
|
-
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Listeners per runtime context, so a consumer can react to the slot being
|
|
102
|
+
* filled in. A router provider publishes its instance during render - TanStack
|
|
103
|
+
* installs it when `RouterWrapper` renders - which is after anything that
|
|
104
|
+
* wraps the app has already evaluated. Without a notification those wrappers
|
|
105
|
+
* would hold a routerless view of the app for the rest of the session.
|
|
106
|
+
*
|
|
107
|
+
* Keyed weakly: the runtime context outlives neither the app nor this map.
|
|
108
|
+
*/
|
|
109
|
+
const routerRuntimeStateListeners = new WeakMap<object, Set<() => void>>();
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Observe the router runtime-state slot for the given runtime context.
|
|
113
|
+
* Returns an unsubscribe function; safe to call for a context that never
|
|
114
|
+
* receives a router.
|
|
115
|
+
*/
|
|
116
|
+
export function subscribeRouterRuntimeState(
|
|
117
|
+
runtimeContext: object,
|
|
118
|
+
listener: () => void,
|
|
119
|
+
): () => void {
|
|
120
|
+
if (!runtimeContext || typeof runtimeContext !== 'object') {
|
|
121
|
+
return () => undefined;
|
|
122
|
+
}
|
|
123
|
+
let listeners = routerRuntimeStateListeners.get(runtimeContext);
|
|
124
|
+
if (!listeners) {
|
|
125
|
+
listeners = new Set();
|
|
126
|
+
routerRuntimeStateListeners.set(runtimeContext, listeners);
|
|
127
|
+
}
|
|
128
|
+
listeners.add(listener);
|
|
129
|
+
return () => {
|
|
130
|
+
listeners?.delete(listener);
|
|
90
131
|
};
|
|
91
132
|
}
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
): InternalRouterRuntimeState {
|
|
96
|
-
const hasSnapshotState =
|
|
97
|
-
Boolean(state.serverSnapshot) ||
|
|
98
|
-
Boolean(state.hydrationScript) ||
|
|
99
|
-
Boolean(state.hydrationScripts?.length) ||
|
|
100
|
-
Boolean(state.matchedRouteIds?.length) ||
|
|
101
|
-
Boolean(state.matches?.length);
|
|
102
|
-
const serverSnapshot = state.serverSnapshot
|
|
103
|
-
? createRouterServerSnapshot({
|
|
104
|
-
...state.serverSnapshot,
|
|
105
|
-
framework: state.serverSnapshot.framework ?? state.framework,
|
|
106
|
-
basename: state.serverSnapshot.basename ?? state.basename,
|
|
107
|
-
hydrationScript:
|
|
108
|
-
state.serverSnapshot.hydrationScript ?? state.hydrationScript,
|
|
109
|
-
hydrationScripts:
|
|
110
|
-
state.serverSnapshot.hydrationScripts ?? state.hydrationScripts,
|
|
111
|
-
matchedRouteIds:
|
|
112
|
-
state.serverSnapshot.matchedRouteIds ?? state.matchedRouteIds,
|
|
113
|
-
matches: state.serverSnapshot.matches ?? state.matches,
|
|
114
|
-
})
|
|
115
|
-
: hasSnapshotState
|
|
116
|
-
? createRouterServerSnapshot({
|
|
117
|
-
framework: state.framework,
|
|
118
|
-
basename: state.basename,
|
|
119
|
-
hydrationScript: state.hydrationScript,
|
|
120
|
-
hydrationScripts: state.hydrationScripts,
|
|
121
|
-
matchedRouteIds: state.matchedRouteIds,
|
|
122
|
-
matches: state.matches,
|
|
123
|
-
})
|
|
124
|
-
: undefined;
|
|
125
|
-
const hydrationScripts = toHydrationScripts({
|
|
126
|
-
hydrationScript: state.hydrationScript ?? serverSnapshot?.hydrationScript,
|
|
127
|
-
hydrationScripts:
|
|
128
|
-
state.hydrationScripts ?? serverSnapshot?.hydrationScripts,
|
|
129
|
-
});
|
|
130
|
-
const matchedRouteIds =
|
|
131
|
-
state.matchedRouteIds ??
|
|
132
|
-
serverSnapshot?.matchedRouteIds ??
|
|
133
|
-
getMatchedRouteIdsFromMatches(state.matches);
|
|
134
|
+
/** Contexts with a notification already scheduled, so a burst coalesces. */
|
|
135
|
+
const pendingRouterRuntimeStateNotifications = new WeakSet<object>();
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
function notifyRouterRuntimeState(runtimeContext: object) {
|
|
138
|
+
const listeners = routerRuntimeStateListeners.get(runtimeContext);
|
|
139
|
+
if (!listeners?.size) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (pendingRouterRuntimeStateNotifications.has(runtimeContext)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
pendingRouterRuntimeStateNotifications.add(runtimeContext);
|
|
146
|
+
// Deferred deliberately. A router provider publishes its instance from
|
|
147
|
+
// inside its own render - `RouterWrapper` does - so calling observers
|
|
148
|
+
// synchronously would schedule an update on a component that is rendering,
|
|
149
|
+
// which React reports as "Cannot update a component while rendering a
|
|
150
|
+
// different component". Delivering after the current render lands keeps the
|
|
151
|
+
// notification correct and silent.
|
|
152
|
+
const deliver = () => {
|
|
153
|
+
pendingRouterRuntimeStateNotifications.delete(runtimeContext);
|
|
154
|
+
const current = routerRuntimeStateListeners.get(runtimeContext);
|
|
155
|
+
if (!current?.size) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const listener of [...current]) {
|
|
159
|
+
try {
|
|
160
|
+
listener();
|
|
161
|
+
} catch {
|
|
162
|
+
// One bad observer must not stop the router from being published.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
145
165
|
};
|
|
166
|
+
if (typeof queueMicrotask === 'function') {
|
|
167
|
+
queueMicrotask(deliver);
|
|
168
|
+
} else {
|
|
169
|
+
setTimeout(deliver, 0);
|
|
170
|
+
}
|
|
146
171
|
}
|
|
147
172
|
|
|
148
173
|
export function applyRouterRuntimeState<Context extends object>(
|
|
149
174
|
runtimeContext: Context,
|
|
150
175
|
state: InternalRouterRuntimeState,
|
|
151
176
|
) {
|
|
152
|
-
const
|
|
153
|
-
routerRuntimeStateExtension.set(runtimeContext,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
const previous = routerRuntimeStateExtension.get(runtimeContext);
|
|
178
|
+
routerRuntimeStateExtension.set(runtimeContext, state);
|
|
179
|
+
// Only a change of identity is worth a re-render; `RouterWrapper` reapplies
|
|
180
|
+
// the same instance on every render of the app.
|
|
181
|
+
if (
|
|
182
|
+
previous?.instance !== state.instance ||
|
|
183
|
+
previous?.framework !== state.framework ||
|
|
184
|
+
previous?.navigation !== state.navigation
|
|
185
|
+
) {
|
|
186
|
+
notifyRouterRuntimeState(runtimeContext);
|
|
159
187
|
}
|
|
160
188
|
|
|
161
189
|
return runtimeContext;
|
|
@@ -165,40 +193,24 @@ export function applyRouterServerPrepareResult<Context extends object>(
|
|
|
165
193
|
runtimeContext: Context,
|
|
166
194
|
result: RouterServerPrepareResult,
|
|
167
195
|
) {
|
|
168
|
-
|
|
196
|
+
if (result.snapshot) {
|
|
197
|
+
routerServerSnapshotExtension.set(
|
|
198
|
+
runtimeContext,
|
|
199
|
+
createRouterServerSnapshot(result.snapshot),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return applyRouterRuntimeState(runtimeContext, {
|
|
169
203
|
...result.state,
|
|
170
204
|
cleanup: result.cleanup ?? result.state.cleanup,
|
|
171
|
-
serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
|
|
172
205
|
});
|
|
173
|
-
applyRouterRuntimeState(runtimeContext, state);
|
|
174
|
-
return runtimeContext;
|
|
175
206
|
}
|
|
176
207
|
|
|
177
208
|
export function getRouterHydrationScripts(runtimeContext: object) {
|
|
178
|
-
|
|
179
|
-
const runtimeState = getRouterRuntimeState(runtimeContext);
|
|
180
|
-
return (
|
|
181
|
-
serverSnapshot?.hydrationScripts ??
|
|
182
|
-
toHydrationScripts({
|
|
183
|
-
hydrationScript: serverSnapshot?.hydrationScript,
|
|
184
|
-
}) ??
|
|
185
|
-
runtimeState?.hydrationScripts ??
|
|
186
|
-
toHydrationScripts({
|
|
187
|
-
hydrationScript: runtimeState?.hydrationScript,
|
|
188
|
-
}) ??
|
|
189
|
-
[]
|
|
190
|
-
);
|
|
209
|
+
return getRouterServerSnapshot(runtimeContext)?.hydrationScripts ?? [];
|
|
191
210
|
}
|
|
192
211
|
|
|
193
212
|
export function getRouterMatchedRouteIds(runtimeContext: object) {
|
|
194
|
-
|
|
195
|
-
const runtimeState = getRouterRuntimeState(runtimeContext);
|
|
196
|
-
return (
|
|
197
|
-
serverSnapshot?.matchedRouteIds ??
|
|
198
|
-
getMatchedRouteIdsFromMatches(serverSnapshot?.matches) ??
|
|
199
|
-
runtimeState?.matchedRouteIds ??
|
|
200
|
-
getMatchedRouteIdsFromMatches(runtimeState?.matches)
|
|
201
|
-
);
|
|
213
|
+
return getRouterServerSnapshot(runtimeContext)?.matchedRouteIds;
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
export async function cleanupRouterRuntimeState(runtimeContext: object) {
|
package/src/routerStatePlugin.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import type { createMemoryRouter } from '@modern-js/runtime-utils/router';
|
|
1
2
|
import { createRouterPrefetchPolicy } from './routerPrefetchPolicy';
|
|
2
3
|
import {
|
|
3
4
|
applyRouterRuntimeState,
|
|
4
|
-
|
|
5
|
+
applyRouterServerPrepareResult,
|
|
6
|
+
getRouterRuntimeState,
|
|
7
|
+
getRouterServerSnapshot,
|
|
5
8
|
} from './routerState';
|
|
9
|
+
import type {
|
|
10
|
+
RouterNavigationCapability,
|
|
11
|
+
RouterNavigationSnapshot,
|
|
12
|
+
} from './routerStateTypes';
|
|
6
13
|
|
|
7
14
|
type ReactRouterServerContext = {
|
|
8
15
|
statusCode: number;
|
|
@@ -19,6 +26,47 @@ type RouterLifecycleEvent = {
|
|
|
19
26
|
router?: unknown;
|
|
20
27
|
};
|
|
21
28
|
|
|
29
|
+
type PreparedRequest<RuntimeContext extends object = object> = {
|
|
30
|
+
runtimeContext: RuntimeContext;
|
|
31
|
+
routerResult?: {
|
|
32
|
+
statusCode?: number;
|
|
33
|
+
errors?: Record<string, unknown> | null;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const ROUTER_CLEANUP_ERROR =
|
|
38
|
+
'An error occurs during router runtime cleanup';
|
|
39
|
+
|
|
40
|
+
// Provider wrappers and application composition may share the same native API.
|
|
41
|
+
// Install their common request policy once for that runtime instance.
|
|
42
|
+
const installedRouterPolicies = new WeakSet<object>();
|
|
43
|
+
|
|
44
|
+
function createReactRouterNavigation(
|
|
45
|
+
router: ReturnType<typeof createMemoryRouter>,
|
|
46
|
+
Link: RouterNavigationCapability['Link'],
|
|
47
|
+
): RouterNavigationCapability {
|
|
48
|
+
let state: typeof router.state | undefined;
|
|
49
|
+
let snapshot: RouterNavigationSnapshot;
|
|
50
|
+
return {
|
|
51
|
+
Link,
|
|
52
|
+
getSnapshot() {
|
|
53
|
+
if (state !== router.state) {
|
|
54
|
+
state = router.state;
|
|
55
|
+
snapshot = {
|
|
56
|
+
location: state.location,
|
|
57
|
+
params: Object.assign(
|
|
58
|
+
{},
|
|
59
|
+
...state.matches.map(match => match.params),
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return snapshot;
|
|
64
|
+
},
|
|
65
|
+
subscribe: listener => router.subscribe(listener),
|
|
66
|
+
navigate: (href, options) => router.navigate(href, options),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
22
70
|
/** Install fork policy through lifecycle hooks supplied by the native owner. */
|
|
23
71
|
export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
|
|
24
72
|
registryHooks,
|
|
@@ -30,16 +78,56 @@ export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
|
|
|
30
78
|
registryHooks,
|
|
31
79
|
setup(api: {
|
|
32
80
|
onBeforeRender: (callback: (context: object) => void) => unknown;
|
|
81
|
+
onRenderPrepared: (
|
|
82
|
+
callback: <RuntimeContext extends object>(
|
|
83
|
+
info: PreparedRequest<RuntimeContext>,
|
|
84
|
+
) => PreparedRequest<RuntimeContext>,
|
|
85
|
+
) => unknown;
|
|
86
|
+
onRequestEnd: (
|
|
87
|
+
callback: (info: { runtimeContext: object }) => Promise<void>,
|
|
88
|
+
) => unknown;
|
|
33
89
|
onAfterCreateRouter: (
|
|
34
90
|
callback: (event: RouterLifecycleEvent) => void,
|
|
35
91
|
) => unknown;
|
|
36
92
|
}) {
|
|
93
|
+
if (installedRouterPolicies.has(api)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
installedRouterPolicies.add(api);
|
|
37
97
|
api.onBeforeRender(context => {
|
|
38
98
|
const runtimeContext = context as {
|
|
39
99
|
linkPrefetchPolicy?: ReturnType<typeof createRouterPrefetchPolicy>;
|
|
40
100
|
};
|
|
41
101
|
runtimeContext.linkPrefetchPolicy ??= createRouterPrefetchPolicy();
|
|
42
102
|
});
|
|
103
|
+
api.onRenderPrepared(info => {
|
|
104
|
+
const snapshot = getRouterServerSnapshot(info.runtimeContext);
|
|
105
|
+
if (!snapshot) {
|
|
106
|
+
return info;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...info,
|
|
110
|
+
routerResult: {
|
|
111
|
+
statusCode: snapshot.statusCode ?? info.routerResult?.statusCode,
|
|
112
|
+
errors: snapshot.errors ?? info.routerResult?.errors,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
api.onRequestEnd(async ({ runtimeContext }) => {
|
|
117
|
+
try {
|
|
118
|
+
await getRouterRuntimeState(runtimeContext)?.cleanup?.();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const { ssrContext } = runtimeContext as {
|
|
121
|
+
ssrContext?: {
|
|
122
|
+
onError: (error: unknown, message: string) => void;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
if (!ssrContext) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
ssrContext.onError(error, ROUTER_CLEANUP_ERROR);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
43
131
|
api.onAfterCreateRouter(event => {
|
|
44
132
|
if (event.framework !== 'react-router') {
|
|
45
133
|
return;
|
|
@@ -51,7 +139,7 @@ export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
|
|
|
51
139
|
if (!routerContext) {
|
|
52
140
|
return;
|
|
53
141
|
}
|
|
54
|
-
const
|
|
142
|
+
const snapshot = {
|
|
55
143
|
framework: event.framework,
|
|
56
144
|
basename: event.basename,
|
|
57
145
|
statusCode: routerContext.statusCode,
|
|
@@ -65,19 +153,27 @@ export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
|
|
|
65
153
|
? [{ routeId: match.route.id }]
|
|
66
154
|
: [],
|
|
67
155
|
),
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
156
|
+
};
|
|
157
|
+
applyRouterServerPrepareResult(event.runtimeContext, {
|
|
158
|
+
snapshot,
|
|
159
|
+
state: {
|
|
160
|
+
framework: event.framework,
|
|
161
|
+
basename: event.basename,
|
|
162
|
+
instance: event.router,
|
|
163
|
+
},
|
|
75
164
|
});
|
|
76
165
|
} else if (event.phase === 'client-create') {
|
|
166
|
+
const router = event.router as ReturnType<typeof createMemoryRouter>;
|
|
167
|
+
const { router: api } = event.runtimeContext as {
|
|
168
|
+
router?: { Link?: RouterNavigationCapability['Link'] };
|
|
169
|
+
};
|
|
77
170
|
applyRouterRuntimeState(event.runtimeContext, {
|
|
78
171
|
framework: event.framework,
|
|
79
172
|
basename: event.basename,
|
|
80
|
-
instance:
|
|
173
|
+
instance: router,
|
|
174
|
+
...(api?.Link
|
|
175
|
+
? { navigation: createReactRouterNavigation(router, api.Link) }
|
|
176
|
+
: {}),
|
|
81
177
|
});
|
|
82
178
|
}
|
|
83
179
|
});
|
package/src/routerStateTypes.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
1
3
|
export type BuiltInRouterFramework = 'react-router' | 'tanstack';
|
|
2
4
|
|
|
3
5
|
export type RouterFramework = BuiltInRouterFramework | (string & {});
|
|
@@ -10,37 +12,65 @@ export interface RouterRouteMatchSnapshot {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export interface InternalRouterServerSnapshot {
|
|
13
|
-
framework?: RouterFramework;
|
|
14
|
-
basename?: string;
|
|
15
|
-
statusCode?: number;
|
|
16
|
-
errors?: Record<string, unknown>;
|
|
17
|
-
routerData?: {
|
|
18
|
-
loaderData?: Record<string, unknown>;
|
|
19
|
-
errors?: Record<string, unknown>;
|
|
15
|
+
readonly framework?: RouterFramework;
|
|
16
|
+
readonly basename?: string;
|
|
17
|
+
readonly statusCode?: number;
|
|
18
|
+
readonly errors?: Record<string, unknown>;
|
|
19
|
+
readonly routerData?: {
|
|
20
|
+
readonly loaderData?: Record<string, unknown>;
|
|
21
|
+
readonly errors?: Record<string, unknown>;
|
|
20
22
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
matches?: RouterRouteMatchSnapshot[];
|
|
23
|
+
readonly hydrationScripts?: readonly string[];
|
|
24
|
+
readonly matchedRouteIds?: readonly string[];
|
|
25
|
+
readonly matches?: readonly RouterRouteMatchSnapshot[];
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface InternalRouterRuntimeState {
|
|
28
29
|
framework: RouterFramework;
|
|
29
30
|
basename?: string;
|
|
30
31
|
instance?: unknown;
|
|
31
|
-
|
|
32
|
-
hydrationScripts?: string[];
|
|
33
|
-
matchedRouteIds?: string[];
|
|
34
|
-
matches?: RouterRouteMatchSnapshot[];
|
|
35
|
-
serverSnapshot?: InternalRouterServerSnapshot;
|
|
32
|
+
navigation?: RouterNavigationCapability;
|
|
36
33
|
cleanup?: () => void | Promise<void>;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
export interface RouterServerPrepareResult {
|
|
40
37
|
state: InternalRouterRuntimeState;
|
|
41
38
|
snapshot?: InternalRouterServerSnapshot;
|
|
42
|
-
redirect?: Response;
|
|
43
39
|
cleanup?: () => void | Promise<void>;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
export type RouterLifecyclePhase = 'ssr-prepare' | 'client-create' | 'hydrate';
|
|
43
|
+
|
|
44
|
+
/** Provider-owned browser navigation. Consumers never inspect router internals. */
|
|
45
|
+
export interface RouterNavigationSnapshot {
|
|
46
|
+
location: { pathname: string; search: string; hash: string };
|
|
47
|
+
params: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RouterLinkTarget {
|
|
51
|
+
pathname: string;
|
|
52
|
+
href: string;
|
|
53
|
+
search?: Record<string, unknown>;
|
|
54
|
+
hash?: string;
|
|
55
|
+
hashScrollIntoView?: boolean | ScrollIntoViewOptions;
|
|
56
|
+
prefetch?: 'intent' | 'render' | 'viewport' | 'none';
|
|
57
|
+
preload?: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RouterNavigationCapability {
|
|
61
|
+
getSnapshot: () => RouterNavigationSnapshot;
|
|
62
|
+
subscribe: (listener: () => void) => () => void;
|
|
63
|
+
navigate: (
|
|
64
|
+
href: string,
|
|
65
|
+
options?: { replace?: boolean; state?: unknown },
|
|
66
|
+
) => void | Promise<void>;
|
|
67
|
+
Link: React.ComponentType<{
|
|
68
|
+
to: string;
|
|
69
|
+
children?: React.ReactNode;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
}>;
|
|
72
|
+
createLinkProps?: (target: RouterLinkTarget) => {
|
|
73
|
+
to: string;
|
|
74
|
+
[key: string]: unknown;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __webpack_require__ = {};
|
|
3
|
-
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
-
var define = (defs, kind)=>{
|
|
6
|
-
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
[kind]: defs[key]
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
define(getters, "get");
|
|
12
|
-
define(values, "value");
|
|
13
|
-
};
|
|
14
|
-
})();
|
|
15
|
-
(()=>{
|
|
16
|
-
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
-
})();
|
|
18
|
-
(()=>{
|
|
19
|
-
__webpack_require__.r = (exports1)=>{
|
|
20
|
-
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
-
value: 'Module'
|
|
22
|
-
});
|
|
23
|
-
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
-
value: true
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
})();
|
|
28
|
-
var __webpack_exports__ = {};
|
|
29
|
-
__webpack_require__.r(__webpack_exports__);
|
|
30
|
-
function readBuildValue(read, fallback) {
|
|
31
|
-
try {
|
|
32
|
-
return read();
|
|
33
|
-
} catch (error) {
|
|
34
|
-
if (null !== error && 'object' == typeof error && 'name' in error && 'ReferenceError' === error.name) return fallback;
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function resolveUltramodernBuildArtifact(artifact, readers) {
|
|
39
|
-
const buildMarker = readers ? readBuildValue(readers.buildMarker, artifact.deliveryUnit.buildMarker) : artifact.deliveryUnit.buildMarker;
|
|
40
|
-
const sourceRevision = readers ? readBuildValue(readers.sourceRevision, artifact.deliveryUnit.sourceRevision) : artifact.deliveryUnit.sourceRevision;
|
|
41
|
-
const identity = {
|
|
42
|
-
build: buildMarker,
|
|
43
|
-
buildMarker,
|
|
44
|
-
sourceRevision
|
|
45
|
-
};
|
|
46
|
-
return {
|
|
47
|
-
...artifact,
|
|
48
|
-
deliveryUnit: {
|
|
49
|
-
...artifact.deliveryUnit,
|
|
50
|
-
...identity
|
|
51
|
-
},
|
|
52
|
-
surfaces: {
|
|
53
|
-
api: {
|
|
54
|
-
...artifact.surfaces.api,
|
|
55
|
-
...identity
|
|
56
|
-
},
|
|
57
|
-
ui: {
|
|
58
|
-
...artifact.surfaces.ui,
|
|
59
|
-
...identity
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
65
|
-
resolveUltramodernBuildArtifact: ()=>resolveUltramodernBuildArtifact
|
|
66
|
-
});
|
|
67
|
-
exports.resolveUltramodernBuildArtifact = __webpack_exports__.resolveUltramodernBuildArtifact;
|
|
68
|
-
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
69
|
-
"resolveUltramodernBuildArtifact"
|
|
70
|
-
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
71
|
-
Object.defineProperty(exports, '__esModule', {
|
|
72
|
-
value: true
|
|
73
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
function readBuildValue(read, fallback) {
|
|
2
|
-
try {
|
|
3
|
-
return read();
|
|
4
|
-
} catch (error) {
|
|
5
|
-
if (null !== error && 'object' == typeof error && 'name' in error && 'ReferenceError' === error.name) return fallback;
|
|
6
|
-
throw error;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
function resolveUltramodernBuildArtifact(artifact, readers) {
|
|
10
|
-
const buildMarker = readers ? readBuildValue(readers.buildMarker, artifact.deliveryUnit.buildMarker) : artifact.deliveryUnit.buildMarker;
|
|
11
|
-
const sourceRevision = readers ? readBuildValue(readers.sourceRevision, artifact.deliveryUnit.sourceRevision) : artifact.deliveryUnit.sourceRevision;
|
|
12
|
-
const identity = {
|
|
13
|
-
build: buildMarker,
|
|
14
|
-
buildMarker,
|
|
15
|
-
sourceRevision
|
|
16
|
-
};
|
|
17
|
-
return {
|
|
18
|
-
...artifact,
|
|
19
|
-
deliveryUnit: {
|
|
20
|
-
...artifact.deliveryUnit,
|
|
21
|
-
...identity
|
|
22
|
-
},
|
|
23
|
-
surfaces: {
|
|
24
|
-
api: {
|
|
25
|
-
...artifact.surfaces.api,
|
|
26
|
-
...identity
|
|
27
|
-
},
|
|
28
|
-
ui: {
|
|
29
|
-
...artifact.surfaces.ui,
|
|
30
|
-
...identity
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
export { resolveUltramodernBuildArtifact };
|