@bleedingdev/modern-js-renderer-octane 0.0.0-bootstrap → 3.9.3-ultramodern.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/octane-tsc.mjs +4 -0
- package/dist/cjs/bootstrap.js +68 -0
- package/dist/cjs/client.js +371 -0
- package/dist/cjs/entry-application.js +83 -0
- package/dist/cjs/entry-client.js +92 -0
- package/dist/cjs/entry-server.js +127 -0
- package/dist/cjs/federation-context.js +72 -0
- package/dist/cjs/federation.js +103 -0
- package/dist/cjs/i18n/I18nProvider.js +67 -0
- package/dist/cjs/i18n/LocalizedLink.js +89 -0
- package/dist/cjs/i18n/context.js +41 -0
- package/dist/cjs/i18n/index.js +86 -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/index.js +46 -0
- package/dist/cjs/manifest.js +167 -0
- package/dist/cjs/plugin/compiler.js +125 -0
- package/dist/cjs/plugin/create.js +265 -0
- package/dist/cjs/plugin/index.js +151 -0
- package/dist/cjs/plugin/profile.js +89 -0
- package/dist/cjs/plugin/source.js +56 -0
- package/dist/cjs/router-client.js +139 -0
- package/dist/cjs/router-handler.js +168 -0
- package/dist/cjs/router-injection.js +139 -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 +108 -0
- package/dist/cjs/router-snapshot.js +270 -0
- package/dist/cjs/router.js +218 -0
- package/dist/cjs/routes.js +187 -0
- package/dist/cjs/server.js +224 -0
- package/dist/cjs/typecheck.js +615 -0
- package/dist/esm/bootstrap.mjs +24 -0
- package/dist/esm/client.mjs +327 -0
- package/dist/esm/entry-application.mjs +39 -0
- package/dist/esm/entry-client.mjs +54 -0
- package/dist/esm/entry-server.mjs +86 -0
- package/dist/esm/federation-context.mjs +25 -0
- package/dist/esm/federation.mjs +65 -0
- package/dist/esm/i18n/I18nProvider.mjs +29 -0
- package/dist/esm/i18n/LocalizedLink.mjs +48 -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/index.mjs +8 -0
- package/dist/esm/manifest.mjs +117 -0
- package/dist/esm/router-client.mjs +98 -0
- package/dist/esm/router-handler.mjs +130 -0
- package/dist/esm/router-injection.mjs +101 -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 +12 -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 +183 -0
- package/dist/esm-node/bootstrap.mjs +24 -0
- package/dist/esm-node/manifest.mjs +117 -0
- package/dist/esm-node/plugin/compiler.mjs +70 -0
- package/dist/esm-node/plugin/create.mjs +227 -0
- package/dist/esm-node/plugin/index.mjs +103 -0
- package/dist/esm-node/plugin/profile.mjs +51 -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 +36 -0
- package/dist/types/entry-application.d.ts +19 -0
- package/dist/types/entry-client.d.ts +8 -0
- package/dist/types/entry-server.d.ts +11 -0
- package/dist/types/federation-context.d.ts +28 -0
- package/dist/types/federation.d.ts +18 -0
- package/dist/types/i18n/I18nProvider.d.ts +8 -0
- package/dist/types/i18n/LocalizedLink.d.ts +41 -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/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-handler.d.ts +13 -0
- package/dist/types/router-injection.d.ts +8 -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 +13 -0
- package/dist/types/router-snapshot.d.ts +14 -0
- package/dist/types/router.d.ts +30 -0
- package/dist/types/routes.d.ts +18 -0
- package/dist/types/server.d.ts +41 -0
- package/dist/types/typecheck.d.ts +31 -0
- package/package.json +211 -4
- package/src/bootstrap.ts +60 -0
- package/src/client.ts +485 -0
- package/src/entry-application.ts +72 -0
- package/src/entry-client.ts +80 -0
- package/src/entry-server.ts +142 -0
- package/src/federation-context.ts +53 -0
- package/src/federation.ts +158 -0
- package/src/i18n/I18nProvider.ts +56 -0
- package/src/i18n/LocalizedLink.ts +112 -0
- package/src/i18n/context.ts +12 -0
- package/src/i18n/index.ts +35 -0
- package/src/i18n/languageSync.ts +132 -0
- package/src/i18n/types.ts +36 -0
- package/src/i18n/useI18n.ts +119 -0
- package/src/index.ts +10 -0
- package/src/manifest.ts +253 -0
- package/src/plugin/compiler-manifest.cjs +178 -0
- package/src/plugin/compiler.ts +112 -0
- package/src/plugin/create.ts +272 -0
- package/src/plugin/index.ts +105 -0
- package/src/plugin/profile.ts +46 -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-handler.ts +182 -0
- package/src/router-injection.ts +145 -0
- package/src/router-server-cleanup.ts +11 -0
- package/src/router-server-snapshot.ts +564 -0
- package/src/router-server.ts +23 -0
- package/src/router-snapshot.ts +251 -0
- package/src/router.ts +192 -0
- package/src/routes.ts +263 -0
- package/src/server.ts +300 -0
- package/src/typecheck.ts +723 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DocumentAsset,
|
|
3
|
+
type DocumentInlineData,
|
|
4
|
+
prepareDocument,
|
|
5
|
+
} from '@modern-js/renderer-core/document';
|
|
6
|
+
import type { NativeFederationBinding } from '@modern-js/renderer-core/federation';
|
|
7
|
+
import {
|
|
8
|
+
type RequestSession,
|
|
9
|
+
type ResponsePolicy,
|
|
10
|
+
untilAborted,
|
|
11
|
+
} from '@modern-js/renderer-core/session';
|
|
12
|
+
import {
|
|
13
|
+
createElement,
|
|
14
|
+
earlySignalBootstrapScript,
|
|
15
|
+
renderToReadableStream,
|
|
16
|
+
type ServerRenderNode,
|
|
17
|
+
type StreamOptions,
|
|
18
|
+
} from 'octane/server';
|
|
19
|
+
import {
|
|
20
|
+
assertNativeHydrationBuildId,
|
|
21
|
+
assertOctaneIdentity,
|
|
22
|
+
} from './bootstrap';
|
|
23
|
+
import { createFederationScope, FederationRoot } from './federation-context';
|
|
24
|
+
|
|
25
|
+
export interface OctaneDocumentOptions {
|
|
26
|
+
/** One identity for this response, shared with the client's signal receiver. */
|
|
27
|
+
readonly documentId: string;
|
|
28
|
+
/** Actual native client compilation hash, separate from application identity. */
|
|
29
|
+
readonly nativeHydrationBuildId: string;
|
|
30
|
+
readonly rootId?: string;
|
|
31
|
+
readonly lang?: string;
|
|
32
|
+
readonly nonce?: string;
|
|
33
|
+
readonly assets?: readonly DocumentAsset[];
|
|
34
|
+
/** Placed after the renderer bootstrap and before the entry scripts. */
|
|
35
|
+
readonly inlineData?: readonly DocumentInlineData[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type { DocumentInlineData as OctaneDocumentInlineData };
|
|
39
|
+
|
|
40
|
+
export interface OctaneDocumentResponseOptions<
|
|
41
|
+
Bindings extends object = object,
|
|
42
|
+
> {
|
|
43
|
+
readonly session: RequestSession<Bindings>;
|
|
44
|
+
readonly document: OctaneDocumentOptions;
|
|
45
|
+
readonly responsePolicy?: ResponsePolicy;
|
|
46
|
+
/** Native route loading finishes its blocking HTTP work before rendering. */
|
|
47
|
+
readonly resolveResponse?: (
|
|
48
|
+
session: RequestSession<Bindings>,
|
|
49
|
+
) => ResponsePolicy | Promise<ResponsePolicy>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RenderOctaneApplicationOptions<
|
|
53
|
+
Bindings extends object = object,
|
|
54
|
+
> extends OctaneDocumentResponseOptions<Bindings> {
|
|
55
|
+
readonly App: ServerRenderNode;
|
|
56
|
+
readonly props?: unknown;
|
|
57
|
+
/** Native router serialization, owned by this request's router SSR context. */
|
|
58
|
+
readonly injection?: StreamOptions['injection'];
|
|
59
|
+
/** This server compilation's native Module Federation runtime. */
|
|
60
|
+
readonly federation?: NativeFederationBinding;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function prepareOctaneDocument<Bindings extends object>(
|
|
64
|
+
session: RequestSession<Bindings>,
|
|
65
|
+
document: OctaneDocumentOptions,
|
|
66
|
+
hydrating: boolean,
|
|
67
|
+
) {
|
|
68
|
+
assertOctaneIdentity(session.identity);
|
|
69
|
+
if (session.platform.kind !== 'node' && session.platform.kind !== 'worker') {
|
|
70
|
+
throw new Error(
|
|
71
|
+
'An Octane document requires a Node or worker request platform.',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
assertNativeHydrationBuildId(document.nativeHydrationBuildId);
|
|
75
|
+
const nonce = document.nonce;
|
|
76
|
+
return prepareDocument(
|
|
77
|
+
{
|
|
78
|
+
identity: session.identity,
|
|
79
|
+
documentId: document.documentId,
|
|
80
|
+
hydrating,
|
|
81
|
+
nativeHydrationBuildId: document.nativeHydrationBuildId,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
rootId: document.rootId,
|
|
85
|
+
lang: document.lang,
|
|
86
|
+
nonce: { script: nonce, style: nonce },
|
|
87
|
+
assets: document.assets,
|
|
88
|
+
inlineData: document.inlineData,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function resolveDocumentResponse<Bindings extends object>(
|
|
94
|
+
input: OctaneDocumentResponseOptions<Bindings>,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const { session } = input;
|
|
97
|
+
if (input.responsePolicy && input.resolveResponse) {
|
|
98
|
+
throw new Error('Choose one blocking Octane response policy resolver.');
|
|
99
|
+
}
|
|
100
|
+
if (input.resolveResponse) {
|
|
101
|
+
// A resolver that ignores cancellation must not hold the request.
|
|
102
|
+
session.resolveResponse(
|
|
103
|
+
await untilAborted(input.resolveResponse(session), session.signal),
|
|
104
|
+
);
|
|
105
|
+
} else if (input.responsePolicy) {
|
|
106
|
+
session.resolveResponse(input.responsePolicy);
|
|
107
|
+
} else if (!session.responsePolicy) {
|
|
108
|
+
session.resolveResponse({
|
|
109
|
+
kind: 'document',
|
|
110
|
+
status: 200,
|
|
111
|
+
headers: [['content-type', 'text/html; charset=utf-8']],
|
|
112
|
+
cache: { mode: 'no-store' },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Deliver the CSR document; native browser mounting owns the empty root. */
|
|
118
|
+
export async function renderOctaneCSRDocument<Bindings extends object>(
|
|
119
|
+
input: OctaneDocumentResponseOptions<Bindings>,
|
|
120
|
+
): Promise<Response> {
|
|
121
|
+
const { session } = input;
|
|
122
|
+
try {
|
|
123
|
+
const { rootId, lang, bootstrap, headAssets, entryScripts } =
|
|
124
|
+
prepareOctaneDocument(session, input.document, false);
|
|
125
|
+
await resolveDocumentResponse(input);
|
|
126
|
+
if (session.responsePolicy?.kind === 'terminal')
|
|
127
|
+
return session.respond(null);
|
|
128
|
+
session.startRendering();
|
|
129
|
+
const bytes = new TextEncoder().encode(
|
|
130
|
+
`<!doctype html><html lang="${lang}"><head>${headAssets}</head><body><div id="${rootId}"></div>${bootstrap}${entryScripts}</body></html>`,
|
|
131
|
+
);
|
|
132
|
+
return session.respond(
|
|
133
|
+
new ReadableStream<Uint8Array>({
|
|
134
|
+
start(controller) {
|
|
135
|
+
controller.enqueue(bytes);
|
|
136
|
+
controller.close();
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
await session.fail(error);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Render a native Octane application into one request-owned HTML document.
|
|
148
|
+
* The dispatcher owns the session; this adapter owns native stream consumption
|
|
149
|
+
* and document placement. A late native error fails the body without changing
|
|
150
|
+
* the status and headers already sent to the client.
|
|
151
|
+
*/
|
|
152
|
+
export async function renderOctaneApplication<Bindings extends object>(
|
|
153
|
+
input: RenderOctaneApplicationOptions<Bindings>,
|
|
154
|
+
): Promise<Response> {
|
|
155
|
+
const { session, document } = input;
|
|
156
|
+
try {
|
|
157
|
+
const { rootId, lang, bootstrap, headAssets, entryScripts } =
|
|
158
|
+
prepareOctaneDocument(session, document, true);
|
|
159
|
+
const earlyBootstrap = earlySignalBootstrapScript(
|
|
160
|
+
document.nonce === undefined ? {} : { nonce: document.nonce },
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
await resolveDocumentResponse(input);
|
|
164
|
+
if (session.responsePolicy?.kind === 'terminal')
|
|
165
|
+
return session.respond(null);
|
|
166
|
+
session.startRendering();
|
|
167
|
+
|
|
168
|
+
let head = '';
|
|
169
|
+
const federation = input.federation
|
|
170
|
+
? createFederationScope(input.federation, true, document.nonce)
|
|
171
|
+
: undefined;
|
|
172
|
+
const App = federation
|
|
173
|
+
? () =>
|
|
174
|
+
createElement(FederationRoot, {
|
|
175
|
+
scope: federation,
|
|
176
|
+
children:
|
|
177
|
+
typeof input.App === 'function'
|
|
178
|
+
? createElement(input.App, input.props)
|
|
179
|
+
: input.App,
|
|
180
|
+
})
|
|
181
|
+
: input.App;
|
|
182
|
+
const nativeStream = await renderToReadableStream(App, input.props, {
|
|
183
|
+
signal: session.signal,
|
|
184
|
+
...(document.nonce === undefined ? {} : { nonce: document.nonce }),
|
|
185
|
+
earlySignalBootstrap: 'external',
|
|
186
|
+
...(input.injection === undefined ? {} : { injection: input.injection }),
|
|
187
|
+
streamedSignals: {
|
|
188
|
+
buildId: document.nativeHydrationBuildId,
|
|
189
|
+
documentId: document.documentId,
|
|
190
|
+
},
|
|
191
|
+
headChannel: 'separate',
|
|
192
|
+
onHeadReady(value) {
|
|
193
|
+
head = value;
|
|
194
|
+
},
|
|
195
|
+
onError(error) {
|
|
196
|
+
// Octane reports recoverable Suspense-boundary errors here as well as
|
|
197
|
+
// fatal ones. A recoverable error streams the boundary's client
|
|
198
|
+
// fallback into an otherwise valid document, so it must not terminate
|
|
199
|
+
// the request. Fatal errors reject the shell or allReady instead.
|
|
200
|
+
if (session.signal.aborted) return;
|
|
201
|
+
// A document carrying an errored boundary must not enter a shared cache.
|
|
202
|
+
session.markFallback();
|
|
203
|
+
console.error('Octane render error:', error);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
// Observe rejection immediately, but consume concurrently: native allReady
|
|
207
|
+
// requires downstream demand and cannot be awaited before reading the body.
|
|
208
|
+
const ready = nativeStream.allReady.then(
|
|
209
|
+
() => ({ ok: true as const }),
|
|
210
|
+
error => ({ ok: false as const, error }),
|
|
211
|
+
);
|
|
212
|
+
const reader = nativeStream.getReader();
|
|
213
|
+
const encoder = new TextEncoder();
|
|
214
|
+
const prefix = encoder.encode(
|
|
215
|
+
`<!doctype html><html lang="${lang}"><head>${head}${headAssets}</head><body>${earlyBootstrap}<div id="${rootId}">`,
|
|
216
|
+
);
|
|
217
|
+
const shellBootstrap = encoder.encode(`</div>${bootstrap}${entryScripts}`);
|
|
218
|
+
const suffix = encoder.encode('</body></html>');
|
|
219
|
+
let phase: 'prefix' | 'shell' | 'bootstrap' | 'native' | 'done' = 'prefix';
|
|
220
|
+
let released = false;
|
|
221
|
+
const release = () => {
|
|
222
|
+
if (released) return;
|
|
223
|
+
released = true;
|
|
224
|
+
reader.releaseLock();
|
|
225
|
+
};
|
|
226
|
+
let cancellation: Promise<void> | undefined;
|
|
227
|
+
const cancelNative = (reason: unknown): Promise<void> => {
|
|
228
|
+
if (cancellation) return cancellation;
|
|
229
|
+
if (released) return Promise.resolve();
|
|
230
|
+
cancellation = reader.cancel(reason).finally(release);
|
|
231
|
+
return cancellation;
|
|
232
|
+
};
|
|
233
|
+
// Cleanup starts native cancellation without waiting for it: a native
|
|
234
|
+
// stream that never finishes cancelling must not hold the session.
|
|
235
|
+
const releaseNative = (reason: unknown) => {
|
|
236
|
+
void cancelNative(reason).catch(() => {});
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
session.registerCleanup(() => releaseNative(session.signal.reason));
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// The request can abort between native shell readiness and registration.
|
|
242
|
+
releaseNative(error);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
const body = new ReadableStream<Uint8Array>(
|
|
246
|
+
{
|
|
247
|
+
async pull(controller) {
|
|
248
|
+
try {
|
|
249
|
+
if (phase === 'prefix') {
|
|
250
|
+
phase = 'shell';
|
|
251
|
+
controller.enqueue(prefix);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (phase === 'bootstrap') {
|
|
255
|
+
phase = 'native';
|
|
256
|
+
controller.enqueue(shellBootstrap);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const next = await reader.read();
|
|
260
|
+
if (!next.done) {
|
|
261
|
+
// Octane 0.7.1 writes its complete initial shell as the first
|
|
262
|
+
// chunk, including initial signal selections. Close only our
|
|
263
|
+
// root after it, then start the browser before deferred segments.
|
|
264
|
+
// Later native carriers find their targets in the whole document.
|
|
265
|
+
if (phase === 'shell') phase = 'bootstrap';
|
|
266
|
+
controller.enqueue(next.value);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (phase === 'shell') {
|
|
270
|
+
throw new Error(
|
|
271
|
+
'Octane ended before publishing its initial shell.',
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const completion = await ready;
|
|
275
|
+
release();
|
|
276
|
+
if (!completion.ok) throw completion.error;
|
|
277
|
+
phase = 'done';
|
|
278
|
+
controller.enqueue(suffix);
|
|
279
|
+
controller.close();
|
|
280
|
+
} catch (error) {
|
|
281
|
+
phase = 'done';
|
|
282
|
+
// An errored ReadableStream does not invoke its cancel callback.
|
|
283
|
+
// Release the native owner here as well as on consumer cancellation.
|
|
284
|
+
releaseNative(error);
|
|
285
|
+
controller.error(error);
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
cancel(reason) {
|
|
289
|
+
phase = 'done';
|
|
290
|
+
releaseNative(reason);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{ highWaterMark: 0 },
|
|
294
|
+
);
|
|
295
|
+
return session.respond(body);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
await session.fail(error);
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|