@bleedingdev/modern-js-runtime-renderer-extensions 3.9.0-ultramodern.6 → 3.9.0-ultramodern.8

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.6",
14
+ "version": "3.9.0-ultramodern.8",
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.6",
40
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.6"
39
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.8",
40
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.8"
41
41
  },
42
42
  "devDependencies": {
43
- "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.0-ultramodern.6",
44
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.6",
45
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.6",
43
+ "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.0-ultramodern.8",
44
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.8",
45
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.8",
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&quot;&amp;&lt;value&gt;" 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&quot;.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&quot; onload=&quot;alert(1)&amp;x=&lt;tag&gt;.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
- });