@bleedingdev/modern-js-runtime-renderer-extensions 3.9.0-ultramodern.6 → 3.9.0-ultramodern.7
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/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"directory": "packages/runtime/renderer-extensions"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
|
-
"version": "3.9.0-ultramodern.
|
|
14
|
+
"version": "3.9.0-ultramodern.7",
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=26.7.0"
|
|
17
17
|
},
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.
|
|
40
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
39
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.7",
|
|
40
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.7"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.0-ultramodern.
|
|
44
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.
|
|
45
|
-
"@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.
|
|
43
|
+
"@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.0-ultramodern.7",
|
|
44
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.7",
|
|
45
|
+
"@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.7",
|
|
46
46
|
"@rslib/core": "1.0.0",
|
|
47
47
|
"@scripts/rstest-config": "2.66.0",
|
|
48
48
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -45,36 +45,5 @@ describe('createFederatedCssLinks', () => {
|
|
|
45
45
|
expect(html).toBe(
|
|
46
46
|
'<link nonce="nonce"&<value>" href="/remote/theme.css" rel="stylesheet" />',
|
|
47
47
|
);
|
|
48
|
-
expect(html.match(/\bhref=/g)).toHaveLength(1);
|
|
49
|
-
expect(html.match(/\brel=/g)).toHaveLength(1);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('SSR federated CSS helper matrix', () => {
|
|
54
|
-
it('escapes federated CSS hrefs and dedupes existing, repeated, and template-present assets', () => {
|
|
55
|
-
expect(
|
|
56
|
-
createFederatedCssLinks(
|
|
57
|
-
[
|
|
58
|
-
'/remote/base.css',
|
|
59
|
-
'/remote/base.css',
|
|
60
|
-
'/remote/theme" onload="alert(1)&x=<tag>.css',
|
|
61
|
-
'/remote/template".css',
|
|
62
|
-
'/remote/existing.css',
|
|
63
|
-
'',
|
|
64
|
-
],
|
|
65
|
-
{
|
|
66
|
-
template:
|
|
67
|
-
'<link href="/remote/template".css" rel="stylesheet" />',
|
|
68
|
-
attributes: {
|
|
69
|
-
'data-mf': 'checkout',
|
|
70
|
-
nonce: 'nonce-2',
|
|
71
|
-
skipped: undefined,
|
|
72
|
-
},
|
|
73
|
-
existingAssets: ['/remote/existing.css'],
|
|
74
|
-
},
|
|
75
|
-
),
|
|
76
|
-
).toBe(
|
|
77
|
-
'<link data-mf="checkout" nonce="nonce-2" href="/remote/base.css" rel="stylesheet" /><link data-mf="checkout" nonce="nonce-2" href="/remote/theme" onload="alert(1)&x=<tag>.css" rel="stylesheet" />',
|
|
78
|
-
);
|
|
79
48
|
});
|
|
80
49
|
});
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { getHelmetContext } from '@modern-js/runtime-extensions/helmet-context';
|
|
2
|
-
import {
|
|
3
|
-
applyRouterRuntimeState,
|
|
4
|
-
getRouterRuntimeState,
|
|
5
|
-
getRouterServerSnapshot,
|
|
6
|
-
} from '@modern-js/runtime-extensions/router-state';
|
|
7
2
|
import React, { useContext } from 'react';
|
|
8
3
|
import { renderToReadableStream, renderToString } from 'react-dom/server';
|
|
9
4
|
import {
|
|
@@ -11,7 +6,6 @@ import {
|
|
|
11
6
|
getGlobalInternalRuntimeContext,
|
|
12
7
|
getInitialContext,
|
|
13
8
|
InternalRuntimeContext,
|
|
14
|
-
RuntimeContext,
|
|
15
9
|
setGlobalContext,
|
|
16
10
|
setGlobalInternalRuntimeContext,
|
|
17
11
|
} from '../../plugin-runtime/src/core/context';
|
|
@@ -127,103 +121,6 @@ describe('registered native runtime/head compatibility', () => {
|
|
|
127
121
|
}
|
|
128
122
|
});
|
|
129
123
|
|
|
130
|
-
it('should keep router runtime state out of public context enumeration', () => {
|
|
131
|
-
let runtimeValue: Record<string, unknown> | undefined;
|
|
132
|
-
let internalValue: Record<string, unknown> | undefined;
|
|
133
|
-
|
|
134
|
-
const Probe = () => {
|
|
135
|
-
runtimeValue = useContext(RuntimeContext) as Record<string, unknown>;
|
|
136
|
-
internalValue = useContext(InternalRuntimeContext) as Record<
|
|
137
|
-
string,
|
|
138
|
-
unknown
|
|
139
|
-
>;
|
|
140
|
-
return null;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const context = getInitialContext(false);
|
|
144
|
-
applyRouterRuntimeState(context, {
|
|
145
|
-
framework: 'custom-router',
|
|
146
|
-
instance: { kind: 'internal-router' },
|
|
147
|
-
serverSnapshot: {
|
|
148
|
-
matchedRouteIds: ['route-a'],
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
renderToString(
|
|
153
|
-
wrapRuntimeContextProvider(
|
|
154
|
-
<Probe />,
|
|
155
|
-
context as Record<string, unknown> as any,
|
|
156
|
-
),
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
expect(getRouterServerSnapshot(internalValue as object)).toMatchObject({
|
|
160
|
-
matchedRouteIds: ['route-a'],
|
|
161
|
-
});
|
|
162
|
-
expect(getRouterRuntimeState(internalValue as object)?.framework).toBe(
|
|
163
|
-
'custom-router',
|
|
164
|
-
);
|
|
165
|
-
expect(getRouterRuntimeState(internalValue as object)?.instance).toEqual({
|
|
166
|
-
kind: 'internal-router',
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// None of the router state may leak into string-key enumeration of
|
|
170
|
-
// either context value — each forbidden key is asserted individually so
|
|
171
|
-
// a partial leak fails too.
|
|
172
|
-
const forbiddenKeys = [
|
|
173
|
-
'routerFramework',
|
|
174
|
-
'routerInstance',
|
|
175
|
-
'routerRuntime',
|
|
176
|
-
'routerServerSnapshot',
|
|
177
|
-
'routerHydrationScript',
|
|
178
|
-
'routerMatchedRouteIds',
|
|
179
|
-
'_helmetContext',
|
|
180
|
-
];
|
|
181
|
-
for (const value of [runtimeValue, internalValue]) {
|
|
182
|
-
const keys = Object.keys(value as object);
|
|
183
|
-
for (const forbidden of forbiddenKeys) {
|
|
184
|
-
expect(keys).not.toContain(forbidden);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
expect(runtimeValue?.routerFramework).toBeUndefined();
|
|
188
|
-
expect(runtimeValue?.routerInstance).toBeUndefined();
|
|
189
|
-
expect(runtimeValue?.routerServerSnapshot).toBeUndefined();
|
|
190
|
-
|
|
191
|
-
// The symbol-keyed extension slot must not be reachable from the PUBLIC
|
|
192
|
-
// context object either: spreads copy enumerable symbol properties, so
|
|
193
|
-
// the wrapper has to strip the slot from the public copy.
|
|
194
|
-
const extensionsSlot = Symbol.for('@modern-js/runtime:context-extensions');
|
|
195
|
-
expect(Object.getOwnPropertySymbols(runtimeValue as object)).not.toContain(
|
|
196
|
-
extensionsSlot,
|
|
197
|
-
);
|
|
198
|
-
expect(getRouterRuntimeState(runtimeValue as object)).toBeUndefined();
|
|
199
|
-
expect(getRouterServerSnapshot(runtimeValue as object)).toBeUndefined();
|
|
200
|
-
|
|
201
|
-
// ...while the internal context keeps carrying it.
|
|
202
|
-
expect(Object.getOwnPropertySymbols(internalValue as object)).toContain(
|
|
203
|
-
extensionsSlot,
|
|
204
|
-
);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should collect head tags in an isolated request context', () => {
|
|
208
|
-
const context = getInitialContext(false);
|
|
209
|
-
|
|
210
|
-
renderToString(
|
|
211
|
-
wrapRuntimeContextProvider(
|
|
212
|
-
<Helmet htmlAttributes={{ lang: 'cs' }}>
|
|
213
|
-
<title>Modern SSR</title>
|
|
214
|
-
</Helmet>,
|
|
215
|
-
context as Record<string, unknown> as any,
|
|
216
|
-
),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
expect(getHelmetContext(context)?.helmet?.htmlAttributes.toString()).toBe(
|
|
220
|
-
'lang="cs"',
|
|
221
|
-
);
|
|
222
|
-
expect(getHelmetContext(context)?.helmet?.title.toString()).toBe(
|
|
223
|
-
'<title data-rh="true">Modern SSR</title>',
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
124
|
it('keeps response functions outside the RSC internal context', () => {
|
|
228
125
|
let internalValue: ReturnType<typeof getInitialContext> | undefined;
|
|
229
126
|
|
|
@@ -257,78 +154,10 @@ describe('registered native runtime/head compatibility', () => {
|
|
|
257
154
|
setGlobalContext({ enableRsc: false });
|
|
258
155
|
}
|
|
259
156
|
|
|
260
|
-
expect(internalValue?.ssrContext).toBeUndefined();
|
|
261
|
-
expect(internalValue?.requestContext.request).toEqual({
|
|
262
|
-
params: { category: 'compact' },
|
|
263
|
-
pathname: '/tractors',
|
|
264
|
-
query: { sort: 'price' },
|
|
265
|
-
headers: { accept: 'text/html' },
|
|
266
|
-
host: 'example.test',
|
|
267
|
-
url: 'https://example.test/tractors?sort=price',
|
|
268
|
-
userAgent: undefined,
|
|
269
|
-
cookie: undefined,
|
|
270
|
-
referer: undefined,
|
|
271
|
-
});
|
|
272
|
-
expect(internalValue?.requestContext.response).toEqual({
|
|
273
|
-
locals: response.locals,
|
|
274
|
-
});
|
|
275
157
|
expect(
|
|
276
158
|
Object.values(internalValue?.requestContext.response ?? {}).some(
|
|
277
159
|
value => typeof value === 'function',
|
|
278
160
|
),
|
|
279
161
|
).toBe(false);
|
|
280
162
|
});
|
|
281
|
-
|
|
282
|
-
it('renders an RSC context without an SSR context', () => {
|
|
283
|
-
let internalValue: ReturnType<typeof getInitialContext> | undefined;
|
|
284
|
-
|
|
285
|
-
const Probe = () => {
|
|
286
|
-
internalValue = useContext(InternalRuntimeContext);
|
|
287
|
-
return <main>RSC request context</main>;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
setGlobalContext({ enableRsc: true });
|
|
291
|
-
try {
|
|
292
|
-
const html = renderToString(
|
|
293
|
-
wrapRuntimeContextProvider(<Probe />, getInitialContext(false)),
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
expect(html).toContain('RSC request context');
|
|
297
|
-
expect(internalValue?.requestContext).toEqual({
|
|
298
|
-
request: {
|
|
299
|
-
params: {},
|
|
300
|
-
pathname: '',
|
|
301
|
-
query: {},
|
|
302
|
-
headers: {},
|
|
303
|
-
host: '',
|
|
304
|
-
url: '',
|
|
305
|
-
},
|
|
306
|
-
response: { locals: {} },
|
|
307
|
-
});
|
|
308
|
-
expect(internalValue?.ssrContext).toBeUndefined();
|
|
309
|
-
} finally {
|
|
310
|
-
setGlobalContext({ enableRsc: false });
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('serializes React head prop names to valid HTML attributes', () => {
|
|
315
|
-
const context = getInitialContext(false);
|
|
316
|
-
|
|
317
|
-
renderToString(
|
|
318
|
-
wrapRuntimeContextProvider(
|
|
319
|
-
<Helmet>
|
|
320
|
-
<link href="/cs" hrefLang="cs" rel="alternate" />
|
|
321
|
-
<meta charSet="utf-8" />
|
|
322
|
-
</Helmet>,
|
|
323
|
-
context as Record<string, unknown> as any,
|
|
324
|
-
),
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
expect(getHelmetContext(context)?.helmet?.link.toString()).toContain(
|
|
328
|
-
'<link data-rh="true" href="/cs" hreflang="cs" rel="alternate">',
|
|
329
|
-
);
|
|
330
|
-
expect(getHelmetContext(context)?.helmet?.meta.toString()).toContain(
|
|
331
|
-
'<meta data-rh="true" charset="utf-8">',
|
|
332
|
-
);
|
|
333
|
-
});
|
|
334
163
|
});
|
|
@@ -130,71 +130,6 @@ describe('buildShellBeforeTemplate', () => {
|
|
|
130
130
|
);
|
|
131
131
|
|
|
132
132
|
expect(html).toBe(expectedHtml);
|
|
133
|
-
expect(orderedFragments.map(fragment => html.indexOf(fragment))).toEqual(
|
|
134
|
-
orderedFragments.map(fragment => expectedHtml.indexOf(fragment)),
|
|
135
|
-
);
|
|
136
|
-
expect(html.split(orderedFragments[1])).toHaveLength(2);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should use shared matched route ids from the router snapshot for css injection', async () => {
|
|
140
|
-
for (const buildTemplate of [
|
|
141
|
-
buildShellBeforeTemplate,
|
|
142
|
-
buildWorkerShellBeforeTemplate,
|
|
143
|
-
]) {
|
|
144
|
-
const html = await buildTemplate(
|
|
145
|
-
`<html><head>${CHUNK_CSS_PLACEHOLDER}</head><body></body></html>`,
|
|
146
|
-
{
|
|
147
|
-
entryName: 'main',
|
|
148
|
-
runtimeContext: withRouterSnapshot(
|
|
149
|
-
{
|
|
150
|
-
routeManifest: {
|
|
151
|
-
routeAssets: {
|
|
152
|
-
'route-a': {
|
|
153
|
-
referenceCssAssets: ['/assets/route-a.css'],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
matchedRouteIds: ['route-a'],
|
|
160
|
-
},
|
|
161
|
-
) as any,
|
|
162
|
-
config: {} as any,
|
|
163
|
-
},
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
expect(html).toContain('/assets/route-a.css');
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should derive css route ids from generic match snapshots', async () => {
|
|
171
|
-
const html = await buildShellBeforeTemplate(
|
|
172
|
-
`<html><head>${CHUNK_CSS_PLACEHOLDER}</head><body></body></html>`,
|
|
173
|
-
{
|
|
174
|
-
entryName: 'main',
|
|
175
|
-
runtimeContext: withRouterSnapshot(
|
|
176
|
-
{
|
|
177
|
-
routeManifest: {
|
|
178
|
-
routeAssets: {
|
|
179
|
-
'asset-route': {
|
|
180
|
-
referenceCssAssets: ['/assets/asset-route.css'],
|
|
181
|
-
},
|
|
182
|
-
legacy: {
|
|
183
|
-
referenceCssAssets: ['/assets/legacy.css'],
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
matches: [{ routeId: 'router-route', assetRouteId: 'asset-route' }],
|
|
190
|
-
},
|
|
191
|
-
) as any,
|
|
192
|
-
config: {} as any,
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
expect(html).toContain('/assets/asset-route.css');
|
|
197
|
-
expect(html).not.toContain('/assets/legacy.css');
|
|
198
133
|
});
|
|
199
134
|
});
|
|
200
135
|
|
|
@@ -258,9 +193,6 @@ const chunk = (url: string, filename = url) => ({
|
|
|
258
193
|
path: url,
|
|
259
194
|
});
|
|
260
195
|
|
|
261
|
-
const scriptUrls = (chunks: Array<{ url?: string }>) =>
|
|
262
|
-
chunks.map(item => item.url).filter(Boolean);
|
|
263
|
-
|
|
264
196
|
describe('createRouteHydrationScriptTags', () => {
|
|
265
197
|
it('does not treat substring matches in the template as existing scripts', () => {
|
|
266
198
|
const runtimeContext = createRuntimeContextWithMatchedRoutes(['route-a']);
|
|
@@ -327,30 +259,6 @@ describe('LoadableCollector federated css', () => {
|
|
|
327
259
|
);
|
|
328
260
|
});
|
|
329
261
|
|
|
330
|
-
it('orders matched route scripts before the async entry script', () => {
|
|
331
|
-
const ordered = orderHydrationScriptChunks({
|
|
332
|
-
entryName: 'index',
|
|
333
|
-
asyncEntryChunks: [
|
|
334
|
-
chunk('/static/js/async/vendor.js'),
|
|
335
|
-
chunk('/static/js/async/async-index-123.js'),
|
|
336
|
-
],
|
|
337
|
-
collectedChunks: [chunk('/static/js/async/loadable-child.js')],
|
|
338
|
-
matchedRouteChunks: [
|
|
339
|
-
chunk('/static/js/async/(lang)/page.js'),
|
|
340
|
-
chunk('/static/js/async/(lang)/checkout/page.js'),
|
|
341
|
-
chunk('/static/js/async/(lang)/page.js'),
|
|
342
|
-
],
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
expect(scriptUrls(ordered)).toEqual([
|
|
346
|
-
'/static/js/async/vendor.js',
|
|
347
|
-
'/static/js/async/loadable-child.js',
|
|
348
|
-
'/static/js/async/(lang)/page.js',
|
|
349
|
-
'/static/js/async/(lang)/checkout/page.js',
|
|
350
|
-
'/static/js/async/async-index-123.js',
|
|
351
|
-
]);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
262
|
it('does not fall back to runtime route manifest when options omit routeManifest', async () => {
|
|
355
263
|
const chunkSet = {
|
|
356
264
|
renderLevel: RenderLevel.CLIENT_RENDER,
|
|
@@ -412,24 +320,6 @@ test('registered asset policy orders native loadable output before the async ent
|
|
|
412
320
|
});
|
|
413
321
|
|
|
414
322
|
describe('SSR hydration helper matrix', () => {
|
|
415
|
-
const createRuntimeContext = (
|
|
416
|
-
matchedRouteIds: string[],
|
|
417
|
-
routeAssets: Record<string, { assets?: string[] }>,
|
|
418
|
-
) => {
|
|
419
|
-
const runtimeContext = {
|
|
420
|
-
routeManifest: {
|
|
421
|
-
routeAssets,
|
|
422
|
-
},
|
|
423
|
-
} as any;
|
|
424
|
-
|
|
425
|
-
applyRouterRuntimeState(runtimeContext, {
|
|
426
|
-
framework: 'custom-router',
|
|
427
|
-
matchedRouteIds,
|
|
428
|
-
} as any);
|
|
429
|
-
|
|
430
|
-
return runtimeContext;
|
|
431
|
-
};
|
|
432
|
-
|
|
433
323
|
it('dedupes hydration script chunks and generated tags by the exact script src', () => {
|
|
434
324
|
expect(
|
|
435
325
|
orderHydrationScriptChunks({
|
|
@@ -483,33 +373,5 @@ describe('SSR hydration helper matrix', () => {
|
|
|
483
373
|
url: '/assets/async-main.123.js',
|
|
484
374
|
},
|
|
485
375
|
]);
|
|
486
|
-
|
|
487
|
-
const runtimeContext = createRuntimeContext(['routes/dashboard'], {
|
|
488
|
-
'routes/dashboard': {
|
|
489
|
-
assets: [
|
|
490
|
-
'/assets/dashboard.js',
|
|
491
|
-
'/assets/shared.js',
|
|
492
|
-
'/assets/dashboard.css',
|
|
493
|
-
'/assets/dashboard.js',
|
|
494
|
-
],
|
|
495
|
-
},
|
|
496
|
-
'async-main': {
|
|
497
|
-
assets: [
|
|
498
|
-
'/assets/async-main.js',
|
|
499
|
-
'/assets/shared.js',
|
|
500
|
-
'/assets/async-main.css',
|
|
501
|
-
],
|
|
502
|
-
},
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
expect(
|
|
506
|
-
createRouteHydrationScriptTags(runtimeContext, 'main', {
|
|
507
|
-
nonce: 'nonce-1',
|
|
508
|
-
template:
|
|
509
|
-
'<script defer src="/assets/dashboard.js"></script><script src="/assets/shared.js?cache=1"></script>',
|
|
510
|
-
}),
|
|
511
|
-
).toBe(
|
|
512
|
-
'<script src="/assets/shared.js" nonce="nonce-1"></script> <script src="/assets/async-main.js" nonce="nonce-1"></script>',
|
|
513
|
-
);
|
|
514
376
|
});
|
|
515
377
|
});
|
|
@@ -38,11 +38,6 @@ const buildShellAfterTemplate = (template: string, options: any) =>
|
|
|
38
38
|
...options,
|
|
39
39
|
lifecycle: lifecycleFor(options),
|
|
40
40
|
});
|
|
41
|
-
const getTemplates = (template: string, options: any) =>
|
|
42
|
-
nativeGetTemplates(template, {
|
|
43
|
-
...options,
|
|
44
|
-
lifecycle: lifecycleFor(options),
|
|
45
|
-
});
|
|
46
41
|
const createDataCollector = (options: any) => {
|
|
47
42
|
const lifecycle = lifecycleFor(options, 'string');
|
|
48
43
|
const collector = new SSRDataCollector({ ...options, lifecycle });
|
|
@@ -66,7 +61,6 @@ import { applyRouterRuntimeState } from '@modern-js/runtime-extensions/router-st
|
|
|
66
61
|
import { RenderLevel } from '../../plugin-runtime/src/core/constants';
|
|
67
62
|
import { SSR_DATA_PLACEHOLDER } from '../../plugin-runtime/src/core/server/constants';
|
|
68
63
|
import { buildShellAfterTemplate as nativeBuildShellAfterTemplate } from '../../plugin-runtime/src/core/server/stream/afterTemplate';
|
|
69
|
-
import { getTemplates as nativeGetTemplates } from '../../plugin-runtime/src/core/server/stream/template';
|
|
70
64
|
import { SSRDataCollector } from '../../plugin-runtime/src/core/server/string/ssrData';
|
|
71
65
|
|
|
72
66
|
const withRouterSnapshot = (
|
|
@@ -86,130 +80,6 @@ const scriptSrcs = (html: string) =>
|
|
|
86
80
|
).map(match => match[1] ?? match[2] ?? match[3]);
|
|
87
81
|
|
|
88
82
|
describe('SSRDataCollector (stream parity)', () => {
|
|
89
|
-
it('should append router hydration script from the shared router snapshot', () => {
|
|
90
|
-
const chunkSet = {
|
|
91
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
92
|
-
ssrScripts: '',
|
|
93
|
-
jsChunk: '',
|
|
94
|
-
cssChunk: '',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const collector = createDataCollector({
|
|
98
|
-
runtimeContext: withRouterSnapshot(
|
|
99
|
-
{
|
|
100
|
-
initialData: {},
|
|
101
|
-
__i18nData__: {},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
hydrationScript: '<script>window.__HYDRATE__ = "router";</script>',
|
|
105
|
-
},
|
|
106
|
-
) as any,
|
|
107
|
-
request: new Request('http://localhost/'),
|
|
108
|
-
chunkSet,
|
|
109
|
-
ssrContext: {
|
|
110
|
-
request: {
|
|
111
|
-
params: {},
|
|
112
|
-
query: {},
|
|
113
|
-
pathname: '/',
|
|
114
|
-
host: 'localhost',
|
|
115
|
-
url: 'http://localhost/',
|
|
116
|
-
headers: {},
|
|
117
|
-
},
|
|
118
|
-
reporter: { sessionId: 'session-1' },
|
|
119
|
-
} as any,
|
|
120
|
-
ssrConfig: {} as any,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
collector.effect();
|
|
124
|
-
|
|
125
|
-
expect(chunkSet.ssrScripts).toContain('window.__HYDRATE__ = "router";');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should inject generic router hydration scripts into stream templates', async () => {
|
|
129
|
-
const html = await buildShellAfterTemplate(SSR_DATA_PLACEHOLDER, {
|
|
130
|
-
entryName: 'main',
|
|
131
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
132
|
-
request: new Request('http://localhost/'),
|
|
133
|
-
runtimeContext: withRouterSnapshot(
|
|
134
|
-
{
|
|
135
|
-
initialData: {},
|
|
136
|
-
__i18nData__: {},
|
|
137
|
-
routeManifest: {},
|
|
138
|
-
ssrContext: {
|
|
139
|
-
request: {
|
|
140
|
-
params: {},
|
|
141
|
-
query: {},
|
|
142
|
-
pathname: '/',
|
|
143
|
-
host: 'localhost',
|
|
144
|
-
url: 'http://localhost/',
|
|
145
|
-
headers: {},
|
|
146
|
-
},
|
|
147
|
-
reporter: { sessionId: 'session-1' },
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
hydrationScripts: [
|
|
152
|
-
'<script>window.__STREAM_ROUTER_A__ = true;</script>',
|
|
153
|
-
'<script>window.__STREAM_ROUTER_B__ = true;</script>',
|
|
154
|
-
],
|
|
155
|
-
},
|
|
156
|
-
) as any,
|
|
157
|
-
ssrConfig: {} as any,
|
|
158
|
-
config: {} as any,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
expect(html).toContain('window.__STREAM_ROUTER_A__ = true;');
|
|
162
|
-
expect(html).toContain('window.__STREAM_ROUTER_B__ = true;');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should inject matched route scripts before hydration', async () => {
|
|
166
|
-
const html = await buildShellAfterTemplate('<!--<?- chunksMap.js ?>-->', {
|
|
167
|
-
entryName: 'main',
|
|
168
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
169
|
-
request: new Request('http://localhost/products/shoe'),
|
|
170
|
-
runtimeContext: withRouterSnapshot(
|
|
171
|
-
{
|
|
172
|
-
routeManifest: {
|
|
173
|
-
routeAssets: {
|
|
174
|
-
layout: {
|
|
175
|
-
assets: ['/assets/layout.js'],
|
|
176
|
-
},
|
|
177
|
-
'products/$slug': {
|
|
178
|
-
assets: ['/assets/product.js', '/assets/product.css'],
|
|
179
|
-
},
|
|
180
|
-
'async-main': {
|
|
181
|
-
assets: ['/assets/main.js'],
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
initialData: {},
|
|
186
|
-
__i18nData__: {},
|
|
187
|
-
ssrContext: {
|
|
188
|
-
request: {
|
|
189
|
-
params: {},
|
|
190
|
-
query: {},
|
|
191
|
-
pathname: '/products/shoe',
|
|
192
|
-
host: 'localhost',
|
|
193
|
-
url: 'http://localhost/products/shoe',
|
|
194
|
-
headers: {},
|
|
195
|
-
},
|
|
196
|
-
reporter: { sessionId: 'session-1' },
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
matchedRouteIds: ['layout', 'products/$slug'],
|
|
201
|
-
},
|
|
202
|
-
) as any,
|
|
203
|
-
ssrConfig: {} as any,
|
|
204
|
-
config: {} as any,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
expect(html).toContain('<script src="/assets/layout.js"></script>');
|
|
208
|
-
expect(html).toContain('<script src="/assets/product.js"></script>');
|
|
209
|
-
expect(html).toContain('<script src="/assets/main.js"></script>');
|
|
210
|
-
expect(html).not.toContain('/assets/product.css');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
83
|
it('should move matched route scripts before the hydration entry script', async () => {
|
|
214
84
|
const html = await buildShellAfterTemplate(
|
|
215
85
|
'<script defer src="/static/js/index.js"></script><!--<?- chunksMap.js ?>-->',
|
|
@@ -262,203 +132,6 @@ describe('SSRDataCollector (stream parity)', () => {
|
|
|
262
132
|
'/static/js/index.js',
|
|
263
133
|
]);
|
|
264
134
|
});
|
|
265
|
-
|
|
266
|
-
it('should place stream hydration bootstrap before an async head entry', async () => {
|
|
267
|
-
const { shellBefore, shellAfter } = await getTemplates(
|
|
268
|
-
[
|
|
269
|
-
'<html><head>',
|
|
270
|
-
'<script async src="/static/js/index.js"></script>',
|
|
271
|
-
'</head><body><div id="root">',
|
|
272
|
-
'<!--<?- html ?>-->',
|
|
273
|
-
'</div>',
|
|
274
|
-
'<!--<?- chunksMap.js ?>-->',
|
|
275
|
-
SSR_DATA_PLACEHOLDER,
|
|
276
|
-
'</body></html>',
|
|
277
|
-
].join(''),
|
|
278
|
-
{
|
|
279
|
-
entryName: 'index',
|
|
280
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
281
|
-
request: new Request('http://localhost/products/shoe'),
|
|
282
|
-
runtimeContext: withRouterSnapshot(
|
|
283
|
-
{
|
|
284
|
-
routeManifest: {
|
|
285
|
-
routeAssets: {
|
|
286
|
-
'products/$slug': {
|
|
287
|
-
assets: ['/static/js/async/products/$slug.js'],
|
|
288
|
-
},
|
|
289
|
-
'async-index': {
|
|
290
|
-
assets: ['/static/js/async/async-index.js'],
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
initialData: {},
|
|
295
|
-
__i18nData__: {},
|
|
296
|
-
ssrContext: {
|
|
297
|
-
request: {
|
|
298
|
-
params: {},
|
|
299
|
-
query: {},
|
|
300
|
-
pathname: '/products/shoe',
|
|
301
|
-
host: 'localhost',
|
|
302
|
-
url: 'http://localhost/products/shoe',
|
|
303
|
-
headers: {},
|
|
304
|
-
},
|
|
305
|
-
reporter: { sessionId: 'session-1' },
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
hydrationScripts: [
|
|
310
|
-
'<script>window.$_TSR = { router: "hydrated" };</script>',
|
|
311
|
-
],
|
|
312
|
-
matchedRouteIds: ['products/$slug'],
|
|
313
|
-
},
|
|
314
|
-
) as any,
|
|
315
|
-
ssrConfig: {} as any,
|
|
316
|
-
config: {} as any,
|
|
317
|
-
},
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
const html = `${shellBefore}<main>server markup</main>${shellAfter}`;
|
|
321
|
-
const entryIndex = html.indexOf(
|
|
322
|
-
'<script async src="/static/js/index.js"></script>',
|
|
323
|
-
);
|
|
324
|
-
const routeIndex = html.indexOf(
|
|
325
|
-
'<script src="/static/js/async/products/$slug.js"></script>',
|
|
326
|
-
);
|
|
327
|
-
const ssrDataIndex = html.indexOf('window._SSR_DATA =');
|
|
328
|
-
const routerBootstrapIndex = html.indexOf('window.$_TSR =');
|
|
329
|
-
|
|
330
|
-
expect(routeIndex).toBeGreaterThan(-1);
|
|
331
|
-
expect(ssrDataIndex).toBeGreaterThan(routeIndex);
|
|
332
|
-
expect(routerBootstrapIndex).toBeGreaterThan(ssrDataIndex);
|
|
333
|
-
expect(entryIndex).toBeGreaterThan(routerBootstrapIndex);
|
|
334
|
-
expect(html).not.toContain('<!--<?- chunksMap.js ?>-->');
|
|
335
|
-
expect(html).not.toContain(SSR_DATA_PLACEHOLDER);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('should preserve JSON bootstrap and nonce before an async head entry', async () => {
|
|
339
|
-
const { shellBefore, shellAfter } = await getTemplates(
|
|
340
|
-
[
|
|
341
|
-
'<html><head>',
|
|
342
|
-
'<script async nonce="nonce-value" src="/static/js/index.js"></script>',
|
|
343
|
-
'</head><body><div id="root">',
|
|
344
|
-
'<!--<?- html ?>-->',
|
|
345
|
-
'</div>',
|
|
346
|
-
'<!--<?- chunksMap.js ?>-->',
|
|
347
|
-
SSR_DATA_PLACEHOLDER,
|
|
348
|
-
'</body></html>',
|
|
349
|
-
].join(''),
|
|
350
|
-
{
|
|
351
|
-
entryName: 'index',
|
|
352
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
353
|
-
request: new Request('http://localhost/products/shoe'),
|
|
354
|
-
runtimeContext: withRouterSnapshot(
|
|
355
|
-
{
|
|
356
|
-
routeManifest: {
|
|
357
|
-
routeAssets: {
|
|
358
|
-
'products/$slug': {
|
|
359
|
-
assets: ['/static/js/async/products/$slug.js'],
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
initialData: {},
|
|
364
|
-
__i18nData__: {},
|
|
365
|
-
ssrContext: {
|
|
366
|
-
request: {
|
|
367
|
-
params: {},
|
|
368
|
-
query: {},
|
|
369
|
-
pathname: '/products/shoe',
|
|
370
|
-
host: 'localhost',
|
|
371
|
-
url: 'http://localhost/products/shoe',
|
|
372
|
-
headers: {},
|
|
373
|
-
},
|
|
374
|
-
reporter: { sessionId: 'session-1' },
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
hydrationScripts: [
|
|
379
|
-
'<script nonce="nonce-value">window.$_TSR = { router: "hydrated" };</script>',
|
|
380
|
-
],
|
|
381
|
-
matchedRouteIds: ['products/$slug'],
|
|
382
|
-
},
|
|
383
|
-
) as any,
|
|
384
|
-
ssrConfig: {} as any,
|
|
385
|
-
config: {
|
|
386
|
-
nonce: 'nonce-value',
|
|
387
|
-
useJsonScript: true,
|
|
388
|
-
} as any,
|
|
389
|
-
},
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
const html = `${shellBefore}<main>server markup</main>${shellAfter}`;
|
|
393
|
-
const routeIndex = html.indexOf(
|
|
394
|
-
'<script src="/static/js/async/products/$slug.js" nonce="nonce-value"></script>',
|
|
395
|
-
);
|
|
396
|
-
const ssrDataIndex = html.indexOf(
|
|
397
|
-
'<script type="application/json" id="__MODERN_SSR_DATA__">',
|
|
398
|
-
);
|
|
399
|
-
const routerBootstrapIndex = html.indexOf('window.$_TSR =');
|
|
400
|
-
const entryIndex = html.indexOf(
|
|
401
|
-
'<script async nonce="nonce-value" src="/static/js/index.js"></script>',
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
expect(routeIndex).toBeGreaterThan(-1);
|
|
405
|
-
expect(ssrDataIndex).toBeGreaterThan(routeIndex);
|
|
406
|
-
expect(routerBootstrapIndex).toBeGreaterThan(ssrDataIndex);
|
|
407
|
-
expect(entryIndex).toBeGreaterThan(routerBootstrapIndex);
|
|
408
|
-
expect(html).not.toContain('window._SSR_DATA =');
|
|
409
|
-
expect(html).not.toContain('<!--<?- chunksMap.js ?>-->');
|
|
410
|
-
expect(html).not.toContain(SSR_DATA_PLACEHOLDER);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('should preserve a custom stream template that omits script markers', async () => {
|
|
414
|
-
const template = '<script async src="/static/js/index.js"></script>';
|
|
415
|
-
const html = await buildShellAfterTemplate(template, {
|
|
416
|
-
entryName: 'index',
|
|
417
|
-
renderLevel: RenderLevel.SERVER_RENDER,
|
|
418
|
-
request: new Request('http://localhost/products/shoe'),
|
|
419
|
-
runtimeContext: withRouterSnapshot(
|
|
420
|
-
{
|
|
421
|
-
initialData: {},
|
|
422
|
-
__i18nData__: {},
|
|
423
|
-
routeManifest: {
|
|
424
|
-
routeAssets: {
|
|
425
|
-
'products/$slug': {
|
|
426
|
-
assets: ['/static/js/async/products/$slug.js'],
|
|
427
|
-
},
|
|
428
|
-
'async-index': {
|
|
429
|
-
assets: ['/static/js/async/async-index.js'],
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
ssrContext: {
|
|
434
|
-
request: {
|
|
435
|
-
params: {},
|
|
436
|
-
query: {},
|
|
437
|
-
pathname: '/products/shoe',
|
|
438
|
-
host: 'localhost',
|
|
439
|
-
url: 'http://localhost/products/shoe',
|
|
440
|
-
headers: {
|
|
441
|
-
'x-request-id': 'request-1',
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
reporter: { sessionId: 'session-1' },
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
hydrationScripts: [
|
|
449
|
-
'<script>window.__OMITTED_BOOTSTRAP__ = true;</script>',
|
|
450
|
-
],
|
|
451
|
-
matchedRouteIds: ['products/$slug'],
|
|
452
|
-
},
|
|
453
|
-
) as any,
|
|
454
|
-
ssrConfig: {
|
|
455
|
-
unsafeHeaders: ['x-request-id'],
|
|
456
|
-
} as any,
|
|
457
|
-
config: {} as any,
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
expect(html).toBe(template);
|
|
461
|
-
});
|
|
462
135
|
});
|
|
463
136
|
|
|
464
137
|
import React from 'react';
|
|
@@ -551,48 +224,6 @@ describe('renderString template assembly (string-mode script ordering)', () => {
|
|
|
551
224
|
expect(html).not.toContain(SSR_DATA_PLACEHOLDER);
|
|
552
225
|
});
|
|
553
226
|
|
|
554
|
-
it('should keep the async entry variant ordered after the bootstrap', async () => {
|
|
555
|
-
const html = await render(
|
|
556
|
-
[
|
|
557
|
-
'<html><head>',
|
|
558
|
-
'<script async src="/static/js/async-index.js"></script>',
|
|
559
|
-
'</head><body><div id="root">',
|
|
560
|
-
'<!--<?- html ?>-->',
|
|
561
|
-
'</div>',
|
|
562
|
-
SSR_DATA_PLACEHOLDER,
|
|
563
|
-
'</body></html>',
|
|
564
|
-
].join(''),
|
|
565
|
-
);
|
|
566
|
-
|
|
567
|
-
const entryIndex = html.indexOf(
|
|
568
|
-
'<script async src="/static/js/async-index.js"></script>',
|
|
569
|
-
);
|
|
570
|
-
const routerBootstrapIndex = html.indexOf('window.$_TSR =');
|
|
571
|
-
|
|
572
|
-
expect(routerBootstrapIndex).toBeGreaterThan(-1);
|
|
573
|
-
expect(entryIndex).toBeGreaterThan(routerBootstrapIndex);
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
it('should fall back to in-place replacement when no entry script tag exists', async () => {
|
|
577
|
-
const html = await render(
|
|
578
|
-
[
|
|
579
|
-
'<html><head></head><body><div id="root">',
|
|
580
|
-
'<!--<?- html ?>-->',
|
|
581
|
-
'</div>',
|
|
582
|
-
'<span id="marker"></span>',
|
|
583
|
-
SSR_DATA_PLACEHOLDER,
|
|
584
|
-
'</body></html>',
|
|
585
|
-
].join(''),
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
const markerIndex = html.indexOf('<span id="marker"></span>');
|
|
589
|
-
const ssrDataIndex = html.indexOf('window._SSR_DATA =');
|
|
590
|
-
|
|
591
|
-
expect(markerIndex).toBeGreaterThan(-1);
|
|
592
|
-
expect(ssrDataIndex).toBeGreaterThan(markerIndex);
|
|
593
|
-
expect(html).not.toContain(SSR_DATA_PLACEHOLDER);
|
|
594
|
-
});
|
|
595
|
-
|
|
596
227
|
it('should preserve a custom template that omits script markers', async () => {
|
|
597
228
|
const template =
|
|
598
229
|
'<html><head><script async src="/static/js/index.js"></script></head><body>custom shell</body></html>';
|
|
@@ -732,87 +363,4 @@ describe('SSR data script generation', () => {
|
|
|
732
363
|
loaderData: { route: { ok: true } },
|
|
733
364
|
});
|
|
734
365
|
});
|
|
735
|
-
|
|
736
|
-
it('should serialize generic router hydration scripts when present', () => {
|
|
737
|
-
const browser = executeScripts(
|
|
738
|
-
createScripts({
|
|
739
|
-
routerServerSnapshot: {
|
|
740
|
-
hydrationScripts: [
|
|
741
|
-
'<script>window.__ROUTER_A__ = true;</script>',
|
|
742
|
-
'<script>window.__ROUTER_B__ = true;</script>',
|
|
743
|
-
],
|
|
744
|
-
},
|
|
745
|
-
}),
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
expect(browser.__ROUTER_A__).toBe(true);
|
|
749
|
-
expect(browser.__ROUTER_B__).toBe(true);
|
|
750
|
-
});
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
import {
|
|
754
|
-
injectBeforeHydrationEntryScript,
|
|
755
|
-
replaceChunkJsPlaceholder,
|
|
756
|
-
} from '@modern-js/runtime-extensions';
|
|
757
|
-
|
|
758
|
-
const matrixScriptSrcs = (html: string) =>
|
|
759
|
-
Array.from(html.matchAll(/<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*>/g)).map(
|
|
760
|
-
match => match[2],
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
describe('string SSR script ordering', () => {
|
|
764
|
-
it('injects hydration route scripts before the entry bootstrap', () => {
|
|
765
|
-
const html = injectBeforeHydrationEntryScript(
|
|
766
|
-
'<head><script defer src="/static/js/index.abc.js"></script></head>',
|
|
767
|
-
'<script defer src="/static/js/async/(lang)/page.js"></script>',
|
|
768
|
-
'index',
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
expect(matrixScriptSrcs(html)).toEqual([
|
|
772
|
-
'/static/js/async/(lang)/page.js',
|
|
773
|
-
'/static/js/index.abc.js',
|
|
774
|
-
]);
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
it('falls back to the async entry when the bootstrap script is absent', () => {
|
|
778
|
-
const html = injectBeforeHydrationEntryScript(
|
|
779
|
-
'<body><script defer src="/static/js/async/async-index.js"></script></body>',
|
|
780
|
-
'<script defer src="/static/js/async/(lang)/page.js"></script>',
|
|
781
|
-
'index',
|
|
782
|
-
);
|
|
783
|
-
|
|
784
|
-
expect(matrixScriptSrcs(html)).toEqual([
|
|
785
|
-
'/static/js/async/(lang)/page.js',
|
|
786
|
-
'/static/js/async/async-index.js',
|
|
787
|
-
]);
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
it('preserves a template that omits the script marker', () => {
|
|
791
|
-
const template =
|
|
792
|
-
'<body><script defer src="/static/js/index.js"></script></body>';
|
|
793
|
-
|
|
794
|
-
expect(
|
|
795
|
-
replaceChunkJsPlaceholder(
|
|
796
|
-
template,
|
|
797
|
-
'<script src="/static/js/async/page.js"></script>',
|
|
798
|
-
undefined,
|
|
799
|
-
'<!--<?- chunksMap.js ?>-->',
|
|
800
|
-
),
|
|
801
|
-
).toBe(template);
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it('orders scripts before the entry when the marker is present', () => {
|
|
805
|
-
const html = replaceChunkJsPlaceholder(
|
|
806
|
-
'<body><script defer src="/static/js/index.js"></script><!--<?- chunksMap.js ?>--></body>',
|
|
807
|
-
'<script src="/static/js/async/page.js"></script>',
|
|
808
|
-
undefined,
|
|
809
|
-
'<!--<?- chunksMap.js ?>-->',
|
|
810
|
-
);
|
|
811
|
-
|
|
812
|
-
expect(matrixScriptSrcs(html)).toEqual([
|
|
813
|
-
'/static/js/async/page.js',
|
|
814
|
-
'/static/js/index.js',
|
|
815
|
-
]);
|
|
816
|
-
expect(html).not.toContain('<!--<?- chunksMap.js ?>-->');
|
|
817
|
-
});
|
|
818
366
|
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { build } from 'esbuild';
|
|
3
|
-
|
|
4
|
-
const packageDirectory = fileURLToPath(new URL('..', import.meta.url));
|
|
5
|
-
|
|
6
|
-
// Resolve the public package entry, including its platform conditions.
|
|
7
|
-
test.each([
|
|
8
|
-
'browser',
|
|
9
|
-
'node',
|
|
10
|
-
] as const)('public %s entry selects the matching renderer adapter', async platform => {
|
|
11
|
-
const output = await build({
|
|
12
|
-
absWorkingDir: packageDirectory,
|
|
13
|
-
stdin: {
|
|
14
|
-
contents:
|
|
15
|
-
"export { default } from '@modern-js/runtime-renderer-extensions'",
|
|
16
|
-
resolveDir: packageDirectory,
|
|
17
|
-
},
|
|
18
|
-
bundle: true,
|
|
19
|
-
write: false,
|
|
20
|
-
metafile: true,
|
|
21
|
-
platform,
|
|
22
|
-
format: 'esm',
|
|
23
|
-
conditions: ['modern:source'],
|
|
24
|
-
});
|
|
25
|
-
const inputs = Object.keys(output.metafile.inputs);
|
|
26
|
-
expect(inputs.some(input => input.endsWith('/runtimePlugin.ts'))).toBe(true);
|
|
27
|
-
expect(
|
|
28
|
-
inputs.some(input => input.endsWith('/runtime-extensions/src/node.ts')),
|
|
29
|
-
).toBe(platform === 'node');
|
|
30
|
-
expect(
|
|
31
|
-
inputs.some(
|
|
32
|
-
input =>
|
|
33
|
-
input === 'src/node.ts' ||
|
|
34
|
-
input.endsWith('/renderer-extensions/src/node.ts'),
|
|
35
|
-
),
|
|
36
|
-
).toBe(platform === 'node');
|
|
37
|
-
});
|