@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.
- package/dist/cjs/core/server/scriptOrder.js +59 -0
- package/dist/cjs/core/server/stream/afterTemplate.js +6 -1
- package/dist/cjs/core/server/string/index.js +12 -5
- package/dist/cjs/core/server/string/loadable.js +42 -4
- package/dist/esm/core/server/scriptOrder.mjs +25 -0
- package/dist/esm/core/server/stream/afterTemplate.mjs +6 -1
- package/dist/esm/core/server/string/index.mjs +12 -5
- package/dist/esm/core/server/string/loadable.mjs +38 -3
- package/dist/esm-node/core/server/scriptOrder.mjs +26 -0
- package/dist/esm-node/core/server/stream/afterTemplate.mjs +6 -1
- package/dist/esm-node/core/server/string/index.mjs +12 -5
- package/dist/esm-node/core/server/string/loadable.mjs +38 -3
- package/dist/types/core/server/scriptOrder.d.ts +1 -0
- package/dist/types/core/server/string/loadable.d.ts +7 -0
- package/package.json +8 -8
|
@@ -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)
|
|
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)=>
|
|
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
|
|
89
|
-
const
|
|
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)
|
|
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)=>
|
|
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
|
|
61
|
-
const
|
|
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)
|
|
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)=>
|
|
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
|
|
65
|
-
const
|
|
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.
|
|
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.
|
|
237
|
-
"@modern-js/
|
|
238
|
-
"@modern-js/
|
|
239
|
-
"@modern-js/
|
|
240
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.
|
|
241
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.
|
|
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.
|
|
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": {
|