@bleedingdev/modern-js-runtime 3.2.0-ultramodern.97 → 3.2.0-ultramodern.99

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.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ injectBeforeHydrationEntryScript: ()=>injectBeforeHydrationEntryScript
28
+ });
29
+ function getScriptTags(template) {
30
+ const scriptRegExp = /<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*><\/script>/g;
31
+ return Array.from(template.matchAll(scriptRegExp)).map((match)=>({
32
+ index: match.index ?? 0,
33
+ tag: match[0],
34
+ src: match[2]
35
+ }));
36
+ }
37
+ function getAssetBasename(src) {
38
+ const withoutQuery = src.split(/[?#]/)[0];
39
+ return withoutQuery.split('/').pop() || withoutQuery;
40
+ }
41
+ function isEntryScript(src, entryName, asyncEntry) {
42
+ const basename = getAssetBasename(src);
43
+ const prefix = asyncEntry ? `async-${entryName}` : entryName;
44
+ return basename === `${prefix}.js` || basename.startsWith(`${prefix}.`) || basename.startsWith(`${prefix}-`);
45
+ }
46
+ function injectBeforeHydrationEntryScript(template, scripts, entryName = 'index') {
47
+ if (!scripts) return template;
48
+ const scriptTags = getScriptTags(template);
49
+ const target = scriptTags.find((match)=>isEntryScript(match.src, entryName, false)) ?? scriptTags.find((match)=>isEntryScript(match.src, entryName, true));
50
+ if (!target) return template;
51
+ return `${template.slice(0, target.index)}${scripts}${template.slice(target.index)}`;
52
+ }
53
+ exports.injectBeforeHydrationEntryScript = __webpack_exports__.injectBeforeHydrationEntryScript;
54
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
55
+ "injectBeforeHydrationEntryScript"
56
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
57
+ Object.defineProperty(exports, '__esModule', {
58
+ value: true
59
+ });
@@ -30,6 +30,7 @@ const node_namespaceObject = require("@modern-js/runtime-utils/node");
30
30
  const lifecycle_js_namespaceObject = require("../../../router/runtime/lifecycle.js");
31
31
  const external_constants_js_namespaceObject = require("../../constants.js");
32
32
  const external_constants_js_namespaceObject_1 = require("../constants.js");
33
+ const external_scriptOrder_js_namespaceObject = require("../scriptOrder.js");
33
34
  const external_shared_js_namespaceObject = require("../shared.js");
34
35
  const external_utils_js_namespaceObject = require("../utils.js");
35
36
  function buildShellAfterTemplate(afterAppTemplate, options) {
@@ -58,7 +59,11 @@ function buildShellAfterTemplate(afterAppTemplate, options) {
58
59
  const jsAssets = Array.from(new Set(assetEntries.flatMap((entry)=>(entry.assets ?? []).filter((asset)=>asset.endsWith('.js')))));
59
60
  const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
60
61
  const jsChunkStr = jsAssets.filter((asset)=>!template.includes(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
61
- if (jsChunkStr) return (0, external_utils_js_namespaceObject.safeReplace)(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
62
+ if (jsChunkStr) {
63
+ const withoutPlaceholder = (0, external_utils_js_namespaceObject.safeReplace)(template, '<!--<?- chunksMap.js ?>-->', '');
64
+ const withEarlyScripts = (0, external_scriptOrder_js_namespaceObject.injectBeforeHydrationEntryScript)(withoutPlaceholder, jsChunkStr, entryName);
65
+ return withEarlyScripts !== withoutPlaceholder ? withEarlyScripts : (0, external_utils_js_namespaceObject.safeReplace)(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
66
+ }
62
67
  return template;
63
68
  }
64
69
  return (0, external_shared_js_namespaceObject.buildHtml)(afterAppTemplate, callbacks);
@@ -44,6 +44,7 @@ const index_js_namespaceObject = require("../../context/index.js");
44
44
  const wrapper_js_namespaceObject = require("../../react/wrapper.js");
45
45
  const external_constants_js_namespaceObject_1 = require("../constants.js");
46
46
  const external_helmet_js_namespaceObject = require("../helmet.js");
47
+ const external_scriptOrder_js_namespaceObject = require("../scriptOrder.js");
47
48
  const external_shared_js_namespaceObject = require("../shared.js");
48
49
  const external_tracer_js_namespaceObject = require("../tracer.js");
49
50
  const external_utils_js_namespaceObject = require("../utils.js");
@@ -96,10 +97,10 @@ const renderString = async (request, serverRoot, options)=>{
96
97
  const rootElement = (0, wrapper_js_namespaceObject.wrapRuntimeContextProvider)(serverRoot, Object.assign(runtimeContext, {
97
98
  ssr: true
98
99
  }));
99
- const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, tracer);
100
+ const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, entryName, tracer);
100
101
  return html;
101
102
  };
102
- async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, { onError, onTiming }) {
103
+ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, entryName, { onError, onTiming }) {
103
104
  let html = '';
104
105
  let helmetData;
105
106
  const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
@@ -120,7 +121,7 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
120
121
  const { ssrScripts, cssChunk, jsChunk } = chunkSet;
121
122
  const finalHtml = await (0, external_shared_js_namespaceObject.buildHtml)(htmlTemplate, [
122
123
  createReplaceHtml(html),
123
- createReplaceChunkJs(jsChunk),
124
+ createReplaceChunkJs(jsChunk, entryName),
124
125
  createReplaceChunkCss(cssChunk),
125
126
  createReplaceSSRDataScript(ssrScripts),
126
127
  (0, external_helmet_js_namespaceObject.createReplaceHelemt)(helmetData),
@@ -134,8 +135,14 @@ function createReplaceHtml(html) {
134
135
  function createReplaceSSRDataScript(data) {
135
136
  return (template)=>(0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.SSR_DATA_PLACEHOLDER, data);
136
137
  }
137
- function createReplaceChunkJs(js) {
138
- return (template)=>(0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.CHUNK_JS_PLACEHOLDER, js);
138
+ function createReplaceChunkJs(js, entryName) {
139
+ return (template)=>{
140
+ if (!js) return (0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.CHUNK_JS_PLACEHOLDER, '');
141
+ const withoutPlaceholder = (0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.CHUNK_JS_PLACEHOLDER, '');
142
+ const withEarlyScripts = (0, external_scriptOrder_js_namespaceObject.injectBeforeHydrationEntryScript)(withoutPlaceholder, js, entryName);
143
+ if (withEarlyScripts !== withoutPlaceholder) return withEarlyScripts;
144
+ return (0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.CHUNK_JS_PLACEHOLDER, js);
145
+ };
139
146
  }
140
147
  function createReplaceChunkCss(css) {
141
148
  return (template)=>(0, external_utils_js_namespaceObject.safeReplace)(template, external_constants_js_namespaceObject_1.CHUNK_CSS_PLACEHOLDER, css);
@@ -24,7 +24,8 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- LoadableCollector: ()=>LoadableCollector
27
+ LoadableCollector: ()=>LoadableCollector,
28
+ orderHydrationScriptChunks: ()=>orderHydrationScriptChunks
28
29
  });
29
30
  const server_namespaceObject = require("@loadable/server");
30
31
  const lifecycle_js_namespaceObject = require("../../../router/runtime/lifecycle.js");
@@ -35,6 +36,33 @@ const extname = (uri)=>{
35
36
  return `.${uri?.split('.').pop()}` || '';
36
37
  };
37
38
  const generateChunks = (chunks, ext)=>chunks.filter((chunk)=>Boolean(chunk.url)).filter((chunk)=>extname(chunk.url).slice(1) === ext);
39
+ const dedupeChunksByUrl = (chunks)=>{
40
+ const seen = new Set();
41
+ return chunks.filter((chunk)=>{
42
+ if (!chunk.url || seen.has(chunk.url)) return false;
43
+ seen.add(chunk.url);
44
+ return true;
45
+ });
46
+ };
47
+ const isAsyncEntryScriptChunk = (chunk, entryName)=>{
48
+ if (!chunk.url?.endsWith('.js')) return false;
49
+ const asyncEntryName = `async-${entryName}`;
50
+ const filename = chunk.filename || chunk.url;
51
+ const basename = filename.split('/').pop() || filename;
52
+ return basename === `${asyncEntryName}.js` || basename.startsWith(`${asyncEntryName}.`) || basename.startsWith(`${asyncEntryName}-`);
53
+ };
54
+ const orderHydrationScriptChunks = ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName })=>{
55
+ const asyncEntryScriptChunks = [];
56
+ const asyncEntryDependencyChunks = [];
57
+ for (const chunk of asyncEntryChunks)if (isAsyncEntryScriptChunk(chunk, entryName)) asyncEntryScriptChunks.push(chunk);
58
+ else asyncEntryDependencyChunks.push(chunk);
59
+ return dedupeChunksByUrl([
60
+ ...asyncEntryDependencyChunks,
61
+ ...collectedChunks,
62
+ ...matchedRouteChunks,
63
+ ...asyncEntryScriptChunks
64
+ ]);
65
+ };
38
66
  const checkIsInline = (chunk, enableInline)=>{
39
67
  if ('production' !== process.env.NODE_ENV) return false;
40
68
  if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
@@ -85,8 +113,16 @@ class LoadableCollector {
85
113
  `async-${entryName}`
86
114
  ]));
87
115
  } catch (e) {}
88
- const chunks = [].concat(asyncChunks).concat(extractor ? extractor.getChunkAssets(extractor.chunks) : []).concat(this.getMatchedRouteChunks());
89
- const scriptChunks = generateChunks(chunks, 'js');
116
+ const collectedChunks = extractor ? extractor.getChunkAssets(extractor.chunks) : [];
117
+ const matchedRouteChunks = this.getMatchedRouteChunks();
118
+ const orderedScriptChunks = orderHydrationScriptChunks({
119
+ asyncEntryChunks: asyncChunks,
120
+ collectedChunks,
121
+ matchedRouteChunks,
122
+ entryName
123
+ });
124
+ const chunks = [].concat(asyncChunks).concat(collectedChunks).concat(matchedRouteChunks);
125
+ const scriptChunks = generateChunks(orderedScriptChunks, 'js');
90
126
  const styleChunks = generateChunks(chunks, 'css');
91
127
  if (extractor) {
92
128
  this.emitLoadableScripts(extractor);
@@ -164,8 +200,10 @@ class LoadableCollector {
164
200
  }
165
201
  }
166
202
  exports.LoadableCollector = __webpack_exports__.LoadableCollector;
203
+ exports.orderHydrationScriptChunks = __webpack_exports__.orderHydrationScriptChunks;
167
204
  for(var __rspack_i in __webpack_exports__)if (-1 === [
168
- "LoadableCollector"
205
+ "LoadableCollector",
206
+ "orderHydrationScriptChunks"
169
207
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
170
208
  Object.defineProperty(exports, '__esModule', {
171
209
  value: true
@@ -0,0 +1,25 @@
1
+ function getScriptTags(template) {
2
+ const scriptRegExp = /<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*><\/script>/g;
3
+ return Array.from(template.matchAll(scriptRegExp)).map((match)=>({
4
+ index: match.index ?? 0,
5
+ tag: match[0],
6
+ src: match[2]
7
+ }));
8
+ }
9
+ function getAssetBasename(src) {
10
+ const withoutQuery = src.split(/[?#]/)[0];
11
+ return withoutQuery.split('/').pop() || withoutQuery;
12
+ }
13
+ function isEntryScript(src, entryName, asyncEntry) {
14
+ const basename = getAssetBasename(src);
15
+ const prefix = asyncEntry ? `async-${entryName}` : entryName;
16
+ return basename === `${prefix}.js` || basename.startsWith(`${prefix}.`) || basename.startsWith(`${prefix}-`);
17
+ }
18
+ function injectBeforeHydrationEntryScript(template, scripts, entryName = 'index') {
19
+ if (!scripts) return template;
20
+ const scriptTags = getScriptTags(template);
21
+ const target = scriptTags.find((match)=>isEntryScript(match.src, entryName, false)) ?? scriptTags.find((match)=>isEntryScript(match.src, entryName, true));
22
+ if (!target) return template;
23
+ return `${template.slice(0, target.index)}${scripts}${template.slice(target.index)}`;
24
+ }
25
+ export { injectBeforeHydrationEntryScript };
@@ -2,6 +2,7 @@ import { serializeJson } from "@modern-js/runtime-utils/node";
2
2
  import { getRouterHydrationScripts, getRouterMatchedRouteIds } from "../../../router/runtime/lifecycle.mjs";
3
3
  import { SSR_DATA_JSON_ID } from "../../constants.mjs";
4
4
  import { SSR_DATA_PLACEHOLDER } from "../constants.mjs";
5
+ import { injectBeforeHydrationEntryScript } from "../scriptOrder.mjs";
5
6
  import { buildHtml } from "../shared.mjs";
6
7
  import { attributesToString, safeReplace } from "../utils.mjs";
7
8
  function buildShellAfterTemplate(afterAppTemplate, options) {
@@ -30,7 +31,11 @@ function buildShellAfterTemplate(afterAppTemplate, options) {
30
31
  const jsAssets = Array.from(new Set(assetEntries.flatMap((entry)=>(entry.assets ?? []).filter((asset)=>asset.endsWith('.js')))));
31
32
  const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
32
33
  const jsChunkStr = jsAssets.filter((asset)=>!template.includes(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
33
- if (jsChunkStr) return safeReplace(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
34
+ if (jsChunkStr) {
35
+ const withoutPlaceholder = safeReplace(template, '<!--<?- chunksMap.js ?>-->', '');
36
+ const withEarlyScripts = injectBeforeHydrationEntryScript(withoutPlaceholder, jsChunkStr, entryName);
37
+ return withEarlyScripts !== withoutPlaceholder ? withEarlyScripts : safeReplace(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
38
+ }
34
39
  return template;
35
40
  }
36
41
  return buildHtml(afterAppTemplate, callbacks);
@@ -6,6 +6,7 @@ import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
6
6
  import { wrapRuntimeContextProvider } from "../../react/wrapper.mjs";
7
7
  import { CHUNK_CSS_PLACEHOLDER, CHUNK_JS_PLACEHOLDER, HTML_PLACEHOLDER, SSR_DATA_PLACEHOLDER } from "../constants.mjs";
8
8
  import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
9
+ import { injectBeforeHydrationEntryScript } from "../scriptOrder.mjs";
9
10
  import { buildHtml } from "../shared.mjs";
10
11
  import { SSRErrors, SSRTimings } from "../tracer.mjs";
11
12
  import { getSSRConfigByEntry, safeReplace } from "../utils.mjs";
@@ -58,10 +59,10 @@ const renderString = async (request, serverRoot, options)=>{
58
59
  const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext, {
59
60
  ssr: true
60
61
  }));
61
- const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, tracer);
62
+ const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, entryName, tracer);
62
63
  return html;
63
64
  };
64
- async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, { onError, onTiming }) {
65
+ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, entryName, { onError, onTiming }) {
65
66
  let html = '';
66
67
  let helmetData;
67
68
  const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
@@ -82,7 +83,7 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
82
83
  const { ssrScripts, cssChunk, jsChunk } = chunkSet;
83
84
  const finalHtml = await buildHtml(htmlTemplate, [
84
85
  createReplaceHtml(html),
85
- createReplaceChunkJs(jsChunk),
86
+ createReplaceChunkJs(jsChunk, entryName),
86
87
  createReplaceChunkCss(cssChunk),
87
88
  createReplaceSSRDataScript(ssrScripts),
88
89
  createReplaceHelemt(helmetData),
@@ -96,8 +97,14 @@ function createReplaceHtml(html) {
96
97
  function createReplaceSSRDataScript(data) {
97
98
  return (template)=>safeReplace(template, SSR_DATA_PLACEHOLDER, data);
98
99
  }
99
- function createReplaceChunkJs(js) {
100
- return (template)=>safeReplace(template, CHUNK_JS_PLACEHOLDER, js);
100
+ function createReplaceChunkJs(js, entryName) {
101
+ return (template)=>{
102
+ if (!js) return safeReplace(template, CHUNK_JS_PLACEHOLDER, '');
103
+ const withoutPlaceholder = safeReplace(template, CHUNK_JS_PLACEHOLDER, '');
104
+ const withEarlyScripts = injectBeforeHydrationEntryScript(withoutPlaceholder, js, entryName);
105
+ if (withEarlyScripts !== withoutPlaceholder) return withEarlyScripts;
106
+ return safeReplace(template, CHUNK_JS_PLACEHOLDER, js);
107
+ };
101
108
  }
102
109
  function createReplaceChunkCss(css) {
103
110
  return (template)=>safeReplace(template, CHUNK_CSS_PLACEHOLDER, css);
@@ -7,6 +7,33 @@ const extname = (uri)=>{
7
7
  return `.${uri?.split('.').pop()}` || '';
8
8
  };
9
9
  const generateChunks = (chunks, ext)=>chunks.filter((chunk)=>Boolean(chunk.url)).filter((chunk)=>extname(chunk.url).slice(1) === ext);
10
+ const dedupeChunksByUrl = (chunks)=>{
11
+ const seen = new Set();
12
+ return chunks.filter((chunk)=>{
13
+ if (!chunk.url || seen.has(chunk.url)) return false;
14
+ seen.add(chunk.url);
15
+ return true;
16
+ });
17
+ };
18
+ const isAsyncEntryScriptChunk = (chunk, entryName)=>{
19
+ if (!chunk.url?.endsWith('.js')) return false;
20
+ const asyncEntryName = `async-${entryName}`;
21
+ const filename = chunk.filename || chunk.url;
22
+ const basename = filename.split('/').pop() || filename;
23
+ return basename === `${asyncEntryName}.js` || basename.startsWith(`${asyncEntryName}.`) || basename.startsWith(`${asyncEntryName}-`);
24
+ };
25
+ const orderHydrationScriptChunks = ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName })=>{
26
+ const asyncEntryScriptChunks = [];
27
+ const asyncEntryDependencyChunks = [];
28
+ for (const chunk of asyncEntryChunks)if (isAsyncEntryScriptChunk(chunk, entryName)) asyncEntryScriptChunks.push(chunk);
29
+ else asyncEntryDependencyChunks.push(chunk);
30
+ return dedupeChunksByUrl([
31
+ ...asyncEntryDependencyChunks,
32
+ ...collectedChunks,
33
+ ...matchedRouteChunks,
34
+ ...asyncEntryScriptChunks
35
+ ]);
36
+ };
10
37
  const checkIsInline = (chunk, enableInline)=>{
11
38
  if ('production' !== process.env.NODE_ENV) return false;
12
39
  if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
@@ -57,8 +84,16 @@ class LoadableCollector {
57
84
  `async-${entryName}`
58
85
  ]));
59
86
  } catch (e) {}
60
- const chunks = [].concat(asyncChunks).concat(extractor ? extractor.getChunkAssets(extractor.chunks) : []).concat(this.getMatchedRouteChunks());
61
- const scriptChunks = generateChunks(chunks, 'js');
87
+ const collectedChunks = extractor ? extractor.getChunkAssets(extractor.chunks) : [];
88
+ const matchedRouteChunks = this.getMatchedRouteChunks();
89
+ const orderedScriptChunks = orderHydrationScriptChunks({
90
+ asyncEntryChunks: asyncChunks,
91
+ collectedChunks,
92
+ matchedRouteChunks,
93
+ entryName
94
+ });
95
+ const chunks = [].concat(asyncChunks).concat(collectedChunks).concat(matchedRouteChunks);
96
+ const scriptChunks = generateChunks(orderedScriptChunks, 'js');
62
97
  const styleChunks = generateChunks(chunks, 'css');
63
98
  if (extractor) {
64
99
  this.emitLoadableScripts(extractor);
@@ -135,4 +170,4 @@ class LoadableCollector {
135
170
  this.options = options;
136
171
  }
137
172
  }
138
- export { LoadableCollector };
173
+ export { LoadableCollector, orderHydrationScriptChunks };
@@ -0,0 +1,26 @@
1
+ import "node:module";
2
+ function getScriptTags(template) {
3
+ const scriptRegExp = /<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*><\/script>/g;
4
+ return Array.from(template.matchAll(scriptRegExp)).map((match)=>({
5
+ index: match.index ?? 0,
6
+ tag: match[0],
7
+ src: match[2]
8
+ }));
9
+ }
10
+ function getAssetBasename(src) {
11
+ const withoutQuery = src.split(/[?#]/)[0];
12
+ return withoutQuery.split('/').pop() || withoutQuery;
13
+ }
14
+ function isEntryScript(src, entryName, asyncEntry) {
15
+ const basename = getAssetBasename(src);
16
+ const prefix = asyncEntry ? `async-${entryName}` : entryName;
17
+ return basename === `${prefix}.js` || basename.startsWith(`${prefix}.`) || basename.startsWith(`${prefix}-`);
18
+ }
19
+ function injectBeforeHydrationEntryScript(template, scripts, entryName = 'index') {
20
+ if (!scripts) return template;
21
+ const scriptTags = getScriptTags(template);
22
+ const target = scriptTags.find((match)=>isEntryScript(match.src, entryName, false)) ?? scriptTags.find((match)=>isEntryScript(match.src, entryName, true));
23
+ if (!target) return template;
24
+ return `${template.slice(0, target.index)}${scripts}${template.slice(target.index)}`;
25
+ }
26
+ export { injectBeforeHydrationEntryScript };
@@ -3,6 +3,7 @@ import { serializeJson } from "@modern-js/runtime-utils/node";
3
3
  import { getRouterHydrationScripts, getRouterMatchedRouteIds } from "../../../router/runtime/lifecycle.mjs";
4
4
  import { SSR_DATA_JSON_ID } from "../../constants.mjs";
5
5
  import { SSR_DATA_PLACEHOLDER } from "../constants.mjs";
6
+ import { injectBeforeHydrationEntryScript } from "../scriptOrder.mjs";
6
7
  import { buildHtml } from "../shared.mjs";
7
8
  import { attributesToString, safeReplace } from "../utils.mjs";
8
9
  function buildShellAfterTemplate(afterAppTemplate, options) {
@@ -31,7 +32,11 @@ function buildShellAfterTemplate(afterAppTemplate, options) {
31
32
  const jsAssets = Array.from(new Set(assetEntries.flatMap((entry)=>(entry.assets ?? []).filter((asset)=>asset.endsWith('.js')))));
32
33
  const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
33
34
  const jsChunkStr = jsAssets.filter((asset)=>!template.includes(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
34
- if (jsChunkStr) return safeReplace(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
35
+ if (jsChunkStr) {
36
+ const withoutPlaceholder = safeReplace(template, '<!--<?- chunksMap.js ?>-->', '');
37
+ const withEarlyScripts = injectBeforeHydrationEntryScript(withoutPlaceholder, jsChunkStr, entryName);
38
+ return withEarlyScripts !== withoutPlaceholder ? withEarlyScripts : safeReplace(template, '<!--<?- chunksMap.js ?>-->', jsChunkStr);
39
+ }
35
40
  return template;
36
41
  }
37
42
  return buildHtml(afterAppTemplate, callbacks);
@@ -7,6 +7,7 @@ import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
7
7
  import { wrapRuntimeContextProvider } from "../../react/wrapper.mjs";
8
8
  import { CHUNK_CSS_PLACEHOLDER, CHUNK_JS_PLACEHOLDER, HTML_PLACEHOLDER, SSR_DATA_PLACEHOLDER } from "../constants.mjs";
9
9
  import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
10
+ import { injectBeforeHydrationEntryScript } from "../scriptOrder.mjs";
10
11
  import { buildHtml } from "../shared.mjs";
11
12
  import { SSRErrors, SSRTimings } from "../tracer.mjs";
12
13
  import { getSSRConfigByEntry, safeReplace } from "../utils.mjs";
@@ -59,10 +60,10 @@ const renderString = async (request, serverRoot, options)=>{
59
60
  const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext, {
60
61
  ssr: true
61
62
  }));
62
- const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, tracer);
63
+ const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], runtimeContext, entryName, tracer);
63
64
  return html;
64
65
  };
65
- async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, { onError, onTiming }) {
66
+ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, runtimeContext, entryName, { onError, onTiming }) {
66
67
  let html = '';
67
68
  let helmetData;
68
69
  const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
@@ -83,7 +84,7 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
83
84
  const { ssrScripts, cssChunk, jsChunk } = chunkSet;
84
85
  const finalHtml = await buildHtml(htmlTemplate, [
85
86
  createReplaceHtml(html),
86
- createReplaceChunkJs(jsChunk),
87
+ createReplaceChunkJs(jsChunk, entryName),
87
88
  createReplaceChunkCss(cssChunk),
88
89
  createReplaceSSRDataScript(ssrScripts),
89
90
  createReplaceHelemt(helmetData),
@@ -97,8 +98,14 @@ function createReplaceHtml(html) {
97
98
  function createReplaceSSRDataScript(data) {
98
99
  return (template)=>safeReplace(template, SSR_DATA_PLACEHOLDER, data);
99
100
  }
100
- function createReplaceChunkJs(js) {
101
- return (template)=>safeReplace(template, CHUNK_JS_PLACEHOLDER, js);
101
+ function createReplaceChunkJs(js, entryName) {
102
+ return (template)=>{
103
+ if (!js) return safeReplace(template, CHUNK_JS_PLACEHOLDER, '');
104
+ const withoutPlaceholder = safeReplace(template, CHUNK_JS_PLACEHOLDER, '');
105
+ const withEarlyScripts = injectBeforeHydrationEntryScript(withoutPlaceholder, js, entryName);
106
+ if (withEarlyScripts !== withoutPlaceholder) return withEarlyScripts;
107
+ return safeReplace(template, CHUNK_JS_PLACEHOLDER, js);
108
+ };
102
109
  }
103
110
  function createReplaceChunkCss(css) {
104
111
  return (template)=>safeReplace(template, CHUNK_CSS_PLACEHOLDER, css);
@@ -11,6 +11,33 @@ const extname = (uri)=>{
11
11
  return `.${uri?.split('.').pop()}` || '';
12
12
  };
13
13
  const generateChunks = (chunks, ext)=>chunks.filter((chunk)=>Boolean(chunk.url)).filter((chunk)=>extname(chunk.url).slice(1) === ext);
14
+ const dedupeChunksByUrl = (chunks)=>{
15
+ const seen = new Set();
16
+ return chunks.filter((chunk)=>{
17
+ if (!chunk.url || seen.has(chunk.url)) return false;
18
+ seen.add(chunk.url);
19
+ return true;
20
+ });
21
+ };
22
+ const isAsyncEntryScriptChunk = (chunk, entryName)=>{
23
+ if (!chunk.url?.endsWith('.js')) return false;
24
+ const asyncEntryName = `async-${entryName}`;
25
+ const filename = chunk.filename || chunk.url;
26
+ const basename = filename.split('/').pop() || filename;
27
+ return basename === `${asyncEntryName}.js` || basename.startsWith(`${asyncEntryName}.`) || basename.startsWith(`${asyncEntryName}-`);
28
+ };
29
+ const orderHydrationScriptChunks = ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName })=>{
30
+ const asyncEntryScriptChunks = [];
31
+ const asyncEntryDependencyChunks = [];
32
+ for (const chunk of asyncEntryChunks)if (isAsyncEntryScriptChunk(chunk, entryName)) asyncEntryScriptChunks.push(chunk);
33
+ else asyncEntryDependencyChunks.push(chunk);
34
+ return dedupeChunksByUrl([
35
+ ...asyncEntryDependencyChunks,
36
+ ...collectedChunks,
37
+ ...matchedRouteChunks,
38
+ ...asyncEntryScriptChunks
39
+ ]);
40
+ };
14
41
  const checkIsInline = (chunk, enableInline)=>{
15
42
  if ('production' !== process.env.NODE_ENV) return false;
16
43
  if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
@@ -61,8 +88,16 @@ class LoadableCollector {
61
88
  `async-${entryName}`
62
89
  ]));
63
90
  } catch (e) {}
64
- const chunks = [].concat(asyncChunks).concat(extractor ? extractor.getChunkAssets(extractor.chunks) : []).concat(this.getMatchedRouteChunks());
65
- const scriptChunks = generateChunks(chunks, 'js');
91
+ const collectedChunks = extractor ? extractor.getChunkAssets(extractor.chunks) : [];
92
+ const matchedRouteChunks = this.getMatchedRouteChunks();
93
+ const orderedScriptChunks = orderHydrationScriptChunks({
94
+ asyncEntryChunks: asyncChunks,
95
+ collectedChunks,
96
+ matchedRouteChunks,
97
+ entryName
98
+ });
99
+ const chunks = [].concat(asyncChunks).concat(collectedChunks).concat(matchedRouteChunks);
100
+ const scriptChunks = generateChunks(orderedScriptChunks, 'js');
66
101
  const styleChunks = generateChunks(chunks, 'css');
67
102
  if (extractor) {
68
103
  this.emitLoadableScripts(extractor);
@@ -139,4 +174,4 @@ class LoadableCollector {
139
174
  this.options = options;
140
175
  }
141
176
  }
142
- export { LoadableCollector };
177
+ export { LoadableCollector, orderHydrationScriptChunks };
@@ -0,0 +1 @@
1
+ export declare function injectBeforeHydrationEntryScript(template: string, scripts: string, entryName?: string): string;
@@ -1,3 +1,4 @@
1
+ import { type Chunk } from '@loadable/server';
1
2
  import type { ReactElement } from 'react';
2
3
  import type { TInternalRuntimeContext } from '../../context';
3
4
  import type { ChunkSet, Collector } from './types';
@@ -7,6 +8,12 @@ declare module '@loadable/server' {
7
8
  getChunkAssets: (chunks: string[]) => Chunk[];
8
9
  }
9
10
  }
11
+ export declare const orderHydrationScriptChunks: ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName, }: {
12
+ asyncEntryChunks: Chunk[];
13
+ collectedChunks: Chunk[];
14
+ matchedRouteChunks: Chunk[];
15
+ entryName: string;
16
+ }) => Chunk[];
10
17
  export interface LoadableCollectorOptions {
11
18
  nonce?: string;
12
19
  stats?: Record<string, any>;
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.2.0-ultramodern.97",
20
+ "version": "3.2.0-ultramodern.99",
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
@@ -233,12 +233,12 @@
233
233
  "isbot": "5.1.40",
234
234
  "react-helmet-async": "3.0.0",
235
235
  "react-is": "^19.2.6",
236
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.97",
237
- "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.2.0-ultramodern.97",
238
- "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.2.0-ultramodern.97",
239
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.97",
240
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.97",
241
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.97"
236
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.99",
237
+ "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.2.0-ultramodern.99",
238
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.99",
239
+ "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.2.0-ultramodern.99",
240
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.99",
241
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.99"
242
242
  },
243
243
  "peerDependencies": {
244
244
  "react": "^19.2.6",
@@ -258,7 +258,7 @@
258
258
  "react": "^19.2.6",
259
259
  "react-dom": "^19.2.6",
260
260
  "@scripts/rstest-config": "2.66.0",
261
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.97"
261
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.99"
262
262
  },
263
263
  "sideEffects": false,
264
264
  "publishConfig": {