@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.3 → 3.9.0-ultramodern.5

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 (200) hide show
  1. package/dist/cjs/buildIdentity.js +73 -0
  2. package/dist/cjs/contextExtensions.js +74 -0
  3. package/dist/cjs/headRuntime.js +91 -0
  4. package/dist/cjs/helmetContext.js +54 -0
  5. package/dist/cjs/index.js +18 -4
  6. package/dist/cjs/localisedLoaderIdentity.js +51 -0
  7. package/dist/cjs/prefetchPolicy.js +52 -0
  8. package/dist/cjs/reactContext.js +45 -0
  9. package/dist/cjs/request-policy/browser.js +139 -0
  10. package/dist/cjs/request-policy/client.js +234 -0
  11. package/dist/cjs/request-policy/node.js +149 -0
  12. package/dist/cjs/request-policy/policyCore.js +266 -0
  13. package/dist/cjs/request-policy/requestContext.js +137 -0
  14. package/dist/cjs/request-policy/traceparent.js +56 -0
  15. package/dist/cjs/request-policy/transport.js +234 -0
  16. package/dist/cjs/request-policy/types.js +57 -0
  17. package/dist/cjs/routeHydration.js +127 -0
  18. package/dist/cjs/routerPrefetchPolicy.js +153 -0
  19. package/dist/cjs/routerProvider.js +170 -0
  20. package/dist/cjs/routerState.js +167 -0
  21. package/dist/cjs/routerStatePlugin.js +86 -0
  22. package/dist/cjs/routerStateTypes.js +18 -0
  23. package/dist/cjs/rsc-disabled/rscDisabledClientBrowserRuntime.js +69 -0
  24. package/dist/cjs/rsc-disabled/rscDisabledClientEdgeRuntime.js +61 -0
  25. package/dist/cjs/rsc-disabled/rscDisabledClientNodeRuntime.js +65 -0
  26. package/dist/cjs/rsc-disabled/rscDisabledRuntimeError.js +42 -0
  27. package/dist/cjs/rsc-disabled/rscDisabledServerEdgeRuntime.js +93 -0
  28. package/dist/cjs/rsc-disabled/rscDisabledServerNodeRuntime.js +101 -0
  29. package/dist/cjs/runtimeContextProjection.js +112 -0
  30. package/dist/cjs/server-config/bffRuntime.js +18 -0
  31. package/dist/cjs/server-config/index.js +18 -0
  32. package/dist/cjs/server-config/serverTelemetry.js +18 -0
  33. package/dist/cjs/shellStream.js +34 -2
  34. package/dist/cjs/workspaceEvents.js +159 -0
  35. package/dist/esm/buildIdentity.mjs +35 -0
  36. package/dist/esm/contextExtensions.mjs +33 -0
  37. package/dist/esm/headRuntime.mjs +43 -0
  38. package/dist/esm/helmetContext.mjs +13 -0
  39. package/dist/esm/index.mjs +2 -0
  40. package/dist/esm/localisedLoaderIdentity.mjs +13 -0
  41. package/dist/esm/prefetchPolicy.mjs +11 -0
  42. package/dist/esm/reactContext.mjs +7 -0
  43. package/dist/esm/request-policy/browser.mjs +11 -0
  44. package/dist/esm/request-policy/client.mjs +195 -0
  45. package/dist/esm/request-policy/policyCore.mjs +171 -0
  46. package/dist/esm/request-policy/requestContext.mjs +75 -0
  47. package/dist/esm/request-policy/traceparent.mjs +18 -0
  48. package/dist/esm/request-policy/transport.mjs +196 -0
  49. package/dist/esm/request-policy/types.mjs +10 -0
  50. package/dist/esm/routeHydration.mjs +77 -0
  51. package/dist/esm/routerPrefetchPolicy.mjs +115 -0
  52. package/dist/esm/routerProvider.mjs +117 -0
  53. package/dist/esm/routerState.mjs +105 -0
  54. package/dist/esm/routerStatePlugin.mjs +48 -0
  55. package/dist/esm/routerStateTypes.mjs +0 -0
  56. package/dist/esm/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +10 -0
  57. package/dist/esm/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +8 -0
  58. package/dist/esm/rsc-disabled/rscDisabledClientNodeRuntime.mjs +9 -0
  59. package/dist/esm/rsc-disabled/rscDisabledRuntimeError.mjs +4 -0
  60. package/dist/esm/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +16 -0
  61. package/dist/esm/rsc-disabled/rscDisabledServerNodeRuntime.mjs +18 -0
  62. package/dist/esm/runtimeContextProjection.mjs +74 -0
  63. package/dist/esm/server-config/bffRuntime.mjs +0 -0
  64. package/dist/esm/server-config/index.mjs +0 -0
  65. package/dist/esm/server-config/serverTelemetry.mjs +0 -0
  66. package/dist/esm/shellStream.mjs +34 -2
  67. package/dist/esm/workspaceEvents.mjs +66 -0
  68. package/dist/esm-node/buildIdentity.mjs +36 -0
  69. package/dist/esm-node/contextExtensions.mjs +34 -0
  70. package/dist/esm-node/headRuntime.mjs +44 -0
  71. package/dist/esm-node/helmetContext.mjs +14 -0
  72. package/dist/esm-node/index.mjs +2 -0
  73. package/dist/esm-node/localisedLoaderIdentity.mjs +14 -0
  74. package/dist/esm-node/prefetchPolicy.mjs +12 -0
  75. package/dist/esm-node/reactContext.mjs +8 -0
  76. package/dist/esm-node/request-policy/browser.mjs +12 -0
  77. package/dist/esm-node/request-policy/client.mjs +196 -0
  78. package/dist/esm-node/request-policy/node.mjs +22 -0
  79. package/dist/esm-node/request-policy/policyCore.mjs +172 -0
  80. package/dist/esm-node/request-policy/requestContext.mjs +76 -0
  81. package/dist/esm-node/request-policy/traceparent.mjs +19 -0
  82. package/dist/esm-node/request-policy/transport.mjs +197 -0
  83. package/dist/esm-node/request-policy/types.mjs +11 -0
  84. package/dist/esm-node/routeHydration.mjs +78 -0
  85. package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
  86. package/dist/esm-node/routerProvider.mjs +118 -0
  87. package/dist/esm-node/routerState.mjs +106 -0
  88. package/dist/esm-node/routerStatePlugin.mjs +49 -0
  89. package/dist/esm-node/routerStateTypes.mjs +1 -0
  90. package/dist/esm-node/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +11 -0
  91. package/dist/esm-node/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +9 -0
  92. package/dist/esm-node/rsc-disabled/rscDisabledClientNodeRuntime.mjs +10 -0
  93. package/dist/esm-node/rsc-disabled/rscDisabledRuntimeError.mjs +5 -0
  94. package/dist/esm-node/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +17 -0
  95. package/dist/esm-node/rsc-disabled/rscDisabledServerNodeRuntime.mjs +19 -0
  96. package/dist/esm-node/runtimeContextProjection.mjs +75 -0
  97. package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
  98. package/dist/esm-node/server-config/index.mjs +1 -0
  99. package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
  100. package/dist/esm-node/shellStream.mjs +34 -2
  101. package/dist/esm-node/workspaceEvents.mjs +67 -0
  102. package/dist/types/buildIdentity.d.ts +37 -0
  103. package/dist/types/contextExtensions.d.ts +34 -0
  104. package/dist/types/headRuntime.d.ts +12 -0
  105. package/dist/types/helmetContext.d.ts +10 -0
  106. package/dist/types/index.d.ts +2 -0
  107. package/dist/types/localisedLoaderIdentity.d.ts +10 -0
  108. package/dist/types/prefetchPolicy.d.ts +4 -0
  109. package/dist/types/reactContext.d.ts +6 -0
  110. package/dist/types/request-policy/browser.d.ts +10 -0
  111. package/dist/types/request-policy/client.d.ts +14 -0
  112. package/dist/types/request-policy/node.d.ts +10 -0
  113. package/dist/types/request-policy/policyCore.d.ts +107 -0
  114. package/dist/types/request-policy/requestContext.d.ts +22 -0
  115. package/dist/types/request-policy/traceparent.d.ts +10 -0
  116. package/dist/types/request-policy/transport.d.ts +12 -0
  117. package/dist/types/request-policy/types.d.ts +144 -0
  118. package/dist/types/routeHydration.d.ts +24 -0
  119. package/dist/types/routerPrefetchPolicy.d.ts +28 -0
  120. package/dist/types/routerProvider.d.ts +77 -0
  121. package/dist/types/routerState.d.ts +12 -0
  122. package/dist/types/routerStatePlugin.d.ts +19 -0
  123. package/dist/types/routerStateTypes.d.ts +40 -0
  124. package/dist/types/rsc-disabled/rscDisabledClientBrowserRuntime.d.ts +9 -0
  125. package/dist/types/rsc-disabled/rscDisabledClientEdgeRuntime.d.ts +7 -0
  126. package/dist/types/rsc-disabled/rscDisabledClientNodeRuntime.d.ts +8 -0
  127. package/dist/types/rsc-disabled/rscDisabledRuntimeError.d.ts +1 -0
  128. package/dist/types/rsc-disabled/rscDisabledServerEdgeRuntime.d.ts +15 -0
  129. package/dist/types/rsc-disabled/rscDisabledServerNodeRuntime.d.ts +17 -0
  130. package/dist/types/runtimeContextProjection.d.ts +30 -0
  131. package/dist/types/server-config/bffRuntime.d.ts +122 -0
  132. package/dist/types/server-config/index.d.ts +2 -0
  133. package/dist/types/server-config/serverTelemetry.d.ts +322 -0
  134. package/dist/types/workspaceEvents.d.ts +62 -0
  135. package/package.json +238 -13
  136. package/rslib.config.mts +17 -2
  137. package/rstest.config.mts +33 -2
  138. package/src/buildIdentity.ts +61 -0
  139. package/src/contextExtensions.ts +81 -0
  140. package/src/headRuntime.tsx +77 -0
  141. package/src/helmetContext.ts +31 -0
  142. package/src/index.ts +2 -0
  143. package/src/localisedLoaderIdentity.ts +30 -0
  144. package/src/prefetchPolicy.ts +33 -0
  145. package/src/reactContext.ts +16 -0
  146. package/src/request-policy/browser.ts +22 -0
  147. package/src/request-policy/client.ts +404 -0
  148. package/src/request-policy/node.ts +41 -0
  149. package/src/request-policy/policyCore.ts +424 -0
  150. package/src/request-policy/requestContext.ts +149 -0
  151. package/src/request-policy/traceparent.ts +43 -0
  152. package/src/request-policy/transport.ts +349 -0
  153. package/src/request-policy/types.ts +229 -0
  154. package/src/routeHydration.ts +208 -0
  155. package/src/routerPrefetchPolicy.ts +195 -0
  156. package/src/routerProvider.ts +340 -0
  157. package/src/routerState.ts +208 -0
  158. package/src/routerStatePlugin.ts +86 -0
  159. package/src/routerStateTypes.ts +46 -0
  160. package/src/rsc-disabled/rscDisabledClientBrowserRuntime.ts +10 -0
  161. package/src/rsc-disabled/rscDisabledClientEdgeRuntime.ts +8 -0
  162. package/src/rsc-disabled/rscDisabledClientNodeRuntime.ts +9 -0
  163. package/src/rsc-disabled/rscDisabledRuntimeError.ts +3 -0
  164. package/src/rsc-disabled/rscDisabledServerEdgeRuntime.ts +16 -0
  165. package/src/rsc-disabled/rscDisabledServerNodeRuntime.ts +18 -0
  166. package/src/runtimeContextProjection.ts +105 -0
  167. package/src/server-config/bffRuntime.ts +136 -0
  168. package/src/server-config/index.ts +2 -0
  169. package/src/server-config/serverTelemetry.ts +337 -0
  170. package/src/shellStream.ts +55 -25
  171. package/src/workspaceEvents.ts +324 -0
  172. package/tests/buildIdentity.test.ts +70 -0
  173. package/tests/contextExtensions.test.ts +71 -0
  174. package/tests/headRuntime.test.tsx +369 -0
  175. package/tests/nativeSafeFailureCompatibility.test.ts +117 -0
  176. package/tests/prefetchPolicy.test.ts +51 -0
  177. package/tests/rendererHead.test.ts +1 -72
  178. package/tests/request-policy/browser-policy.test.ts +608 -0
  179. package/tests/request-policy/client-isolation.test.ts +149 -0
  180. package/tests/request-policy/identityBinding.test.ts +75 -0
  181. package/tests/request-policy/node-policy.test.ts +741 -0
  182. package/tests/request-policy/policyCore.test.ts +250 -0
  183. package/tests/request-policy/requestContext.test.ts +80 -0
  184. package/tests/request-policy/requestFactory.outbound-contract.test.ts +691 -0
  185. package/tests/request-policy/traceparent.test.ts +55 -0
  186. package/tests/request-policy/transport-retry.test.ts +370 -0
  187. package/tests/routeHydration.test.ts +180 -0
  188. package/tests/routerPrefetchPolicy.test.ts +102 -0
  189. package/tests/routerProvider.test.ts +38 -0
  190. package/tests/routerState.test.ts +157 -0
  191. package/tests/routerStatePlugin.test.ts +108 -0
  192. package/tests/runtimeContextProjection.test.ts +135 -0
  193. package/tests/shellStream.test.ts +37 -0
  194. package/tests/workspaceEvents.test.ts +391 -0
  195. package/dist/cjs/rscHtmlStream.js +0 -128
  196. package/dist/esm/rscHtmlStream.mjs +0 -86
  197. package/dist/esm-node/rscHtmlStream.mjs +0 -87
  198. package/dist/types/rscHtmlStream.d.ts +0 -4
  199. package/src/rscHtmlStream.ts +0 -142
  200. package/tests/rscHtmlStream.test.ts +0 -153
@@ -0,0 +1,340 @@
1
+ // @effect-diagnostics globalConsole:off
2
+ import { merge } from '@modern-js/runtime-utils/merge';
3
+
4
+ /** Structural plugin contract; the composing integration supplies native hooks. */
5
+ export type RouterProviderPlugin = {
6
+ name?: string;
7
+ registryHooks?: Record<string, unknown>;
8
+ setup?: (api: any) => unknown;
9
+ [key: string]: unknown;
10
+ };
11
+
12
+ /**
13
+ * Guard for the wrapper plugin (`router/internal`): a resolved provider is
14
+ * only invoked through `setup`, so registry hooks outside the canonical
15
+ * router hook contract cannot be registered for it. Returns the offending
16
+ * hook names and warns once so they are surfaced instead of silently dropped.
17
+ */
18
+ export function reportUnsupportedProviderRegistryHooks(
19
+ providerPlugin: {
20
+ name?: string;
21
+ registryHooks?: Record<string, unknown>;
22
+ },
23
+ routerProviderRegistryHooks: Record<string, unknown>,
24
+ ): string[] {
25
+ const unsupportedHookNames = Object.keys(
26
+ providerPlugin.registryHooks ?? {},
27
+ ).filter(hookName => !(hookName in routerProviderRegistryHooks));
28
+
29
+ if (unsupportedHookNames.length > 0) {
30
+ console.warn(
31
+ `[@modern-js/runtime] The router provider "${providerPlugin.name}" declares registry hooks outside the router hook contract: ${unsupportedHookNames.join(
32
+ ', ',
33
+ )}. These hooks are not registered when the provider is resolved through \`runtime.router.framework\` — declare them on a separate runtime plugin instead.`,
34
+ );
35
+ }
36
+
37
+ return unsupportedHookNames;
38
+ }
39
+
40
+ export type RouterProviderFactory = (userConfig?: any) => RouterProviderPlugin;
41
+
42
+ export type RouterProviderRegistration = {
43
+ name: string;
44
+ factory: RouterProviderFactory;
45
+ isDefault?: boolean;
46
+ };
47
+
48
+ /**
49
+ * App-owned provider catalog. Provider factories close over their module graph,
50
+ * so they must be resolved from the runtime wrapper that owns the app instead
51
+ * of from a page-global first-registration winner.
52
+ */
53
+ export type RouterProviderRealm = Readonly<{
54
+ defaultProvider?: string;
55
+ get: (name: string) => RouterProviderFactory | undefined;
56
+ names: () => readonly string[];
57
+ }>;
58
+
59
+ export function createRouterProviderRealm(
60
+ registrations: readonly RouterProviderRegistration[],
61
+ ): RouterProviderRealm {
62
+ const providers = new Map<string, RouterProviderFactory>();
63
+ let defaultProvider: string | undefined;
64
+
65
+ for (const registration of registrations) {
66
+ if (registration.name.length === 0) {
67
+ throw new Error(
68
+ '[@modern-js/runtime] A router provider realm cannot contain an unnamed provider.',
69
+ );
70
+ }
71
+ if (providers.has(registration.name)) {
72
+ throw new Error(
73
+ `[@modern-js/runtime] Router provider "${registration.name}" is declared more than once in the same runtime realm.`,
74
+ );
75
+ }
76
+ if (
77
+ registration.isDefault === true &&
78
+ defaultProvider !== undefined &&
79
+ defaultProvider !== registration.name
80
+ ) {
81
+ throw new Error(
82
+ `[@modern-js/runtime] Router provider realm declares both "${defaultProvider}" and "${registration.name}" as defaults.`,
83
+ );
84
+ }
85
+
86
+ providers.set(registration.name, registration.factory);
87
+ if (registration.isDefault === true) {
88
+ defaultProvider = registration.name;
89
+ }
90
+ }
91
+
92
+ const providerNames = Object.freeze([...providers.keys()]);
93
+ return Object.freeze({
94
+ ...(defaultProvider !== undefined ? { defaultProvider } : {}),
95
+ get: (name: string) => providers.get(name),
96
+ names: () => providerNames,
97
+ });
98
+ }
99
+
100
+ type RouterProviderRegistry = {
101
+ providers: Map<string, RouterProviderFactory>;
102
+ defaultProvider?: string;
103
+ /** Names that were registered by more than one module instance. */
104
+ duplicateProviders: Set<string>;
105
+ /** Duplicate names already reported when the compatibility fallback won. */
106
+ warnedDuplicates: Set<string>;
107
+ };
108
+
109
+ /**
110
+ * Versioned compatibility-registry key. The unsuffixed key
111
+ * ('@modern-js/runtime:router-providers') is owned by older published copies
112
+ * of this module whose `registerRouterProvider` *throws* on a same-name
113
+ * re-registration. `:v2` added keep-first registration but also enforced one
114
+ * non-default provider across the whole JavaScript realm. `:v3` removes that
115
+ * page-global app invariant: explicit runtime realms own provider selection,
116
+ * while this registry remains only a mixed-version fallback. New copies read
117
+ * but never mutate the v2 slot so older published copies remain compatible.
118
+ */
119
+ const REGISTRY_SLOT: unique symbol = Symbol.for(
120
+ '@modern-js/runtime:router-providers:v3',
121
+ );
122
+ const LEGACY_V2_REGISTRY_SLOT: unique symbol = Symbol.for(
123
+ '@modern-js/runtime:router-providers:v2',
124
+ );
125
+
126
+ function getRegistry(): RouterProviderRegistry {
127
+ const host = globalThis as { [REGISTRY_SLOT]?: RouterProviderRegistry };
128
+ host[REGISTRY_SLOT] ??= {
129
+ providers: new Map(),
130
+ duplicateProviders: new Set(),
131
+ warnedDuplicates: new Set(),
132
+ };
133
+ // Defense in depth within the v3 key: if a future v3-keyed copy of this
134
+ // module ever predates a later-added field (mixed v3 minors in a Module
135
+ // Federation setup), heal the shape instead of crashing on `undefined`.
136
+ host[REGISTRY_SLOT].duplicateProviders ??= new Set();
137
+ host[REGISTRY_SLOT].warnedDuplicates ??= new Set();
138
+ return host[REGISTRY_SLOT];
139
+ }
140
+
141
+ function getLegacyV2Registry():
142
+ | Pick<RouterProviderRegistry, 'providers' | 'defaultProvider'>
143
+ | undefined {
144
+ const host = globalThis as {
145
+ [LEGACY_V2_REGISTRY_SLOT]?: Pick<
146
+ RouterProviderRegistry,
147
+ 'providers' | 'defaultProvider'
148
+ >;
149
+ };
150
+ return host[LEGACY_V2_REGISTRY_SLOT];
151
+ }
152
+
153
+ export function registerRouterProvider(
154
+ name: string,
155
+ factory: RouterProviderFactory,
156
+ options: { isDefault?: boolean } = {},
157
+ ): void {
158
+ const registry = getRegistry();
159
+ const existing = registry.providers.get(name);
160
+
161
+ if (existing !== undefined) {
162
+ // Keep-first semantics. A same-name re-registration with a *different*
163
+ // factory is almost always two bundled copies of the same provider
164
+ // module — e.g. a Module Federation remote that does not share
165
+ // '@modern-js/plugin-tanstack/runtime' evaluates its own copy, which
166
+ // creates a fresh factory function per evaluation. That must not crash
167
+ // the app; the first registration remains available for compatibility.
168
+ // Merely loading independent app-owned realms is valid, so defer the
169
+ // warning until a caller actually consumes this ambiguous fallback.
170
+ if (existing !== factory) {
171
+ registry.duplicateProviders.add(name);
172
+ }
173
+ return;
174
+ }
175
+
176
+ if (options.isDefault === true) {
177
+ registry.defaultProvider = name;
178
+ }
179
+
180
+ registry.providers.set(name, factory);
181
+ }
182
+
183
+ export function resolveRouterProvider(
184
+ framework?: string,
185
+ options: {
186
+ /**
187
+ * Provider factories owned by the resolving app/runtime wrapper. A realm
188
+ * is the exclusive provider source when supplied because its factories
189
+ * close over the correct app module graph.
190
+ */
191
+ realm?: RouterProviderRealm;
192
+ /**
193
+ * Legacy single-provider override retained for published wrappers. New
194
+ * integrations should pass an app-owned `realm`, which supports every
195
+ * provider rather than only the default.
196
+ */
197
+ localDefault?: { name: string; factory: RouterProviderFactory };
198
+ } = {},
199
+ ): RouterProviderFactory {
200
+ if (options.realm !== undefined) {
201
+ const name = framework || options.realm.defaultProvider;
202
+ const realmProviderNames = options.realm.names();
203
+
204
+ if (name === undefined) {
205
+ throw new Error(
206
+ `[@modern-js/runtime] The app-owned router provider realm does not declare a default provider. Available realm providers: ${
207
+ realmProviderNames.join(', ') || '(none)'
208
+ }.`,
209
+ );
210
+ }
211
+
212
+ const factory = options.realm.get(name);
213
+ if (factory === undefined) {
214
+ throw new Error(
215
+ `[@modern-js/runtime] Router provider "${name}" is not registered in the app-owned router provider realm. ` +
216
+ `Available realm providers: ${
217
+ realmProviderNames.join(', ') || '(none)'
218
+ }. Compatibility registry fallback is disabled for app-owned realms.`,
219
+ );
220
+ }
221
+
222
+ return factory;
223
+ }
224
+
225
+ const registry = getRegistry();
226
+ const legacyV2Registry = getLegacyV2Registry();
227
+ // `||` on purpose: a falsy framework value (empty string from env
228
+ // templating, `false`, `undefined`) falls back to the default provider
229
+ // instead of erroring on an unknown framework "". Prefer the older
230
+ // localDefault contract before compatibility slots.
231
+ const name =
232
+ framework ||
233
+ options.localDefault?.name ||
234
+ registry.defaultProvider ||
235
+ legacyV2Registry?.defaultProvider;
236
+
237
+ if (name === undefined) {
238
+ throw new Error(
239
+ '[@modern-js/runtime] No default router provider is registered. This is a bug in the runtime setup.',
240
+ );
241
+ }
242
+
243
+ if (
244
+ options.localDefault !== undefined &&
245
+ name === options.localDefault.name
246
+ ) {
247
+ return options.localDefault.factory;
248
+ }
249
+
250
+ const factory = registry.providers.get(name);
251
+ if (factory !== undefined) {
252
+ if (
253
+ registry.duplicateProviders.has(name) &&
254
+ !registry.warnedDuplicates.has(name)
255
+ ) {
256
+ registry.warnedDuplicates.add(name);
257
+ console.warn(
258
+ `[@modern-js/runtime] The router provider "${name}" was registered more than once with different module instances, and a router wrapper without an app-owned provider realm is resolving the mixed-version compatibility fallback. The fallback is keeping the first registration. ` +
259
+ 'Modern router wrappers isolate app-owned realms automatically. ' +
260
+ 'For an older wrapper, share the provider runtime between Module Federation host and remotes or upgrade every app to a realm-aware runtime.',
261
+ );
262
+ }
263
+ return factory;
264
+ }
265
+
266
+ const legacyV2Factory = legacyV2Registry?.providers.get(name);
267
+ if (legacyV2Factory !== undefined) {
268
+ return legacyV2Factory;
269
+ }
270
+
271
+ if (name === 'tanstack') {
272
+ throw new Error(
273
+ '[@modern-js/runtime] `runtime.router.framework` is set to "tanstack", but no TanStack router provider is registered. ' +
274
+ 'Install @modern-js/plugin-tanstack, add `tanstackRouterPlugin()` to the `plugins` array in modern.config.ts, ' +
275
+ "and make sure '@modern-js/plugin-tanstack/runtime' is imported (e.g. in modern.runtime.ts).",
276
+ );
277
+ }
278
+
279
+ throw new Error(
280
+ `[@modern-js/runtime] Unknown router framework "${name}". ` +
281
+ `Registered providers: ${
282
+ [
283
+ ...new Set([
284
+ ...registry.providers.keys(),
285
+ ...(legacyV2Registry?.providers.keys() ?? []),
286
+ ]),
287
+ ].join(', ') || '(none)'
288
+ }. Install and register the plugin that provides this router framework.`,
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Test-only escape hatch: compatibility registries live on `globalThis`, so
294
+ * unit tests need a way to restore a pristine state between cases.
295
+ */
296
+ export function unsafe_resetRouterProvidersForTesting(): void {
297
+ const host = globalThis as {
298
+ [REGISTRY_SLOT]?: RouterProviderRegistry;
299
+ [LEGACY_V2_REGISTRY_SLOT]?: unknown;
300
+ };
301
+ delete host[REGISTRY_SLOT];
302
+ delete host[LEGACY_V2_REGISTRY_SLOT];
303
+ }
304
+
305
+ /** Compose app-owned providers without importing the native runtime graph. */
306
+ export function createRouterPlugin<Hooks extends Record<string, unknown>>({
307
+ defaultProvider,
308
+ registryHooks,
309
+ localProviders = [],
310
+ }: {
311
+ defaultProvider: { name: string; factory: RouterProviderFactory };
312
+ registryHooks: Hooks;
313
+ localProviders?: readonly Omit<RouterProviderRegistration, 'isDefault'>[];
314
+ }) {
315
+ registerRouterProvider(defaultProvider.name, defaultProvider.factory, {
316
+ isDefault: true,
317
+ });
318
+ const realm = createRouterProviderRealm([
319
+ { ...defaultProvider, isDefault: true },
320
+ ...localProviders,
321
+ ]);
322
+
323
+ return (userConfig: Record<string, any> = {}) => ({
324
+ name: '@modern-js/plugin-router',
325
+ registryHooks,
326
+ setup(api: {
327
+ getRuntimeConfig: () => { router?: Record<string, unknown> };
328
+ [key: string]: any;
329
+ }) {
330
+ const mergedConfig = merge(
331
+ api.getRuntimeConfig().router || {},
332
+ userConfig,
333
+ );
334
+ const factory = resolveRouterProvider(mergedConfig.framework, { realm });
335
+ const provider = factory(userConfig);
336
+ reportUnsupportedProviderRegistryHooks(provider, registryHooks);
337
+ provider.setup?.(api);
338
+ },
339
+ });
340
+ }
@@ -0,0 +1,208 @@
1
+ // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
+ import { createRuntimeContextExtension } from './contextExtensions';
3
+ import type {
4
+ InternalRouterRuntimeState,
5
+ InternalRouterServerSnapshot,
6
+ RouterRouteMatchSnapshot,
7
+ RouterServerPrepareResult,
8
+ } from './routerStateTypes';
9
+
10
+ export type {
11
+ BuiltInRouterFramework,
12
+ InternalRouterRuntimeState,
13
+ InternalRouterServerSnapshot,
14
+ RouterFramework,
15
+ RouterLifecyclePhase,
16
+ RouterRouteMatchSnapshot,
17
+ RouterServerPrepareResult,
18
+ } from './routerStateTypes';
19
+
20
+ /**
21
+ * Router runtime state is shared by every router provider (react-router,
22
+ * @modern-js/plugin-tanstack, ...) and consumed by the SSR pipeline. It lives
23
+ * in the runtime-context extension slot instead of ad-hoc fields on
24
+ * `TInternalRuntimeContext`.
25
+ */
26
+ const routerRuntimeStateExtension =
27
+ createRuntimeContextExtension<InternalRouterRuntimeState>(
28
+ '@modern-js/runtime:router-runtime-state',
29
+ );
30
+
31
+ /**
32
+ * The server snapshot is tracked separately: a later
33
+ * `applyRouterRuntimeState` call without a snapshot must not clear a
34
+ * previously captured one.
35
+ */
36
+ const routerServerSnapshotExtension =
37
+ createRuntimeContextExtension<InternalRouterServerSnapshot>(
38
+ '@modern-js/runtime:router-server-snapshot',
39
+ );
40
+
41
+ export function getRouterRuntimeState(
42
+ runtimeContext: object,
43
+ ): InternalRouterRuntimeState | undefined {
44
+ return routerRuntimeStateExtension.get(runtimeContext);
45
+ }
46
+
47
+ export function getRouterServerSnapshot(
48
+ runtimeContext: object,
49
+ ): InternalRouterServerSnapshot | undefined {
50
+ return routerServerSnapshotExtension.get(runtimeContext);
51
+ }
52
+
53
+ type RouterSnapshotLike = Partial<InternalRouterServerSnapshot>;
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
+
74
+ export function createRouterServerSnapshot(
75
+ state: RouterSnapshotLike,
76
+ ): InternalRouterServerSnapshot {
77
+ const hydrationScripts = toHydrationScripts(state);
78
+ const matchedRouteIds =
79
+ state.matchedRouteIds ?? getMatchedRouteIdsFromMatches(state.matches);
80
+
81
+ return {
82
+ ...state,
83
+ ...(hydrationScripts?.length
84
+ ? {
85
+ hydrationScript: state.hydrationScript ?? hydrationScripts[0],
86
+ hydrationScripts,
87
+ }
88
+ : {}),
89
+ ...(matchedRouteIds ? { matchedRouteIds } : {}),
90
+ };
91
+ }
92
+
93
+ export function createRouterRuntimeState(
94
+ state: InternalRouterRuntimeState,
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
+
135
+ return {
136
+ ...state,
137
+ ...(hydrationScripts?.length
138
+ ? {
139
+ hydrationScript: state.hydrationScript ?? hydrationScripts[0],
140
+ hydrationScripts,
141
+ }
142
+ : {}),
143
+ ...(matchedRouteIds ? { matchedRouteIds } : {}),
144
+ ...(serverSnapshot ? { serverSnapshot } : {}),
145
+ };
146
+ }
147
+
148
+ export function applyRouterRuntimeState<Context extends object>(
149
+ runtimeContext: Context,
150
+ state: InternalRouterRuntimeState,
151
+ ) {
152
+ const normalized = createRouterRuntimeState(state);
153
+ routerRuntimeStateExtension.set(runtimeContext, normalized);
154
+ if (normalized.serverSnapshot) {
155
+ routerServerSnapshotExtension.set(
156
+ runtimeContext,
157
+ normalized.serverSnapshot,
158
+ );
159
+ }
160
+
161
+ return runtimeContext;
162
+ }
163
+
164
+ export function applyRouterServerPrepareResult<Context extends object>(
165
+ runtimeContext: Context,
166
+ result: RouterServerPrepareResult,
167
+ ) {
168
+ const state = createRouterRuntimeState({
169
+ ...result.state,
170
+ cleanup: result.cleanup ?? result.state.cleanup,
171
+ serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
172
+ });
173
+ applyRouterRuntimeState(runtimeContext, state);
174
+ return runtimeContext;
175
+ }
176
+
177
+ export function getRouterHydrationScripts(runtimeContext: object) {
178
+ const serverSnapshot = getRouterServerSnapshot(runtimeContext);
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
+ );
191
+ }
192
+
193
+ export function getRouterMatchedRouteIds(runtimeContext: object) {
194
+ const serverSnapshot = getRouterServerSnapshot(runtimeContext);
195
+ const runtimeState = getRouterRuntimeState(runtimeContext);
196
+ return (
197
+ serverSnapshot?.matchedRouteIds ??
198
+ getMatchedRouteIdsFromMatches(serverSnapshot?.matches) ??
199
+ runtimeState?.matchedRouteIds ??
200
+ getMatchedRouteIdsFromMatches(runtimeState?.matches)
201
+ );
202
+ }
203
+
204
+ export async function cleanupRouterRuntimeState(runtimeContext: object) {
205
+ try {
206
+ await getRouterRuntimeState(runtimeContext)?.cleanup?.();
207
+ } catch {}
208
+ }
@@ -0,0 +1,86 @@
1
+ import { createRouterPrefetchPolicy } from './routerPrefetchPolicy';
2
+ import {
3
+ applyRouterRuntimeState,
4
+ createRouterServerSnapshot,
5
+ } from './routerState';
6
+
7
+ type ReactRouterServerContext = {
8
+ statusCode: number;
9
+ errors?: Record<string, unknown> | null;
10
+ loaderData: Record<string, unknown>;
11
+ matches: { route: { id?: string } }[];
12
+ };
13
+
14
+ type RouterLifecycleEvent = {
15
+ framework: string;
16
+ phase: string;
17
+ runtimeContext: object;
18
+ basename?: string;
19
+ router?: unknown;
20
+ };
21
+
22
+ /** Install fork policy through lifecycle hooks supplied by the native owner. */
23
+ export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
24
+ registryHooks,
25
+ }: {
26
+ registryHooks: Hooks;
27
+ }) {
28
+ return {
29
+ name: '@modern-js/router-runtime-policy',
30
+ registryHooks,
31
+ setup(api: {
32
+ onBeforeRender: (callback: (context: object) => void) => unknown;
33
+ onAfterCreateRouter: (
34
+ callback: (event: RouterLifecycleEvent) => void,
35
+ ) => unknown;
36
+ }) {
37
+ api.onBeforeRender(context => {
38
+ const runtimeContext = context as {
39
+ linkPrefetchPolicy?: ReturnType<typeof createRouterPrefetchPolicy>;
40
+ };
41
+ runtimeContext.linkPrefetchPolicy ??= createRouterPrefetchPolicy();
42
+ });
43
+ api.onAfterCreateRouter(event => {
44
+ if (event.framework !== 'react-router') {
45
+ return;
46
+ }
47
+ if (event.phase === 'ssr-prepare') {
48
+ const { routerContext } = event.runtimeContext as {
49
+ routerContext?: ReactRouterServerContext;
50
+ };
51
+ if (!routerContext) {
52
+ return;
53
+ }
54
+ const serverSnapshot = createRouterServerSnapshot({
55
+ framework: event.framework,
56
+ basename: event.basename,
57
+ statusCode: routerContext.statusCode,
58
+ errors: routerContext.errors ?? undefined,
59
+ routerData: {
60
+ loaderData: routerContext.loaderData,
61
+ errors: routerContext.errors ?? undefined,
62
+ },
63
+ matches: routerContext.matches.flatMap(match =>
64
+ typeof match.route.id === 'string'
65
+ ? [{ routeId: match.route.id }]
66
+ : [],
67
+ ),
68
+ });
69
+ applyRouterRuntimeState(event.runtimeContext, {
70
+ framework: event.framework,
71
+ basename: event.basename,
72
+ instance: event.router,
73
+ matchedRouteIds: serverSnapshot.matchedRouteIds,
74
+ serverSnapshot,
75
+ });
76
+ } else if (event.phase === 'client-create') {
77
+ applyRouterRuntimeState(event.runtimeContext, {
78
+ framework: event.framework,
79
+ basename: event.basename,
80
+ instance: event.router,
81
+ });
82
+ }
83
+ });
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,46 @@
1
+ export type BuiltInRouterFramework = 'react-router' | 'tanstack';
2
+
3
+ export type RouterFramework = BuiltInRouterFramework | (string & {});
4
+
5
+ export interface RouterRouteMatchSnapshot {
6
+ routeId: string;
7
+ assetRouteId?: string;
8
+ pathname?: string;
9
+ params?: Record<string, string>;
10
+ }
11
+
12
+ 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>;
20
+ };
21
+ hydrationScript?: string;
22
+ hydrationScripts?: string[];
23
+ matchedRouteIds?: string[];
24
+ matches?: RouterRouteMatchSnapshot[];
25
+ }
26
+
27
+ export interface InternalRouterRuntimeState {
28
+ framework: RouterFramework;
29
+ basename?: string;
30
+ instance?: unknown;
31
+ hydrationScript?: string;
32
+ hydrationScripts?: string[];
33
+ matchedRouteIds?: string[];
34
+ matches?: RouterRouteMatchSnapshot[];
35
+ serverSnapshot?: InternalRouterServerSnapshot;
36
+ cleanup?: () => void | Promise<void>;
37
+ }
38
+
39
+ export interface RouterServerPrepareResult {
40
+ state: InternalRouterRuntimeState;
41
+ snapshot?: InternalRouterServerSnapshot;
42
+ redirect?: Response;
43
+ cleanup?: () => void | Promise<void>;
44
+ }
45
+
46
+ export type RouterLifecyclePhase = 'ssr-prepare' | 'client-create' | 'hydrate';
@@ -0,0 +1,10 @@
1
+ import { disabledRscRuntime } from './rscDisabledRuntimeError';
2
+
3
+ export const createFromFetch = disabledRscRuntime;
4
+ export const createFromReadableStream = disabledRscRuntime;
5
+ export const createServerReference = disabledRscRuntime;
6
+ export const createTemporaryReferenceSet = disabledRscRuntime;
7
+ export const encodeReply = disabledRscRuntime;
8
+ export const registerServerReference = disabledRscRuntime;
9
+ export const setFindSourceMapURLCallback = disabledRscRuntime;
10
+ export const setServerCallback = disabledRscRuntime;
@@ -0,0 +1,8 @@
1
+ import { disabledRscRuntime } from './rscDisabledRuntimeError';
2
+
3
+ export const createFromFetch = disabledRscRuntime;
4
+ export const createFromReadableStream = disabledRscRuntime;
5
+ export const createServerReference = disabledRscRuntime;
6
+ export const createTemporaryReferenceSet = disabledRscRuntime;
7
+ export const encodeReply = disabledRscRuntime;
8
+ export const registerServerReference = disabledRscRuntime;