@bleedingdev/modern-js-runtime 3.8.3-ultramodern.1 → 3.8.3-ultramodern.3

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.
Files changed (81) hide show
  1. package/dist/cjs/cli/ssr/loadable-bundler-plugin.js +1 -1
  2. package/dist/cjs/core/browser/hydrate.js +1 -11
  3. package/dist/cjs/core/context/extensions.js +7 -2
  4. package/dist/cjs/core/context/runtime.js +13 -2
  5. package/dist/cjs/core/react/wrapper.js +10 -8
  6. package/dist/cjs/core/server/federatedCss.js +7 -4
  7. package/dist/cjs/core/server/helmet.js +2 -0
  8. package/dist/cjs/core/server/requestResponse.js +35 -11
  9. package/dist/cjs/core/server/routerCleanup.js +10 -1
  10. package/dist/cjs/core/server/scriptOrder.js +5 -2
  11. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -1
  12. package/dist/cjs/core/server/stream/createReadableStream.js +18 -5
  13. package/dist/cjs/core/server/stream/createReadableStream.worker.js +10 -2
  14. package/dist/cjs/core/server/stream/deferredScript.js +2 -1
  15. package/dist/cjs/core/server/string/index.js +4 -0
  16. package/dist/cjs/core/server/string/loadable.js +11 -2
  17. package/dist/cjs/core/server/utils.js +12 -5
  18. package/dist/cjs/exports/head.js +7 -242
  19. package/dist/cjs/module-federation/last-known-good.js +35 -2
  20. package/dist/cjs/router/cli/nestedRoutesSpec.js +7 -38
  21. package/dist/cjs/router/runtime/PrefetchLink.js +55 -8
  22. package/dist/cjs/router/runtime/redirect.js +11 -1
  23. package/dist/cjs/router/runtime/utils.js +2 -0
  24. package/dist/esm/cli/ssr/loadable-bundler-plugin.mjs +1 -1
  25. package/dist/esm/core/browser/hydrate.mjs +1 -11
  26. package/dist/esm/core/context/extensions.mjs +7 -2
  27. package/dist/esm/core/context/runtime.mjs +13 -2
  28. package/dist/esm/core/react/wrapper.mjs +11 -9
  29. package/dist/esm/core/server/federatedCss.mjs +7 -4
  30. package/dist/esm/core/server/helmet.mjs +2 -0
  31. package/dist/esm/core/server/requestResponse.mjs +35 -11
  32. package/dist/esm/core/server/routerCleanup.mjs +10 -1
  33. package/dist/esm/core/server/scriptOrder.mjs +6 -3
  34. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
  35. package/dist/esm/core/server/stream/createReadableStream.mjs +16 -3
  36. package/dist/esm/core/server/stream/createReadableStream.worker.mjs +10 -2
  37. package/dist/esm/core/server/stream/deferredScript.mjs +2 -1
  38. package/dist/esm/core/server/string/index.mjs +4 -0
  39. package/dist/esm/core/server/string/loadable.mjs +11 -2
  40. package/dist/esm/core/server/utils.mjs +8 -4
  41. package/dist/esm/exports/head.mjs +8 -237
  42. package/dist/esm/module-federation/last-known-good.mjs +35 -2
  43. package/dist/esm/router/cli/nestedRoutesSpec.mjs +7 -38
  44. package/dist/esm/router/runtime/PrefetchLink.mjs +28 -8
  45. package/dist/esm/router/runtime/redirect.mjs +11 -1
  46. package/dist/esm/router/runtime/utils.mjs +2 -0
  47. package/dist/esm/rslib-runtime.mjs +9 -0
  48. package/dist/esm-node/cli/ssr/loadable-bundler-plugin.mjs +1 -1
  49. package/dist/esm-node/core/browser/hydrate.mjs +1 -11
  50. package/dist/esm-node/core/context/extensions.mjs +7 -2
  51. package/dist/esm-node/core/context/runtime.mjs +13 -2
  52. package/dist/esm-node/core/react/wrapper.mjs +11 -9
  53. package/dist/esm-node/core/server/federatedCss.mjs +7 -4
  54. package/dist/esm-node/core/server/helmet.mjs +2 -0
  55. package/dist/esm-node/core/server/requestResponse.mjs +35 -11
  56. package/dist/esm-node/core/server/routerCleanup.mjs +10 -1
  57. package/dist/esm-node/core/server/scriptOrder.mjs +6 -3
  58. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
  59. package/dist/esm-node/core/server/stream/createReadableStream.mjs +16 -3
  60. package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +10 -2
  61. package/dist/esm-node/core/server/stream/deferredScript.mjs +2 -1
  62. package/dist/esm-node/core/server/string/index.mjs +4 -0
  63. package/dist/esm-node/core/server/string/loadable.mjs +11 -2
  64. package/dist/esm-node/core/server/utils.mjs +8 -4
  65. package/dist/esm-node/exports/head.mjs +8 -237
  66. package/dist/esm-node/module-federation/last-known-good.mjs +35 -2
  67. package/dist/esm-node/router/cli/nestedRoutesSpec.mjs +7 -38
  68. package/dist/esm-node/router/runtime/PrefetchLink.mjs +28 -8
  69. package/dist/esm-node/router/runtime/redirect.mjs +11 -1
  70. package/dist/esm-node/router/runtime/utils.mjs +2 -0
  71. package/dist/esm-node/rslib-runtime.mjs +10 -0
  72. package/dist/types/core/context/extensions.d.ts +2 -4
  73. package/dist/types/core/context/public.d.ts +4 -8
  74. package/dist/types/core/server/requestResponse.d.ts +1 -1
  75. package/dist/types/core/server/routerCleanup.d.ts +2 -0
  76. package/dist/types/core/server/string/loadable.d.ts +1 -0
  77. package/dist/types/core/server/utils.d.ts +1 -0
  78. package/dist/types/exports/head.d.ts +1 -2
  79. package/dist/types/module-federation/last-known-good.d.ts +0 -20
  80. package/package.json +10 -9
  81. package/rstest.config.mts +11 -4
@@ -20,6 +20,7 @@ function helmetReplace(content, helmetData) {
20
20
  const htmlAttributes = helmetData.htmlAttributes.toString();
21
21
  if (htmlAttributes) result = safeReplace(result, RE_HTML_ATTR, `<html ${htmlAttributes}>`);
22
22
  const base = helmetData.base.toString();
23
+ const priority = helmetData.priority.toString();
23
24
  const link = helmetData.link.toString();
24
25
  const meta = helmetData.meta.toString();
25
26
  const noscript = helmetData.noscript.toString();
@@ -31,6 +32,7 @@ function helmetReplace(content, helmetData) {
31
32
  if (shouldReplaceTitle) result = safeReplace(result, RE_TITLE, title);
32
33
  const helmetStr = [
33
34
  base,
35
+ priority,
34
36
  link,
35
37
  meta,
36
38
  noscript,
@@ -2,9 +2,15 @@ import "node:module";
2
2
  import { getRouterServerSnapshot } from "../../router/runtime/lifecycle.mjs";
3
3
  import { handleRSCRedirect } from "../../router/runtime/redirect.mjs";
4
4
  import { SSRErrors } from "./tracer.mjs";
5
- const isRedirectStatus = (status)=>status >= 300 && status <= 399;
5
+ const isRedirectStatus = (status)=>301 === status || 302 === status || 303 === status || 307 === status || 308 === status;
6
6
  const isNullBodyStatus = (status)=>204 === status || 205 === status || 304 === status;
7
+ const getRedirectLocation = (headers)=>{
8
+ const location = headers.get('Location');
9
+ return location && URL.canParse(location, 'http://localhost') ? location : void 0;
10
+ };
7
11
  const processRedirect = (headers, status, ctx)=>{
12
+ headers.delete('content-length');
13
+ headers.delete('transfer-encoding');
8
14
  if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
9
15
  return new Response(null, {
10
16
  status,
@@ -21,20 +27,38 @@ const applyRouterSnapshotResult = (context, onError)=>{
21
27
  const createLoaderRedirectResponse = (beforeRenderResult, redirectCtx)=>{
22
28
  if (void 0 === beforeRenderResult || !isRedirectStatus(beforeRenderResult.status)) return;
23
29
  if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
24
- const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
25
- return processRedirect(new Headers({
26
- Location: redirectUrl
27
- }), beforeRenderResult.status, redirectCtx);
30
+ const redirectUrl = getRedirectLocation(beforeRenderResult.headers);
31
+ if (!redirectUrl) return;
32
+ return processRedirect(new Headers(beforeRenderResult.headers), beforeRenderResult.status, redirectCtx);
28
33
  };
29
- const finalizeRenderResponse = (response, responseProxy, redirectCtx, routerCleanup)=>{
30
- if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && void 0 !== responseProxy.headers.Location && '' !== responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
34
+ const finalizeRenderResponse = async (response, responseProxy, redirectCtx, routerCleanup)=>{
35
+ const proxyHeaders = new Headers(responseProxy.headers);
36
+ if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && void 0 !== getRedirectLocation(proxyHeaders)) {
37
+ await routerCleanup.discardBody(response);
38
+ return processRedirect(proxyHeaders, responseProxy.status, redirectCtx);
39
+ }
40
+ const headers = new Headers(response.headers);
41
+ Object.entries(responseProxy.headers).forEach(([key, value])=>{
42
+ headers.set(key, value);
43
+ });
44
+ if (-1 !== responseProxy.status) {
45
+ if (isNullBodyStatus(responseProxy.status)) {
46
+ await routerCleanup.discardBody(response);
47
+ headers.delete('content-length');
48
+ headers.delete('transfer-encoding');
49
+ return new Response(null, {
50
+ status: responseProxy.status,
51
+ headers
52
+ });
53
+ }
54
+ return routerCleanup.deferUntilBodyDone(new Response(response.body, {
55
+ status: responseProxy.status,
56
+ headers
57
+ }));
58
+ }
31
59
  Object.entries(responseProxy.headers).forEach(([key, value])=>{
32
60
  response.headers.set(key, value);
33
61
  });
34
- if (-1 !== responseProxy.status) return routerCleanup.deferUntilBodyDone(new Response(isNullBodyStatus(responseProxy.status) ? null : response.body, {
35
- status: responseProxy.status,
36
- headers: response.headers
37
- }));
38
62
  return routerCleanup.deferUntilBodyDone(response);
39
63
  };
40
64
  export { applyRouterSnapshotResult, createLoaderRedirectResponse, finalizeRenderResponse };
@@ -63,12 +63,21 @@ function createRouterCleanup(runtimeContext, onError) {
63
63
  headers: response.headers
64
64
  });
65
65
  };
66
+ const discardBody = async (response)=>{
67
+ const { body } = response;
68
+ if (!body) return void await run();
69
+ deferred = true;
70
+ if (body.locked) throw new TypeError('Cannot discard a locked response body before router cleanup');
71
+ await body.cancel('Response body discarded during finalization');
72
+ await run();
73
+ };
66
74
  return {
67
75
  get deferred () {
68
76
  return deferred;
69
77
  },
70
78
  run,
71
- deferUntilBodyDone
79
+ deferUntilBodyDone,
80
+ discardBody
72
81
  };
73
82
  }
74
83
  export { ROUTER_CLEANUP_ERROR, createRouterCleanup, finishWithRouterCleanup, runWithRouterCleanupOnError };
@@ -1,7 +1,7 @@
1
1
  import "node:module";
2
2
  import { getRouterMatchedRouteIds } from "../../router/runtime/lifecycle.mjs";
3
3
  import { CHUNK_JS_PLACEHOLDER } from "./constants.mjs";
4
- import { safeReplace } from "./utils.mjs";
4
+ import { attributesToString, safeReplace } from "./utils.mjs";
5
5
  function getScriptTags(template) {
6
6
  const scriptRegExp = /<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*><\/script>/g;
7
7
  return Array.from(template.matchAll(scriptRegExp)).map((match)=>({
@@ -67,6 +67,7 @@ function injectBeforeHydrationEntryScript(template, scripts, entryName = 'index'
67
67
  return `${template.slice(0, target.index)}${scripts}${template.slice(target.index)}`;
68
68
  }
69
69
  function replaceChunkJsPlaceholder(template, scripts, entryName, placeholder = CHUNK_JS_PLACEHOLDER) {
70
+ if (!template.includes(placeholder)) return template;
70
71
  if ('' === scripts) return safeReplace(template, placeholder, '');
71
72
  const withoutPlaceholder = safeReplace(template, placeholder, '');
72
73
  const withEarlyScripts = injectBeforeHydrationEntryScript(withoutPlaceholder, scripts, entryName);
@@ -82,7 +83,9 @@ function createRouteHydrationScriptTags(runtimeContext, entryName, options = {})
82
83
  ...getMatchedRouteAssets(runtimeContext),
83
84
  ...routeAssets[`async-${entryName}`]?.assets ?? []
84
85
  ].filter((asset)=>asset.endsWith('.js'))));
85
- const nonceAttr = void 0 === nonce || '' === nonce ? '' : ` nonce="${nonce}"`;
86
- return jsAssets.filter((asset)=>!existingScriptSrcs.has(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
86
+ return jsAssets.filter((asset)=>!existingScriptSrcs.has(asset)).map((asset)=>`<script${attributesToString({
87
+ src: asset,
88
+ nonce: nonce || void 0
89
+ })}></script>`).join(' ');
87
90
  }
88
91
  export { createRouteHydrationScriptTags, getMatchedRouteChunks, injectBeforeHydrationEntryScript, orderHydrationScriptChunks, replaceChunkJsPlaceholder };
@@ -4,7 +4,7 @@ import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
4
4
  import { createFederatedCssLinks } from "../federatedCss.mjs";
5
5
  import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
6
6
  import { buildHtml } from "../shared.mjs";
7
- import { safeReplace } from "../utils.mjs";
7
+ import { hasStylesheetLink, safeReplace } from "../utils.mjs";
8
8
  const checkIsInline = (chunk, enableInline)=>{
9
9
  if ('production' !== process.env.NODE_ENV) return false;
10
10
  if (enableInline instanceof RegExp) return enableInline.test(chunk);
@@ -37,7 +37,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
37
37
  if (asyncEntry) matchedRouteManifests.push(asyncEntry);
38
38
  const cssChunks = matchedRouteManifests.reduce((chunks, routeManifest)=>{
39
39
  const { referenceCssAssets = [] } = routeManifest;
40
- const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !template.includes(asset));
40
+ const _cssChunks = referenceCssAssets.filter((asset)=>asset?.endsWith('.css') && !hasStylesheetLink(template, asset));
41
41
  return [
42
42
  ...chunks,
43
43
  ..._cssChunks
@@ -6,9 +6,11 @@ import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
6
6
  import { RenderLevel } from "../../constants.mjs";
7
7
  import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
8
8
  import { getMonitors } from "../../context/monitors/index.mjs";
9
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
9
10
  import { enqueueFromEntries } from "./deferredScript.mjs";
10
11
  import { ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
11
12
  import { getTemplates } from "./template.mjs";
13
+ import * as __rspack_external__modern_js_runtime_extensions_node_f016b1ee from "@modern-js/runtime-extensions/node";
12
14
  const defaultExtender = {
13
15
  modifyRootElement: (rootElement)=>rootElement,
14
16
  getStyleTags: ()=>'',
@@ -37,6 +39,8 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
37
39
  });
38
40
  const chunkVec = [];
39
41
  let hasStartedPipe = false;
42
+ __rspack_external__modern_js_runtime_extensions_node_f016b1ee.beginHeadRender(runtimeContext);
43
+ const reportError = __rspack_external__modern_js_runtime_extensions_node_f016b1ee.createOnceErrorReporter(options.onError);
40
44
  return new Promise((resolve)=>{
41
45
  const { pipe: reactStreamingPipe } = renderToPipeableStream(processedRootElement, {
42
46
  nonce: config.nonce,
@@ -70,8 +74,10 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
70
74
  if (-1 !== markerIndex) {
71
75
  const beforeMark = concatedChunk.slice(0, markerIndex);
72
76
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
77
+ __rspack_external__modern_js_runtime_extensions_node_f016b1ee.publishHeadRender(runtimeContext);
78
+ const completedShellBefore = createReplaceHelemt(getHelmetData(runtimeContext))(shellBefore);
73
79
  shellChunkStatus = ShellChunkStatus.FINISH;
74
- this.push(`${shellBefore}${beforeMark}${shellAfter}`);
80
+ this.push(`${completedShellBefore}${beforeMark}${shellAfter}`);
75
81
  if (afterMark) this.push(afterMark);
76
82
  if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
77
83
  }
@@ -89,8 +95,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
89
95
  extenders.forEach((extender)=>{
90
96
  if (extender.processStream) processedStream = extender.processStream(processedStream);
91
97
  });
98
+ __rspack_external__modern_js_runtime_extensions_node_f016b1ee.pipeNodeHeadStream({
99
+ source: processedStream,
100
+ destination: body,
101
+ context: runtimeContext,
102
+ terminalMarker: ESCAPED_SHELL_STREAM_END_MARK,
103
+ onError: reportError
104
+ });
92
105
  reactStreamingPipe(passThrough);
93
- processedStream.pipe(body);
94
106
  try {
95
107
  const storageContext = storage.useContext?.();
96
108
  const activeDeferreds = storageContext?.activeDeferreds;
@@ -109,6 +121,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
109
121
  });
110
122
  },
111
123
  onShellError (error) {
124
+ __rspack_external__modern_js_runtime_extensions_node_f016b1ee.abortHeadRender(runtimeContext);
112
125
  renderLevel = RenderLevel.CLIENT_RENDER;
113
126
  getTemplates(htmlTemplate, {
114
127
  request,
@@ -127,7 +140,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
127
140
  },
128
141
  onError (error) {
129
142
  renderLevel = RenderLevel.CLIENT_RENDER;
130
- options?.onError?.(error);
143
+ reportError(error);
131
144
  }
132
145
  });
133
146
  });
@@ -3,13 +3,16 @@ import { renderSSRStream } from "@modern-js/render/ssr";
3
3
  import { storage } from "@modern-js/runtime-utils/node";
4
4
  import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
5
5
  import { RenderLevel } from "../../constants.mjs";
6
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
6
7
  import { enqueueFromEntries } from "./deferredScript.mjs";
7
8
  import { ShellChunkStatus, encodeForWebStream, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
8
9
  import { getTemplates } from "./template.mjs";
10
+ import * as __rspack_external__modern_js_runtime_extensions_ce611631 from "@modern-js/runtime-extensions";
9
11
  const createReadableStreamFromElement = async (request, rootElement, options)=>{
10
12
  let shellChunkStatus = ShellChunkStatus.START;
11
13
  const chunkVec = [];
12
14
  const { htmlTemplate, runtimeContext, config, ssrConfig, entryName, moduleFederationCssAssets, rscManifest, rscRoot } = options;
15
+ __rspack_external__modern_js_runtime_extensions_ce611631.beginHeadRender(runtimeContext);
13
16
  const { shellBefore, shellAfter } = await getTemplates(htmlTemplate, {
14
17
  renderLevel: RenderLevel.SERVER_RENDER,
15
18
  runtimeContext,
@@ -37,7 +40,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
37
40
  const forceStreamToString = Boolean("u" > typeof process && process.env?.MODERN_JS_STREAM_TO_STRING);
38
41
  const { waitForAllReady } = resolveStreamingMode(request, forceStreamToString);
39
42
  if (waitForAllReady) await readableOriginal.allReady;
40
- const reader = readableOriginal.getReader();
43
+ const reader = __rspack_external__modern_js_runtime_extensions_ce611631.createConservingWebShellStream(readableOriginal, runtimeContext, ESCAPED_SHELL_STREAM_END_MARK, options.onError).getReader();
41
44
  const stream = new ReadableStream({
42
45
  start (controller) {
43
46
  const pendingScripts = [];
@@ -82,14 +85,17 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
82
85
  if (-1 !== markerIndex) {
83
86
  const beforeMark = concatedChunk.slice(0, markerIndex);
84
87
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
88
+ __rspack_external__modern_js_runtime_extensions_ce611631.publishHeadRender(runtimeContext);
89
+ const completedShellBefore = createReplaceHelemt(getHelmetData(runtimeContext))(shellBefore);
85
90
  shellChunkStatus = ShellChunkStatus.FINISH;
86
- safeEnqueue(encodeForWebStream(`${shellBefore}${beforeMark}${shellAfter}`));
91
+ safeEnqueue(encodeForWebStream(`${completedShellBefore}${beforeMark}${shellAfter}`));
87
92
  if (afterMark.length > 0) safeEnqueue(encodeForWebStream(afterMark));
88
93
  flushPendingScripts();
89
94
  }
90
95
  } else safeEnqueue(value);
91
96
  if (!isClosed) push();
92
97
  } catch (error) {
98
+ __rspack_external__modern_js_runtime_extensions_ce611631.abortHeadRender(runtimeContext);
93
99
  if (!isClosed) {
94
100
  isClosed = true;
95
101
  try {
@@ -101,11 +107,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
101
107
  push();
102
108
  },
103
109
  cancel (reason) {
110
+ __rspack_external__modern_js_runtime_extensions_ce611631.abortHeadRender(runtimeContext);
104
111
  reader.cancel(reason).catch(()=>{});
105
112
  }
106
113
  });
107
114
  return stream;
108
115
  } catch (e) {
116
+ __rspack_external__modern_js_runtime_extensions_ce611631.abortHeadRender(runtimeContext);
109
117
  const fallbackHtml = `${shellBefore}${shellAfter}`;
110
118
  const stream = getReadableStreamFromString(fallbackHtml);
111
119
  return stream;
@@ -1,5 +1,6 @@
1
1
  import "node:module";
2
2
  import { runRouterDataFnStr } from "../../../router/runtime/constants.mjs";
3
+ import { shouldRedactServerError } from "../utils.mjs";
3
4
  const HTML_ESCAPE_MAP = {
4
5
  '&': '&amp;',
5
6
  '<': '&lt;',
@@ -23,7 +24,7 @@ function isPromiseLike(value) {
23
24
  return !!value && 'function' == typeof value.then;
24
25
  }
25
26
  function toErrorInfo(error) {
26
- if ('production' === process.env.NODE_ENV) return {
27
+ if (shouldRedactServerError()) return {
27
28
  message: 'Unexpected Server Error'
28
29
  };
29
30
  if (error && 'object' == typeof error) {
@@ -13,6 +13,7 @@ import { SSRErrors, SSRTimings } from "../tracer.mjs";
13
13
  import { getSSRConfigByEntry, safeReplace } from "../utils.mjs";
14
14
  import { LoadableCollector } from "./loadable.mjs";
15
15
  import { SSRDataCollector } from "./ssrData.mjs";
16
+ import * as __rspack_external__modern_js_runtime_extensions_ce611631 from "@modern-js/runtime-extensions";
16
17
  const renderString = async (request, serverRoot, options)=>{
17
18
  const { resource, runtimeContext, config, onError, onTiming } = options;
18
19
  const tracer = {
@@ -67,16 +68,19 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
67
68
  let html = '';
68
69
  let helmetData;
69
70
  const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
71
+ __rspack_external__modern_js_runtime_extensions_ce611631.beginHeadRender(runtimeContext);
70
72
  try {
71
73
  const end = time();
72
74
  html = server.renderToString(finalApp, {
73
75
  identifierPrefix: SSR_HYDRATION_ID_PREFIX
74
76
  });
77
+ html = __rspack_external__modern_js_runtime_extensions_ce611631.completeHeadRender(runtimeContext, html);
75
78
  chunkSet.renderLevel = RenderLevel.SERVER_RENDER;
76
79
  helmetData = getHelmetData(runtimeContext);
77
80
  const cost = end();
78
81
  onTiming(SSRTimings.RENDER_HTML, cost);
79
82
  } catch (e) {
83
+ __rspack_external__modern_js_runtime_extensions_ce611631.abortHeadRender(runtimeContext);
80
84
  chunkSet.renderLevel = RenderLevel.CLIENT_RENDER;
81
85
  onError(e, SSRErrors.RENDER_HTML);
82
86
  }
@@ -32,6 +32,10 @@ const readAsset = async (chunk)=>{
32
32
  return fs.readFile(filepath, 'utf-8');
33
33
  };
34
34
  class LoadableCollector {
35
+ get existsAssets() {
36
+ const { routeManifest, entryName } = this.options;
37
+ return routeManifest?.routeAssets?.[entryName]?.assets;
38
+ }
35
39
  collect(comopnent) {
36
40
  const { stats, entryName } = this.options;
37
41
  if (!stats) return comopnent;
@@ -93,7 +97,7 @@ class LoadableCollector {
93
97
  const matchs = template.matchAll(jsScriptRegExp);
94
98
  const existedScript = [];
95
99
  for (const match of matchs)existedScript.push(match[1]);
96
- const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url)).map(async (chunk)=>{
100
+ const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
97
101
  const script = `<script${attributes} src="${chunk.url}"></script>`;
98
102
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<script>${content}</script>`).catch((_)=>script);
99
103
  return script;
@@ -104,7 +108,12 @@ class LoadableCollector {
104
108
  const { template, chunkSet, config, moduleFederationCssAssets } = this.options;
105
109
  const { inlineStyles } = config;
106
110
  const atrributes = attributesToString(this.generateAttributes());
107
- const emittedChunks = chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url));
111
+ const emittedAssetUrls = new Set();
112
+ const emittedChunks = chunks.filter((chunk)=>{
113
+ const shouldEmit = !hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path) && !emittedAssetUrls.has(chunk.url);
114
+ if (shouldEmit) emittedAssetUrls.add(chunk.url);
115
+ return shouldEmit;
116
+ });
108
117
  const css = await Promise.all(emittedChunks.map(async (chunk)=>{
109
118
  const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
110
119
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
@@ -1,7 +1,8 @@
1
1
  import "node:module";
2
+ import { escapeHtmlAttribute, isSafeHtmlAttributeName } from "@modern-js/runtime-extensions";
2
3
  import { isRouteErrorResponse } from "../../router/runtime/routerHelper.mjs";
3
4
  function attributesToString(attributes) {
4
- return Object.entries(attributes).reduce((str, [key, value])=>void 0 === value ? str : `${str} ${key}="${value}"`, '');
5
+ return Object.entries(attributes).reduce((str, [key, value])=>void 0 !== value && isSafeHtmlAttributeName(key) ? `${str} ${key}="${escapeHtmlAttribute(value)}"` : str, '');
5
6
  }
6
7
  function safeReplace(source, searchValue, replaceValue) {
7
8
  return source.replace(searchValue, ()=>replaceValue);
@@ -39,12 +40,14 @@ function serializeRouteErrorResponse(error) {
39
40
  status: error.status,
40
41
  statusText: 'Internal Server Error',
41
42
  data: 'Unexpected Server Error',
43
+ internal: error.internal,
42
44
  __type: 'RouteErrorResponse'
43
45
  };
44
46
  return {
45
47
  status: error.status,
46
48
  statusText: error.statusText,
47
49
  data: error.data,
50
+ internal: error.internal,
48
51
  __type: 'RouteErrorResponse'
49
52
  };
50
53
  }
@@ -66,8 +69,9 @@ const getLinkAttributes = (linkTag)=>{
66
69
  let match;
67
70
  while(match = attributeRegExp.exec(linkTag)){
68
71
  const [, name, doubleQuotedValue, singleQuotedValue, unquotedValue] = match;
69
- if ('link' === name.toLowerCase()) continue;
70
- attributes.set(name.toLowerCase(), doubleQuotedValue ?? singleQuotedValue ?? unquotedValue ?? '');
72
+ const normalizedName = name.toLowerCase();
73
+ if ('link' === normalizedName || attributes.has(normalizedName)) continue;
74
+ attributes.set(normalizedName, doubleQuotedValue ?? singleQuotedValue ?? unquotedValue ?? '');
71
75
  }
72
76
  return attributes;
73
77
  };
@@ -80,4 +84,4 @@ const hasStylesheetLink = (template, href)=>{
80
84
  return linkHref === href && rel?.split(/\s+/).some((relToken)=>'stylesheet' === relToken.toLowerCase());
81
85
  });
82
86
  };
83
- export { attributesToString, checkIsNode, getSSRConfigByEntry, getSSRMode, hasStylesheetLink, safeReplace, serializeErrors };
87
+ export { attributesToString, checkIsNode, getSSRConfigByEntry, getSSRMode, hasStylesheetLink, safeReplace, serializeErrors, shouldRedactServerError };