@bleedingdev/modern-js-renderer-octane 3.9.3-ultramodern.7 → 3.9.3-ultramodern.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +263 -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 +11 -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 +216 -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 +11 -17
- 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/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/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 +159 -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/index.ts +4 -2
- package/src/plugin/profile.ts +14 -10
- package/src/router-client.ts +1 -1
- package/src/router-external-snapshot.ts +306 -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 +28 -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
|
@@ -31,7 +31,7 @@ function isReactiveMatchIds(value) {
|
|
|
31
31
|
}
|
|
32
32
|
function waitForCommittedMatches(router, signal) {
|
|
33
33
|
signal?.throwIfAborted();
|
|
34
|
-
const matchesId = router.stores.
|
|
34
|
+
const matchesId = router.stores.ids;
|
|
35
35
|
if (matchesId.get().length > 0) return Promise.resolve();
|
|
36
36
|
if (!isReactiveMatchIds(matchesId)) throw new Error('Octane router hydration requires its native reactive match store');
|
|
37
37
|
return new Promise((resolve, reject)=>{
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { assertDefaultRouterRenderHost, associateRouterRenderProfile, captureRouterRenderSnapshot, createRouterRenderRouteTree, defaultParseSearch, defaultStringifySearch, disposeRouterRenderSnapshot, getRouterRenderProfile, matchContext, registerRouterRenderProfile, restoreRouterRenderSnapshot, routerContext } from "@octanejs/tanstack-router";
|
|
2
|
+
import { OCTANE_WORKER_CONTEXT_KEYS, getOctaneWorkerRouterRewrite, registerOctaneWorkerContext } from "./worker-federation-contexts.mjs";
|
|
3
|
+
const OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE = Object.freeze({
|
|
4
|
+
key: 'ultramodern.file-routes',
|
|
5
|
+
version: '1'
|
|
6
|
+
});
|
|
7
|
+
const origins = new WeakMap();
|
|
8
|
+
let profileRegistered = false;
|
|
9
|
+
function plainRecord(value) {
|
|
10
|
+
return null !== value && 'object' == typeof value && [
|
|
11
|
+
Object.prototype,
|
|
12
|
+
null
|
|
13
|
+
].includes(Object.getPrototypeOf(value));
|
|
14
|
+
}
|
|
15
|
+
function record(value, fields) {
|
|
16
|
+
if (!plainRecord(value)) throw new TypeError('Worker router context requires a plain record');
|
|
17
|
+
for (const key of Reflect.ownKeys(value)){
|
|
18
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
19
|
+
if ('string' != typeof key || fields && !fields.includes(key) || !descriptor || !descriptor.enumerable || !('value' in descriptor)) throw new TypeError('Unknown Worker router context field or accessor');
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function nonempty(value) {
|
|
24
|
+
if ('string' != typeof value || !value.trim() || value.length > 1024) throw new TypeError('Invalid Worker router identity');
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function publicContext(value) {
|
|
28
|
+
const context = record(value, [
|
|
29
|
+
'ultramodern'
|
|
30
|
+
]);
|
|
31
|
+
const metadata = record(context.ultramodern, [
|
|
32
|
+
'rendererIdentity'
|
|
33
|
+
]);
|
|
34
|
+
const identity = record(metadata.rendererIdentity, [
|
|
35
|
+
'renderer',
|
|
36
|
+
'appId',
|
|
37
|
+
'entryName',
|
|
38
|
+
'protocolVersion',
|
|
39
|
+
'buildId'
|
|
40
|
+
]);
|
|
41
|
+
if ('octane' !== identity.renderer || 1 !== identity.protocolVersion) throw new TypeError('Unsupported Worker router identity');
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
ultramodern: Object.freeze({
|
|
44
|
+
rendererIdentity: Object.freeze({
|
|
45
|
+
renderer: 'octane',
|
|
46
|
+
appId: nonempty(identity.appId),
|
|
47
|
+
entryName: nonempty(identity.entryName),
|
|
48
|
+
protocolVersion: 1,
|
|
49
|
+
buildId: nonempty(identity.buildId)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function localizedUrls(value) {
|
|
55
|
+
if (void 0 === value || 'boolean' == typeof value) return value;
|
|
56
|
+
const input = record(value);
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const [path, translations] of Object.entries(input)){
|
|
59
|
+
const names = {};
|
|
60
|
+
for (const [language, target] of Object.entries(record(translations)))names[nonempty(language)] = nonempty(target);
|
|
61
|
+
result[path] = names;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function rewriteDescriptor(value) {
|
|
66
|
+
if (void 0 === value) return;
|
|
67
|
+
const input = record(value, [
|
|
68
|
+
'language',
|
|
69
|
+
'languages',
|
|
70
|
+
'localisedUrls'
|
|
71
|
+
]);
|
|
72
|
+
if (!Array.isArray(input.languages) || !input.languages.length || input.languages.length > 64) throw new TypeError('Invalid Worker router i18n languages');
|
|
73
|
+
const languages = input.languages.map(nonempty);
|
|
74
|
+
const language = nonempty(input.language);
|
|
75
|
+
if (new Set(languages).size !== languages.length || !languages.includes(language)) throw new TypeError('Invalid Worker router i18n language');
|
|
76
|
+
const urls = localizedUrls(input.localisedUrls);
|
|
77
|
+
return {
|
|
78
|
+
language,
|
|
79
|
+
languages,
|
|
80
|
+
...void 0 === urls ? {} : {
|
|
81
|
+
localisedUrls: urls
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function nativeRewriteDescriptor(router) {
|
|
86
|
+
if (!router.options.rewrite) return;
|
|
87
|
+
const known = getOctaneWorkerRouterRewrite()?.capture(router.options.rewrite);
|
|
88
|
+
if (!known) throw new TypeError('Custom router rewrite requires an exact Worker profile');
|
|
89
|
+
const descriptor = rewriteDescriptor(known);
|
|
90
|
+
if (!descriptor) throw new TypeError('Missing native router rewrite provenance');
|
|
91
|
+
return {
|
|
92
|
+
language: descriptor.language,
|
|
93
|
+
languages: [
|
|
94
|
+
...descriptor.languages
|
|
95
|
+
],
|
|
96
|
+
...void 0 === descriptor.localisedUrls ? {} : {
|
|
97
|
+
localisedUrls: descriptor.localisedUrls
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function validateDefaultSnapshot(snapshot) {
|
|
102
|
+
publicContext(snapshot.publicContext);
|
|
103
|
+
rewriteDescriptor(snapshot.i18n);
|
|
104
|
+
}
|
|
105
|
+
function ensureDefaultProfile() {
|
|
106
|
+
if (profileRegistered) return;
|
|
107
|
+
registerRouterRenderProfile({
|
|
108
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
109
|
+
assertHost: (router, snapshot)=>{
|
|
110
|
+
const origin = origins.get(router);
|
|
111
|
+
if (!origin || origin.rewrite !== router.options.rewrite || JSON.stringify(publicContext(router.options.context)) !== JSON.stringify(origin.publicContext)) throw new TypeError('Generated Worker router origin changed');
|
|
112
|
+
assertDefaultRouterRenderHost(router, void 0 !== snapshot.i18n);
|
|
113
|
+
validateDefaultSnapshot(snapshot);
|
|
114
|
+
if (JSON.stringify(nativeRewriteDescriptor(router)) !== JSON.stringify(snapshot.i18n)) throw new TypeError('Worker router rewrite differs from its native provenance');
|
|
115
|
+
},
|
|
116
|
+
createRoutingTree: (snapshot)=>{
|
|
117
|
+
validateDefaultSnapshot(snapshot);
|
|
118
|
+
return createRouterRenderRouteTree(snapshot.routes);
|
|
119
|
+
},
|
|
120
|
+
routingOptions: (snapshot)=>{
|
|
121
|
+
const defaults = {
|
|
122
|
+
parseSearch: defaultParseSearch,
|
|
123
|
+
stringifySearch: defaultStringifySearch
|
|
124
|
+
};
|
|
125
|
+
const i18n = rewriteDescriptor(snapshot.i18n);
|
|
126
|
+
if (!i18n) return defaults;
|
|
127
|
+
const codec = getOctaneWorkerRouterRewrite();
|
|
128
|
+
if (!codec) throw new TypeError('Missing owning Worker router i18n codec');
|
|
129
|
+
return {
|
|
130
|
+
...defaults,
|
|
131
|
+
rewrite: codec.restore(i18n)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
profileRegistered = true;
|
|
136
|
+
}
|
|
137
|
+
function associateOctaneFileRouteSnapshot(router) {
|
|
138
|
+
ensureDefaultProfile();
|
|
139
|
+
const context = publicContext(router.options.context);
|
|
140
|
+
origins.set(router, {
|
|
141
|
+
publicContext: context,
|
|
142
|
+
rewrite: router.options.rewrite
|
|
143
|
+
});
|
|
144
|
+
associateRouterRenderProfile(router, {
|
|
145
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
146
|
+
publicContext: context
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function captureOctaneRouterSnapshot(router) {
|
|
150
|
+
const origin = origins.get(router);
|
|
151
|
+
const declared = getRouterRenderProfile(router);
|
|
152
|
+
if (origin && declared?.key === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.key && declared.version === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.version) {
|
|
153
|
+
const i18n = nativeRewriteDescriptor(router);
|
|
154
|
+
associateRouterRenderProfile(router, {
|
|
155
|
+
id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
|
|
156
|
+
publicContext: origin.publicContext,
|
|
157
|
+
...void 0 === i18n ? {} : {
|
|
158
|
+
i18n
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return captureRouterRenderSnapshot(router);
|
|
163
|
+
}
|
|
164
|
+
let registrations = 0;
|
|
165
|
+
let releaseContexts;
|
|
166
|
+
function registerOctaneRouterWorkerContexts() {
|
|
167
|
+
ensureDefaultProfile();
|
|
168
|
+
if (!releaseContexts) {
|
|
169
|
+
const releaseRouter = registerOctaneWorkerContext(routerContext, {
|
|
170
|
+
key: OCTANE_WORKER_CONTEXT_KEYS.router,
|
|
171
|
+
encode: (value)=>{
|
|
172
|
+
if (!value) throw new TypeError('Missing native router provider');
|
|
173
|
+
return captureOctaneRouterSnapshot(value);
|
|
174
|
+
},
|
|
175
|
+
decode: (value, signal)=>restoreRouterRenderSnapshot(value, {
|
|
176
|
+
signal
|
|
177
|
+
}),
|
|
178
|
+
dispose: (value)=>{
|
|
179
|
+
if (value) disposeRouterRenderSnapshot(value);
|
|
180
|
+
},
|
|
181
|
+
isProvided: (value)=>void 0 !== value
|
|
182
|
+
});
|
|
183
|
+
try {
|
|
184
|
+
const releaseMatch = registerOctaneWorkerContext(matchContext, {
|
|
185
|
+
key: OCTANE_WORKER_CONTEXT_KEYS.match,
|
|
186
|
+
encode: nonempty,
|
|
187
|
+
decode: nonempty,
|
|
188
|
+
validate: (value, readContext)=>{
|
|
189
|
+
const selected = readContext(routerContext);
|
|
190
|
+
const router = selected.present ? selected.value : void 0;
|
|
191
|
+
const current = router?.state.matches.find((match)=>match.routeId === value);
|
|
192
|
+
if (!router || !current || 'string' != typeof value || router.stores.getMatchStore(value).get() !== current) throw new TypeError('Worker match context requires its selected native router and a current match');
|
|
193
|
+
},
|
|
194
|
+
isProvided: (value)=>void 0 !== value
|
|
195
|
+
});
|
|
196
|
+
releaseContexts = ()=>{
|
|
197
|
+
releaseMatch();
|
|
198
|
+
releaseRouter();
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
releaseRouter();
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
registrations++;
|
|
206
|
+
let released = false;
|
|
207
|
+
return ()=>{
|
|
208
|
+
if (released) return;
|
|
209
|
+
released = true;
|
|
210
|
+
if (0 === --registrations) {
|
|
211
|
+
releaseContexts?.();
|
|
212
|
+
releaseContexts = void 0;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export { OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE, associateOctaneFileRouteSnapshot, captureOctaneRouterSnapshot, registerOctaneRouterWorkerContexts };
|
|
@@ -52,8 +52,8 @@ function requestHeaders(router) {
|
|
|
52
52
|
if (field?.get || field?.set) throw new TypeError('Native Octane response headers must be owned values');
|
|
53
53
|
appendHeaders(headers, field?.value);
|
|
54
54
|
}
|
|
55
|
-
const
|
|
56
|
-
if (redirect) appendHeaders(headers, redirect.headers);
|
|
55
|
+
const result = router._serverResult;
|
|
56
|
+
if (result?.type === 'redirect') appendHeaders(headers, result.redirect.headers);
|
|
57
57
|
return headers;
|
|
58
58
|
}
|
|
59
59
|
function createOctaneRequestHandler({ createRouter, request, session, getRouterManifest, serialization }) {
|
|
@@ -74,7 +74,6 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
|
|
|
74
74
|
...serialization?.forbiddenValues ?? []
|
|
75
75
|
]
|
|
76
76
|
});
|
|
77
|
-
session.registerCleanup(()=>router.cancelMatches());
|
|
78
77
|
const { url } = getNormalizedURL(request.url, 'http://localhost');
|
|
79
78
|
const href = url.href.replace(url.origin, '');
|
|
80
79
|
router.update({
|
|
@@ -86,11 +85,13 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
|
|
|
86
85
|
}),
|
|
87
86
|
origin: router.options.origin ?? getOrigin(request)
|
|
88
87
|
});
|
|
89
|
-
await withAbort(router.load(
|
|
88
|
+
await withAbort(router.load({
|
|
89
|
+
_signal: session.signal
|
|
90
|
+
}), session.signal);
|
|
90
91
|
guard.assertActive();
|
|
91
|
-
const
|
|
92
|
-
if (redirect) {
|
|
93
|
-
const resolved =
|
|
92
|
+
const loadResult = router._serverResult;
|
|
93
|
+
if (loadResult?.type === 'redirect') {
|
|
94
|
+
const resolved = loadResult.redirect;
|
|
94
95
|
const headers = requestHeaders(router);
|
|
95
96
|
return new Response(null, {
|
|
96
97
|
status: resolved.status,
|
|
@@ -101,7 +102,9 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
|
|
|
101
102
|
guard.assertMatches();
|
|
102
103
|
const serverSsr = router.serverSsr;
|
|
103
104
|
if (!serverSsr) throw new Error('Native Octane router SSR state is required');
|
|
104
|
-
await withAbort(serverSsr.dehydrate(
|
|
105
|
+
await withAbort(serverSsr.dehydrate({
|
|
106
|
+
signal: session.signal
|
|
107
|
+
}), session.signal);
|
|
105
108
|
guard.assertActive();
|
|
106
109
|
const responseHeaders = requestHeaders(router);
|
|
107
110
|
const rendered = Promise.resolve(callback({
|
|
@@ -7,6 +7,5 @@ function OctaneRouterServer({ router }) {
|
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
9
|
export { createOctaneRequestHandler } from "./router-handler.mjs";
|
|
10
|
-
export { createOctaneRouterInjection } from "./router-injection.mjs";
|
|
11
10
|
export { prepareOctaneRouterSerialization } from "./router-server-snapshot.mjs";
|
|
12
11
|
export { OctaneRouterServer };
|
package/dist/esm/router.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createDataClient } from "@modern-js/renderer-core/data";
|
|
2
2
|
import { RouterProvider, createRouter, redirect, useMatch, useRouter } from "@octanejs/tanstack-router";
|
|
3
|
-
import { createSubSlot, useCallback,
|
|
3
|
+
import { createSubSlot, useCallback, useLayoutEffect, useRef } from "octane";
|
|
4
4
|
import { assertOctaneIdentity } from "./bootstrap.mjs";
|
|
5
5
|
import { RouteDataError, createFileSystemRouteTree, matchApplicationRouteIds, matchApplicationRoutes, resolveRouteData, selectApplicationDataRoute } from "./routes.mjs";
|
|
6
6
|
export * from "@octanejs/tanstack-router";
|
|
@@ -69,7 +69,7 @@ function useOctaneRouteAction(...args) {
|
|
|
69
69
|
const identity = useApplicationIdentity();
|
|
70
70
|
const routeId = useApplicationRouteId();
|
|
71
71
|
const owner = useRef(null, actionSlot(slot, 'owner'));
|
|
72
|
-
|
|
72
|
+
useLayoutEffect(()=>{
|
|
73
73
|
const controller = new AbortController();
|
|
74
74
|
owner.current = controller;
|
|
75
75
|
return ()=>controller.abort(new DOMException('The route action owner unmounted', 'AbortError'));
|
package/dist/esm/routes.mjs
CHANGED
|
@@ -10,21 +10,12 @@ function routes_resolveRouteData(routeId, outcome) {
|
|
|
10
10
|
function isReactiveNativeMatch(value) {
|
|
11
11
|
return 'object' == typeof value && null !== value && 'function' == typeof Reflect.get(value, 'get') && 'function' == typeof Reflect.get(value, 'subscribe');
|
|
12
12
|
}
|
|
13
|
-
function observeRouteCompletion(router, controller, signal, completion) {
|
|
13
|
+
function observeRouteCompletion(router, routeId, controller, signal, completion) {
|
|
14
14
|
completion.catch((error)=>{
|
|
15
15
|
if (signal.aborted) return;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
...router.stores.cachedMatches.get()
|
|
20
|
-
].find((candidate)=>candidate.abortController === controller);
|
|
21
|
-
if (!match) return;
|
|
22
|
-
const store = [
|
|
23
|
-
router.stores.pendingMatchStores.get(match.id),
|
|
24
|
-
router.stores.matchStores.get(match.id),
|
|
25
|
-
router.stores.cachedMatchStores.get(match.id)
|
|
26
|
-
].find((candidate)=>candidate?.get().abortController === controller);
|
|
27
|
-
if (!store) return;
|
|
16
|
+
const route = Object.values(router.routesById).find((candidate)=>candidate.options.staticData?.ultramodernRouteId === routeId);
|
|
17
|
+
if (!route) return;
|
|
18
|
+
const store = router.stores.getMatchStore(route.id);
|
|
28
19
|
let subscription;
|
|
29
20
|
let settled = false;
|
|
30
21
|
const cleanup = ()=>{
|
|
@@ -35,9 +26,10 @@ function observeRouteCompletion(router, controller, signal, completion) {
|
|
|
35
26
|
settled = true;
|
|
36
27
|
cleanup();
|
|
37
28
|
};
|
|
38
|
-
const publish = ()=>{
|
|
29
|
+
const publish = async ()=>{
|
|
39
30
|
if (settled) return;
|
|
40
31
|
const current = store.get();
|
|
32
|
+
if (!current) return;
|
|
41
33
|
if (current.abortController !== controller || signal.aborted || 'pending' !== current.status && 'success' !== current.status) return void onAbort();
|
|
42
34
|
if ('success' !== current.status || current.isFetching) return;
|
|
43
35
|
settled = true;
|
|
@@ -45,17 +37,19 @@ function observeRouteCompletion(router, controller, signal, completion) {
|
|
|
45
37
|
const route = router.routesById[current.routeId];
|
|
46
38
|
let failure = error;
|
|
47
39
|
try {
|
|
40
|
+
await route?.options.errorComponent?.preload?.();
|
|
41
|
+
if (signal.aborted || store.get()?.abortController !== controller) return;
|
|
48
42
|
route?.options.onError?.(failure);
|
|
49
43
|
} catch (onErrorFailure) {
|
|
50
44
|
failure = onErrorFailure;
|
|
51
45
|
}
|
|
52
46
|
if (signal.aborted) return;
|
|
53
|
-
router.
|
|
47
|
+
router.stores.setMatches(router.stores.matches.get().map((previous)=>previous.routeId !== route.id || previous.abortController !== controller || 'success' !== previous.status || previous.isFetching ? previous : {
|
|
54
48
|
...previous,
|
|
55
49
|
error: failure,
|
|
56
50
|
status: 'error',
|
|
57
51
|
isFetching: false
|
|
58
|
-
});
|
|
52
|
+
}));
|
|
59
53
|
};
|
|
60
54
|
signal.addEventListener('abort', onAbort, {
|
|
61
55
|
once: true
|
|
@@ -99,7 +93,7 @@ function createFileSystemRouteTree(routes, modules, options = {}) {
|
|
|
99
93
|
await Promise.resolve();
|
|
100
94
|
signal.throwIfAborted();
|
|
101
95
|
if (failed) throw failure;
|
|
102
|
-
observeRouteCompletion(router, match.abortController, signal, outcome.completion);
|
|
96
|
+
observeRouteCompletion(router, route.id, match.abortController, signal, outcome.completion);
|
|
103
97
|
}
|
|
104
98
|
options.onOutcome?.(route.id, outcome);
|
|
105
99
|
return routes_resolveRouteData(route.id, outcome);
|
package/dist/esm/server.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { prepareDocument } from "@modern-js/renderer-core/document";
|
|
2
2
|
import { untilAborted } from "@modern-js/renderer-core/session";
|
|
3
|
+
import { transformReadableStreamWithRouter } from "@octanejs/tanstack-router/ssr/server";
|
|
3
4
|
import { createElement, earlySignalBootstrapScript, renderToReadableStream } from "octane/server";
|
|
4
5
|
import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
|
|
5
6
|
import { FederationRoot, createFederationScope } from "./federation-context.mjs";
|
|
7
|
+
import { WorkerFederationRoot } from "./worker-federation-context.mjs";
|
|
6
8
|
function prepareOctaneDocument(session, document, hydrating) {
|
|
7
9
|
assertOctaneIdentity(session.identity);
|
|
8
10
|
if ('node' !== session.platform.kind && 'worker' !== session.platform.kind) throw new Error('An Octane document requires a Node or worker request platform.');
|
|
@@ -73,12 +75,21 @@ async function renderOctaneApplication(input) {
|
|
|
73
75
|
if (session.responsePolicy?.kind === 'terminal') return session.respond(null);
|
|
74
76
|
session.startRendering();
|
|
75
77
|
let head = '';
|
|
76
|
-
const federation = input.federation ? createFederationScope(input.federation, true, document.nonce) : void 0;
|
|
78
|
+
const federation = input.federation || input.workerFederation ? createFederationScope(input.federation, true, document.nonce) : void 0;
|
|
77
79
|
const App = federation ? ()=>createElement(FederationRoot, {
|
|
78
80
|
scope: federation,
|
|
79
81
|
children: 'function' == typeof input.App ? createElement(input.App, input.props) : input.App
|
|
80
82
|
}) : input.App;
|
|
81
|
-
const
|
|
83
|
+
const workerFederation = input.workerFederation ? {
|
|
84
|
+
transport: input.workerFederation,
|
|
85
|
+
locale: input.workerFederationLocale ?? 'default',
|
|
86
|
+
signal: session.signal
|
|
87
|
+
} : void 0;
|
|
88
|
+
const NativeApp = workerFederation ? ()=>createElement(WorkerFederationRoot, {
|
|
89
|
+
scope: workerFederation,
|
|
90
|
+
children: 'function' == typeof App ? createElement(App, input.props) : App
|
|
91
|
+
}) : App;
|
|
92
|
+
const nativeStream = await renderToReadableStream(NativeApp, input.props, {
|
|
82
93
|
signal: session.signal,
|
|
83
94
|
...void 0 === document.nonce ? {} : {
|
|
84
95
|
nonce: document.nonce
|
|
@@ -91,6 +102,9 @@ async function renderOctaneApplication(input) {
|
|
|
91
102
|
buildId: document.nativeHydrationBuildId,
|
|
92
103
|
documentId: document.documentId
|
|
93
104
|
},
|
|
105
|
+
externalSnapshots: {
|
|
106
|
+
documentId: document.documentId
|
|
107
|
+
},
|
|
94
108
|
headChannel: 'separate',
|
|
95
109
|
onHeadReady (value) {
|
|
96
110
|
head = value;
|
|
@@ -174,7 +188,14 @@ async function renderOctaneApplication(input) {
|
|
|
174
188
|
}, {
|
|
175
189
|
highWaterMark: 0
|
|
176
190
|
});
|
|
177
|
-
return session.respond(body
|
|
191
|
+
return session.respond(input.router ? transformReadableStreamWithRouter(input.router, body, {
|
|
192
|
+
signal: session.signal,
|
|
193
|
+
rendererSafePoint: "script-close",
|
|
194
|
+
timeoutMs: input.routerSerializationTimeoutMs,
|
|
195
|
+
onAbort: (reason)=>{
|
|
196
|
+
session.fail(reason);
|
|
197
|
+
}
|
|
198
|
+
}) : body);
|
|
178
199
|
} catch (error) {
|
|
179
200
|
await session.fail(error);
|
|
180
201
|
throw error;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, createElement } from "octane";
|
|
2
|
+
import { FederationApplication } from "./federation-context.mjs";
|
|
3
|
+
const WorkerFederationContext = createContext(null);
|
|
4
|
+
function WorkerFederationRoot(props) {
|
|
5
|
+
return createElement(WorkerFederationContext, {
|
|
6
|
+
value: props.scope,
|
|
7
|
+
children: props.children
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function WorkerFederationApplication(props) {
|
|
11
|
+
return createElement(WorkerFederationRoot, {
|
|
12
|
+
scope: props.scope,
|
|
13
|
+
children: props.federation ? createElement(FederationApplication, {
|
|
14
|
+
scope: props.federation,
|
|
15
|
+
application: props.application
|
|
16
|
+
}) : createElement(props.application.default, props.application.props)
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export { WorkerFederationApplication, WorkerFederationContext, WorkerFederationRoot };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { registerExternalSnapshotContext, useContext } from "octane";
|
|
2
|
+
const OCTANE_WORKER_CONTEXT_KEYS = Object.freeze({
|
|
3
|
+
router: 'ultramodern.octane.router-context.v1',
|
|
4
|
+
match: 'ultramodern.octane.match-context.v1',
|
|
5
|
+
i18n: 'ultramodern.octane.i18n-context.v1'
|
|
6
|
+
});
|
|
7
|
+
const builtinKeys = Object.values(OCTANE_WORKER_CONTEXT_KEYS);
|
|
8
|
+
const contexts = new Map();
|
|
9
|
+
function registerOctaneWorkerContext(context, codec) {
|
|
10
|
+
const { key, isProvided } = codec;
|
|
11
|
+
if (!builtinKeys.includes(key) || 'function' != typeof isProvided) throw new TypeError('Invalid Octane Worker builtin context codec');
|
|
12
|
+
const unregister = registerExternalSnapshotContext(context, codec);
|
|
13
|
+
const hasProvider = ()=>isProvided(useContext(context));
|
|
14
|
+
contexts.set(key, hasProvider);
|
|
15
|
+
return ()=>{
|
|
16
|
+
if (contexts.get(key) !== hasProvider) return;
|
|
17
|
+
unregister();
|
|
18
|
+
contexts.delete(key);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function useOctaneWorkerContextKeys(explicit = []) {
|
|
22
|
+
const selected = [];
|
|
23
|
+
for (const key of builtinKeys){
|
|
24
|
+
const hasProvider = contexts.get(key);
|
|
25
|
+
if (hasProvider?.() && !explicit.includes(key)) selected.push(key);
|
|
26
|
+
}
|
|
27
|
+
return [
|
|
28
|
+
...selected,
|
|
29
|
+
...explicit
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
let routerRewrite;
|
|
33
|
+
function registerOctaneWorkerRouterRewrite(codec) {
|
|
34
|
+
if (routerRewrite || 'function' != typeof codec.capture || 'function' != typeof codec.restore) throw new TypeError('Invalid or duplicate Octane Worker router rewrite codec');
|
|
35
|
+
const captured = Object.freeze({
|
|
36
|
+
capture: codec.capture,
|
|
37
|
+
restore: codec.restore
|
|
38
|
+
});
|
|
39
|
+
routerRewrite = captured;
|
|
40
|
+
return ()=>{
|
|
41
|
+
if (routerRewrite === captured) routerRewrite = void 0;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function getOctaneWorkerRouterRewrite() {
|
|
45
|
+
return routerRewrite;
|
|
46
|
+
}
|
|
47
|
+
export { OCTANE_WORKER_CONTEXT_KEYS, getOctaneWorkerRouterRewrite, registerOctaneWorkerContext, registerOctaneWorkerRouterRewrite, useOctaneWorkerContextKeys };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createWorkerFederationTransport } from "@modern-js/renderer-core/worker-federation";
|
|
2
|
+
import { prepareExternalSnapshotRequest, releasePreparedExternalSnapshotRequest, renderExternalSnapshot, serializeExternalSnapshot } from "octane/server";
|
|
3
|
+
import { FederationContext, createFederationScope } from "./federation-context.mjs";
|
|
4
|
+
import { WorkerFederationContext } from "./worker-federation-context.mjs";
|
|
5
|
+
function isNativeWorkerComponent(value) {
|
|
6
|
+
return 'function' == typeof value;
|
|
7
|
+
}
|
|
8
|
+
function createNativeWorkerFederationRuntime(options, compiler) {
|
|
9
|
+
const { resources } = options;
|
|
10
|
+
const bindings = options.bindings;
|
|
11
|
+
const transport = resources.workerFederationServices?.length ? createWorkerFederationTransport({
|
|
12
|
+
admitRendererContract: compiler.admitRendererContract,
|
|
13
|
+
services: resources.workerFederationServices.map((service)=>{
|
|
14
|
+
const binding = bindings && Reflect.get(bindings, service.binding);
|
|
15
|
+
if (!binding || 'object' != typeof binding && 'function' != typeof binding || !('fetch' in binding) || 'function' != typeof binding.fetch) throw new Error(`Native Worker federation requires service binding ${service.binding}.`);
|
|
16
|
+
return {
|
|
17
|
+
...service,
|
|
18
|
+
fetch: binding.fetch.bind(binding)
|
|
19
|
+
};
|
|
20
|
+
})
|
|
21
|
+
}) : void 0;
|
|
22
|
+
let handle;
|
|
23
|
+
if (resources.workerFederation) {
|
|
24
|
+
if (!compiler.registry || !resources.workerFederationManifestUrl) throw new Error('Native Worker federation requires its compiled expose registry and pinned manifest URL.');
|
|
25
|
+
handle = compiler.registry.initialize({
|
|
26
|
+
publication: resources.workerFederation.worker,
|
|
27
|
+
manifestUrl: resources.workerFederationManifestUrl,
|
|
28
|
+
admitRendererContract: compiler.admitRendererContract,
|
|
29
|
+
admitNativeRequest (payload, { admitted, signal }) {
|
|
30
|
+
const initializeContexts = transport ? (provide, request)=>{
|
|
31
|
+
provide(FederationContext, createFederationScope(void 0, true, request.nonce));
|
|
32
|
+
provide(WorkerFederationContext, {
|
|
33
|
+
transport,
|
|
34
|
+
locale: admitted.locale,
|
|
35
|
+
signal
|
|
36
|
+
});
|
|
37
|
+
} : void 0;
|
|
38
|
+
return prepareExternalSnapshotRequest(payload, {
|
|
39
|
+
authority: admitted.coordinate.native,
|
|
40
|
+
signal,
|
|
41
|
+
...initializeContexts ? {
|
|
42
|
+
initializeContexts
|
|
43
|
+
} : {}
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
releaseNativeRequest: releasePreparedExternalSnapshotRequest,
|
|
47
|
+
async renderNativeSnapshot (module, request, { admitted }) {
|
|
48
|
+
const component = module && 'object' == typeof module && 'default' in module ? module.default : void 0;
|
|
49
|
+
if (!isNativeWorkerComponent(component)) throw new TypeError(`Native Worker expose ${admitted.expose} requires a default Octane component.`);
|
|
50
|
+
return serializeExternalSnapshot(await renderExternalSnapshot(component, request));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
transport,
|
|
56
|
+
handleRequest: handle ?? (()=>Promise.resolve(void 0))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export { createNativeWorkerFederationRuntime };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nativeModuleManifestFilename, readRendererIdentity } from "@modern-js/renderer-core/identity";
|
|
2
2
|
import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
|
|
3
|
-
const OCTANE_RUNTIME_VERSION = '0.
|
|
4
|
-
const OCTANE_COMPILER_VERSION = '0.
|
|
3
|
+
const OCTANE_RUNTIME_VERSION = '0.13.8';
|
|
4
|
+
const OCTANE_COMPILER_VERSION = '0.2.4';
|
|
5
5
|
function octaneModuleManifestFileName(entryName) {
|
|
6
6
|
return nativeModuleManifestFilename('octane', entryName);
|
|
7
7
|
}
|
|
@@ -20,7 +20,8 @@ function createOctaneCompilerPlugin(options) {
|
|
|
20
20
|
const directory = pluginSourceDirectory();
|
|
21
21
|
const require = createRequire(node_path.join(directory, 'index.cjs'));
|
|
22
22
|
const { OctaneCompilerManifestPlugin } = require('./compiler-manifest.cjs');
|
|
23
|
-
const
|
|
23
|
+
const appRequire = createRequire(node_path.join(api.context.rootPath, 'package.json'));
|
|
24
|
+
const { inferRspackEnvironment, OctaneRspackPlugin } = appRequire('@octanejs/rspack-plugin');
|
|
24
25
|
api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>mergeRsbuildConfig(config, {
|
|
25
26
|
source: {
|
|
26
27
|
include: [
|
|
@@ -30,10 +31,6 @@ function createOctaneCompilerPlugin(options) {
|
|
|
30
31
|
}));
|
|
31
32
|
api.modifyRspackConfig((config, { environment: rsbuildEnvironment })=>{
|
|
32
33
|
const environment = rsbuildEnvironment.name === options.workerEnvironmentName ? 'server' : inferRspackEnvironment(config.target);
|
|
33
|
-
config.resolve ??= {};
|
|
34
|
-
const extensions = (config.resolve.extensions ?? []).filter((extension)=>'.tsrx' !== extension);
|
|
35
|
-
extensions.splice(Math.max(extensions.indexOf('.ts'), extensions.indexOf('.tsx')) + 1, 0, '.tsrx');
|
|
36
|
-
config.resolve.extensions = extensions;
|
|
37
34
|
config.plugins ??= [];
|
|
38
35
|
config.plugins.push(new OctaneRspackPlugin({
|
|
39
36
|
root: api.context.rootPath,
|
|
@@ -77,7 +77,7 @@ const rendererAdapter = defineRendererAdapter({
|
|
|
77
77
|
create: {
|
|
78
78
|
templates: {
|
|
79
79
|
federation: true,
|
|
80
|
-
workers:
|
|
80
|
+
workers: true
|
|
81
81
|
},
|
|
82
82
|
dependencies: (profile, { typescriptVersion })=>({
|
|
83
83
|
frameworkDependencies: [
|
|
@@ -86,7 +86,7 @@ const rendererAdapter = defineRendererAdapter({
|
|
|
86
86
|
],
|
|
87
87
|
dependencies: nativeRendererDependencies(profile),
|
|
88
88
|
devDependencies: {
|
|
89
|
-
[profile.compiler.name]: profile.compiler.version,
|
|
89
|
+
[profile.compiler.name]: profile.dependencies[profile.compiler.name] ?? profile.compiler.version,
|
|
90
90
|
typescript: typescriptVersion
|
|
91
91
|
},
|
|
92
92
|
typecheckCommand: 'octane-tsc --noEmit --project tsconfig.json',
|
|
@@ -2,7 +2,7 @@ const octaneProfile = {
|
|
|
2
2
|
renderer: 'octane',
|
|
3
3
|
status: 'preview',
|
|
4
4
|
protocolVersion: 1,
|
|
5
|
-
minimumNode: '26.
|
|
5
|
+
minimumNode: '26.12.0',
|
|
6
6
|
hmr: {
|
|
7
7
|
editedBoundary: 'may-reset',
|
|
8
8
|
unaffectedComponents: 'preserved',
|
|
@@ -12,17 +12,17 @@ const octaneProfile = {
|
|
|
12
12
|
},
|
|
13
13
|
compiler: {
|
|
14
14
|
name: '@octanejs/rspack-plugin',
|
|
15
|
-
version: '0.
|
|
15
|
+
version: '0.2.4'
|
|
16
16
|
},
|
|
17
17
|
hydration: {
|
|
18
18
|
name: 'octane',
|
|
19
|
-
version: '0.
|
|
19
|
+
version: '0.13.8'
|
|
20
20
|
},
|
|
21
21
|
router: {
|
|
22
22
|
name: '@octanejs/tanstack-router',
|
|
23
|
-
version: '0.
|
|
23
|
+
version: '0.2.10',
|
|
24
24
|
coreName: '@tanstack/router-core',
|
|
25
|
-
coreVersion: '1.171.
|
|
25
|
+
coreVersion: '1.171.38'
|
|
26
26
|
},
|
|
27
27
|
sourceExtensions: [
|
|
28
28
|
'.tsx',
|
|
@@ -32,12 +32,14 @@ const octaneProfile = {
|
|
|
32
32
|
],
|
|
33
33
|
jsxImportSource: 'octane',
|
|
34
34
|
dependencies: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'@octanejs/
|
|
35
|
+
'@tsrx/oxc': '0.20.0',
|
|
36
|
+
octane: 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octane-0.13.8.tgz',
|
|
37
|
+
'@octanejs/tanstack-router': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octanejs-tanstack-router-0.2.10.tgz',
|
|
38
|
+
'@octanejs/rspack-plugin': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octanejs-rspack-plugin-0.2.4.tgz',
|
|
39
|
+
'@tanstack/router-core': 'https://github.com/BleedingDev/router/releases/download/ultramodern-router-core-585d3233f7f5/tanstack-router-core-1.171.38.tgz',
|
|
38
40
|
'@modern-js/renderer-octane': '3.8.3',
|
|
39
|
-
seroval: '1.6.
|
|
40
|
-
'seroval-plugins': '1.6.
|
|
41
|
+
seroval: '1.6.9',
|
|
42
|
+
'seroval-plugins': '1.6.9'
|
|
41
43
|
},
|
|
42
44
|
capabilities: {
|
|
43
45
|
worker: true,
|