@modern-js/runtime 3.3.0 → 3.5.0
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/stream/beforeTemplate.js +1 -1
- package/dist/cjs/core/server/stream/createReadableStream.js +7 -5
- package/dist/cjs/core/server/stream/createReadableStream.worker.js +6 -2
- package/dist/cjs/core/server/string/loadable.js +1 -5
- package/dist/cjs/core/server/utils.js +23 -0
- package/dist/cjs/document/cli/index.js +3 -6
- package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -2
- package/dist/esm/core/server/stream/createReadableStream.mjs +7 -5
- package/dist/esm/core/server/stream/createReadableStream.worker.mjs +6 -2
- package/dist/esm/core/server/string/loadable.mjs +2 -6
- package/dist/esm/core/server/utils.mjs +21 -1
- package/dist/esm/document/cli/index.mjs +3 -6
- package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -2
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +7 -5
- package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +6 -2
- package/dist/esm-node/core/server/string/loadable.mjs +2 -6
- package/dist/esm-node/core/server/utils.mjs +21 -1
- package/dist/esm-node/document/cli/index.mjs +3 -6
- package/dist/types/core/server/utils.d.ts +7 -0
- package/package.json +11 -11
|
@@ -86,7 +86,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
|
|
|
86
86
|
if (asyncEntry) matchedRouteManifests?.push(asyncEntry);
|
|
87
87
|
const cssChunks = matchedRouteManifests ? matchedRouteManifests?.reduce((chunks, routeManifest)=>{
|
|
88
88
|
const { referenceCssAssets = [] } = routeManifest;
|
|
89
|
-
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !
|
|
89
|
+
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !(0, external_utils_js_namespaceObject.hasStylesheetLink)(template, asset));
|
|
90
90
|
return [
|
|
91
91
|
...chunks,
|
|
92
92
|
..._cssChunks
|
|
@@ -92,12 +92,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
92
92
|
try {
|
|
93
93
|
if (shellChunkStatus !== external_shared_js_namespaceObject.ShellChunkStatus.FINISH) {
|
|
94
94
|
chunkVec.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const concatedChunk = Buffer.concat(chunkVec).toString('utf-8');
|
|
96
|
+
const markerIndex = concatedChunk.indexOf(external_common_js_namespaceObject.ESCAPED_SHELL_STREAM_END_MARK);
|
|
97
|
+
if (-1 !== markerIndex) {
|
|
98
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
99
|
+
const afterMark = concatedChunk.slice(markerIndex + external_common_js_namespaceObject.ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
99
100
|
shellChunkStatus = external_shared_js_namespaceObject.ShellChunkStatus.FINISH;
|
|
100
|
-
this.push(`${shellBefore}${
|
|
101
|
+
this.push(`${shellBefore}${beforeMark}${shellAfter}`);
|
|
102
|
+
if (afterMark) this.push(afterMark);
|
|
101
103
|
if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
|
|
102
104
|
}
|
|
103
105
|
} else this.push(chunk);
|
|
@@ -107,9 +107,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
107
107
|
if (shellChunkStatus !== external_shared_js_namespaceObject.ShellChunkStatus.FINISH) {
|
|
108
108
|
chunkVec.push(new TextDecoder().decode(value));
|
|
109
109
|
const concatedChunk = chunkVec.join('');
|
|
110
|
-
|
|
110
|
+
const markerIndex = concatedChunk.indexOf(external_common_js_namespaceObject.ESCAPED_SHELL_STREAM_END_MARK);
|
|
111
|
+
if (-1 !== markerIndex) {
|
|
112
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
113
|
+
const afterMark = concatedChunk.slice(markerIndex + external_common_js_namespaceObject.ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
111
114
|
shellChunkStatus = external_shared_js_namespaceObject.ShellChunkStatus.FINISH;
|
|
112
|
-
safeEnqueue((0, external_shared_js_namespaceObject.encodeForWebStream)(`${shellBefore}${
|
|
115
|
+
safeEnqueue((0, external_shared_js_namespaceObject.encodeForWebStream)(`${shellBefore}${beforeMark}${shellAfter}`));
|
|
116
|
+
if (afterMark) safeEnqueue((0, external_shared_js_namespaceObject.encodeForWebStream)(afterMark));
|
|
113
117
|
flushPendingScripts();
|
|
114
118
|
}
|
|
115
119
|
} else safeEnqueue(value);
|
|
@@ -116,11 +116,7 @@ class LoadableCollector {
|
|
|
116
116
|
const { template, chunkSet, config, entryName } = this.options;
|
|
117
117
|
const { inlineStyles } = config;
|
|
118
118
|
const atrributes = (0, external_utils_js_namespaceObject.attributesToString)(this.generateAttributes());
|
|
119
|
-
const
|
|
120
|
-
const matchs = template.matchAll(linkRegExp);
|
|
121
|
-
const existedLinks = [];
|
|
122
|
-
for (const match of matchs)existedLinks.push(match[1]);
|
|
123
|
-
const css = await Promise.all(chunks.filter((chunk)=>!existedLinks.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
119
|
+
const css = await Promise.all(chunks.filter((chunk)=>!(0, external_utils_js_namespaceObject.hasStylesheetLink)(template, chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
124
120
|
const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
|
|
125
121
|
if ((0, external_utils_js_namespaceObject.checkIsNode)() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
|
|
126
122
|
return link;
|
|
@@ -32,6 +32,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
32
32
|
checkIsNode: ()=>checkIsNode,
|
|
33
33
|
getSSRConfigByEntry: ()=>getSSRConfigByEntry,
|
|
34
34
|
getSSRMode: ()=>getSSRMode,
|
|
35
|
+
hasStylesheetLink: ()=>hasStylesheetLink,
|
|
35
36
|
safeReplace: ()=>safeReplace,
|
|
36
37
|
serializeErrors: ()=>serializeErrors
|
|
37
38
|
});
|
|
@@ -73,10 +74,31 @@ function getSSRMode(ssrConfig) {
|
|
|
73
74
|
const result = ssrConfig?.mode === 'string' ? 'string' : 'stream';
|
|
74
75
|
return result;
|
|
75
76
|
}
|
|
77
|
+
const getLinkAttributes = (linkTag)=>{
|
|
78
|
+
const attributes = new Map();
|
|
79
|
+
const attributeRegExp = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
80
|
+
let match;
|
|
81
|
+
while(match = attributeRegExp.exec(linkTag)){
|
|
82
|
+
const [, name, doubleQuotedValue, singleQuotedValue, unquotedValue] = match;
|
|
83
|
+
if ('link' === name.toLowerCase()) continue;
|
|
84
|
+
attributes.set(name.toLowerCase(), doubleQuotedValue ?? singleQuotedValue ?? unquotedValue ?? '');
|
|
85
|
+
}
|
|
86
|
+
return attributes;
|
|
87
|
+
};
|
|
88
|
+
const hasStylesheetLink = (template, href)=>{
|
|
89
|
+
const linkTags = template.match(/<link\b[^>]*>/gi) ?? [];
|
|
90
|
+
return linkTags.some((linkTag)=>{
|
|
91
|
+
const attributes = getLinkAttributes(linkTag);
|
|
92
|
+
const linkHref = attributes.get('href');
|
|
93
|
+
const rel = attributes.get('rel');
|
|
94
|
+
return linkHref === href && rel?.split(/\s+/).some((relToken)=>'stylesheet' === relToken.toLowerCase());
|
|
95
|
+
});
|
|
96
|
+
};
|
|
76
97
|
exports.attributesToString = __webpack_exports__.attributesToString;
|
|
77
98
|
exports.checkIsNode = __webpack_exports__.checkIsNode;
|
|
78
99
|
exports.getSSRConfigByEntry = __webpack_exports__.getSSRConfigByEntry;
|
|
79
100
|
exports.getSSRMode = __webpack_exports__.getSSRMode;
|
|
101
|
+
exports.hasStylesheetLink = __webpack_exports__.hasStylesheetLink;
|
|
80
102
|
exports.safeReplace = __webpack_exports__.safeReplace;
|
|
81
103
|
exports.serializeErrors = __webpack_exports__.serializeErrors;
|
|
82
104
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
@@ -84,6 +106,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
84
106
|
"checkIsNode",
|
|
85
107
|
"getSSRConfigByEntry",
|
|
86
108
|
"getSSRMode",
|
|
109
|
+
"hasStylesheetLink",
|
|
87
110
|
"safeReplace",
|
|
88
111
|
"serializeErrors"
|
|
89
112
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
@@ -340,12 +340,13 @@ const documentPlugin = ()=>({
|
|
|
340
340
|
processedHtml = processCommentPlaceholders(processedHtml);
|
|
341
341
|
return `<!DOCTYPE html>${processedHtml}`.replace(external_constants_js_namespaceObject.DOCUMENT_META_PLACEHOLDER, ()=>metas).replace(external_constants_js_namespaceObject.DOCUMENT_SSR_PLACEHOLDER, ()=>external_constants_js_namespaceObject.HTML_SEPARATOR).replace(external_constants_js_namespaceObject.DOCUMENT_SCRIPTS_PLACEHOLDER, ()=>scripts).replace(external_constants_js_namespaceObject.DOCUMENT_LINKS_PLACEHOLDER, ()=>links).replace(external_constants_js_namespaceObject.DOCUMENT_CHUNKSMAP_PLACEHOLDER, ()=>external_constants_js_namespaceObject.PLACEHOLDER_REPLACER_MAP[external_constants_js_namespaceObject.DOCUMENT_CHUNKSMAP_PLACEHOLDER]).replace(external_constants_js_namespaceObject.DOCUMENT_SSRDATASCRIPT_PLACEHOLDER, ()=>external_constants_js_namespaceObject.PLACEHOLDER_REPLACER_MAP[external_constants_js_namespaceObject.DOCUMENT_SSRDATASCRIPT_PLACEHOLDER]).replace(external_constants_js_namespaceObject.DOCUMENT_TITLE_PLACEHOLDER, ()=>titles);
|
|
342
342
|
};
|
|
343
|
-
const documentEntry = (entryName
|
|
343
|
+
const documentEntry = (entryName)=>{
|
|
344
344
|
const { entrypoints, internalDirectory, appDirectory } = api.getAppContext();
|
|
345
345
|
const documentFilePath = getDocumentByEntryName(entrypoints, entryName, appDirectory);
|
|
346
346
|
if (!documentFilePath) return null;
|
|
347
347
|
return async (templateData)=>{
|
|
348
348
|
const config = api.getNormalizedConfig();
|
|
349
|
+
const { compilation: _compilation, htmlPlugin, rspackConfig: _rspackConfig, ...templateParameters } = templateData;
|
|
349
350
|
const documentParams = getDocParams({
|
|
350
351
|
config: config,
|
|
351
352
|
entryName,
|
|
@@ -363,7 +364,6 @@ const documentPlugin = ()=>({
|
|
|
363
364
|
debug("entry %s's document jsx rendered html: %o", entryName, html);
|
|
364
365
|
const { partialsByEntrypoint } = api.getAppContext();
|
|
365
366
|
html = processPartials(html, entryName, partialsByEntrypoint || {});
|
|
366
|
-
const htmlPlugin = templateData.htmlPlugin || templateData.htmlWebpackPlugin || templateData.htmlRspackPlugin;
|
|
367
367
|
if (!htmlPlugin) throw new Error('Failed to get HTML plugin tags from template parameters.');
|
|
368
368
|
const { scripts, links, metas, titles } = extractHtmlTags(htmlPlugin, templateParameters);
|
|
369
369
|
return processPlaceholders(html, config, scripts, links, metas, titles);
|
|
@@ -375,10 +375,7 @@ const documentPlugin = ()=>({
|
|
|
375
375
|
return {
|
|
376
376
|
tools: {
|
|
377
377
|
htmlPlugin: (options, entry)=>{
|
|
378
|
-
const
|
|
379
|
-
...options?.templateParameters
|
|
380
|
-
};
|
|
381
|
-
const templateContent = documentEntry(entry.entryName, hackParameters);
|
|
378
|
+
const templateContent = documentEntry(entry.entryName);
|
|
382
379
|
const documentHtmlOptions = templateContent ? {
|
|
383
380
|
templateContent,
|
|
384
381
|
inject: false
|
|
@@ -3,7 +3,7 @@ import react_helmet from "react-helmet";
|
|
|
3
3
|
import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
|
|
4
4
|
import { createReplaceHelemt } from "../helmet.mjs";
|
|
5
5
|
import { buildHtml } from "../shared.mjs";
|
|
6
|
-
import { checkIsNode, safeReplace } from "../utils.mjs";
|
|
6
|
+
import { checkIsNode, hasStylesheetLink, safeReplace } from "../utils.mjs";
|
|
7
7
|
const readAsset = async (chunk)=>{
|
|
8
8
|
const fs = await import("fs/promises");
|
|
9
9
|
const path = await import("path");
|
|
@@ -44,7 +44,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
|
|
|
44
44
|
if (asyncEntry) matchedRouteManifests?.push(asyncEntry);
|
|
45
45
|
const cssChunks = matchedRouteManifests ? matchedRouteManifests?.reduce((chunks, routeManifest)=>{
|
|
46
46
|
const { referenceCssAssets = [] } = routeManifest;
|
|
47
|
-
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !template
|
|
47
|
+
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !hasStylesheetLink(template, asset));
|
|
48
48
|
return [
|
|
49
49
|
...chunks,
|
|
50
50
|
..._cssChunks
|
|
@@ -60,12 +60,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
60
60
|
try {
|
|
61
61
|
if (shellChunkStatus !== ShellChunkStatus.FINISH) {
|
|
62
62
|
chunkVec.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const concatedChunk = Buffer.concat(chunkVec).toString('utf-8');
|
|
64
|
+
const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
|
|
65
|
+
if (-1 !== markerIndex) {
|
|
66
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
67
|
+
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
67
68
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
68
|
-
this.push(`${shellBefore}${
|
|
69
|
+
this.push(`${shellBefore}${beforeMark}${shellAfter}`);
|
|
70
|
+
if (afterMark) this.push(afterMark);
|
|
69
71
|
if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
|
|
70
72
|
}
|
|
71
73
|
} else this.push(chunk);
|
|
@@ -75,9 +75,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
75
75
|
if (shellChunkStatus !== ShellChunkStatus.FINISH) {
|
|
76
76
|
chunkVec.push(new TextDecoder().decode(value));
|
|
77
77
|
const concatedChunk = chunkVec.join('');
|
|
78
|
-
|
|
78
|
+
const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
|
|
79
|
+
if (-1 !== markerIndex) {
|
|
80
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
81
|
+
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
79
82
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
80
|
-
safeEnqueue(encodeForWebStream(`${shellBefore}${
|
|
83
|
+
safeEnqueue(encodeForWebStream(`${shellBefore}${beforeMark}${shellAfter}`));
|
|
84
|
+
if (afterMark) safeEnqueue(encodeForWebStream(afterMark));
|
|
81
85
|
flushPendingScripts();
|
|
82
86
|
}
|
|
83
87
|
} else safeEnqueue(value);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChunkExtractor } from "@loadable/server";
|
|
2
|
-
import { attributesToString, checkIsNode } from "../utils.mjs";
|
|
2
|
+
import { attributesToString, checkIsNode, hasStylesheetLink } from "../utils.mjs";
|
|
3
3
|
const extname = (uri)=>{
|
|
4
4
|
if ('string' != typeof uri || !uri.includes('.')) return '';
|
|
5
5
|
return `.${uri?.split('.').pop()}` || '';
|
|
@@ -84,11 +84,7 @@ class LoadableCollector {
|
|
|
84
84
|
const { template, chunkSet, config, entryName } = this.options;
|
|
85
85
|
const { inlineStyles } = config;
|
|
86
86
|
const atrributes = attributesToString(this.generateAttributes());
|
|
87
|
-
const
|
|
88
|
-
const matchs = template.matchAll(linkRegExp);
|
|
89
|
-
const existedLinks = [];
|
|
90
|
-
for (const match of matchs)existedLinks.push(match[1]);
|
|
91
|
-
const css = await Promise.all(chunks.filter((chunk)=>!existedLinks.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
87
|
+
const css = await Promise.all(chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
92
88
|
const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
|
|
93
89
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
|
|
94
90
|
return link;
|
|
@@ -36,4 +36,24 @@ function getSSRMode(ssrConfig) {
|
|
|
36
36
|
const result = ssrConfig?.mode === 'string' ? 'string' : 'stream';
|
|
37
37
|
return result;
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
const getLinkAttributes = (linkTag)=>{
|
|
40
|
+
const attributes = new Map();
|
|
41
|
+
const attributeRegExp = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
42
|
+
let match;
|
|
43
|
+
while(match = attributeRegExp.exec(linkTag)){
|
|
44
|
+
const [, name, doubleQuotedValue, singleQuotedValue, unquotedValue] = match;
|
|
45
|
+
if ('link' === name.toLowerCase()) continue;
|
|
46
|
+
attributes.set(name.toLowerCase(), doubleQuotedValue ?? singleQuotedValue ?? unquotedValue ?? '');
|
|
47
|
+
}
|
|
48
|
+
return attributes;
|
|
49
|
+
};
|
|
50
|
+
const hasStylesheetLink = (template, href)=>{
|
|
51
|
+
const linkTags = template.match(/<link\b[^>]*>/gi) ?? [];
|
|
52
|
+
return linkTags.some((linkTag)=>{
|
|
53
|
+
const attributes = getLinkAttributes(linkTag);
|
|
54
|
+
const linkHref = attributes.get('href');
|
|
55
|
+
const rel = attributes.get('rel');
|
|
56
|
+
return linkHref === href && rel?.split(/\s+/).some((relToken)=>'stylesheet' === relToken.toLowerCase());
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
export { attributesToString, checkIsNode, getSSRConfigByEntry, getSSRMode, hasStylesheetLink, safeReplace, serializeErrors };
|
|
@@ -295,12 +295,13 @@ const documentPlugin = ()=>({
|
|
|
295
295
|
processedHtml = processCommentPlaceholders(processedHtml);
|
|
296
296
|
return `<!DOCTYPE html>${processedHtml}`.replace(DOCUMENT_META_PLACEHOLDER, ()=>metas).replace(DOCUMENT_SSR_PLACEHOLDER, ()=>HTML_SEPARATOR).replace(DOCUMENT_SCRIPTS_PLACEHOLDER, ()=>scripts).replace(DOCUMENT_LINKS_PLACEHOLDER, ()=>links).replace(DOCUMENT_CHUNKSMAP_PLACEHOLDER, ()=>PLACEHOLDER_REPLACER_MAP[DOCUMENT_CHUNKSMAP_PLACEHOLDER]).replace(DOCUMENT_SSRDATASCRIPT_PLACEHOLDER, ()=>PLACEHOLDER_REPLACER_MAP[DOCUMENT_SSRDATASCRIPT_PLACEHOLDER]).replace(DOCUMENT_TITLE_PLACEHOLDER, ()=>titles);
|
|
297
297
|
};
|
|
298
|
-
const documentEntry = (entryName
|
|
298
|
+
const documentEntry = (entryName)=>{
|
|
299
299
|
const { entrypoints, internalDirectory, appDirectory } = api.getAppContext();
|
|
300
300
|
const documentFilePath = getDocumentByEntryName(entrypoints, entryName, appDirectory);
|
|
301
301
|
if (!documentFilePath) return null;
|
|
302
302
|
return async (templateData)=>{
|
|
303
303
|
const config = api.getNormalizedConfig();
|
|
304
|
+
const { compilation: _compilation, htmlPlugin, rspackConfig: _rspackConfig, ...templateParameters } = templateData;
|
|
304
305
|
const documentParams = getDocParams({
|
|
305
306
|
config: config,
|
|
306
307
|
entryName,
|
|
@@ -318,7 +319,6 @@ const documentPlugin = ()=>({
|
|
|
318
319
|
debug("entry %s's document jsx rendered html: %o", entryName, html);
|
|
319
320
|
const { partialsByEntrypoint } = api.getAppContext();
|
|
320
321
|
html = processPartials(html, entryName, partialsByEntrypoint || {});
|
|
321
|
-
const htmlPlugin = templateData.htmlPlugin || templateData.htmlWebpackPlugin || templateData.htmlRspackPlugin;
|
|
322
322
|
if (!htmlPlugin) throw new Error('Failed to get HTML plugin tags from template parameters.');
|
|
323
323
|
const { scripts, links, metas, titles } = extractHtmlTags(htmlPlugin, templateParameters);
|
|
324
324
|
return processPlaceholders(html, config, scripts, links, metas, titles);
|
|
@@ -330,10 +330,7 @@ const documentPlugin = ()=>({
|
|
|
330
330
|
return {
|
|
331
331
|
tools: {
|
|
332
332
|
htmlPlugin: (options, entry)=>{
|
|
333
|
-
const
|
|
334
|
-
...options?.templateParameters
|
|
335
|
-
};
|
|
336
|
-
const templateContent = documentEntry(entry.entryName, hackParameters);
|
|
333
|
+
const templateContent = documentEntry(entry.entryName);
|
|
337
334
|
const documentHtmlOptions = templateContent ? {
|
|
338
335
|
templateContent,
|
|
339
336
|
inject: false
|
|
@@ -4,7 +4,7 @@ import react_helmet from "react-helmet";
|
|
|
4
4
|
import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
|
|
5
5
|
import { createReplaceHelemt } from "../helmet.mjs";
|
|
6
6
|
import { buildHtml } from "../shared.mjs";
|
|
7
|
-
import { checkIsNode, safeReplace } from "../utils.mjs";
|
|
7
|
+
import { checkIsNode, hasStylesheetLink, safeReplace } from "../utils.mjs";
|
|
8
8
|
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
9
9
|
import { dirname as __rspack_dirname } from "node:path";
|
|
10
10
|
var beforeTemplate_dirname = __rspack_dirname(__rspack_fileURLToPath(import.meta.url));
|
|
@@ -48,7 +48,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
|
|
|
48
48
|
if (asyncEntry) matchedRouteManifests?.push(asyncEntry);
|
|
49
49
|
const cssChunks = matchedRouteManifests ? matchedRouteManifests?.reduce((chunks, routeManifest)=>{
|
|
50
50
|
const { referenceCssAssets = [] } = routeManifest;
|
|
51
|
-
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !template
|
|
51
|
+
const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !hasStylesheetLink(template, asset));
|
|
52
52
|
return [
|
|
53
53
|
...chunks,
|
|
54
54
|
..._cssChunks
|
|
@@ -61,12 +61,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
61
61
|
try {
|
|
62
62
|
if (shellChunkStatus !== ShellChunkStatus.FINISH) {
|
|
63
63
|
chunkVec.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const concatedChunk = Buffer.concat(chunkVec).toString('utf-8');
|
|
65
|
+
const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
|
|
66
|
+
if (-1 !== markerIndex) {
|
|
67
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
68
|
+
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
68
69
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
69
|
-
this.push(`${shellBefore}${
|
|
70
|
+
this.push(`${shellBefore}${beforeMark}${shellAfter}`);
|
|
71
|
+
if (afterMark) this.push(afterMark);
|
|
70
72
|
if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
|
|
71
73
|
}
|
|
72
74
|
} else this.push(chunk);
|
|
@@ -76,9 +76,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
76
76
|
if (shellChunkStatus !== ShellChunkStatus.FINISH) {
|
|
77
77
|
chunkVec.push(new TextDecoder().decode(value));
|
|
78
78
|
const concatedChunk = chunkVec.join('');
|
|
79
|
-
|
|
79
|
+
const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
|
|
80
|
+
if (-1 !== markerIndex) {
|
|
81
|
+
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
82
|
+
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
80
83
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
81
|
-
safeEnqueue(encodeForWebStream(`${shellBefore}${
|
|
84
|
+
safeEnqueue(encodeForWebStream(`${shellBefore}${beforeMark}${shellAfter}`));
|
|
85
|
+
if (afterMark) safeEnqueue(encodeForWebStream(afterMark));
|
|
82
86
|
flushPendingScripts();
|
|
83
87
|
}
|
|
84
88
|
} else safeEnqueue(value);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { ChunkExtractor } from "@loadable/server";
|
|
3
|
-
import { attributesToString, checkIsNode } from "../utils.mjs";
|
|
3
|
+
import { attributesToString, checkIsNode, hasStylesheetLink } from "../utils.mjs";
|
|
4
4
|
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
5
5
|
import { dirname as __rspack_dirname } from "node:path";
|
|
6
6
|
var loadable_dirname = __rspack_dirname(__rspack_fileURLToPath(import.meta.url));
|
|
@@ -88,11 +88,7 @@ class LoadableCollector {
|
|
|
88
88
|
const { template, chunkSet, config, entryName } = this.options;
|
|
89
89
|
const { inlineStyles } = config;
|
|
90
90
|
const atrributes = attributesToString(this.generateAttributes());
|
|
91
|
-
const
|
|
92
|
-
const matchs = template.matchAll(linkRegExp);
|
|
93
|
-
const existedLinks = [];
|
|
94
|
-
for (const match of matchs)existedLinks.push(match[1]);
|
|
95
|
-
const css = await Promise.all(chunks.filter((chunk)=>!existedLinks.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
91
|
+
const css = await Promise.all(chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
96
92
|
const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
|
|
97
93
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
|
|
98
94
|
return link;
|
|
@@ -37,4 +37,24 @@ function getSSRMode(ssrConfig) {
|
|
|
37
37
|
const result = ssrConfig?.mode === 'string' ? 'string' : 'stream';
|
|
38
38
|
return result;
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
const getLinkAttributes = (linkTag)=>{
|
|
41
|
+
const attributes = new Map();
|
|
42
|
+
const attributeRegExp = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
43
|
+
let match;
|
|
44
|
+
while(match = attributeRegExp.exec(linkTag)){
|
|
45
|
+
const [, name, doubleQuotedValue, singleQuotedValue, unquotedValue] = match;
|
|
46
|
+
if ('link' === name.toLowerCase()) continue;
|
|
47
|
+
attributes.set(name.toLowerCase(), doubleQuotedValue ?? singleQuotedValue ?? unquotedValue ?? '');
|
|
48
|
+
}
|
|
49
|
+
return attributes;
|
|
50
|
+
};
|
|
51
|
+
const hasStylesheetLink = (template, href)=>{
|
|
52
|
+
const linkTags = template.match(/<link\b[^>]*>/gi) ?? [];
|
|
53
|
+
return linkTags.some((linkTag)=>{
|
|
54
|
+
const attributes = getLinkAttributes(linkTag);
|
|
55
|
+
const linkHref = attributes.get('href');
|
|
56
|
+
const rel = attributes.get('rel');
|
|
57
|
+
return linkHref === href && rel?.split(/\s+/).some((relToken)=>'stylesheet' === relToken.toLowerCase());
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
export { attributesToString, checkIsNode, getSSRConfigByEntry, getSSRMode, hasStylesheetLink, safeReplace, serializeErrors };
|
|
@@ -297,12 +297,13 @@ const documentPlugin = ()=>({
|
|
|
297
297
|
processedHtml = processCommentPlaceholders(processedHtml);
|
|
298
298
|
return `<!DOCTYPE html>${processedHtml}`.replace(DOCUMENT_META_PLACEHOLDER, ()=>metas).replace(DOCUMENT_SSR_PLACEHOLDER, ()=>HTML_SEPARATOR).replace(DOCUMENT_SCRIPTS_PLACEHOLDER, ()=>scripts).replace(DOCUMENT_LINKS_PLACEHOLDER, ()=>links).replace(DOCUMENT_CHUNKSMAP_PLACEHOLDER, ()=>PLACEHOLDER_REPLACER_MAP[DOCUMENT_CHUNKSMAP_PLACEHOLDER]).replace(DOCUMENT_SSRDATASCRIPT_PLACEHOLDER, ()=>PLACEHOLDER_REPLACER_MAP[DOCUMENT_SSRDATASCRIPT_PLACEHOLDER]).replace(DOCUMENT_TITLE_PLACEHOLDER, ()=>titles);
|
|
299
299
|
};
|
|
300
|
-
const documentEntry = (entryName
|
|
300
|
+
const documentEntry = (entryName)=>{
|
|
301
301
|
const { entrypoints, internalDirectory, appDirectory } = api.getAppContext();
|
|
302
302
|
const documentFilePath = getDocumentByEntryName(entrypoints, entryName, appDirectory);
|
|
303
303
|
if (!documentFilePath) return null;
|
|
304
304
|
return async (templateData)=>{
|
|
305
305
|
const config = api.getNormalizedConfig();
|
|
306
|
+
const { compilation: _compilation, htmlPlugin, rspackConfig: _rspackConfig, ...templateParameters } = templateData;
|
|
306
307
|
const documentParams = getDocParams({
|
|
307
308
|
config: config,
|
|
308
309
|
entryName,
|
|
@@ -320,7 +321,6 @@ const documentPlugin = ()=>({
|
|
|
320
321
|
debug("entry %s's document jsx rendered html: %o", entryName, html);
|
|
321
322
|
const { partialsByEntrypoint } = api.getAppContext();
|
|
322
323
|
html = processPartials(html, entryName, partialsByEntrypoint || {});
|
|
323
|
-
const htmlPlugin = templateData.htmlPlugin || templateData.htmlWebpackPlugin || templateData.htmlRspackPlugin;
|
|
324
324
|
if (!htmlPlugin) throw new Error('Failed to get HTML plugin tags from template parameters.');
|
|
325
325
|
const { scripts, links, metas, titles } = extractHtmlTags(htmlPlugin, templateParameters);
|
|
326
326
|
return processPlaceholders(html, config, scripts, links, metas, titles);
|
|
@@ -332,10 +332,7 @@ const documentPlugin = ()=>({
|
|
|
332
332
|
return {
|
|
333
333
|
tools: {
|
|
334
334
|
htmlPlugin: (options, entry)=>{
|
|
335
|
-
const
|
|
336
|
-
...options?.templateParameters
|
|
337
|
-
};
|
|
338
|
-
const templateContent = documentEntry(entry.entryName, hackParameters);
|
|
335
|
+
const templateContent = documentEntry(entry.entryName);
|
|
339
336
|
const documentHtmlOptions = templateContent ? {
|
|
340
337
|
templateContent,
|
|
341
338
|
inject: false
|
|
@@ -23,3 +23,10 @@ export declare function getSSRConfigByEntry(entryName: string, ssr?: ServerUserC
|
|
|
23
23
|
loaderFailureMode?: "clientRender" | "errorBoundary";
|
|
24
24
|
};
|
|
25
25
|
export declare function getSSRMode(ssrConfig?: SSRConfig): 'string' | 'stream' | false;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the template already contains a `<link rel="stylesheet">` for `href`.
|
|
28
|
+
* Other link rels (e.g. `<link rel="prefetch">` emitted by `performance.prefetch`,
|
|
29
|
+
* or `<link rel="preload" as="style">`) may reference the same css URL but do not
|
|
30
|
+
* apply styles, so they must not block stylesheet injection during SSR.
|
|
31
|
+
*/
|
|
32
|
+
export declare const hasStylesheetLink: (template: string, href: string) => boolean;
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"modern",
|
|
16
16
|
"modern.js"
|
|
17
17
|
],
|
|
18
|
-
"version": "3.
|
|
18
|
+
"version": "3.5.0",
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"dependencies": {
|
|
204
204
|
"@loadable/component": "5.16.7",
|
|
205
205
|
"@loadable/server": "5.16.7",
|
|
206
|
-
"@swc/core": "1.15.
|
|
206
|
+
"@swc/core": "1.15.41",
|
|
207
207
|
"@swc/helpers": "^0.5.17",
|
|
208
208
|
"@swc/plugin-loadable-components": "^11.12.0",
|
|
209
209
|
"@types/loadable__component": "^5.13.10",
|
|
@@ -215,12 +215,12 @@
|
|
|
215
215
|
"isbot": "3.8.0",
|
|
216
216
|
"react-helmet": "^6.1.0",
|
|
217
217
|
"react-is": "^18.3.1",
|
|
218
|
-
"@modern-js/plugin": "3.
|
|
219
|
-
"@modern-js/
|
|
220
|
-
"@modern-js/
|
|
221
|
-
"@modern-js/runtime-utils": "3.
|
|
222
|
-
"@modern-js/types": "3.
|
|
223
|
-
"@modern-js/utils": "3.
|
|
218
|
+
"@modern-js/plugin": "3.5.0",
|
|
219
|
+
"@modern-js/plugin-data-loader": "3.5.0",
|
|
220
|
+
"@modern-js/render": "3.5.0",
|
|
221
|
+
"@modern-js/runtime-utils": "3.5.0",
|
|
222
|
+
"@modern-js/types": "3.5.0",
|
|
223
|
+
"@modern-js/utils": "3.5.0"
|
|
224
224
|
},
|
|
225
225
|
"peerDependencies": {
|
|
226
226
|
"react": ">=17.0.2",
|
|
@@ -228,8 +228,8 @@
|
|
|
228
228
|
},
|
|
229
229
|
"devDependencies": {
|
|
230
230
|
"@remix-run/web-fetch": "^4.1.3",
|
|
231
|
-
"@rsbuild/core": "2.0
|
|
232
|
-
"@rslib/core": "0.
|
|
231
|
+
"@rsbuild/core": "2.1.0",
|
|
232
|
+
"@rslib/core": "0.23.0",
|
|
233
233
|
"@testing-library/dom": "^10.4.1",
|
|
234
234
|
"@testing-library/react": "^16.3.2",
|
|
235
235
|
"@types/cookie": "0.6.0",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"react-dom": "^19.2.7",
|
|
241
241
|
"ts-node": "^10.9.2",
|
|
242
242
|
"typescript": "^5",
|
|
243
|
-
"@modern-js/app-tools": "3.
|
|
243
|
+
"@modern-js/app-tools": "3.5.0",
|
|
244
244
|
"@modern-js/rslib": "2.68.10",
|
|
245
245
|
"@scripts/rstest-config": "2.66.0"
|
|
246
246
|
},
|