@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { RendererIdentity } from '@modern-js/renderer-core/identity';
|
|
2
|
+
export type { OctaneDocumentBootstrap } from './bootstrap';
|
|
3
|
+
|
|
4
|
+
export const OCTANE_RUNTIME_PROFILE = Object.freeze({
|
|
5
|
+
renderer: 'octane',
|
|
6
|
+
runtime: '0.13.8',
|
|
7
|
+
router: '0.2.10',
|
|
8
|
+
routerCore: '1.171.34',
|
|
9
|
+
history: '1.162.4',
|
|
10
|
+
});
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { NativeCompilerArtifacts } from '@modern-js/renderer-core/adapter';
|
|
2
|
+
import {
|
|
3
|
+
nativeModuleManifestFilename,
|
|
4
|
+
type RendererIdentity,
|
|
5
|
+
readRendererIdentity,
|
|
6
|
+
} from '@modern-js/renderer-core/identity';
|
|
7
|
+
import {
|
|
8
|
+
assertNativeHydrationBuildId,
|
|
9
|
+
assertOctaneIdentity,
|
|
10
|
+
} from './bootstrap';
|
|
11
|
+
|
|
12
|
+
export const OCTANE_RUNTIME_VERSION = '0.13.8';
|
|
13
|
+
export const OCTANE_COMPILER_VERSION = '0.2.4';
|
|
14
|
+
|
|
15
|
+
export interface OctaneCompiledSource {
|
|
16
|
+
readonly resource: string;
|
|
17
|
+
readonly canonicalId: string;
|
|
18
|
+
readonly moduleId: string | number;
|
|
19
|
+
readonly transformKind: 'compile' | 'slots' | 'client-only-stub';
|
|
20
|
+
readonly emittedSourceSha256: string;
|
|
21
|
+
readonly assets: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OctaneModuleManifest {
|
|
25
|
+
readonly schemaVersion: 1;
|
|
26
|
+
readonly renderer: 'octane';
|
|
27
|
+
readonly runtimeVersion: typeof OCTANE_RUNTIME_VERSION;
|
|
28
|
+
readonly compilerVersion: typeof OCTANE_COMPILER_VERSION;
|
|
29
|
+
readonly rendererIdentity: Readonly<RendererIdentity>;
|
|
30
|
+
readonly nativeHydrationBuildId: string;
|
|
31
|
+
readonly sourceModules: readonly OctaneCompiledSource[];
|
|
32
|
+
readonly assets: readonly {
|
|
33
|
+
readonly file: string;
|
|
34
|
+
readonly sha256: string;
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function octaneModuleManifestFileName(entryName: string): string {
|
|
39
|
+
return nativeModuleManifestFilename('octane', entryName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function record(
|
|
43
|
+
value: unknown,
|
|
44
|
+
fields: readonly string[],
|
|
45
|
+
): Record<string, unknown> {
|
|
46
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
47
|
+
throw new Error('Malformed Octane compiler manifest object.');
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(value).some(key => !fields.includes(key))) {
|
|
50
|
+
throw new Error('Octane compiler manifest contains an unknown field.');
|
|
51
|
+
}
|
|
52
|
+
return value as Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function digest(value: unknown): value is string {
|
|
56
|
+
return typeof value === 'string' && /^[a-f\d]{64}$/u.test(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function nonempty(value: unknown): value is string {
|
|
60
|
+
return (
|
|
61
|
+
typeof value === 'string' &&
|
|
62
|
+
value.trim().length > 0 &&
|
|
63
|
+
!value.includes('\0')
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function assetPath(value: unknown): value is string {
|
|
68
|
+
return (
|
|
69
|
+
nonempty(value) &&
|
|
70
|
+
value.endsWith('.js') &&
|
|
71
|
+
!value.startsWith('/') &&
|
|
72
|
+
!value.includes('\\') &&
|
|
73
|
+
!value.split('/').some(part => part === '..' || part === '.' || !part)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A build's manifest is an immutable artifact: its shape is checked once. */
|
|
78
|
+
const validatedManifests = new WeakMap<object, OctaneModuleManifest>();
|
|
79
|
+
|
|
80
|
+
/** Validates completed native bytes separately from the source/profile build. */
|
|
81
|
+
export function validateOctaneModuleManifest(
|
|
82
|
+
value: unknown,
|
|
83
|
+
expectedIdentity: RendererIdentity,
|
|
84
|
+
expectedNativeHydrationBuildId?: string,
|
|
85
|
+
): OctaneModuleManifest {
|
|
86
|
+
assertOctaneIdentity(expectedIdentity);
|
|
87
|
+
const cached =
|
|
88
|
+
value && typeof value === 'object'
|
|
89
|
+
? validatedManifests.get(value)
|
|
90
|
+
: undefined;
|
|
91
|
+
const manifest = cached ?? readOctaneModuleManifest(value);
|
|
92
|
+
readRendererIdentity(manifest.rendererIdentity, expectedIdentity);
|
|
93
|
+
if (expectedNativeHydrationBuildId !== undefined) {
|
|
94
|
+
assertNativeHydrationBuildId(expectedNativeHydrationBuildId);
|
|
95
|
+
if (manifest.nativeHydrationBuildId !== expectedNativeHydrationBuildId) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'Octane native hydration build differs from the compiled client.',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!cached) {
|
|
102
|
+
validatedManifests.set(value as object, manifest);
|
|
103
|
+
validatedManifests.set(manifest, manifest);
|
|
104
|
+
}
|
|
105
|
+
return manifest;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readOctaneModuleManifest(value: unknown): OctaneModuleManifest {
|
|
109
|
+
const manifest = record(value, [
|
|
110
|
+
'schemaVersion',
|
|
111
|
+
'renderer',
|
|
112
|
+
'runtimeVersion',
|
|
113
|
+
'compilerVersion',
|
|
114
|
+
'rendererIdentity',
|
|
115
|
+
'nativeHydrationBuildId',
|
|
116
|
+
'sourceModules',
|
|
117
|
+
'assets',
|
|
118
|
+
]);
|
|
119
|
+
if (
|
|
120
|
+
manifest.schemaVersion !== 1 ||
|
|
121
|
+
manifest.renderer !== 'octane' ||
|
|
122
|
+
manifest.runtimeVersion !== OCTANE_RUNTIME_VERSION ||
|
|
123
|
+
manifest.compilerVersion !== OCTANE_COMPILER_VERSION
|
|
124
|
+
) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'Octane compiler manifest ABI conflicts with the application.',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
// The caller compares this well-formed record with its expected identity.
|
|
130
|
+
const rendererIdentity = readRendererIdentity(
|
|
131
|
+
manifest.rendererIdentity,
|
|
132
|
+
manifest.rendererIdentity as RendererIdentity,
|
|
133
|
+
);
|
|
134
|
+
const nativeHydrationBuildId = manifest.nativeHydrationBuildId;
|
|
135
|
+
assertNativeHydrationBuildId(nativeHydrationBuildId);
|
|
136
|
+
if (!Array.isArray(manifest.assets) || !manifest.assets.length) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'Octane compiler manifest has no emitted JavaScript closure.',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const assetNames = new Set<string>();
|
|
142
|
+
const assets = manifest.assets.map(value => {
|
|
143
|
+
const asset = record(value, ['file', 'sha256']);
|
|
144
|
+
if (
|
|
145
|
+
!assetPath(asset.file) ||
|
|
146
|
+
!digest(asset.sha256) ||
|
|
147
|
+
assetNames.has(asset.file)
|
|
148
|
+
) {
|
|
149
|
+
throw new Error('Invalid Octane emitted asset digest.');
|
|
150
|
+
}
|
|
151
|
+
assetNames.add(asset.file);
|
|
152
|
+
return Object.freeze({ file: asset.file, sha256: asset.sha256 });
|
|
153
|
+
});
|
|
154
|
+
if (
|
|
155
|
+
!Array.isArray(manifest.sourceModules) ||
|
|
156
|
+
!manifest.sourceModules.length
|
|
157
|
+
) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'Octane compiler manifest has no authenticated native sources.',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const resources = new Set<string>();
|
|
163
|
+
const sourceModules = manifest.sourceModules.map(value => {
|
|
164
|
+
const source = record(value, [
|
|
165
|
+
'resource',
|
|
166
|
+
'canonicalId',
|
|
167
|
+
'moduleId',
|
|
168
|
+
'transformKind',
|
|
169
|
+
'emittedSourceSha256',
|
|
170
|
+
'assets',
|
|
171
|
+
]);
|
|
172
|
+
if (
|
|
173
|
+
!nonempty(source.resource) ||
|
|
174
|
+
source.resource.startsWith('/') ||
|
|
175
|
+
source.resource.includes('\\') ||
|
|
176
|
+
!nonempty(source.canonicalId) ||
|
|
177
|
+
!(
|
|
178
|
+
(typeof source.moduleId === 'string' && nonempty(source.moduleId)) ||
|
|
179
|
+
(typeof source.moduleId === 'number' &&
|
|
180
|
+
Number.isFinite(source.moduleId))
|
|
181
|
+
) ||
|
|
182
|
+
!(
|
|
183
|
+
source.transformKind === 'compile' ||
|
|
184
|
+
source.transformKind === 'slots' ||
|
|
185
|
+
source.transformKind === 'client-only-stub'
|
|
186
|
+
) ||
|
|
187
|
+
!digest(source.emittedSourceSha256) ||
|
|
188
|
+
!Array.isArray(source.assets) ||
|
|
189
|
+
!source.assets.length ||
|
|
190
|
+
source.assets.some(
|
|
191
|
+
file => typeof file !== 'string' || !assetNames.has(file),
|
|
192
|
+
) ||
|
|
193
|
+
new Set(source.assets).size !== source.assets.length ||
|
|
194
|
+
resources.has(source.resource)
|
|
195
|
+
) {
|
|
196
|
+
throw new Error('Invalid Octane native source-to-asset provenance.');
|
|
197
|
+
}
|
|
198
|
+
resources.add(source.resource);
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
resource: source.resource,
|
|
201
|
+
canonicalId: source.canonicalId,
|
|
202
|
+
moduleId: source.moduleId,
|
|
203
|
+
transformKind: source.transformKind,
|
|
204
|
+
emittedSourceSha256: source.emittedSourceSha256,
|
|
205
|
+
assets: Object.freeze([...source.assets] as string[]),
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
schemaVersion: 1,
|
|
210
|
+
renderer: 'octane',
|
|
211
|
+
runtimeVersion: OCTANE_RUNTIME_VERSION,
|
|
212
|
+
compilerVersion: OCTANE_COMPILER_VERSION,
|
|
213
|
+
rendererIdentity,
|
|
214
|
+
nativeHydrationBuildId,
|
|
215
|
+
sourceModules: Object.freeze(sourceModules),
|
|
216
|
+
assets: Object.freeze(assets),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The compiler artifact ABI. It lives in this runtime module, not in the
|
|
222
|
+
* build-only `./plugin`, because the production server validates manifests
|
|
223
|
+
* with it and deployments trace it from there.
|
|
224
|
+
*/
|
|
225
|
+
export const compilerArtifacts: NativeCompilerArtifacts = {
|
|
226
|
+
clientManifestFile: octaneModuleManifestFileName,
|
|
227
|
+
async validateClientManifest(value, identity, context) {
|
|
228
|
+
const hydrationBuildId =
|
|
229
|
+
context.compilationHash ?? context.hydrationBuildId;
|
|
230
|
+
if (
|
|
231
|
+
context.development &&
|
|
232
|
+
(typeof hydrationBuildId !== 'string' || !hydrationBuildId.trim())
|
|
233
|
+
)
|
|
234
|
+
throw new Error(
|
|
235
|
+
'Octane development snapshot has no native hydration build.',
|
|
236
|
+
);
|
|
237
|
+
const nativeManifest = validateOctaneModuleManifest(
|
|
238
|
+
value,
|
|
239
|
+
identity,
|
|
240
|
+
hydrationBuildId,
|
|
241
|
+
);
|
|
242
|
+
return {
|
|
243
|
+
nativeManifest,
|
|
244
|
+
hydrationBuildId: nativeManifest.nativeHydrationBuildId,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
isMutableDevelopmentAsset(filename, entryNames) {
|
|
248
|
+
return (
|
|
249
|
+
filename === 'octane-client-build.json' ||
|
|
250
|
+
entryNames.some(entry => filename === octaneModuleManifestFileName(entry))
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const { createHash } = require('node:crypto');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { getOctaneRspackBuildInfo } = require('@octanejs/rspack-plugin');
|
|
4
|
+
|
|
5
|
+
const name = 'ultramodern:octane:compiler-manifest';
|
|
6
|
+
const digest = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
7
|
+
|
|
8
|
+
/** The released native compiler remains responsible for every transformation. */
|
|
9
|
+
class OctaneCompilerManifestPlugin {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
apply(compiler) {
|
|
15
|
+
compiler.hooks.normalModuleFactory.tap(name, factory => {
|
|
16
|
+
factory.hooks.beforeResolve.tap(name, result => {
|
|
17
|
+
if (/\.svg\?(?:[^#]*&)?react(?:[=&]|$)/u.test(result.request)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'unsupported-renderer-capability: `?react` SVG imports are React components; import Octane SVG components with `?component`.',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
factory.hooks.afterResolve.tap(name, result => {
|
|
24
|
+
const resource = result.createData?.resource?.split('?')[0];
|
|
25
|
+
if (resource && path.extname(resource).toLowerCase() === '.jsx') {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`unsupported-renderer-capability: Octane does not support .jsx source: ${resource}. Supported source extensions: .tsx, .tsrx, .ts, .js.`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
if (!this.options.emitClientManifest) return;
|
|
33
|
+
compiler.hooks.thisCompilation.tap(name, compilation => {
|
|
34
|
+
compilation.hooks.processAssets.tap(
|
|
35
|
+
{
|
|
36
|
+
name,
|
|
37
|
+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT + 1,
|
|
38
|
+
},
|
|
39
|
+
() => {
|
|
40
|
+
// Preserve the owning resolver/compiler diagnostic on an invalid graph.
|
|
41
|
+
if (compilation.errors.length) return;
|
|
42
|
+
const buildAsset = compilation.getAsset('octane-client-build.json');
|
|
43
|
+
if (!buildAsset)
|
|
44
|
+
throw new Error(
|
|
45
|
+
'The native Octane client build manifest is missing.',
|
|
46
|
+
);
|
|
47
|
+
const nativeBuild = JSON.parse(buildAsset.source.source().toString());
|
|
48
|
+
if (
|
|
49
|
+
nativeBuild.version !== 1 ||
|
|
50
|
+
nativeBuild.buildId !== compilation.hash
|
|
51
|
+
) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'The native Octane client manifest does not match the completed compilation.',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const sourcesByModule = new Map();
|
|
57
|
+
const moduleAssets = new Map();
|
|
58
|
+
const visit = (module, inheritedAssets = []) => {
|
|
59
|
+
const chunks = [
|
|
60
|
+
...compilation.chunkGraph.getModuleChunksIterable(module),
|
|
61
|
+
];
|
|
62
|
+
const previousAssets = moduleAssets.get(module);
|
|
63
|
+
const assets = [
|
|
64
|
+
...new Set([
|
|
65
|
+
...(previousAssets ?? []),
|
|
66
|
+
...inheritedAssets,
|
|
67
|
+
...chunks.flatMap(chunk => [...chunk.files]),
|
|
68
|
+
]),
|
|
69
|
+
]
|
|
70
|
+
.filter(file => file.endsWith('.js'))
|
|
71
|
+
.sort();
|
|
72
|
+
// Rspack also lists concatenated children as standalone modules.
|
|
73
|
+
// A later parent visit can supply their first emitted chunk.
|
|
74
|
+
if (
|
|
75
|
+
previousAssets &&
|
|
76
|
+
assets.every(file => previousAssets.has(file))
|
|
77
|
+
)
|
|
78
|
+
return;
|
|
79
|
+
moduleAssets.set(module, new Set(assets));
|
|
80
|
+
const info = getOctaneRspackBuildInfo(module);
|
|
81
|
+
if (info && assets.length) {
|
|
82
|
+
const resource = module.resource?.split('?')[0];
|
|
83
|
+
const emitted = module.originalSource();
|
|
84
|
+
if (!resource || !emitted) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Octane compiled module has no source: ${module.identifier()}.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const relative = path.relative(this.options.root, resource);
|
|
90
|
+
sourcesByModule.set(module, {
|
|
91
|
+
resource:
|
|
92
|
+
relative.split(path.sep).join('/') +
|
|
93
|
+
(info.resourceQuery ?? ''),
|
|
94
|
+
canonicalId: info.canonicalId,
|
|
95
|
+
moduleId:
|
|
96
|
+
compilation.chunkGraph.getModuleId(module) ??
|
|
97
|
+
module.identifier(),
|
|
98
|
+
transformKind: info.transformKind,
|
|
99
|
+
emittedSourceSha256: digest(emitted.buffer()),
|
|
100
|
+
assets,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
for (const nested of module.modules ?? []) visit(nested, assets);
|
|
104
|
+
};
|
|
105
|
+
for (const module of compilation.modules) visit(module);
|
|
106
|
+
const sources = [...sourcesByModule.values()];
|
|
107
|
+
sources.sort((left, right) =>
|
|
108
|
+
left.resource.localeCompare(right.resource),
|
|
109
|
+
);
|
|
110
|
+
// Module Federation publishes exposed native modules through a
|
|
111
|
+
// separate container entry in this same compilation. Its browser
|
|
112
|
+
// closure belongs to this application's publication, even when the
|
|
113
|
+
// document itself never imports an exposed component.
|
|
114
|
+
const containerChunks = new Set();
|
|
115
|
+
for (const [entryName, entry] of compilation.entries) {
|
|
116
|
+
if (
|
|
117
|
+
!entry.dependencies.some(
|
|
118
|
+
dependency => dependency.type === 'container entry',
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
continue;
|
|
122
|
+
const container = compilation.entrypoints.get(entryName);
|
|
123
|
+
if (!container)
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Octane compiler has no federation container entry for ${entryName}.`,
|
|
126
|
+
);
|
|
127
|
+
for (const chunk of container.chunks) {
|
|
128
|
+
containerChunks.add(chunk);
|
|
129
|
+
for (const referenced of chunk.getAllReferencedChunks())
|
|
130
|
+
containerChunks.add(referenced);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const identities = this.options.rendererIdentities();
|
|
134
|
+
if (!identities || !Object.keys(identities).length) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
'Octane compiler requires immutable application entry identities.',
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
for (const [entryName, rendererIdentity] of Object.entries(
|
|
140
|
+
identities,
|
|
141
|
+
)) {
|
|
142
|
+
if (
|
|
143
|
+
rendererIdentity?.renderer !== 'octane' ||
|
|
144
|
+
rendererIdentity.entryName !== entryName
|
|
145
|
+
) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Octane compiler identity conflicts with entry ${entryName}.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const entrypoint = compilation.entrypoints.get(entryName);
|
|
151
|
+
if (!entrypoint)
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Octane compiler has no client entry for ${entryName}.`,
|
|
154
|
+
);
|
|
155
|
+
const entryChunks = new Set([
|
|
156
|
+
...entrypoint.chunks,
|
|
157
|
+
...containerChunks,
|
|
158
|
+
]);
|
|
159
|
+
for (const chunk of entrypoint.chunks) {
|
|
160
|
+
for (const referenced of chunk.getAllReferencedChunks())
|
|
161
|
+
entryChunks.add(referenced);
|
|
162
|
+
}
|
|
163
|
+
const files = [
|
|
164
|
+
...new Set(
|
|
165
|
+
[...entryChunks]
|
|
166
|
+
.flatMap(chunk => [...chunk.files])
|
|
167
|
+
.filter(file => file.endsWith('.js')),
|
|
168
|
+
),
|
|
169
|
+
].sort();
|
|
170
|
+
const fileSet = new Set(files);
|
|
171
|
+
const manifest = {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
renderer: 'octane',
|
|
174
|
+
runtimeVersion: this.options.runtimeVersion,
|
|
175
|
+
compilerVersion: this.options.compilerVersion,
|
|
176
|
+
rendererIdentity,
|
|
177
|
+
nativeHydrationBuildId: nativeBuild.buildId,
|
|
178
|
+
sourceModules: sources.flatMap(source => {
|
|
179
|
+
const assets = source.assets.filter(file => fileSet.has(file));
|
|
180
|
+
return assets.length ? [{ ...source, assets }] : [];
|
|
181
|
+
}),
|
|
182
|
+
assets: files.map(file => ({
|
|
183
|
+
file,
|
|
184
|
+
sha256: digest(compilation.getAsset(file).source.buffer()),
|
|
185
|
+
})),
|
|
186
|
+
};
|
|
187
|
+
this.options.validateManifest(
|
|
188
|
+
manifest,
|
|
189
|
+
rendererIdentity,
|
|
190
|
+
compilation.hash,
|
|
191
|
+
);
|
|
192
|
+
compilation.emitAsset(
|
|
193
|
+
this.options.manifestFilename(entryName),
|
|
194
|
+
new compiler.webpack.sources.RawSource(
|
|
195
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
module.exports = { OctaneCompilerManifestPlugin };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import type { NativeRendererCompilerOptions } from '@modern-js/renderer-core/adapter';
|
|
6
|
+
import type { RsbuildPlugin } from '@rsbuild/core';
|
|
7
|
+
import {
|
|
8
|
+
OCTANE_COMPILER_VERSION,
|
|
9
|
+
OCTANE_RUNTIME_VERSION,
|
|
10
|
+
octaneModuleManifestFileName,
|
|
11
|
+
validateOctaneModuleManifest,
|
|
12
|
+
} from '../manifest';
|
|
13
|
+
|
|
14
|
+
export function pluginSourceDirectory(): string {
|
|
15
|
+
let directory = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
while (true) {
|
|
17
|
+
const compiler = path.join(directory, 'src/plugin');
|
|
18
|
+
if (
|
|
19
|
+
fs.existsSync(path.join(directory, 'package.json')) &&
|
|
20
|
+
fs.existsSync(path.join(compiler, 'compiler-manifest.cjs'))
|
|
21
|
+
)
|
|
22
|
+
return compiler;
|
|
23
|
+
const parent = path.dirname(directory);
|
|
24
|
+
if (parent === directory)
|
|
25
|
+
throw new Error('Cannot locate the UltraModern Octane compiler package.');
|
|
26
|
+
directory = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Octane owns native compilation; UltraModern retains the application host. */
|
|
31
|
+
export function createOctaneCompilerPlugin(
|
|
32
|
+
options: NativeRendererCompilerOptions,
|
|
33
|
+
): RsbuildPlugin {
|
|
34
|
+
return {
|
|
35
|
+
name: 'ultramodern:octane:compiler',
|
|
36
|
+
setup(api) {
|
|
37
|
+
const directory = pluginSourceDirectory();
|
|
38
|
+
const require = createRequire(path.join(directory, 'index.cjs'));
|
|
39
|
+
const {
|
|
40
|
+
OctaneCompilerManifestPlugin,
|
|
41
|
+
} = require('./compiler-manifest.cjs');
|
|
42
|
+
// The application installs the Octane compiler; resolve it lazily so
|
|
43
|
+
// descriptor readers never load it.
|
|
44
|
+
const appRequire = createRequire(
|
|
45
|
+
path.join(api.context.rootPath, 'package.json'),
|
|
46
|
+
);
|
|
47
|
+
const {
|
|
48
|
+
inferRspackEnvironment,
|
|
49
|
+
OctaneRspackPlugin,
|
|
50
|
+
}: typeof import('@octanejs/rspack-plugin') = appRequire(
|
|
51
|
+
'@octanejs/rspack-plugin',
|
|
52
|
+
);
|
|
53
|
+
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) =>
|
|
54
|
+
mergeRsbuildConfig(config, {
|
|
55
|
+
source: {
|
|
56
|
+
// Released native bindings export raw TypeScript and TSRX.
|
|
57
|
+
include: [/[/\\]@octanejs[/\\]/u],
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
api.modifyRspackConfig((config, { environment: rsbuildEnvironment }) => {
|
|
62
|
+
// The Cloudflare SSR worker targets `webworker`; it still renders
|
|
63
|
+
// the server document rather than a client hydration bundle.
|
|
64
|
+
const environment =
|
|
65
|
+
rsbuildEnvironment.name === options.workerEnvironmentName
|
|
66
|
+
? 'server'
|
|
67
|
+
: inferRspackEnvironment(config.target);
|
|
68
|
+
config.plugins ??= [];
|
|
69
|
+
config.plugins.push(
|
|
70
|
+
new OctaneRspackPlugin({
|
|
71
|
+
root: api.context.rootPath,
|
|
72
|
+
environment,
|
|
73
|
+
transpile: false,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
config.plugins.push(
|
|
77
|
+
new OctaneCompilerManifestPlugin({
|
|
78
|
+
emitClientManifest: environment === 'client',
|
|
79
|
+
root: api.context.rootPath,
|
|
80
|
+
runtimeVersion: OCTANE_RUNTIME_VERSION,
|
|
81
|
+
compilerVersion: OCTANE_COMPILER_VERSION,
|
|
82
|
+
rendererIdentities: options.rendererIdentities,
|
|
83
|
+
manifestFilename: octaneModuleManifestFileName,
|
|
84
|
+
validateManifest: validateOctaneModuleManifest,
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
config.module ??= {};
|
|
88
|
+
config.module.rules ??= [];
|
|
89
|
+
// Rsbuild's existing SWC rule handles ordinary .ts/.tsx sources.
|
|
90
|
+
// TSRX needs the same stripping stage after Octane's pre-loader.
|
|
91
|
+
config.module.rules.push({
|
|
92
|
+
test: /\.tsrx$/u,
|
|
93
|
+
type: 'javascript/auto',
|
|
94
|
+
use: [
|
|
95
|
+
{
|
|
96
|
+
loader: 'builtin:swc-loader',
|
|
97
|
+
options: { detectSyntax: 'auto' },
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|