@bleedingdev/modern-js-renderer-octane 0.0.0-stage → 3.9.3-ultramodern.10
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/LICENSE +21 -0
- package/bin/octane-tsc.mjs +4 -0
- package/dist/cjs/bootstrap.js +68 -0
- package/dist/cjs/client.js +421 -0
- package/dist/cjs/entry-application.js +88 -0
- package/dist/cjs/entry-client.js +95 -0
- package/dist/cjs/entry-server.js +135 -0
- package/dist/cjs/federation-context.js +72 -0
- package/dist/cjs/federation.js +205 -0
- package/dist/cjs/i18n/I18nProvider.js +59 -0
- package/dist/cjs/i18n/LocalizedLink.js +80 -0
- package/dist/cjs/i18n/context.js +41 -0
- package/dist/cjs/i18n/index.js +83 -0
- package/dist/cjs/i18n/languageSync.js +99 -0
- package/dist/cjs/i18n/types.js +18 -0
- package/dist/cjs/i18n/useI18n.js +110 -0
- package/dist/cjs/i18n-external-snapshot.js +645 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/manifest.js +167 -0
- package/dist/cjs/plugin/compiler.js +122 -0
- package/dist/cjs/plugin/create.js +266 -0
- package/dist/cjs/plugin/index.js +151 -0
- package/dist/cjs/plugin/profile.js +91 -0
- package/dist/cjs/plugin/source.js +56 -0
- package/dist/cjs/router-client.js +139 -0
- package/dist/cjs/router-external-snapshot.js +291 -0
- package/dist/cjs/router-handler.js +171 -0
- package/dist/cjs/router-server-cleanup.js +46 -0
- package/dist/cjs/router-server-snapshot.js +405 -0
- package/dist/cjs/router-server.js +100 -0
- package/dist/cjs/router-snapshot.js +270 -0
- package/dist/cjs/router.js +218 -0
- package/dist/cjs/routes.js +190 -0
- package/dist/cjs/server.js +245 -0
- package/dist/cjs/typecheck.js +615 -0
- 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/bootstrap.mjs +24 -0
- package/dist/esm/client.mjs +377 -0
- package/dist/esm/entry-application.mjs +44 -0
- package/dist/esm/entry-client.mjs +57 -0
- package/dist/esm/entry-server.mjs +91 -0
- package/dist/esm/federation-context.mjs +25 -0
- package/dist/esm/federation.mjs +167 -0
- package/dist/esm/i18n/I18nProvider.mjs +21 -0
- package/dist/esm/i18n/LocalizedLink.mjs +42 -0
- package/dist/esm/i18n/context.mjs +3 -0
- package/dist/esm/i18n/index.mjs +8 -0
- package/dist/esm/i18n/languageSync.mjs +61 -0
- package/dist/esm/i18n/types.mjs +0 -0
- package/dist/esm/i18n/useI18n.mjs +72 -0
- package/dist/esm/i18n-external-snapshot.mjs +576 -0
- package/dist/esm/index.mjs +8 -0
- package/dist/esm/manifest.mjs +117 -0
- package/dist/esm/router-client.mjs +98 -0
- package/dist/esm/router-external-snapshot.mjs +244 -0
- package/dist/esm/router-handler.mjs +133 -0
- package/dist/esm/router-server-cleanup.mjs +8 -0
- package/dist/esm/router-server-snapshot.mjs +367 -0
- package/dist/esm/router-server.mjs +11 -0
- package/dist/esm/router-snapshot.mjs +199 -0
- package/dist/esm/router.mjs +94 -0
- package/dist/esm/routes.mjs +134 -0
- package/dist/esm/server.mjs +204 -0
- 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/bootstrap.mjs +24 -0
- package/dist/esm-node/manifest.mjs +117 -0
- package/dist/esm-node/plugin/compiler.mjs +67 -0
- package/dist/esm-node/plugin/create.mjs +228 -0
- package/dist/esm-node/plugin/index.mjs +103 -0
- package/dist/esm-node/plugin/profile.mjs +53 -0
- package/dist/esm-node/plugin/source.mjs +7 -0
- package/dist/esm-node/typecheck.mjs +560 -0
- package/dist/types/bootstrap.d.ts +10 -0
- package/dist/types/client.d.ts +40 -0
- package/dist/types/entry-application.d.ts +19 -0
- package/dist/types/entry-client.d.ts +10 -0
- package/dist/types/entry-server.d.ts +14 -0
- package/dist/types/federation-context.d.ts +28 -0
- package/dist/types/federation.d.ts +20 -0
- package/dist/types/i18n/I18nProvider.d.ts +8 -0
- package/dist/types/i18n/LocalizedLink.d.ts +14 -0
- package/dist/types/i18n/context.d.ts +9 -0
- package/dist/types/i18n/index.d.ts +9 -0
- package/dist/types/i18n/languageSync.d.ts +26 -0
- package/dist/types/i18n/types.d.ts +34 -0
- package/dist/types/i18n/useI18n.d.ts +21 -0
- package/dist/types/i18n-external-snapshot.d.ts +43 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/manifest.d.ts +34 -0
- package/dist/types/plugin/compiler-manifest.d.cts +10 -0
- package/dist/types/plugin/compiler.d.ts +5 -0
- package/dist/types/plugin/create.d.ts +23 -0
- package/dist/types/plugin/index.d.ts +3 -0
- package/dist/types/plugin/profile.d.ts +2 -0
- package/dist/types/plugin/source.d.ts +1 -0
- package/dist/types/plugin/svg-component-template.d.cts +6 -0
- package/dist/types/router-client.d.ts +12 -0
- package/dist/types/router-external-snapshot.d.ts +10 -0
- package/dist/types/router-handler.d.ts +13 -0
- package/dist/types/router-server-cleanup.d.ts +3 -0
- package/dist/types/router-server-snapshot.d.ts +15 -0
- package/dist/types/router-server.d.ts +11 -0
- package/dist/types/router-snapshot.d.ts +14 -0
- package/dist/types/router.d.ts +30 -0
- package/dist/types/routes.d.ts +20 -0
- package/dist/types/server.d.ts +47 -0
- package/dist/types/typecheck.d.ts +31 -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 +278 -4
- package/src/bootstrap.ts +60 -0
- package/src/client.ts +568 -0
- package/src/entry-application.ts +76 -0
- package/src/entry-client.ts +90 -0
- package/src/entry-server.ts +153 -0
- package/src/federation-context.ts +53 -0
- package/src/federation.ts +307 -0
- package/src/i18n/I18nProvider.ts +47 -0
- package/src/i18n/LocalizedLink.ts +86 -0
- package/src/i18n/context.ts +12 -0
- package/src/i18n/index.ts +31 -0
- package/src/i18n/languageSync.ts +132 -0
- package/src/i18n/types.ts +36 -0
- package/src/i18n/useI18n.ts +119 -0
- package/src/i18n-external-snapshot.ts +803 -0
- package/src/index.ts +10 -0
- package/src/manifest.ts +253 -0
- package/src/plugin/compiler-manifest.cjs +204 -0
- package/src/plugin/compiler.ts +104 -0
- package/src/plugin/create.ts +273 -0
- package/src/plugin/index.ts +107 -0
- package/src/plugin/profile.ts +50 -0
- package/src/plugin/source.ts +16 -0
- package/src/plugin/svg-component-template.cjs +17 -0
- package/src/router-client.ts +144 -0
- package/src/router-external-snapshot.ts +363 -0
- package/src/router-handler.ts +185 -0
- package/src/router-server-cleanup.ts +11 -0
- package/src/router-server-snapshot.ts +564 -0
- package/src/router-server.ts +21 -0
- package/src/router-snapshot.ts +251 -0
- package/src/router.ts +192 -0
- package/src/routes.ts +274 -0
- package/src/server.ts +340 -0
- package/src/typecheck.ts +723 -0
- package/src/worker-federation-context.ts +51 -0
- package/src/worker-federation-contexts.ts +91 -0
- package/src/worker-federation.ts +120 -0
- package/README.md +0 -3
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { RouteDataError, loadFileSystemRoute, matchApplicationRouteIds, matchApplicationRoutes, nativeRoutePath, resolveRouteData, selectApplicationDataRoute, splitFileSystemRoutes } from "@modern-js/renderer-core/router";
|
|
2
|
+
import { ErrorComponent, HeadContent, Outlet, Scripts, createRootRoute, createRoute, notFound, redirect } from "@octanejs/tanstack-router";
|
|
3
|
+
import { Fragment, createElement } from "octane";
|
|
4
|
+
const fileRouteHeads = new WeakMap();
|
|
5
|
+
function hasUnchangedOctaneFileRouteHead(route) {
|
|
6
|
+
return fileRouteHeads.get(route) === route.options.head;
|
|
7
|
+
}
|
|
8
|
+
function routes_resolveRouteData(routeId, outcome) {
|
|
9
|
+
return resolveRouteData({
|
|
10
|
+
redirect: redirect,
|
|
11
|
+
notFound: notFound
|
|
12
|
+
}, routeId, outcome);
|
|
13
|
+
}
|
|
14
|
+
function isReactiveNativeMatch(value) {
|
|
15
|
+
return 'object' == typeof value && null !== value && 'function' == typeof Reflect.get(value, 'get') && 'function' == typeof Reflect.get(value, 'subscribe');
|
|
16
|
+
}
|
|
17
|
+
function observeRouteCompletion(router, routeId, controller, signal, completion) {
|
|
18
|
+
completion.catch((error)=>{
|
|
19
|
+
if (signal.aborted) return;
|
|
20
|
+
const route = Object.values(router.routesById).find((candidate)=>candidate.options.staticData?.ultramodernRouteId === routeId);
|
|
21
|
+
if (!route) return;
|
|
22
|
+
const store = router.stores.getMatchStore(route.id);
|
|
23
|
+
let subscription;
|
|
24
|
+
let settled = false;
|
|
25
|
+
const cleanup = ()=>{
|
|
26
|
+
subscription?.unsubscribe();
|
|
27
|
+
signal.removeEventListener('abort', onAbort);
|
|
28
|
+
};
|
|
29
|
+
const onAbort = ()=>{
|
|
30
|
+
settled = true;
|
|
31
|
+
cleanup();
|
|
32
|
+
};
|
|
33
|
+
const publish = async ()=>{
|
|
34
|
+
if (settled) return;
|
|
35
|
+
const current = store.get();
|
|
36
|
+
if (!current) return;
|
|
37
|
+
if (current.abortController !== controller || signal.aborted || 'pending' !== current.status && 'success' !== current.status) return void onAbort();
|
|
38
|
+
if ('success' !== current.status || current.isFetching) return;
|
|
39
|
+
settled = true;
|
|
40
|
+
cleanup();
|
|
41
|
+
const route = router.routesById[current.routeId];
|
|
42
|
+
let failure = error;
|
|
43
|
+
try {
|
|
44
|
+
await route?.options.errorComponent?.preload?.();
|
|
45
|
+
if (signal.aborted || store.get()?.abortController !== controller) return;
|
|
46
|
+
route?.options.onError?.(failure);
|
|
47
|
+
} catch (onErrorFailure) {
|
|
48
|
+
failure = onErrorFailure;
|
|
49
|
+
}
|
|
50
|
+
if (signal.aborted) return;
|
|
51
|
+
router.stores.setMatches(router.stores.matches.get().map((previous)=>previous.routeId !== route.id || previous.abortController !== controller || 'success' !== previous.status || previous.isFetching ? previous : {
|
|
52
|
+
...previous,
|
|
53
|
+
error: failure,
|
|
54
|
+
status: 'error',
|
|
55
|
+
isFetching: false
|
|
56
|
+
}));
|
|
57
|
+
};
|
|
58
|
+
signal.addEventListener('abort', onAbort, {
|
|
59
|
+
once: true
|
|
60
|
+
});
|
|
61
|
+
if (isReactiveNativeMatch(store)) subscription = store.subscribe(publish);
|
|
62
|
+
if (settled) subscription?.unsubscribe();
|
|
63
|
+
else publish();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function createFileSystemRouteTree(routes, modules, options = {}) {
|
|
67
|
+
const { root: rootDescriptor, children } = splitFileSystemRoutes(routes);
|
|
68
|
+
function bindings(route, errorComponent) {
|
|
69
|
+
const needsData = Boolean(route.modules?.data || route.modules?.clientData);
|
|
70
|
+
if (needsData && !options.loadRoute) throw new Error(`An Octane data route requires its loader: ${route.id}`);
|
|
71
|
+
const loadRoute = options.loadRoute;
|
|
72
|
+
const { head, ...module } = modules[route.id] ?? {};
|
|
73
|
+
return {
|
|
74
|
+
...module,
|
|
75
|
+
...void 0 !== head ? {
|
|
76
|
+
head
|
|
77
|
+
} : {},
|
|
78
|
+
...errorComponent ? {
|
|
79
|
+
errorComponent
|
|
80
|
+
} : {},
|
|
81
|
+
component: module.component ?? Outlet,
|
|
82
|
+
staticData: {
|
|
83
|
+
ultramodernRouteId: route.id
|
|
84
|
+
},
|
|
85
|
+
...needsData && loadRoute ? {
|
|
86
|
+
loader: async (match)=>{
|
|
87
|
+
const { outcome, signal } = await loadFileSystemRoute(route, loadRoute, options, match);
|
|
88
|
+
if ('completion' in outcome && outcome.completion) {
|
|
89
|
+
const router = options.getRouter?.();
|
|
90
|
+
if (!router) throw new Error('An Octane streamed data route requires its native router');
|
|
91
|
+
let failed = false;
|
|
92
|
+
let failure;
|
|
93
|
+
outcome.completion.catch((error)=>{
|
|
94
|
+
failed = true;
|
|
95
|
+
failure = error;
|
|
96
|
+
});
|
|
97
|
+
await Promise.resolve();
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
if (failed) throw failure;
|
|
100
|
+
observeRouteCompletion(router, route.id, match.abortController, signal, outcome.completion);
|
|
101
|
+
}
|
|
102
|
+
options.onOutcome?.(route.id, outcome);
|
|
103
|
+
return routes_resolveRouteData(route.id, outcome);
|
|
104
|
+
}
|
|
105
|
+
} : {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const rootBindings = rootDescriptor ? bindings(rootDescriptor) : {
|
|
109
|
+
component: Outlet
|
|
110
|
+
};
|
|
111
|
+
const authoredRoot = rootBindings.component;
|
|
112
|
+
function OctaneDocumentRoot(props) {
|
|
113
|
+
return createElement(Fragment, null, createElement(HeadContent), createElement(authoredRoot, props), createElement(Scripts));
|
|
114
|
+
}
|
|
115
|
+
if (void 0 !== authoredRoot.preload) OctaneDocumentRoot.preload = authoredRoot.preload;
|
|
116
|
+
const rootErrorComponent = rootDescriptor && modules[rootDescriptor.id]?.errorComponent || ErrorComponent;
|
|
117
|
+
const root = createRootRoute({
|
|
118
|
+
...rootBindings,
|
|
119
|
+
component: OctaneDocumentRoot
|
|
120
|
+
});
|
|
121
|
+
fileRouteHeads.set(root, root.options.head);
|
|
122
|
+
function bind(route, parent, inheritedErrorComponent) {
|
|
123
|
+
const errorComponent = modules[route.id]?.errorComponent ?? inheritedErrorComponent;
|
|
124
|
+
const native = createRoute({
|
|
125
|
+
getParentRoute: ()=>parent,
|
|
126
|
+
...nativeRoutePath(route),
|
|
127
|
+
...bindings(route, errorComponent)
|
|
128
|
+
});
|
|
129
|
+
fileRouteHeads.set(native, native.options.head);
|
|
130
|
+
return native.addChildren(route.children.map((child)=>bind(child, native, errorComponent)));
|
|
131
|
+
}
|
|
132
|
+
return root.addChildren(children.map((route)=>bind(route, root, rootErrorComponent)));
|
|
133
|
+
}
|
|
134
|
+
export { RouteDataError, createFileSystemRouteTree, hasUnchangedOctaneFileRouteHead, matchApplicationRouteIds, matchApplicationRoutes, routes_resolveRouteData as resolveRouteData, selectApplicationDataRoute };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { prepareDocument } from "@modern-js/renderer-core/document";
|
|
2
|
+
import { untilAborted } from "@modern-js/renderer-core/session";
|
|
3
|
+
import { transformReadableStreamWithRouter } from "@octanejs/tanstack-router/ssr/server";
|
|
4
|
+
import { createElement, earlySignalBootstrapScript, renderToReadableStream } from "octane/server";
|
|
5
|
+
import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
|
|
6
|
+
import { FederationRoot, createFederationScope } from "./federation-context.mjs";
|
|
7
|
+
import { WorkerFederationRoot } from "./worker-federation-context.mjs";
|
|
8
|
+
function prepareOctaneDocument(session, document, hydrating) {
|
|
9
|
+
assertOctaneIdentity(session.identity);
|
|
10
|
+
if ('node' !== session.platform.kind && 'worker' !== session.platform.kind) throw new Error('An Octane document requires a Node or worker request platform.');
|
|
11
|
+
assertNativeHydrationBuildId(document.nativeHydrationBuildId);
|
|
12
|
+
const nonce = document.nonce;
|
|
13
|
+
return prepareDocument({
|
|
14
|
+
identity: session.identity,
|
|
15
|
+
documentId: document.documentId,
|
|
16
|
+
hydrating,
|
|
17
|
+
nativeHydrationBuildId: document.nativeHydrationBuildId
|
|
18
|
+
}, {
|
|
19
|
+
rootId: document.rootId,
|
|
20
|
+
lang: document.lang,
|
|
21
|
+
nonce: {
|
|
22
|
+
script: nonce,
|
|
23
|
+
style: nonce
|
|
24
|
+
},
|
|
25
|
+
assets: document.assets,
|
|
26
|
+
inlineData: document.inlineData
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function resolveDocumentResponse(input) {
|
|
30
|
+
const { session } = input;
|
|
31
|
+
if (input.responsePolicy && input.resolveResponse) throw new Error('Choose one blocking Octane response policy resolver.');
|
|
32
|
+
if (input.resolveResponse) session.resolveResponse(await untilAborted(input.resolveResponse(session), session.signal));
|
|
33
|
+
else if (input.responsePolicy) session.resolveResponse(input.responsePolicy);
|
|
34
|
+
else if (!session.responsePolicy) session.resolveResponse({
|
|
35
|
+
kind: 'document',
|
|
36
|
+
status: 200,
|
|
37
|
+
headers: [
|
|
38
|
+
[
|
|
39
|
+
'content-type',
|
|
40
|
+
'text/html; charset=utf-8'
|
|
41
|
+
]
|
|
42
|
+
],
|
|
43
|
+
cache: {
|
|
44
|
+
mode: 'no-store'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function renderOctaneCSRDocument(input) {
|
|
49
|
+
const { session } = input;
|
|
50
|
+
try {
|
|
51
|
+
const { rootId, lang, bootstrap, headAssets, entryScripts } = prepareOctaneDocument(session, input.document, false);
|
|
52
|
+
await resolveDocumentResponse(input);
|
|
53
|
+
if (session.responsePolicy?.kind === 'terminal') return session.respond(null);
|
|
54
|
+
session.startRendering();
|
|
55
|
+
const bytes = new TextEncoder().encode(`<!doctype html><html lang="${lang}"><head>${headAssets}</head><body><div id="${rootId}"></div>${bootstrap}${entryScripts}</body></html>`);
|
|
56
|
+
return session.respond(new ReadableStream({
|
|
57
|
+
start (controller) {
|
|
58
|
+
controller.enqueue(bytes);
|
|
59
|
+
controller.close();
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
await session.fail(error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function renderOctaneApplication(input) {
|
|
68
|
+
const { session, document } = input;
|
|
69
|
+
try {
|
|
70
|
+
const { rootId, lang, bootstrap, headAssets, entryScripts } = prepareOctaneDocument(session, document, true);
|
|
71
|
+
const earlyBootstrap = earlySignalBootstrapScript(void 0 === document.nonce ? {} : {
|
|
72
|
+
nonce: document.nonce
|
|
73
|
+
});
|
|
74
|
+
await resolveDocumentResponse(input);
|
|
75
|
+
if (session.responsePolicy?.kind === 'terminal') return session.respond(null);
|
|
76
|
+
session.startRendering();
|
|
77
|
+
let head = '';
|
|
78
|
+
const federation = input.federation || input.workerFederation ? createFederationScope(input.federation, true, document.nonce) : void 0;
|
|
79
|
+
const App = federation ? ()=>createElement(FederationRoot, {
|
|
80
|
+
scope: federation,
|
|
81
|
+
children: 'function' == typeof input.App ? createElement(input.App, input.props) : input.App
|
|
82
|
+
}) : input.App;
|
|
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, {
|
|
93
|
+
signal: session.signal,
|
|
94
|
+
...void 0 === document.nonce ? {} : {
|
|
95
|
+
nonce: document.nonce
|
|
96
|
+
},
|
|
97
|
+
earlySignalBootstrap: 'external',
|
|
98
|
+
...void 0 === input.injection ? {} : {
|
|
99
|
+
injection: input.injection
|
|
100
|
+
},
|
|
101
|
+
streamedSignals: {
|
|
102
|
+
buildId: document.nativeHydrationBuildId,
|
|
103
|
+
documentId: document.documentId
|
|
104
|
+
},
|
|
105
|
+
externalSnapshots: {
|
|
106
|
+
documentId: document.documentId
|
|
107
|
+
},
|
|
108
|
+
headChannel: 'separate',
|
|
109
|
+
onHeadReady (value) {
|
|
110
|
+
head = value;
|
|
111
|
+
},
|
|
112
|
+
onError (error) {
|
|
113
|
+
if (session.signal.aborted) return;
|
|
114
|
+
session.markFallback();
|
|
115
|
+
console.error('Octane render error:', error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const ready = nativeStream.allReady.then(()=>({
|
|
119
|
+
ok: true
|
|
120
|
+
}), (error)=>({
|
|
121
|
+
ok: false,
|
|
122
|
+
error
|
|
123
|
+
}));
|
|
124
|
+
const reader = nativeStream.getReader();
|
|
125
|
+
const encoder = new TextEncoder();
|
|
126
|
+
const prefix = encoder.encode(`<!doctype html><html lang="${lang}"><head>${head}${headAssets}</head><body>${earlyBootstrap}<div id="${rootId}">`);
|
|
127
|
+
const shellBootstrap = encoder.encode(`</div>${bootstrap}${entryScripts}`);
|
|
128
|
+
const suffix = encoder.encode('</body></html>');
|
|
129
|
+
let phase = 'prefix';
|
|
130
|
+
let released = false;
|
|
131
|
+
const release = ()=>{
|
|
132
|
+
if (released) return;
|
|
133
|
+
released = true;
|
|
134
|
+
reader.releaseLock();
|
|
135
|
+
};
|
|
136
|
+
let cancellation;
|
|
137
|
+
const cancelNative = (reason)=>{
|
|
138
|
+
if (cancellation) return cancellation;
|
|
139
|
+
if (released) return Promise.resolve();
|
|
140
|
+
cancellation = reader.cancel(reason).finally(release);
|
|
141
|
+
return cancellation;
|
|
142
|
+
};
|
|
143
|
+
const releaseNative = (reason)=>{
|
|
144
|
+
cancelNative(reason).catch(()=>{});
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
session.registerCleanup(()=>releaseNative(session.signal.reason));
|
|
148
|
+
} catch (error) {
|
|
149
|
+
releaseNative(error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
const body = new ReadableStream({
|
|
153
|
+
async pull (controller) {
|
|
154
|
+
try {
|
|
155
|
+
if ('prefix' === phase) {
|
|
156
|
+
phase = 'shell';
|
|
157
|
+
controller.enqueue(prefix);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if ('bootstrap' === phase) {
|
|
161
|
+
phase = 'native';
|
|
162
|
+
controller.enqueue(shellBootstrap);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const next = await reader.read();
|
|
166
|
+
if (!next.done) {
|
|
167
|
+
if ('shell' === phase) phase = 'bootstrap';
|
|
168
|
+
controller.enqueue(next.value);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if ('shell' === phase) throw new Error('Octane ended before publishing its initial shell.');
|
|
172
|
+
const completion = await ready;
|
|
173
|
+
release();
|
|
174
|
+
if (!completion.ok) throw completion.error;
|
|
175
|
+
phase = 'done';
|
|
176
|
+
controller.enqueue(suffix);
|
|
177
|
+
controller.close();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
phase = 'done';
|
|
180
|
+
releaseNative(error);
|
|
181
|
+
controller.error(error);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
cancel (reason) {
|
|
185
|
+
phase = 'done';
|
|
186
|
+
releaseNative(reason);
|
|
187
|
+
}
|
|
188
|
+
}, {
|
|
189
|
+
highWaterMark: 0
|
|
190
|
+
});
|
|
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);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
await session.fail(error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export { renderOctaneApplication, renderOctaneCSRDocument };
|
|
@@ -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 };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readDocumentBootstrap } from "@modern-js/renderer-core/document";
|
|
2
|
+
import { readRendererIdentity } from "@modern-js/renderer-core/identity";
|
|
3
|
+
function assertNativeHydrationBuildId(value) {
|
|
4
|
+
if ('string' != typeof value || 0 === value.trim().length) throw new Error('An Octane document requires its native client compilation identity.');
|
|
5
|
+
}
|
|
6
|
+
function assertOctaneIdentity(identity) {
|
|
7
|
+
readRendererIdentity(identity, identity);
|
|
8
|
+
if ('octane' !== identity.renderer) throw new Error('An Octane application requires an Octane renderer identity.');
|
|
9
|
+
}
|
|
10
|
+
function readOctaneDocumentBootstrap(document, expectedIdentity, expectedNativeHydrationBuildId) {
|
|
11
|
+
assertOctaneIdentity(expectedIdentity);
|
|
12
|
+
const { identity, documentId, hydrating, nativeHydrationBuildId } = readDocumentBootstrap(document, expectedIdentity, [
|
|
13
|
+
'nativeHydrationBuildId'
|
|
14
|
+
]);
|
|
15
|
+
assertNativeHydrationBuildId(nativeHydrationBuildId);
|
|
16
|
+
if (void 0 !== expectedNativeHydrationBuildId && nativeHydrationBuildId !== expectedNativeHydrationBuildId) throw new Error('Octane hydration bytes belong to a different native client compilation.');
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
identity,
|
|
19
|
+
documentId,
|
|
20
|
+
hydrating,
|
|
21
|
+
nativeHydrationBuildId
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export { assertNativeHydrationBuildId, assertOctaneIdentity, readOctaneDocumentBootstrap };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { nativeModuleManifestFilename, readRendererIdentity } from "@modern-js/renderer-core/identity";
|
|
2
|
+
import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
|
|
3
|
+
const OCTANE_RUNTIME_VERSION = '0.13.8';
|
|
4
|
+
const OCTANE_COMPILER_VERSION = '0.2.4';
|
|
5
|
+
function octaneModuleManifestFileName(entryName) {
|
|
6
|
+
return nativeModuleManifestFilename('octane', entryName);
|
|
7
|
+
}
|
|
8
|
+
function record(value, fields) {
|
|
9
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) throw new Error('Malformed Octane compiler manifest object.');
|
|
10
|
+
if (Object.keys(value).some((key)=>!fields.includes(key))) throw new Error('Octane compiler manifest contains an unknown field.');
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function digest(value) {
|
|
14
|
+
return 'string' == typeof value && /^[a-f\d]{64}$/u.test(value);
|
|
15
|
+
}
|
|
16
|
+
function nonempty(value) {
|
|
17
|
+
return 'string' == typeof value && value.trim().length > 0 && !value.includes('\0');
|
|
18
|
+
}
|
|
19
|
+
function assetPath(value) {
|
|
20
|
+
return nonempty(value) && value.endsWith('.js') && !value.startsWith('/') && !value.includes('\\') && !value.split('/').some((part)=>'..' === part || '.' === part || !part);
|
|
21
|
+
}
|
|
22
|
+
const validatedManifests = new WeakMap();
|
|
23
|
+
function validateOctaneModuleManifest(value, expectedIdentity, expectedNativeHydrationBuildId) {
|
|
24
|
+
assertOctaneIdentity(expectedIdentity);
|
|
25
|
+
const cached = value && 'object' == typeof value ? validatedManifests.get(value) : void 0;
|
|
26
|
+
const manifest = cached ?? readOctaneModuleManifest(value);
|
|
27
|
+
readRendererIdentity(manifest.rendererIdentity, expectedIdentity);
|
|
28
|
+
if (void 0 !== expectedNativeHydrationBuildId) {
|
|
29
|
+
assertNativeHydrationBuildId(expectedNativeHydrationBuildId);
|
|
30
|
+
if (manifest.nativeHydrationBuildId !== expectedNativeHydrationBuildId) throw new Error('Octane native hydration build differs from the compiled client.');
|
|
31
|
+
}
|
|
32
|
+
if (!cached) {
|
|
33
|
+
validatedManifests.set(value, manifest);
|
|
34
|
+
validatedManifests.set(manifest, manifest);
|
|
35
|
+
}
|
|
36
|
+
return manifest;
|
|
37
|
+
}
|
|
38
|
+
function readOctaneModuleManifest(value) {
|
|
39
|
+
const manifest = record(value, [
|
|
40
|
+
'schemaVersion',
|
|
41
|
+
'renderer',
|
|
42
|
+
'runtimeVersion',
|
|
43
|
+
'compilerVersion',
|
|
44
|
+
'rendererIdentity',
|
|
45
|
+
'nativeHydrationBuildId',
|
|
46
|
+
'sourceModules',
|
|
47
|
+
'assets'
|
|
48
|
+
]);
|
|
49
|
+
if (1 !== manifest.schemaVersion || 'octane' !== manifest.renderer || manifest.runtimeVersion !== OCTANE_RUNTIME_VERSION || manifest.compilerVersion !== OCTANE_COMPILER_VERSION) throw new Error('Octane compiler manifest ABI conflicts with the application.');
|
|
50
|
+
const rendererIdentity = readRendererIdentity(manifest.rendererIdentity, manifest.rendererIdentity);
|
|
51
|
+
const nativeHydrationBuildId = manifest.nativeHydrationBuildId;
|
|
52
|
+
assertNativeHydrationBuildId(nativeHydrationBuildId);
|
|
53
|
+
if (!Array.isArray(manifest.assets) || !manifest.assets.length) throw new Error('Octane compiler manifest has no emitted JavaScript closure.');
|
|
54
|
+
const assetNames = new Set();
|
|
55
|
+
const assets = manifest.assets.map((value)=>{
|
|
56
|
+
const asset = record(value, [
|
|
57
|
+
'file',
|
|
58
|
+
'sha256'
|
|
59
|
+
]);
|
|
60
|
+
if (!assetPath(asset.file) || !digest(asset.sha256) || assetNames.has(asset.file)) throw new Error('Invalid Octane emitted asset digest.');
|
|
61
|
+
assetNames.add(asset.file);
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
file: asset.file,
|
|
64
|
+
sha256: asset.sha256
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
if (!Array.isArray(manifest.sourceModules) || !manifest.sourceModules.length) throw new Error('Octane compiler manifest has no authenticated native sources.');
|
|
68
|
+
const resources = new Set();
|
|
69
|
+
const sourceModules = manifest.sourceModules.map((value)=>{
|
|
70
|
+
const source = record(value, [
|
|
71
|
+
'resource',
|
|
72
|
+
'canonicalId',
|
|
73
|
+
'moduleId',
|
|
74
|
+
'transformKind',
|
|
75
|
+
'emittedSourceSha256',
|
|
76
|
+
'assets'
|
|
77
|
+
]);
|
|
78
|
+
if (!nonempty(source.resource) || source.resource.startsWith('/') || source.resource.includes('\\') || !nonempty(source.canonicalId) || !('string' == typeof source.moduleId && nonempty(source.moduleId) || 'number' == typeof source.moduleId && Number.isFinite(source.moduleId)) || !('compile' === source.transformKind || 'slots' === source.transformKind || 'client-only-stub' === source.transformKind) || !digest(source.emittedSourceSha256) || !Array.isArray(source.assets) || !source.assets.length || source.assets.some((file)=>'string' != typeof file || !assetNames.has(file)) || new Set(source.assets).size !== source.assets.length || resources.has(source.resource)) throw new Error('Invalid Octane native source-to-asset provenance.');
|
|
79
|
+
resources.add(source.resource);
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
resource: source.resource,
|
|
82
|
+
canonicalId: source.canonicalId,
|
|
83
|
+
moduleId: source.moduleId,
|
|
84
|
+
transformKind: source.transformKind,
|
|
85
|
+
emittedSourceSha256: source.emittedSourceSha256,
|
|
86
|
+
assets: Object.freeze([
|
|
87
|
+
...source.assets
|
|
88
|
+
])
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
schemaVersion: 1,
|
|
93
|
+
renderer: 'octane',
|
|
94
|
+
runtimeVersion: OCTANE_RUNTIME_VERSION,
|
|
95
|
+
compilerVersion: OCTANE_COMPILER_VERSION,
|
|
96
|
+
rendererIdentity,
|
|
97
|
+
nativeHydrationBuildId,
|
|
98
|
+
sourceModules: Object.freeze(sourceModules),
|
|
99
|
+
assets: Object.freeze(assets)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const compilerArtifacts = {
|
|
103
|
+
clientManifestFile: octaneModuleManifestFileName,
|
|
104
|
+
async validateClientManifest (value, identity, context) {
|
|
105
|
+
const hydrationBuildId = context.compilationHash ?? context.hydrationBuildId;
|
|
106
|
+
if (context.development && ('string' != typeof hydrationBuildId || !hydrationBuildId.trim())) throw new Error('Octane development snapshot has no native hydration build.');
|
|
107
|
+
const nativeManifest = validateOctaneModuleManifest(value, identity, hydrationBuildId);
|
|
108
|
+
return {
|
|
109
|
+
nativeManifest,
|
|
110
|
+
hydrationBuildId: nativeManifest.nativeHydrationBuildId
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
isMutableDevelopmentAsset (filename, entryNames) {
|
|
114
|
+
return 'octane-client-build.json' === filename || entryNames.some((entry)=>filename === octaneModuleManifestFileName(entry));
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
export { OCTANE_COMPILER_VERSION, OCTANE_RUNTIME_VERSION, compilerArtifacts, octaneModuleManifestFileName, validateOctaneModuleManifest };
|