@bleedingdev/modern-js-renderer-octane 3.9.3-ultramodern.1 → 3.9.3-ultramodern.11
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/client.js +56 -6
- package/dist/cjs/entry-application.js +7 -2
- package/dist/cjs/entry-client.js +3 -0
- package/dist/cjs/entry-server.js +13 -5
- package/dist/cjs/federation.js +108 -6
- package/dist/cjs/i18n/I18nProvider.js +6 -14
- package/dist/cjs/i18n/LocalizedLink.js +24 -33
- package/dist/cjs/i18n/index.js +0 -3
- package/dist/cjs/i18n-external-snapshot.js +645 -0
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/manifest.js +2 -2
- package/dist/cjs/plugin/compiler.js +2 -5
- package/dist/cjs/plugin/create.js +27 -26
- package/dist/cjs/plugin/index.js +2 -2
- package/dist/cjs/plugin/profile.js +12 -10
- package/dist/cjs/router-client.js +1 -1
- package/dist/cjs/router-external-snapshot.js +291 -0
- package/dist/cjs/router-handler.js +11 -8
- package/dist/cjs/router-server.js +3 -11
- package/dist/cjs/router.js +1 -1
- package/dist/cjs/routes.js +20 -17
- package/dist/cjs/server.js +28 -7
- package/dist/cjs/worker-federation-context.js +63 -0
- package/dist/cjs/worker-federation-contexts.js +97 -0
- package/dist/cjs/worker-federation.js +97 -0
- package/dist/esm/client.mjs +56 -6
- package/dist/esm/entry-application.mjs +7 -2
- package/dist/esm/entry-client.mjs +3 -0
- package/dist/esm/entry-server.mjs +11 -6
- package/dist/esm/federation.mjs +110 -8
- package/dist/esm/i18n/I18nProvider.mjs +7 -15
- package/dist/esm/i18n/LocalizedLink.mjs +24 -30
- package/dist/esm/i18n/index.mjs +1 -1
- package/dist/esm/i18n-external-snapshot.mjs +576 -0
- package/dist/esm/index.mjs +4 -4
- package/dist/esm/manifest.mjs +2 -2
- package/dist/esm/router-client.mjs +1 -1
- package/dist/esm/router-external-snapshot.mjs +244 -0
- package/dist/esm/router-handler.mjs +11 -8
- package/dist/esm/router-server.mjs +0 -1
- package/dist/esm/router.mjs +2 -2
- package/dist/esm/routes.mjs +18 -18
- package/dist/esm/server.mjs +24 -3
- package/dist/esm/worker-federation-context.mjs +19 -0
- package/dist/esm/worker-federation-contexts.mjs +47 -0
- package/dist/esm/worker-federation.mjs +59 -0
- package/dist/esm-node/manifest.mjs +2 -2
- package/dist/esm-node/plugin/compiler.mjs +2 -5
- package/dist/esm-node/plugin/create.mjs +27 -26
- package/dist/esm-node/plugin/index.mjs +2 -2
- package/dist/esm-node/plugin/profile.mjs +12 -10
- package/dist/types/client.d.ts +5 -1
- package/dist/types/entry-client.d.ts +2 -0
- package/dist/types/entry-server.d.ts +4 -1
- package/dist/types/federation.d.ts +2 -0
- package/dist/types/i18n/LocalizedLink.d.ts +5 -32
- package/dist/types/i18n/context.d.ts +1 -1
- package/dist/types/i18n/index.d.ts +1 -1
- package/dist/types/i18n-external-snapshot.d.ts +43 -0
- package/dist/types/index.d.ts +4 -4
- package/dist/types/manifest.d.ts +2 -2
- package/dist/types/router-external-snapshot.d.ts +10 -0
- package/dist/types/router-server.d.ts +0 -2
- package/dist/types/routes.d.ts +2 -0
- package/dist/types/server.d.ts +6 -0
- package/dist/types/worker-federation-context.d.ts +34 -0
- package/dist/types/worker-federation-contexts.d.ts +25 -0
- package/dist/types/worker-federation.d.ts +20 -0
- package/package.json +84 -22
- package/src/client.ts +99 -16
- package/src/entry-application.ts +7 -3
- package/src/entry-client.ts +10 -0
- package/src/entry-server.ts +20 -9
- package/src/federation.ts +164 -15
- package/src/i18n/I18nProvider.ts +11 -20
- package/src/i18n/LocalizedLink.ts +46 -72
- package/src/i18n/context.ts +1 -1
- package/src/i18n/index.ts +1 -5
- package/src/i18n-external-snapshot.ts +803 -0
- package/src/index.ts +4 -4
- package/src/manifest.ts +2 -2
- package/src/plugin/compiler-manifest.cjs +27 -1
- package/src/plugin/compiler.ts +6 -14
- package/src/plugin/create.ts +27 -26
- package/src/plugin/index.ts +4 -2
- package/src/plugin/profile.ts +14 -10
- package/src/router-client.ts +1 -1
- package/src/router-external-snapshot.ts +363 -0
- package/src/router-handler.ts +11 -8
- package/src/router-server.ts +0 -2
- package/src/router.ts +2 -2
- package/src/routes.ts +37 -26
- package/src/server.ts +45 -5
- package/src/worker-federation-context.ts +51 -0
- package/src/worker-federation-contexts.ts +91 -0
- package/src/worker-federation.ts +120 -0
- package/dist/cjs/router-injection.js +0 -139
- package/dist/esm/router-injection.mjs +0 -101
- package/dist/types/router-injection.d.ts +0 -8
- package/src/router-injection.ts +0 -145
package/src/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ export type { OctaneDocumentBootstrap } from './bootstrap';
|
|
|
3
3
|
|
|
4
4
|
export const OCTANE_RUNTIME_PROFILE = Object.freeze({
|
|
5
5
|
renderer: 'octane',
|
|
6
|
-
runtime: '0.
|
|
7
|
-
router: '0.
|
|
8
|
-
routerCore: '1.171.
|
|
9
|
-
history: '1.162.
|
|
6
|
+
runtime: '0.13.8',
|
|
7
|
+
router: '0.2.10',
|
|
8
|
+
routerCore: '1.171.34',
|
|
9
|
+
history: '1.162.4',
|
|
10
10
|
});
|
package/src/manifest.ts
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
assertOctaneIdentity,
|
|
10
10
|
} from './bootstrap';
|
|
11
11
|
|
|
12
|
-
export const OCTANE_RUNTIME_VERSION = '0.
|
|
13
|
-
export const OCTANE_COMPILER_VERSION = '0.
|
|
12
|
+
export const OCTANE_RUNTIME_VERSION = '0.13.8';
|
|
13
|
+
export const OCTANE_COMPILER_VERSION = '0.2.4';
|
|
14
14
|
|
|
15
15
|
export interface OctaneCompiledSource {
|
|
16
16
|
readonly resource: string;
|
|
@@ -107,6 +107,29 @@ class OctaneCompilerManifestPlugin {
|
|
|
107
107
|
sources.sort((left, right) =>
|
|
108
108
|
left.resource.localeCompare(right.resource),
|
|
109
109
|
);
|
|
110
|
+
// Module Federation publishes exposed native modules through a
|
|
111
|
+
// separate container entry in this same compilation. Its browser
|
|
112
|
+
// closure belongs to this application's publication, even when the
|
|
113
|
+
// document itself never imports an exposed component.
|
|
114
|
+
const containerChunks = new Set();
|
|
115
|
+
for (const [entryName, entry] of compilation.entries) {
|
|
116
|
+
if (
|
|
117
|
+
!entry.dependencies.some(
|
|
118
|
+
dependency => dependency.type === 'container entry',
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
continue;
|
|
122
|
+
const container = compilation.entrypoints.get(entryName);
|
|
123
|
+
if (!container)
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Octane compiler has no federation container entry for ${entryName}.`,
|
|
126
|
+
);
|
|
127
|
+
for (const chunk of container.chunks) {
|
|
128
|
+
containerChunks.add(chunk);
|
|
129
|
+
for (const referenced of chunk.getAllReferencedChunks())
|
|
130
|
+
containerChunks.add(referenced);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
110
133
|
const identities = this.options.rendererIdentities();
|
|
111
134
|
if (!identities || !Object.keys(identities).length) {
|
|
112
135
|
throw new Error(
|
|
@@ -129,7 +152,10 @@ class OctaneCompilerManifestPlugin {
|
|
|
129
152
|
throw new Error(
|
|
130
153
|
`Octane compiler has no client entry for ${entryName}.`,
|
|
131
154
|
);
|
|
132
|
-
const entryChunks = new Set(
|
|
155
|
+
const entryChunks = new Set([
|
|
156
|
+
...entrypoint.chunks,
|
|
157
|
+
...containerChunks,
|
|
158
|
+
]);
|
|
133
159
|
for (const chunk of entrypoint.chunks) {
|
|
134
160
|
for (const referenced of chunk.getAllReferencedChunks())
|
|
135
161
|
entryChunks.add(referenced);
|
package/src/plugin/compiler.ts
CHANGED
|
@@ -41,10 +41,15 @@ export function createOctaneCompilerPlugin(
|
|
|
41
41
|
} = require('./compiler-manifest.cjs');
|
|
42
42
|
// The application installs the Octane compiler; resolve it lazily so
|
|
43
43
|
// descriptor readers never load it.
|
|
44
|
+
const appRequire = createRequire(
|
|
45
|
+
path.join(api.context.rootPath, 'package.json'),
|
|
46
|
+
);
|
|
44
47
|
const {
|
|
45
48
|
inferRspackEnvironment,
|
|
46
49
|
OctaneRspackPlugin,
|
|
47
|
-
}: typeof import('@octanejs/rspack-plugin') =
|
|
50
|
+
}: typeof import('@octanejs/rspack-plugin') = appRequire(
|
|
51
|
+
'@octanejs/rspack-plugin',
|
|
52
|
+
);
|
|
48
53
|
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) =>
|
|
49
54
|
mergeRsbuildConfig(config, {
|
|
50
55
|
source: {
|
|
@@ -60,19 +65,6 @@ export function createOctaneCompilerPlugin(
|
|
|
60
65
|
rsbuildEnvironment.name === options.workerEnvironmentName
|
|
61
66
|
? 'server'
|
|
62
67
|
: inferRspackEnvironment(config.target);
|
|
63
|
-
// Octane prepends '.tsrx' only when it is absent. Keep TypeScript
|
|
64
|
-
// modules first so an extensionless './link' cannot select a
|
|
65
|
-
// case-colliding Link.tsrx on case-insensitive file systems.
|
|
66
|
-
config.resolve ??= {};
|
|
67
|
-
const extensions = (config.resolve.extensions ?? []).filter(
|
|
68
|
-
extension => extension !== '.tsrx',
|
|
69
|
-
);
|
|
70
|
-
extensions.splice(
|
|
71
|
-
Math.max(extensions.indexOf('.ts'), extensions.indexOf('.tsx')) + 1,
|
|
72
|
-
0,
|
|
73
|
-
'.tsrx',
|
|
74
|
-
);
|
|
75
|
-
config.resolve.extensions = extensions;
|
|
76
68
|
config.plugins ??= [];
|
|
77
69
|
config.plugins.push(
|
|
78
70
|
new OctaneRspackPlugin({
|
package/src/plugin/create.ts
CHANGED
|
@@ -60,7 +60,7 @@ export function generateOctaneAppSources(
|
|
|
60
60
|
{
|
|
61
61
|
path: 'src/routes/layout.tsx',
|
|
62
62
|
content: `import { Link, Outlet } from '@modern-js/renderer-octane/router';
|
|
63
|
-
import Stable from '../components/
|
|
63
|
+
import Stable from '../components/stable';
|
|
64
64
|
import './index.css';
|
|
65
65
|
|
|
66
66
|
const appTitle = ${title};
|
|
@@ -91,7 +91,7 @@ ${options.capabilities.federation ? ' <Link to="/remotes">Federated app
|
|
|
91
91
|
useOctaneRouteAction,
|
|
92
92
|
} from '@modern-js/renderer-octane/router';
|
|
93
93
|
import { useActionState } from 'octane';
|
|
94
|
-
import Counter from '../components/
|
|
94
|
+
import Counter from '../components/counter';
|
|
95
95
|
|
|
96
96
|
const appId = ${appId};
|
|
97
97
|
const appTitle = ${title};
|
|
@@ -162,7 +162,7 @@ export const head: NonNullable<FileSystemRouteModule['head']> = () => ({
|
|
|
162
162
|
`,
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
|
-
path: 'src/components/
|
|
165
|
+
path: 'src/components/counter.tsx',
|
|
166
166
|
content: `import { useState } from 'octane';
|
|
167
167
|
|
|
168
168
|
export default function Counter() {
|
|
@@ -180,7 +180,7 @@ export default function Counter() {
|
|
|
180
180
|
`,
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
|
-
path: 'src/components/
|
|
183
|
+
path: 'src/components/stable.tsx',
|
|
184
184
|
content: `import { useState } from 'octane';
|
|
185
185
|
|
|
186
186
|
export default function Stable() {
|
|
@@ -207,34 +207,35 @@ export function loader({ request }: DataHandlerInput) {
|
|
|
207
207
|
if (url.searchParams.get('case') === 'error')
|
|
208
208
|
throw new Error('Native loader failure');
|
|
209
209
|
if (url.searchParams.get('case') === 'redirect')
|
|
210
|
-
return new Response(null, {
|
|
210
|
+
return new Response(null, { headers: { location: '/about' }, status: 302 });
|
|
211
211
|
return {
|
|
212
212
|
message: 'Native loader value',
|
|
213
213
|
privateValue: request.headers.get('x-conformance-private'),
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
export
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
217
|
+
export function action({ request }: DataHandlerInput) {
|
|
218
|
+
return request.formData().then(form => {
|
|
219
|
+
const name = form.get('name');
|
|
220
|
+
if (typeof name !== 'string' || name.trim().length === 0)
|
|
221
|
+
return Response.json(
|
|
222
|
+
{ fieldErrors: { name: 'Name required' } },
|
|
223
|
+
{ status: 422 },
|
|
224
|
+
);
|
|
225
|
+
if (form.get('intent') === 'redirect')
|
|
226
|
+
return new Response(null, {
|
|
227
|
+
headers: {
|
|
228
|
+
location: '/about',
|
|
229
|
+
'set-cookie': 'conformance-saved=1; Path=/; SameSite=Lax',
|
|
230
|
+
},
|
|
231
|
+
status: 303,
|
|
232
|
+
});
|
|
233
|
+
if (form.get('intent') === 'throw') throw new Error('Native action failure');
|
|
234
|
+
return {
|
|
235
|
+
saved: name,
|
|
236
|
+
privateValue: request.headers.get('x-conformance-private'),
|
|
237
|
+
};
|
|
238
|
+
});
|
|
238
239
|
}
|
|
239
240
|
`,
|
|
240
241
|
},
|
package/src/plugin/index.ts
CHANGED
|
@@ -81,7 +81,7 @@ export const rendererAdapter: NativeRendererAdapter<'octane'> =
|
|
|
81
81
|
artifacts: compilerArtifacts,
|
|
82
82
|
assertSupportedSource: assertOctaneEntrySource,
|
|
83
83
|
create: {
|
|
84
|
-
templates: { federation: true, workers:
|
|
84
|
+
templates: { federation: true, workers: true },
|
|
85
85
|
dependencies: (profile, { typescriptVersion }) => ({
|
|
86
86
|
frameworkDependencies: [
|
|
87
87
|
'@modern-js/renderer-core',
|
|
@@ -89,7 +89,9 @@ export const rendererAdapter: NativeRendererAdapter<'octane'> =
|
|
|
89
89
|
],
|
|
90
90
|
dependencies: nativeRendererDependencies(profile),
|
|
91
91
|
devDependencies: {
|
|
92
|
-
[profile.compiler.name]:
|
|
92
|
+
[profile.compiler.name]:
|
|
93
|
+
profile.dependencies[profile.compiler.name] ??
|
|
94
|
+
profile.compiler.version,
|
|
93
95
|
typescript: typescriptVersion,
|
|
94
96
|
},
|
|
95
97
|
typecheckCommand: 'octane-tsc --noEmit --project tsconfig.json',
|
package/src/plugin/profile.ts
CHANGED
|
@@ -4,7 +4,7 @@ export const octaneProfile: RendererBuildProfile<'octane'> = {
|
|
|
4
4
|
renderer: 'octane',
|
|
5
5
|
status: 'preview',
|
|
6
6
|
protocolVersion: 1,
|
|
7
|
-
minimumNode: '26.
|
|
7
|
+
minimumNode: '26.12.0',
|
|
8
8
|
hmr: {
|
|
9
9
|
editedBoundary: 'may-reset',
|
|
10
10
|
unaffectedComponents: 'preserved',
|
|
@@ -12,28 +12,32 @@ export const octaneProfile: RendererBuildProfile<'octane'> = {
|
|
|
12
12
|
roots: 'single',
|
|
13
13
|
cleanup: 'exactly-once',
|
|
14
14
|
},
|
|
15
|
-
compiler: { name: '@octanejs/rspack-plugin', version: '0.
|
|
15
|
+
compiler: { name: '@octanejs/rspack-plugin', version: '0.2.4' },
|
|
16
16
|
hydration: {
|
|
17
17
|
name: 'octane',
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.13.8',
|
|
19
19
|
},
|
|
20
20
|
router: {
|
|
21
21
|
name: '@octanejs/tanstack-router',
|
|
22
|
-
version: '0.
|
|
22
|
+
version: '0.2.10',
|
|
23
23
|
coreName: '@tanstack/router-core',
|
|
24
|
-
coreVersion: '1.171.
|
|
24
|
+
coreVersion: '1.171.38',
|
|
25
25
|
},
|
|
26
26
|
sourceExtensions: ['.tsx', '.tsrx', '.ts', '.js'],
|
|
27
27
|
jsxImportSource: 'octane',
|
|
28
28
|
dependencies: {
|
|
29
|
+
'@tsrx/oxc': '0.20.0',
|
|
29
30
|
octane:
|
|
30
|
-
'https://github.com/
|
|
31
|
+
'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octane-0.13.8.tgz',
|
|
31
32
|
'@octanejs/tanstack-router':
|
|
32
|
-
'https://github.com/
|
|
33
|
-
'@octanejs/rspack-plugin':
|
|
33
|
+
'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-tanstack-router-0.2.10.tgz',
|
|
34
|
+
'@octanejs/rspack-plugin':
|
|
35
|
+
'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-rspack-plugin-0.2.4.tgz',
|
|
36
|
+
'@tanstack/router-core':
|
|
37
|
+
'https://github.com/BleedingDev/router/releases/download/ultramodern-router-core-585d3233f7f5/tanstack-router-core-1.171.38.tgz',
|
|
34
38
|
'@modern-js/renderer-octane': '3.8.3',
|
|
35
|
-
seroval: '1.6.
|
|
36
|
-
'seroval-plugins': '1.6.
|
|
39
|
+
seroval: '1.6.9',
|
|
40
|
+
'seroval-plugins': '1.6.9',
|
|
37
41
|
},
|
|
38
42
|
capabilities: {
|
|
39
43
|
worker: true,
|
package/src/router-client.ts
CHANGED
|
@@ -64,7 +64,7 @@ function waitForCommittedMatches(
|
|
|
64
64
|
signal?: AbortSignal,
|
|
65
65
|
): Promise<void> {
|
|
66
66
|
signal?.throwIfAborted();
|
|
67
|
-
const matchesId = router.stores.
|
|
67
|
+
const matchesId = router.stores.ids;
|
|
68
68
|
if (matchesId.get().length > 0) return Promise.resolve();
|
|
69
69
|
if (!isReactiveMatchIds(matchesId)) {
|
|
70
70
|
throw new Error(
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions/paths';
|
|
2
|
+
import {
|
|
3
|
+
type AnyRoute,
|
|
4
|
+
type AnyRouter,
|
|
5
|
+
associateRouterRenderProfile,
|
|
6
|
+
captureRouterRenderSnapshot,
|
|
7
|
+
createRouterRenderRouteTree,
|
|
8
|
+
defaultParseSearch,
|
|
9
|
+
defaultStringifySearch,
|
|
10
|
+
disposeRouterRenderSnapshot,
|
|
11
|
+
getRouterRenderProfile,
|
|
12
|
+
matchContext,
|
|
13
|
+
type RouterRenderJsonRecord,
|
|
14
|
+
type RouterRenderSnapshot,
|
|
15
|
+
registerRouterRenderProfile,
|
|
16
|
+
restoreRouterRenderSnapshot,
|
|
17
|
+
routerContext,
|
|
18
|
+
} from '@octanejs/tanstack-router';
|
|
19
|
+
import { hasUnchangedOctaneFileRouteHead } from './routes';
|
|
20
|
+
import {
|
|
21
|
+
getOctaneWorkerRouterRewrite,
|
|
22
|
+
OCTANE_WORKER_CONTEXT_KEYS,
|
|
23
|
+
registerOctaneWorkerContext,
|
|
24
|
+
} from './worker-federation-contexts';
|
|
25
|
+
|
|
26
|
+
export const OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE = Object.freeze({
|
|
27
|
+
key: 'ultramodern.file-routes',
|
|
28
|
+
version: '1',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
interface GeneratedRouterOrigin {
|
|
32
|
+
readonly publicContext: RouterRenderJsonRecord;
|
|
33
|
+
readonly rewrite: AnyRouter['options']['rewrite'];
|
|
34
|
+
}
|
|
35
|
+
const origins = new WeakMap<AnyRouter, GeneratedRouterOrigin>();
|
|
36
|
+
let profileRegistered = false;
|
|
37
|
+
|
|
38
|
+
function plainRecord(value: unknown): value is Record<string, unknown> {
|
|
39
|
+
return (
|
|
40
|
+
value !== null &&
|
|
41
|
+
typeof value === 'object' &&
|
|
42
|
+
[Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function record(
|
|
47
|
+
value: unknown,
|
|
48
|
+
fields?: readonly string[],
|
|
49
|
+
): Record<string, unknown> {
|
|
50
|
+
if (!plainRecord(value))
|
|
51
|
+
throw new TypeError('Worker router context requires a plain record');
|
|
52
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
53
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
54
|
+
if (
|
|
55
|
+
typeof key !== 'string' ||
|
|
56
|
+
(fields && !fields.includes(key)) ||
|
|
57
|
+
!descriptor ||
|
|
58
|
+
!descriptor.enumerable ||
|
|
59
|
+
!('value' in descriptor)
|
|
60
|
+
)
|
|
61
|
+
throw new TypeError('Unknown Worker router context field or accessor');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nonempty(value: unknown): string {
|
|
67
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 1024)
|
|
68
|
+
throw new TypeError('Invalid Worker router identity');
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Only the host entry's public logical identity crosses the Worker boundary. */
|
|
73
|
+
function publicContext(value: unknown): RouterRenderJsonRecord {
|
|
74
|
+
const context = record(value, ['ultramodern']);
|
|
75
|
+
const metadata = record(context.ultramodern, ['rendererIdentity']);
|
|
76
|
+
const identity = record(metadata.rendererIdentity, [
|
|
77
|
+
'renderer',
|
|
78
|
+
'appId',
|
|
79
|
+
'entryName',
|
|
80
|
+
'protocolVersion',
|
|
81
|
+
'buildId',
|
|
82
|
+
]);
|
|
83
|
+
if (identity.renderer !== 'octane' || identity.protocolVersion !== 1)
|
|
84
|
+
throw new TypeError('Unsupported Worker router identity');
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
ultramodern: Object.freeze({
|
|
87
|
+
rendererIdentity: Object.freeze({
|
|
88
|
+
renderer: 'octane',
|
|
89
|
+
appId: nonempty(identity.appId),
|
|
90
|
+
entryName: nonempty(identity.entryName),
|
|
91
|
+
protocolVersion: 1,
|
|
92
|
+
buildId: nonempty(identity.buildId),
|
|
93
|
+
}),
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function localizedUrls(value: unknown): LocalisedUrlsOption | undefined {
|
|
99
|
+
if (value === undefined || typeof value === 'boolean') return value;
|
|
100
|
+
const input = record(value);
|
|
101
|
+
const result: Exclude<LocalisedUrlsOption, boolean> = {};
|
|
102
|
+
for (const [path, translations] of Object.entries(input)) {
|
|
103
|
+
const names: Record<string, string> = {};
|
|
104
|
+
for (const [language, target] of Object.entries(record(translations)))
|
|
105
|
+
names[nonempty(language)] = nonempty(target);
|
|
106
|
+
result[path] = names;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function rewriteDescriptor(value: unknown):
|
|
112
|
+
| {
|
|
113
|
+
readonly language: string;
|
|
114
|
+
readonly languages: readonly string[];
|
|
115
|
+
readonly localisedUrls?: LocalisedUrlsOption;
|
|
116
|
+
}
|
|
117
|
+
| undefined {
|
|
118
|
+
if (value === undefined) return undefined;
|
|
119
|
+
const input = record(value, ['language', 'languages', 'localisedUrls']);
|
|
120
|
+
if (
|
|
121
|
+
!Array.isArray(input.languages) ||
|
|
122
|
+
!input.languages.length ||
|
|
123
|
+
input.languages.length > 64
|
|
124
|
+
)
|
|
125
|
+
throw new TypeError('Invalid Worker router i18n languages');
|
|
126
|
+
const languages = input.languages.map(nonempty);
|
|
127
|
+
const language = nonempty(input.language);
|
|
128
|
+
if (
|
|
129
|
+
new Set(languages).size !== languages.length ||
|
|
130
|
+
!languages.includes(language)
|
|
131
|
+
)
|
|
132
|
+
throw new TypeError('Invalid Worker router i18n language');
|
|
133
|
+
const urls = localizedUrls(input.localisedUrls);
|
|
134
|
+
return {
|
|
135
|
+
language,
|
|
136
|
+
languages,
|
|
137
|
+
...(urls === undefined ? {} : { localisedUrls: urls }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function nativeRewriteDescriptor(
|
|
142
|
+
router: AnyRouter,
|
|
143
|
+
): RouterRenderJsonRecord | undefined {
|
|
144
|
+
if (!router.options.rewrite) return undefined;
|
|
145
|
+
const known = getOctaneWorkerRouterRewrite()?.capture(router.options.rewrite);
|
|
146
|
+
if (!known)
|
|
147
|
+
throw new TypeError(
|
|
148
|
+
'Custom router rewrite requires an exact Worker profile',
|
|
149
|
+
);
|
|
150
|
+
const descriptor = rewriteDescriptor(known);
|
|
151
|
+
if (!descriptor)
|
|
152
|
+
throw new TypeError('Missing native router rewrite provenance');
|
|
153
|
+
return {
|
|
154
|
+
language: descriptor.language,
|
|
155
|
+
languages: [...descriptor.languages],
|
|
156
|
+
...(descriptor.localisedUrls === undefined
|
|
157
|
+
? {}
|
|
158
|
+
: { localisedUrls: descriptor.localisedUrls }),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validateDefaultSnapshot(snapshot: RouterRenderSnapshot): void {
|
|
163
|
+
publicContext(snapshot.publicContext);
|
|
164
|
+
rewriteDescriptor(snapshot.i18n);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** File-route heads are host presentation work, like the already settled loaders. */
|
|
168
|
+
function assertFileRouteRenderHost(
|
|
169
|
+
router: AnyRouter,
|
|
170
|
+
allowRewrite: boolean,
|
|
171
|
+
): void {
|
|
172
|
+
if (
|
|
173
|
+
router.options.parseSearch !== defaultParseSearch ||
|
|
174
|
+
router.options.stringifySearch !== defaultStringifySearch ||
|
|
175
|
+
(!allowRewrite && router.options.rewrite) ||
|
|
176
|
+
router.options.routeMasks?.length
|
|
177
|
+
)
|
|
178
|
+
throw new TypeError(
|
|
179
|
+
'Router render snapshot: authored router behavior requires an exact local profile',
|
|
180
|
+
);
|
|
181
|
+
const authoredRouting = [
|
|
182
|
+
'caseSensitive',
|
|
183
|
+
'validateSearch',
|
|
184
|
+
'loaderDeps',
|
|
185
|
+
'beforeLoad',
|
|
186
|
+
'context',
|
|
187
|
+
'onEnter',
|
|
188
|
+
'onStay',
|
|
189
|
+
'onLeave',
|
|
190
|
+
'lazyFn',
|
|
191
|
+
'scripts',
|
|
192
|
+
'headers',
|
|
193
|
+
'onError',
|
|
194
|
+
'parseParams',
|
|
195
|
+
'stringifyParams',
|
|
196
|
+
];
|
|
197
|
+
const seen = new Set<AnyRoute>();
|
|
198
|
+
const visit = (route: AnyRoute, depth: number) => {
|
|
199
|
+
if (seen.has(route) || seen.size >= 4096 || depth > 64)
|
|
200
|
+
throw new TypeError(
|
|
201
|
+
'Router render snapshot: route geometry must be bounded and acyclic',
|
|
202
|
+
);
|
|
203
|
+
seen.add(route);
|
|
204
|
+
if (
|
|
205
|
+
authoredRouting.some(
|
|
206
|
+
key => Reflect.get(route.options, key) !== undefined,
|
|
207
|
+
) ||
|
|
208
|
+
route.options.params?.parse ||
|
|
209
|
+
route.options.params?.stringify ||
|
|
210
|
+
route.options.search?.middlewares?.length ||
|
|
211
|
+
route.lazyFn ||
|
|
212
|
+
('path' in route.options && 'id' in route.options) ||
|
|
213
|
+
!hasUnchangedOctaneFileRouteHead(route)
|
|
214
|
+
)
|
|
215
|
+
throw new TypeError(
|
|
216
|
+
'Router render snapshot: authored route behavior requires an exact local profile',
|
|
217
|
+
);
|
|
218
|
+
for (const child of route.children ?? []) visit(child, depth + 1);
|
|
219
|
+
};
|
|
220
|
+
visit(router.options.routeTree, 0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function ensureDefaultProfile(): void {
|
|
224
|
+
if (profileRegistered) return;
|
|
225
|
+
registerRouterRenderProfile({
|
|
226
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
227
|
+
assertHost: (router, snapshot) => {
|
|
228
|
+
const origin = origins.get(router);
|
|
229
|
+
if (
|
|
230
|
+
!origin ||
|
|
231
|
+
origin.rewrite !== router.options.rewrite ||
|
|
232
|
+
JSON.stringify(publicContext(router.options.context)) !==
|
|
233
|
+
JSON.stringify(origin.publicContext)
|
|
234
|
+
)
|
|
235
|
+
throw new TypeError('Generated Worker router origin changed');
|
|
236
|
+
assertFileRouteRenderHost(router, snapshot.i18n !== undefined);
|
|
237
|
+
validateDefaultSnapshot(snapshot);
|
|
238
|
+
if (
|
|
239
|
+
JSON.stringify(nativeRewriteDescriptor(router)) !==
|
|
240
|
+
JSON.stringify(snapshot.i18n)
|
|
241
|
+
)
|
|
242
|
+
throw new TypeError(
|
|
243
|
+
'Worker router rewrite differs from its native provenance',
|
|
244
|
+
);
|
|
245
|
+
},
|
|
246
|
+
createRoutingTree: snapshot => {
|
|
247
|
+
validateDefaultSnapshot(snapshot);
|
|
248
|
+
return createRouterRenderRouteTree(snapshot.routes);
|
|
249
|
+
},
|
|
250
|
+
routingOptions: snapshot => {
|
|
251
|
+
const defaults = {
|
|
252
|
+
parseSearch: defaultParseSearch,
|
|
253
|
+
stringifySearch: defaultStringifySearch,
|
|
254
|
+
};
|
|
255
|
+
const i18n = rewriteDescriptor(snapshot.i18n);
|
|
256
|
+
if (!i18n) return defaults;
|
|
257
|
+
const codec = getOctaneWorkerRouterRewrite();
|
|
258
|
+
if (!codec)
|
|
259
|
+
throw new TypeError('Missing owning Worker router i18n codec');
|
|
260
|
+
return { ...defaults, rewrite: codec.restore(i18n) };
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
profileRegistered = true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Associate the genuine router produced by the generated file-route factory. */
|
|
267
|
+
export function associateOctaneFileRouteSnapshot(router: AnyRouter): void {
|
|
268
|
+
ensureDefaultProfile();
|
|
269
|
+
const context = publicContext(router.options.context);
|
|
270
|
+
origins.set(router, {
|
|
271
|
+
publicContext: context,
|
|
272
|
+
rewrite: router.options.rewrite,
|
|
273
|
+
});
|
|
274
|
+
associateRouterRenderProfile(router, {
|
|
275
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
276
|
+
publicContext: context,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function captureOctaneRouterSnapshot(
|
|
281
|
+
router: AnyRouter,
|
|
282
|
+
): RouterRenderSnapshot {
|
|
283
|
+
const origin = origins.get(router);
|
|
284
|
+
const declared = getRouterRenderProfile(router);
|
|
285
|
+
if (
|
|
286
|
+
origin &&
|
|
287
|
+
declared?.key === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.key &&
|
|
288
|
+
declared.version === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.version
|
|
289
|
+
) {
|
|
290
|
+
const i18n = nativeRewriteDescriptor(router);
|
|
291
|
+
associateRouterRenderProfile(router, {
|
|
292
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
293
|
+
publicContext: origin.publicContext,
|
|
294
|
+
...(i18n === undefined ? {} : { i18n }),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return captureRouterRenderSnapshot(router);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let registrations = 0;
|
|
301
|
+
let releaseContexts: (() => void) | undefined;
|
|
302
|
+
|
|
303
|
+
/** Startup registration uses the actual physical native router/match Contexts. */
|
|
304
|
+
export function registerOctaneRouterWorkerContexts(): () => void {
|
|
305
|
+
ensureDefaultProfile();
|
|
306
|
+
if (!releaseContexts) {
|
|
307
|
+
const releaseRouter = registerOctaneWorkerContext(routerContext, {
|
|
308
|
+
key: OCTANE_WORKER_CONTEXT_KEYS.router,
|
|
309
|
+
encode: value => {
|
|
310
|
+
if (!value) throw new TypeError('Missing native router provider');
|
|
311
|
+
return captureOctaneRouterSnapshot(value);
|
|
312
|
+
},
|
|
313
|
+
decode: (value, signal) => restoreRouterRenderSnapshot(value, { signal }),
|
|
314
|
+
dispose: value => {
|
|
315
|
+
if (value) disposeRouterRenderSnapshot(value);
|
|
316
|
+
},
|
|
317
|
+
isProvided: value => value !== undefined,
|
|
318
|
+
});
|
|
319
|
+
try {
|
|
320
|
+
const releaseMatch = registerOctaneWorkerContext(matchContext, {
|
|
321
|
+
key: OCTANE_WORKER_CONTEXT_KEYS.match,
|
|
322
|
+
encode: nonempty,
|
|
323
|
+
decode: nonempty,
|
|
324
|
+
validate: (value, readContext) => {
|
|
325
|
+
const selected = readContext(routerContext);
|
|
326
|
+
const router = selected.present ? selected.value : undefined;
|
|
327
|
+
// Native Match and useMatch share the active route store key, not
|
|
328
|
+
// the location-specific match identity (which includes parameters).
|
|
329
|
+
const current = router?.state.matches.find(
|
|
330
|
+
match => match.routeId === value,
|
|
331
|
+
);
|
|
332
|
+
if (
|
|
333
|
+
!router ||
|
|
334
|
+
!current ||
|
|
335
|
+
typeof value !== 'string' ||
|
|
336
|
+
router.stores.getMatchStore(value).get() !== current
|
|
337
|
+
)
|
|
338
|
+
throw new TypeError(
|
|
339
|
+
'Worker match context requires its selected native router and a current match',
|
|
340
|
+
);
|
|
341
|
+
},
|
|
342
|
+
isProvided: value => value !== undefined,
|
|
343
|
+
});
|
|
344
|
+
releaseContexts = () => {
|
|
345
|
+
releaseMatch();
|
|
346
|
+
releaseRouter();
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
releaseRouter();
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
registrations++;
|
|
354
|
+
let released = false;
|
|
355
|
+
return () => {
|
|
356
|
+
if (released) return;
|
|
357
|
+
released = true;
|
|
358
|
+
if (--registrations === 0) {
|
|
359
|
+
releaseContexts?.();
|
|
360
|
+
releaseContexts = undefined;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|