@bleedingdev/modern-js-server-runtime-extensions 0.0.0-trusted-publisher-bootstrap
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/README.md +67 -0
- package/dist/cjs/contractGateAutopilot.js +162 -0
- package/dist/cjs/contractGateSnapshotStore.js +253 -0
- package/dist/cjs/env.js +58 -0
- package/dist/cjs/index.js +162 -0
- package/dist/cjs/mfCache.js +106 -0
- package/dist/cjs/moduleFederationCss.js +285 -0
- package/dist/cjs/runtimeFallbackSignal.js +311 -0
- package/dist/cjs/telemetry.js +373 -0
- package/dist/cjs/telemetryCore.js +819 -0
- package/dist/esm/contractGateAutopilot.mjs +124 -0
- package/dist/esm/contractGateSnapshotStore.mjs +190 -0
- package/dist/esm/env.mjs +17 -0
- package/dist/esm/index.mjs +6 -0
- package/dist/esm/mfCache.mjs +55 -0
- package/dist/esm/moduleFederationCss.mjs +225 -0
- package/dist/esm/runtimeFallbackSignal.mjs +222 -0
- package/dist/esm/telemetry.mjs +275 -0
- package/dist/esm/telemetryCore.mjs +759 -0
- package/dist/esm-node/contractGateAutopilot.mjs +125 -0
- package/dist/esm-node/contractGateSnapshotStore.mjs +192 -0
- package/dist/esm-node/env.mjs +18 -0
- package/dist/esm-node/index.mjs +7 -0
- package/dist/esm-node/mfCache.mjs +56 -0
- package/dist/esm-node/moduleFederationCss.mjs +226 -0
- package/dist/esm-node/runtimeFallbackSignal.mjs +223 -0
- package/dist/esm-node/telemetry.mjs +276 -0
- package/dist/esm-node/telemetryCore.mjs +760 -0
- package/dist/types/contractGateAutopilot.d.ts +35 -0
- package/dist/types/contractGateSnapshotStore.d.ts +57 -0
- package/dist/types/env.d.ts +40 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mfCache.d.ts +27 -0
- package/dist/types/moduleFederationCss.d.ts +87 -0
- package/dist/types/runtimeFallbackSignal.d.ts +94 -0
- package/dist/types/telemetry.d.ts +12 -0
- package/dist/types/telemetryCore.d.ts +257 -0
- package/package.json +69 -0
- package/rslib.config.mts +4 -0
- package/rstest.config.mts +7 -0
- package/src/contractGateAutopilot.ts +247 -0
- package/src/contractGateSnapshotStore.ts +420 -0
- package/src/env.ts +63 -0
- package/src/index.ts +84 -0
- package/src/mfCache.ts +119 -0
- package/src/moduleFederationCss.ts +473 -0
- package/src/runtimeFallbackSignal.ts +584 -0
- package/src/telemetry.ts +554 -0
- package/src/telemetryCore.ts +1332 -0
- package/tests/contractGateAutopilot.test.ts +203 -0
- package/tests/contractGateSnapshotStore.test.ts +223 -0
- package/tests/env.test.ts +73 -0
- package/tests/helpers.ts +19 -0
- package/tests/mfCache.test.ts +150 -0
- package/tests/moduleFederationCss.test.ts +392 -0
- package/tests/registration.test.ts +112 -0
- package/tests/telemetry.test.ts +360 -0
- package/tests/telemetryAutopilot.test.ts +993 -0
- package/tests/telemetryCanaryOrchestrator.test.ts +140 -0
- package/tests/telemetryLifecycle.test.ts +168 -0
- package/tests/telemetryTraceparent.test.ts +167 -0
- package/tests/tsconfig.json +11 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
collectDirectRemoteModuleFederationCss,
|
|
6
|
+
collectDirectRemoteModuleFederationCssWithMeta,
|
|
7
|
+
collectModuleFederationManifestCss,
|
|
8
|
+
createModuleFederationCssCollector,
|
|
9
|
+
} from '../src/moduleFederationCss';
|
|
10
|
+
|
|
11
|
+
const tempDirs: string[] = [];
|
|
12
|
+
|
|
13
|
+
const createTempDir = async () => {
|
|
14
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'modern-mf-css-'));
|
|
15
|
+
tempDirs.push(dir);
|
|
16
|
+
return dir;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await Promise.all(
|
|
21
|
+
tempDirs.splice(0).map(dir =>
|
|
22
|
+
rm(dir, {
|
|
23
|
+
recursive: true,
|
|
24
|
+
force: true,
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('module federation css collection', () => {
|
|
31
|
+
it('resolves manifest css against publicPath and dedupes assets', () => {
|
|
32
|
+
const css = collectModuleFederationManifestCss(
|
|
33
|
+
{
|
|
34
|
+
metaData: {
|
|
35
|
+
publicPath: 'https://cdn.example.com/remote',
|
|
36
|
+
},
|
|
37
|
+
shared: [
|
|
38
|
+
{
|
|
39
|
+
assets: {
|
|
40
|
+
css: {
|
|
41
|
+
sync: ['static/css/shared.css'],
|
|
42
|
+
async: ['static/css/shared.css', '/root.css'],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
exposes: [
|
|
48
|
+
{
|
|
49
|
+
assets: {
|
|
50
|
+
css: {
|
|
51
|
+
sync: ['static/css/expose.css'],
|
|
52
|
+
async: ['https://assets.example.com/async.css'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
remotes: [
|
|
58
|
+
{
|
|
59
|
+
assets: {
|
|
60
|
+
css: {
|
|
61
|
+
sync: ['static/css/nested-remote.css'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
'https://origin.example.com/mf-manifest.json',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(css).toEqual([
|
|
71
|
+
'https://cdn.example.com/remote/static/css/shared.css',
|
|
72
|
+
'https://cdn.example.com/root.css',
|
|
73
|
+
'https://cdn.example.com/remote/static/css/expose.css',
|
|
74
|
+
'https://assets.example.com/async.css',
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('resolves publicPath variants without localhost or doubled static prefixes', () => {
|
|
79
|
+
const cases = [
|
|
80
|
+
{
|
|
81
|
+
publicPath: '/',
|
|
82
|
+
expected: 'https://remote.example.com/static/css/main.css',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
publicPath: '/static-base',
|
|
86
|
+
expected: 'https://remote.example.com/static-base/static/css/main.css',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
publicPath: '/static-base/',
|
|
90
|
+
expected: 'https://remote.example.com/static-base/static/css/main.css',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
publicPath: 'https://cdn.example.com/assets',
|
|
94
|
+
expected: 'https://cdn.example.com/assets/static/css/main.css',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
publicPath: 'https://cdn.example.com/assets/',
|
|
98
|
+
expected: 'https://cdn.example.com/assets/static/css/main.css',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const { publicPath, expected } of cases) {
|
|
103
|
+
const css = collectModuleFederationManifestCss(
|
|
104
|
+
{
|
|
105
|
+
metaData: {
|
|
106
|
+
publicPath,
|
|
107
|
+
},
|
|
108
|
+
exposes: [
|
|
109
|
+
{
|
|
110
|
+
assets: {
|
|
111
|
+
css: {
|
|
112
|
+
sync: ['static/css/main.css'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
'https://remote.example.com/nested/mf-manifest.json',
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(css).toEqual([expected]);
|
|
122
|
+
expect(css[0]).not.toContain('localhost');
|
|
123
|
+
expect(css[0]).not.toContain('//static');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('falls back to the remote manifest URL when publicPath is absent', () => {
|
|
128
|
+
const css = collectModuleFederationManifestCss(
|
|
129
|
+
{
|
|
130
|
+
exposes: [
|
|
131
|
+
{
|
|
132
|
+
assets: {
|
|
133
|
+
css: {
|
|
134
|
+
sync: ['static/css/expose.css', '/root.css'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
'http://localhost:3010/nested/mf-manifest.json',
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(css).toEqual([
|
|
144
|
+
'http://localhost:3010/nested/static/css/expose.css',
|
|
145
|
+
'http://localhost:3010/root.css',
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('loads direct remote manifests from the host manifest without throwing on failures', async () => {
|
|
150
|
+
const pwd = await createTempDir();
|
|
151
|
+
await writeFile(
|
|
152
|
+
path.join(pwd, 'mf-manifest.json'),
|
|
153
|
+
JSON.stringify({
|
|
154
|
+
remotes: [
|
|
155
|
+
{
|
|
156
|
+
entry: 'remoteA@https://remote-a.example.com/mf-manifest.json',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
entry: 'https://remote-b.example.com/mf-manifest.json',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const warn = rs.fn();
|
|
166
|
+
const fetcher = rs.fn(async (url: string) => {
|
|
167
|
+
if (url.includes('remote-b')) {
|
|
168
|
+
return new Response('missing', { status: 404 });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return Response.json({
|
|
172
|
+
metaData: {
|
|
173
|
+
publicPath: 'https://cdn.example.com/a/',
|
|
174
|
+
},
|
|
175
|
+
shared: [
|
|
176
|
+
{
|
|
177
|
+
assets: {
|
|
178
|
+
css: {
|
|
179
|
+
sync: ['static/css/shared.css'],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
exposes: [
|
|
185
|
+
{
|
|
186
|
+
assets: {
|
|
187
|
+
css: {
|
|
188
|
+
sync: ['static/css/expose.css'],
|
|
189
|
+
async: ['static/css/shared.css'],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const css = await collectDirectRemoteModuleFederationCss(pwd, {
|
|
198
|
+
fetcher,
|
|
199
|
+
monitors: {
|
|
200
|
+
warn,
|
|
201
|
+
} as any,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(css).toEqual([
|
|
205
|
+
'https://cdn.example.com/a/static/css/shared.css',
|
|
206
|
+
'https://cdn.example.com/a/static/css/expose.css',
|
|
207
|
+
]);
|
|
208
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
209
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('flags partial collections as errored when a remote manifest fails', async () => {
|
|
213
|
+
const pwd = await createTempDir();
|
|
214
|
+
await writeFile(
|
|
215
|
+
path.join(pwd, 'mf-manifest.json'),
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
remotes: [
|
|
218
|
+
{
|
|
219
|
+
entry: 'https://remote-a.example.com/mf-manifest.json',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
entry: 'https://remote-b.example.com/mf-manifest.json',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const fetcher = rs.fn(async (url: string) => {
|
|
229
|
+
if (url.includes('remote-b')) {
|
|
230
|
+
return new Response('boom', { status: 500 });
|
|
231
|
+
}
|
|
232
|
+
return Response.json({
|
|
233
|
+
exposes: [
|
|
234
|
+
{
|
|
235
|
+
assets: {
|
|
236
|
+
css: {
|
|
237
|
+
sync: ['static/css/a.css'],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const result = await collectDirectRemoteModuleFederationCssWithMeta(pwd, {
|
|
246
|
+
fetcher,
|
|
247
|
+
monitors: { warn: rs.fn() } as any,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(result.errored).toBe(true);
|
|
251
|
+
expect(result.assets).toEqual([
|
|
252
|
+
'https://remote-a.example.com/static/css/a.css',
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('module federation css collector cache', () => {
|
|
258
|
+
const writeHostManifest = async () => {
|
|
259
|
+
const pwd = await createTempDir();
|
|
260
|
+
await writeFile(
|
|
261
|
+
path.join(pwd, 'mf-manifest.json'),
|
|
262
|
+
JSON.stringify({
|
|
263
|
+
remotes: [
|
|
264
|
+
{
|
|
265
|
+
entry: 'https://remote-a.example.com/mf-manifest.json',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
return pwd;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const remoteManifestWithCss = (cssAsset: string) =>
|
|
274
|
+
Response.json({
|
|
275
|
+
metaData: {
|
|
276
|
+
publicPath: 'https://cdn.example.com/a/',
|
|
277
|
+
},
|
|
278
|
+
exposes: [
|
|
279
|
+
{
|
|
280
|
+
assets: {
|
|
281
|
+
css: {
|
|
282
|
+
sync: [cssAsset],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('serves cached assets within the ttl and refetches after expiry', async () => {
|
|
290
|
+
const pwd = await writeHostManifest();
|
|
291
|
+
|
|
292
|
+
let nowMs = 10_000;
|
|
293
|
+
let cssAsset = 'static/css/one.css';
|
|
294
|
+
const fetcher = rs.fn(async () => remoteManifestWithCss(cssAsset));
|
|
295
|
+
|
|
296
|
+
const collector = createModuleFederationCssCollector(pwd, {
|
|
297
|
+
fetcher,
|
|
298
|
+
ttlMs: 5_000,
|
|
299
|
+
now: () => nowMs,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(await collector.collect()).toEqual([
|
|
303
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
304
|
+
]);
|
|
305
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
306
|
+
|
|
307
|
+
// The remote redeployed, but the cache is still fresh.
|
|
308
|
+
cssAsset = 'static/css/two.css';
|
|
309
|
+
nowMs += 4_999;
|
|
310
|
+
expect(await collector.collect()).toEqual([
|
|
311
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
312
|
+
]);
|
|
313
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
314
|
+
|
|
315
|
+
// Past the ttl the collection is refreshed and picks up the redeploy.
|
|
316
|
+
nowMs += 2;
|
|
317
|
+
expect(await collector.collect()).toEqual([
|
|
318
|
+
'https://cdn.example.com/a/static/css/two.css',
|
|
319
|
+
]);
|
|
320
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('does not cache errored collections and serves the last good list', async () => {
|
|
324
|
+
const pwd = await writeHostManifest();
|
|
325
|
+
|
|
326
|
+
let nowMs = 10_000;
|
|
327
|
+
let failing = false;
|
|
328
|
+
const fetcher = rs.fn(async () => {
|
|
329
|
+
if (failing) {
|
|
330
|
+
return new Response('boom', { status: 500 });
|
|
331
|
+
}
|
|
332
|
+
return remoteManifestWithCss('static/css/one.css');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const collector = createModuleFederationCssCollector(pwd, {
|
|
336
|
+
fetcher,
|
|
337
|
+
ttlMs: 5_000,
|
|
338
|
+
now: () => nowMs,
|
|
339
|
+
monitors: { warn: rs.fn() } as any,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(await collector.collect()).toEqual([
|
|
343
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
344
|
+
]);
|
|
345
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
346
|
+
|
|
347
|
+
// Cache expired and the remote is now failing: serve last-good...
|
|
348
|
+
nowMs += 5_001;
|
|
349
|
+
failing = true;
|
|
350
|
+
expect(await collector.collect()).toEqual([
|
|
351
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
352
|
+
]);
|
|
353
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
354
|
+
|
|
355
|
+
// ...and do NOT cache the failure: the next request retries immediately.
|
|
356
|
+
expect(await collector.collect()).toEqual([
|
|
357
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
358
|
+
]);
|
|
359
|
+
expect(fetcher).toHaveBeenCalledTimes(3);
|
|
360
|
+
|
|
361
|
+
failing = false;
|
|
362
|
+
expect(await collector.collect()).toEqual([
|
|
363
|
+
'https://cdn.example.com/a/static/css/one.css',
|
|
364
|
+
]);
|
|
365
|
+
expect(fetcher).toHaveBeenCalledTimes(4);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('coalesces concurrent collections into a single in-flight fetch', async () => {
|
|
369
|
+
const pwd = await writeHostManifest();
|
|
370
|
+
|
|
371
|
+
const fetcher = rs.fn(async () =>
|
|
372
|
+
remoteManifestWithCss('static/css/one.css'),
|
|
373
|
+
);
|
|
374
|
+
const collector = createModuleFederationCssCollector(pwd, {
|
|
375
|
+
fetcher,
|
|
376
|
+
ttlMs: 0,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const [first, second] = await Promise.all([
|
|
380
|
+
collector.collect(),
|
|
381
|
+
collector.collect(),
|
|
382
|
+
]);
|
|
383
|
+
|
|
384
|
+
expect(first).toEqual(['https://cdn.example.com/a/static/css/one.css']);
|
|
385
|
+
expect(second).toEqual(first);
|
|
386
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
387
|
+
|
|
388
|
+
// ttlMs 0 keeps the previous per-request freshness for dev.
|
|
389
|
+
await collector.collect();
|
|
390
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultPlugins,
|
|
3
|
+
createServerBase,
|
|
4
|
+
type ServerPlugin,
|
|
5
|
+
} from '@modern-js/server-core';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { injectModuleFederationCssPlugin } from '../src/moduleFederationCss';
|
|
10
|
+
import { getDefaultAppContext, getDefaultConfig } from './helpers';
|
|
11
|
+
|
|
12
|
+
describe('plugin registration', () => {
|
|
13
|
+
test('bare server-core default chain does not contain fork plugins', () => {
|
|
14
|
+
const names = createDefaultPlugins().map(plugin => plugin.name);
|
|
15
|
+
|
|
16
|
+
expect(names).not.toContain('@modern-js/inject-telemetry');
|
|
17
|
+
expect(names).not.toContain('@modern-js/inject-module-federation-css');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('bare server-core ignores telemetry config without explicit registration', async () => {
|
|
21
|
+
const config = getDefaultConfig();
|
|
22
|
+
config.server = {
|
|
23
|
+
telemetry: {
|
|
24
|
+
enabled: true,
|
|
25
|
+
canary: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
} as any;
|
|
30
|
+
|
|
31
|
+
const server = createServerBase({
|
|
32
|
+
config,
|
|
33
|
+
pwd: process.cwd(),
|
|
34
|
+
appContext: getDefaultAppContext(),
|
|
35
|
+
});
|
|
36
|
+
server.addPlugins([...createDefaultPlugins({ logger: false })]);
|
|
37
|
+
await server.init();
|
|
38
|
+
|
|
39
|
+
const response = await server.request('/_modern/runtime/status', {}, {});
|
|
40
|
+
expect(response.status).toBe(404);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('injectModuleFederationCssPlugin enriches the request server manifest', async () => {
|
|
44
|
+
const tempDir = fs.mkdtempSync(
|
|
45
|
+
path.join(os.tmpdir(), 'modern-mf-css-reg-'),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
fs.writeFileSync(
|
|
50
|
+
path.join(tempDir, 'mf-manifest.json'),
|
|
51
|
+
JSON.stringify({ remotes: [] }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
let observedManifest: Record<string, unknown> | undefined;
|
|
55
|
+
|
|
56
|
+
const stubResourcePlugin: ServerPlugin = {
|
|
57
|
+
name: 'stub-inject-resource',
|
|
58
|
+
setup(api) {
|
|
59
|
+
api.onPrepare(() => {
|
|
60
|
+
const { middlewares } = api.getServerContext();
|
|
61
|
+
middlewares.push({
|
|
62
|
+
name: 'stub-inject-server-manifest',
|
|
63
|
+
handler: async (c: any, next: any) => {
|
|
64
|
+
c.set('serverManifest', { loaderBundles: {} } as any);
|
|
65
|
+
await next();
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const capturePlugin: ServerPlugin = {
|
|
73
|
+
name: 'capture-server-manifest',
|
|
74
|
+
setup(api) {
|
|
75
|
+
api.onPrepare(() => {
|
|
76
|
+
const { middlewares } = api.getServerContext();
|
|
77
|
+
middlewares.push({
|
|
78
|
+
name: 'capture-server-manifest',
|
|
79
|
+
handler: async (c: any) => {
|
|
80
|
+
observedManifest = c.get('serverManifest') as unknown as Record<
|
|
81
|
+
string,
|
|
82
|
+
unknown
|
|
83
|
+
>;
|
|
84
|
+
return c.json({ ok: true });
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const server = createServerBase({
|
|
92
|
+
config: getDefaultConfig(),
|
|
93
|
+
pwd: tempDir,
|
|
94
|
+
appContext: getDefaultAppContext(),
|
|
95
|
+
});
|
|
96
|
+
server.addPlugins([
|
|
97
|
+
...createDefaultPlugins({ logger: false }),
|
|
98
|
+
stubResourcePlugin,
|
|
99
|
+
injectModuleFederationCssPlugin(),
|
|
100
|
+
capturePlugin,
|
|
101
|
+
]);
|
|
102
|
+
await server.init();
|
|
103
|
+
|
|
104
|
+
const response = await server.request('/', {}, {});
|
|
105
|
+
expect(response.status).toBe(200);
|
|
106
|
+
expect(observedManifest).toBeTruthy();
|
|
107
|
+
expect(observedManifest!.moduleFederationCssAssets).toEqual([]);
|
|
108
|
+
} finally {
|
|
109
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|