@bleedingdev/modern-js-runtime 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1

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 (134) hide show
  1. package/dist/cjs/cli/code.js +2 -2
  2. package/dist/cjs/cli/index.js +0 -7
  3. package/dist/cjs/cli/ssr/loadable-bundler-plugin.js +2 -0
  4. package/dist/cjs/cli/template.server.js +1 -2
  5. package/dist/cjs/core/browser/hydrate.js +3 -9
  6. package/dist/cjs/core/browser/index.js +4 -5
  7. package/dist/cjs/core/context/index.js +8 -2
  8. package/dist/cjs/core/context/public.js +4 -4
  9. package/dist/cjs/core/context/runtime.js +3 -9
  10. package/dist/cjs/core/index.js +3 -3
  11. package/dist/cjs/core/plugin/index.js +2 -2
  12. package/dist/cjs/core/react/index.js +3 -3
  13. package/dist/cjs/core/react/wrapper.js +7 -8
  14. package/dist/cjs/core/server/requestHandler.js +253 -25
  15. package/dist/cjs/core/server/stream/beforeTemplate.js +3 -3
  16. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +2 -2
  17. package/dist/cjs/core/server/stream/createReadableStream.js +51 -19
  18. package/dist/cjs/core/server/stream/createReadableStream.worker.js +54 -27
  19. package/dist/cjs/core/server/stream/deferredScript.js +12 -6
  20. package/dist/cjs/core/server/stream/deferredScriptOutputCoordinator.js +370 -0
  21. package/dist/cjs/core/server/stream/shared.js +4 -0
  22. package/dist/cjs/core/server/string/index.js +6 -4
  23. package/dist/cjs/core/server/string/loadable.js +4 -4
  24. package/dist/cjs/document/cli/index.js +14 -5
  25. package/dist/cjs/exports/head.js +2 -2
  26. package/dist/cjs/internal.js +2 -2
  27. package/dist/cjs/router/cli/code/nestedRoutes.js +1 -2
  28. package/dist/cjs/router/cli/code/templates.js +2 -2
  29. package/dist/cjs/router/cli/code/utils.js +5 -4
  30. package/dist/cjs/router/runtime/DeferredDataScripts.node.js +13 -10
  31. package/dist/cjs/router/runtime/PrefetchLink.js +4 -4
  32. package/dist/cjs/router/runtime/hooks.js +6 -7
  33. package/dist/cjs/router/runtime/plugin.js +22 -27
  34. package/dist/cjs/router/runtime/plugin.node.js +8 -9
  35. package/dist/esm/cli/code.mjs +2 -2
  36. package/dist/esm/cli/index.mjs +0 -7
  37. package/dist/esm/cli/ssr/loadable-bundler-plugin.mjs +2 -0
  38. package/dist/esm/cli/template.server.mjs +1 -2
  39. package/dist/esm/core/browser/hydrate.mjs +3 -9
  40. package/dist/esm/core/browser/index.mjs +1 -2
  41. package/dist/esm/core/context/index.mjs +1 -1
  42. package/dist/esm/core/context/public.mjs +1 -1
  43. package/dist/esm/core/context/runtime.mjs +3 -9
  44. package/dist/esm/core/index.mjs +1 -1
  45. package/dist/esm/core/plugin/index.mjs +1 -1
  46. package/dist/esm/core/react/index.mjs +1 -1
  47. package/dist/esm/core/react/wrapper.mjs +1 -2
  48. package/dist/esm/core/server/requestHandler.mjs +223 -19
  49. package/dist/esm/core/server/stream/beforeTemplate.mjs +3 -3
  50. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
  51. package/dist/esm/core/server/stream/createReadableStream.mjs +48 -16
  52. package/dist/esm/core/server/stream/createReadableStream.worker.mjs +53 -26
  53. package/dist/esm/core/server/stream/deferredScript.mjs +12 -6
  54. package/dist/esm/core/server/stream/deferredScriptOutputCoordinator.mjs +332 -0
  55. package/dist/esm/core/server/stream/shared.mjs +2 -1
  56. package/dist/esm/core/server/string/index.mjs +3 -1
  57. package/dist/esm/core/server/string/loadable.mjs +4 -4
  58. package/dist/esm/document/cli/index.mjs +12 -6
  59. package/dist/esm/exports/head.mjs +1 -1
  60. package/dist/esm/internal.mjs +1 -1
  61. package/dist/esm/router/cli/code/nestedRoutes.mjs +1 -2
  62. package/dist/esm/router/cli/code/templates.mjs +2 -2
  63. package/dist/esm/router/cli/code/utils.mjs +6 -5
  64. package/dist/esm/router/runtime/DeferredDataScripts.node.mjs +13 -10
  65. package/dist/esm/router/runtime/PrefetchLink.mjs +1 -1
  66. package/dist/esm/router/runtime/hooks.mjs +6 -7
  67. package/dist/esm/router/runtime/plugin.mjs +14 -19
  68. package/dist/esm/router/runtime/plugin.node.mjs +3 -4
  69. package/dist/esm-node/cli/code.mjs +2 -2
  70. package/dist/esm-node/cli/index.mjs +1 -9
  71. package/dist/esm-node/cli/ssr/loadable-bundler-plugin.mjs +2 -0
  72. package/dist/esm-node/cli/template.server.mjs +1 -2
  73. package/dist/esm-node/core/browser/hydrate.mjs +3 -9
  74. package/dist/esm-node/core/browser/index.mjs +1 -2
  75. package/dist/esm-node/core/context/index.mjs +1 -1
  76. package/dist/esm-node/core/context/public.mjs +1 -1
  77. package/dist/esm-node/core/context/runtime.mjs +3 -9
  78. package/dist/esm-node/core/index.mjs +1 -1
  79. package/dist/esm-node/core/plugin/index.mjs +1 -1
  80. package/dist/esm-node/core/react/index.mjs +1 -1
  81. package/dist/esm-node/core/react/wrapper.mjs +1 -2
  82. package/dist/esm-node/core/server/requestHandler.mjs +223 -19
  83. package/dist/esm-node/core/server/stream/beforeTemplate.mjs +3 -3
  84. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
  85. package/dist/esm-node/core/server/stream/createReadableStream.mjs +48 -16
  86. package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +53 -26
  87. package/dist/esm-node/core/server/stream/deferredScript.mjs +12 -6
  88. package/dist/esm-node/core/server/stream/deferredScriptOutputCoordinator.mjs +333 -0
  89. package/dist/esm-node/core/server/stream/shared.mjs +2 -1
  90. package/dist/esm-node/core/server/string/index.mjs +3 -1
  91. package/dist/esm-node/core/server/string/loadable.mjs +4 -4
  92. package/dist/esm-node/document/cli/index.mjs +12 -6
  93. package/dist/esm-node/exports/head.mjs +1 -1
  94. package/dist/esm-node/internal.mjs +1 -1
  95. package/dist/esm-node/router/cli/code/nestedRoutes.mjs +1 -2
  96. package/dist/esm-node/router/cli/code/templates.mjs +2 -2
  97. package/dist/esm-node/router/cli/code/utils.mjs +6 -5
  98. package/dist/esm-node/router/runtime/DeferredDataScripts.node.mjs +13 -10
  99. package/dist/esm-node/router/runtime/PrefetchLink.mjs +1 -1
  100. package/dist/esm-node/router/runtime/hooks.mjs +6 -7
  101. package/dist/esm-node/router/runtime/plugin.mjs +14 -19
  102. package/dist/esm-node/router/runtime/plugin.node.mjs +3 -4
  103. package/dist/types/common.d.ts +2 -8
  104. package/dist/types/core/context/index.d.ts +1 -1
  105. package/dist/types/core/context/public.d.ts +1 -1
  106. package/dist/types/core/index.d.ts +1 -1
  107. package/dist/types/core/react/wrapper.d.ts +1 -1
  108. package/dist/types/core/server/requestHandler.d.ts +32 -3
  109. package/dist/types/core/server/shared.d.ts +1 -1
  110. package/dist/types/core/server/stream/deferredScript.d.ts +1 -1
  111. package/dist/types/core/server/stream/deferredScriptOutputCoordinator.d.ts +72 -0
  112. package/dist/types/core/server/stream/shared.d.ts +15 -1
  113. package/dist/types/core/server/tracer.d.ts +1 -1
  114. package/dist/types/core/server/utils.d.ts +1 -1
  115. package/dist/types/core/types.d.ts +1 -2
  116. package/dist/types/document/cli/index.d.ts +1 -0
  117. package/dist/types/internal.d.ts +1 -1
  118. package/dist/types/router/cli/code/utils.d.ts +1 -1
  119. package/dist/types/router/runtime/hooks.d.ts +12 -16
  120. package/package.json +32 -25
  121. package/rslib.config.mts +3 -0
  122. package/rstest.config.mts +0 -4
  123. package/dist/cjs/core/server/requestResponse.js +0 -107
  124. package/dist/cjs/core/server/routerCleanup.js +0 -129
  125. package/dist/cjs/rsc/server.worker.js +0 -62
  126. package/dist/esm/core/server/requestResponse.mjs +0 -63
  127. package/dist/esm/core/server/routerCleanup.mjs +0 -82
  128. package/dist/esm/rsc/server.worker.mjs +0 -1
  129. package/dist/esm-node/core/server/requestResponse.mjs +0 -64
  130. package/dist/esm-node/core/server/routerCleanup.mjs +0 -83
  131. package/dist/esm-node/rsc/server.worker.mjs +0 -2
  132. package/dist/types/core/server/requestResponse.d.ts +0 -15
  133. package/dist/types/core/server/routerCleanup.d.ts +0 -31
  134. package/dist/types/rsc/server.worker.d.ts +0 -1
@@ -1,14 +1,8 @@
1
1
  import { createContext, useContext } from "react";
2
2
  const ROUTE_MANIFEST = '_MODERNJS_ROUTE_MANIFEST';
3
- function getRuntimeReactContext(kind, create) {
4
- const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
5
- const contexts = globalThis;
6
- contexts[key] ??= create();
7
- return contexts[key];
8
- }
9
- const InternalRuntimeContext = getRuntimeReactContext('internal', ()=>createContext({}));
10
- const RuntimeContext = getRuntimeReactContext('public', ()=>createContext({}));
11
- const RuntimeComponentResolverContext = getRuntimeReactContext('component-resolver', ()=>createContext(void 0));
3
+ const InternalRuntimeContext = createContext({});
4
+ const RuntimeContext = createContext({});
5
+ const RuntimeComponentResolverContext = createContext(void 0);
12
6
  const ReactRuntimeContext = RuntimeContext;
13
7
  const useRuntimeContext = ()=>useContext(RuntimeContext);
14
8
  const getInitialContext = (isBrowser = true, routeManifest)=>{
@@ -1,2 +1,2 @@
1
+ export { RuntimeContext, useRuntimeContext } from "@modern-js/runtime/context";
1
2
  export { defineRuntimeConfig } from "./config.mjs";
2
- export { RuntimeContext, useRuntimeContext } from "./context/runtime.mjs";
@@ -1,9 +1,9 @@
1
1
  import { runtime } from "@modern-js/plugin/runtime";
2
+ import { setGlobalInternalRuntimeContext } from "@modern-js/runtime/context";
2
3
  import { merge } from "@modern-js/runtime-utils/merge";
3
4
  import { compatPlugin } from "../compat/index.mjs";
4
5
  import { handleSetupResult } from "../compat/hooks.mjs";
5
6
  import { requestContextPlugin } from "../compat/requestContext.mjs";
6
- import { setGlobalInternalRuntimeContext } from "../context/index.mjs";
7
7
  function registerPlugin(internalPlugins, runtimeConfig) {
8
8
  const { plugins = [] } = runtimeConfig || {};
9
9
  const { runtimeContext } = runtime.run({
@@ -1,7 +1,7 @@
1
+ import { getGlobalApp, getGlobalInternalRuntimeContext } from "@modern-js/runtime/context";
1
2
  import { parsedJSONFromElement } from "@modern-js/runtime-utils/parsed";
2
3
  import { isBrowser } from "../../common.mjs";
3
4
  import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID } from "../constants.mjs";
4
- import { getGlobalApp, getGlobalInternalRuntimeContext } from "../context/index.mjs";
5
5
  function createRoot(UserApp) {
6
6
  const App = UserApp || getGlobalApp();
7
7
  if (isBrowser()) {
@@ -1,6 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { InternalRuntimeContext, RuntimeContext, getGlobalEnableRsc, getGlobalInternalRuntimeContext } from "../context/index.mjs";
3
- import { RuntimeComponentResolverContext } from "../context/runtime.mjs";
2
+ import { InternalRuntimeContext, RuntimeComponentResolverContext, RuntimeContext, getGlobalEnableRsc, getGlobalInternalRuntimeContext } from "@modern-js/runtime/context";
4
3
  function wrapRuntimeComponentResolver(root, hooks = getGlobalInternalRuntimeContext()?.hooks) {
5
4
  return /*#__PURE__*/ jsx(RuntimeComponentResolverContext.Provider, {
6
5
  value: hooks?.resolveComponent?.call,
@@ -1,15 +1,195 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { getGlobalInternalRuntimeContext, getGlobalRSCRoot, getInitialContext } from "@modern-js/runtime/context";
2
3
  import { storage } from "@modern-js/runtime-utils/node";
3
4
  import { getPathname, parseCookie, parseHeaders, parseQuery } from "@modern-js/runtime-utils/universal/request";
4
5
  import react, { Fragment } from "react";
5
- import { getGlobalInternalRuntimeContext, getGlobalRSCRoot } from "../context/index.mjs";
6
- import { getInitialContext } from "../context/runtime.mjs";
6
+ import { handleRSCRedirect } from "../../router/runtime/redirect.mjs";
7
7
  import { getServerPayload } from "../context/serverPayload/index.mjs";
8
8
  import { createRoot } from "../react/index.mjs";
9
9
  import { CHUNK_CSS_PLACEHOLDER } from "./constants.mjs";
10
- import { applyRouterSnapshotResult, createLoaderRedirectResponse, finalizeRenderResponse } from "./requestResponse.mjs";
11
- import { createRouterCleanup, finishWithRouterCleanup, runWithRouterCleanupOnError } from "./routerCleanup.mjs";
10
+ import { SSRErrors } from "./tracer.mjs";
12
11
  import { getSSRConfigByEntry, getSSRMode } from "./utils.mjs";
12
+ const REQUEST_END_ERROR = 'An error occurs during request lifecycle completion';
13
+ const RESPONSE_BODY_CANCEL_ERROR = 'An error occurs while cancelling a discarded response body';
14
+ async function runWithRequestLifecycleOnError(lifecycle, callback) {
15
+ try {
16
+ return await callback();
17
+ } catch (error) {
18
+ await lifecycle.run({
19
+ status: 'error',
20
+ error
21
+ });
22
+ throw error;
23
+ }
24
+ }
25
+ async function finishWithRequestLifecycle(lifecycle, callback) {
26
+ try {
27
+ return await callback();
28
+ } catch (error) {
29
+ if (!lifecycle.deferred) await lifecycle.run({
30
+ status: 'error',
31
+ error
32
+ });
33
+ throw error;
34
+ } finally{
35
+ if (!lifecycle.deferred) await lifecycle.run();
36
+ }
37
+ }
38
+ function createRequestLifecycle(onEnd, onError) {
39
+ let deferred = false;
40
+ let finished;
41
+ const run = (terminal = {
42
+ status: 'complete'
43
+ })=>{
44
+ finished ??= Promise.resolve().then(()=>onEnd(terminal)).catch((error)=>{
45
+ onError(error, REQUEST_END_ERROR);
46
+ });
47
+ return finished;
48
+ };
49
+ const deferUntilBodyDone = (response)=>{
50
+ const { body } = response;
51
+ if (!body) return response;
52
+ deferred = true;
53
+ if (body.locked) throw new TypeError('Cannot observe a locked response body before request completion');
54
+ let cancelled = false;
55
+ const reader = body.getReader();
56
+ const wrappedBody = new ReadableStream({
57
+ async pull (controller) {
58
+ let result;
59
+ try {
60
+ result = await reader.read();
61
+ } catch (error) {
62
+ if (!cancelled) {
63
+ await run({
64
+ status: 'error',
65
+ error
66
+ });
67
+ controller.error(error);
68
+ }
69
+ return;
70
+ }
71
+ if (cancelled) return;
72
+ if (result.done) {
73
+ await run();
74
+ if (!cancelled) controller.close();
75
+ return;
76
+ }
77
+ controller.enqueue(result.value);
78
+ },
79
+ async cancel (reason) {
80
+ cancelled = true;
81
+ try {
82
+ await reader.cancel(reason);
83
+ } catch {}
84
+ await run({
85
+ status: 'cancelled',
86
+ reason
87
+ });
88
+ }
89
+ });
90
+ return new Response(wrappedBody, {
91
+ status: response.status,
92
+ statusText: response.statusText,
93
+ headers: response.headers
94
+ });
95
+ };
96
+ const discardBody = async (response, terminal = {
97
+ status: 'discarded'
98
+ })=>{
99
+ const { body } = response;
100
+ if (!body) return void await run(terminal);
101
+ deferred = true;
102
+ if (body.locked) throw new TypeError('Cannot discard a locked response body before request completion');
103
+ try {
104
+ await body.cancel('Response body discarded during finalization');
105
+ } catch (error) {
106
+ await run('error' === terminal.status ? terminal : {
107
+ status: 'error',
108
+ error
109
+ });
110
+ if ('error' === terminal.status) onError(error, RESPONSE_BODY_CANCEL_ERROR);
111
+ throw error;
112
+ }
113
+ await run(terminal);
114
+ };
115
+ return {
116
+ get deferred () {
117
+ return deferred;
118
+ },
119
+ run,
120
+ deferUntilBodyDone,
121
+ discardBody
122
+ };
123
+ }
124
+ const isRedirectStatus = (status)=>301 === status || 302 === status || 303 === status || 307 === status || 308 === status;
125
+ const isNullBodyStatus = (status)=>204 === status || 205 === status || 304 === status;
126
+ const getRedirectLocation = (headers)=>{
127
+ const location = headers.get('Location');
128
+ return null !== location && '' !== location && URL.canParse(location, 'http://localhost') ? location : void 0;
129
+ };
130
+ const processRedirect = (headers, status, ctx)=>{
131
+ headers.delete('content-length');
132
+ headers.delete('transfer-encoding');
133
+ if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
134
+ return new Response(null, {
135
+ status,
136
+ headers
137
+ });
138
+ };
139
+ const applyRouterResult = (context, routerResult, onError)=>{
140
+ const routerStatusCode = routerResult?.statusCode;
141
+ if (void 0 !== routerStatusCode && 0 !== routerStatusCode && !Number.isNaN(routerStatusCode) && 200 !== routerStatusCode) context.ssrContext?.response.status(routerStatusCode);
142
+ const errors = Object.values(routerResult?.errors || {});
143
+ if (errors.length > 0) onError(errors[0], SSRErrors.LOADER_ERROR);
144
+ };
145
+ const createLoaderRedirectResponse = (beforeRenderResult, redirectCtx)=>{
146
+ if (void 0 === beforeRenderResult || !isRedirectStatus(beforeRenderResult.status)) return;
147
+ if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
148
+ const redirectUrl = getRedirectLocation(beforeRenderResult.headers);
149
+ if (void 0 === redirectUrl) return;
150
+ return processRedirect(new Headers(beforeRenderResult.headers), beforeRenderResult.status, redirectCtx);
151
+ };
152
+ const finalizeRenderResponse = async (response, responseProxy, redirectCtx, lifecycle)=>{
153
+ try {
154
+ const proxyHeaders = new Headers(responseProxy.headers);
155
+ if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && void 0 !== getRedirectLocation(proxyHeaders)) {
156
+ await lifecycle.discardBody(response);
157
+ return processRedirect(proxyHeaders, responseProxy.status, redirectCtx);
158
+ }
159
+ const headers = new Headers(response.headers);
160
+ Object.entries(responseProxy.headers).forEach(([key, value])=>{
161
+ headers.set(key, value);
162
+ });
163
+ if (-1 !== responseProxy.status) {
164
+ if (isNullBodyStatus(responseProxy.status)) {
165
+ await lifecycle.discardBody(response);
166
+ headers.delete('content-length');
167
+ headers.delete('transfer-encoding');
168
+ return new Response(null, {
169
+ status: responseProxy.status,
170
+ headers
171
+ });
172
+ }
173
+ if (response.body?.locked) lifecycle.deferUntilBodyDone(response);
174
+ return lifecycle.deferUntilBodyDone(new Response(response.body, {
175
+ status: responseProxy.status,
176
+ headers
177
+ }));
178
+ }
179
+ Object.entries(responseProxy.headers).forEach(([key, value])=>{
180
+ response.headers.set(key, value);
181
+ });
182
+ return lifecycle.deferUntilBodyDone(response);
183
+ } catch (error) {
184
+ if (!lifecycle.deferred) try {
185
+ await lifecycle.discardBody(response, {
186
+ status: 'error',
187
+ error
188
+ });
189
+ } catch {}
190
+ throw error;
191
+ }
192
+ };
13
193
  async function handleRSCRequest(request, Root, context, options, handleRequest) {
14
194
  const serverPayload = getServerPayload();
15
195
  if (void 0 !== serverPayload) return await handleRequest(request, Root, {
@@ -100,12 +280,14 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
100
280
  status: -1
101
281
  };
102
282
  const activeDeferreds = new Map();
283
+ const deferredScriptKeys = new Map();
103
284
  return storage.run({
104
285
  headers: headersData,
105
286
  request,
106
287
  monitors: options.monitors,
107
288
  responseProxy,
108
289
  activeDeferreds,
290
+ deferredScriptKeys,
109
291
  serverPayload: void 0
110
292
  }, async ()=>{
111
293
  const Root = createRoot();
@@ -130,28 +312,50 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
130
312
  isRSCNavigation: 'true' === request.headers.get('x-rsc-tree'),
131
313
  basename: ssrContext.baseUrl || '/'
132
314
  };
133
- const routerCleanup = createRouterCleanup(context, options.onError);
134
- const beforeRenderResult = await runWithRouterCleanupOnError(routerCleanup, ()=>runBeforeRender(context));
135
- await runWithRouterCleanupOnError(routerCleanup, ()=>{
136
- applyRouterSnapshotResult(context, options.onError);
137
- });
138
- if ("u" > typeof Response) {
139
- const redirectResponse = await runWithRouterCleanupOnError(routerCleanup, ()=>createLoaderRedirectResponse(beforeRenderResult, redirectCtx));
140
- if (redirectResponse) {
141
- await routerCleanup.run();
142
- return redirectResponse;
143
- }
315
+ const lifecycle = createRequestLifecycle(async (terminal)=>{
316
+ await hooks.onRequestEnd?.call({
317
+ runtimeContext: context,
318
+ terminal
319
+ });
320
+ }, options.onError);
321
+ const beforeRenderResult = await runWithRequestLifecycleOnError(lifecycle, ()=>runBeforeRender(context));
322
+ let redirectResponse;
323
+ try {
324
+ const prepared = {
325
+ runtimeContext: context,
326
+ routerResult: context.routerContext
327
+ };
328
+ const result = await hooks.onRenderPrepared?.call(prepared) ?? prepared;
329
+ applyRouterResult(context, result.routerResult, options.onError);
330
+ if ("u" > typeof Response) redirectResponse = createLoaderRedirectResponse(beforeRenderResult, redirectCtx);
331
+ } catch (error) {
332
+ if (beforeRenderResult) try {
333
+ await lifecycle.discardBody(beforeRenderResult, {
334
+ status: 'error',
335
+ error
336
+ });
337
+ } catch {}
338
+ else await lifecycle.run({
339
+ status: 'error',
340
+ error
341
+ });
342
+ throw error;
343
+ }
344
+ if (redirectResponse) {
345
+ const response = redirectResponse;
346
+ if (beforeRenderResult?.body && response !== beforeRenderResult) await lifecycle.discardBody(beforeRenderResult);
347
+ return finishWithRequestLifecycle(lifecycle, ()=>lifecycle.deferUntilBodyDone(response));
144
348
  }
145
- await runWithRouterCleanupOnError(routerCleanup, ()=>{
349
+ await runWithRequestLifecycleOnError(lifecycle, ()=>{
146
350
  if (!createRequestOptions?.enableRsc) {
147
351
  const { htmlTemplate } = options.resource;
148
352
  options.resource.htmlTemplate = htmlTemplate.replace('</head>', `${CHUNK_CSS_PLACEHOLDER}</head>`);
149
353
  }
150
354
  });
151
- const response = await runWithRouterCleanupOnError(routerCleanup, ()=>renderRequest(request, Root, context, options, handleRequest, !!createRequestOptions?.enableRsc));
152
- return finishWithRouterCleanup(routerCleanup, ()=>finalizeRenderResponse(response, responseProxy, redirectCtx, routerCleanup));
355
+ const response = await runWithRequestLifecycleOnError(lifecycle, ()=>renderRequest(request, Root, context, options, handleRequest, !!createRequestOptions?.enableRsc));
356
+ return finishWithRequestLifecycle(lifecycle, ()=>finalizeRenderResponse(response, responseProxy, redirectCtx, lifecycle));
153
357
  });
154
358
  };
155
359
  return requestHandler;
156
360
  };
157
- export { createRequestHandler };
361
+ export { REQUEST_END_ERROR, RESPONSE_BODY_CANCEL_ERROR, applyRouterResult, createLoaderRedirectResponse, createRequestHandler, createRequestLifecycle, finalizeRenderResponse, finishWithRequestLifecycle, runWithRequestLifecycleOnError };
@@ -11,8 +11,8 @@ const readAsset = async (chunk)=>{
11
11
  };
12
12
  const checkIsInline = (chunk, enableInline)=>{
13
13
  if ('production' !== process.env.NODE_ENV) return false;
14
- if (enableInline instanceof RegExp) return enableInline.test(chunk);
15
- return Boolean(enableInline);
14
+ else if (enableInline instanceof RegExp) return enableInline.test(chunk);
15
+ else return Boolean(enableInline);
16
16
  };
17
17
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
18
18
  const { config, runtimeContext, styledComponentsStyleTags, entryName, lifecycle, helmetData } = options;
@@ -70,7 +70,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
70
70
  const styles = await Promise.all(cssChunks.map(async (chunk)=>{
71
71
  const link = `<link href="${chunk}" rel="stylesheet" />`;
72
72
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
73
- return link;
73
+ else return link;
74
74
  }));
75
75
  return `${styles.join('')}`;
76
76
  }
@@ -4,8 +4,8 @@ import { buildHtml, replaceSSRTemplateChunk } from "../shared.mjs";
4
4
  import { hasStylesheetLink } from "../utils.mjs";
5
5
  const checkIsInline = (chunk, enableInline)=>{
6
6
  if ('production' !== process.env.NODE_ENV) return false;
7
- if (enableInline instanceof RegExp) return enableInline.test(chunk);
8
- return Boolean(enableInline);
7
+ else if (enableInline instanceof RegExp) return enableInline.test(chunk);
8
+ else return Boolean(enableInline);
9
9
  };
10
10
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
11
11
  const { config, runtimeContext, styledComponentsStyleTags, entryName, lifecycle, helmetData } = options;
@@ -1,15 +1,17 @@
1
+ import { getGlobalEnableRsc, getGlobalInternalRuntimeContext } from "@modern-js/runtime/context";
1
2
  import { storage } from "@modern-js/runtime-utils/node";
2
3
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
3
4
  import { PassThrough, Readable, Transform, finished, pipeline } from "stream";
5
+ import { StringDecoder } from "string_decoder";
4
6
  import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
5
7
  import { RenderLevel } from "../../constants.mjs";
6
- import { getGlobalEnableRsc, getGlobalInternalRuntimeContext } from "../../context/index.mjs";
7
8
  import { getMonitors } from "../../context/monitors/index.mjs";
8
9
  import { wrapRuntimeComponentResolver } from "../../react/wrapper.mjs";
9
10
  import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
10
11
  import { createSSRRenderLifecycle, createSSRStreamErrorReporter, observeSSRStream, orderSSRStreamTransforms } from "../shared.mjs";
11
12
  import { enqueueFromEntries } from "./deferredScript.mjs";
12
- import { ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
13
+ import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
14
+ import { SHELL_PROGRESSIVE_CHUNK_SIZE, ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
13
15
  import { getTemplates } from "./template.mjs";
14
16
  const createReadableStreamFromElement = async (request, rootElement, options)=>{
15
17
  const { renderToPipeableStream } = await import("react-dom/server");
@@ -23,6 +25,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
23
25
  config,
24
26
  platform: 'node',
25
27
  mode: 'stream',
28
+ monitors: getMonitors(),
26
29
  isRsc,
27
30
  terminalMarker: ESCAPED_SHELL_STREAM_END_MARK
28
31
  }) || [];
@@ -121,29 +124,42 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
121
124
  });
122
125
  if (failed) return;
123
126
  const chunks = [];
127
+ let bufferedLength = 0;
124
128
  const marker = Buffer.from(ESCAPED_SHELL_STREAM_END_MARK);
129
+ let markerTail = Buffer.alloc(0);
130
+ const decoder = new StringDecoder('utf8');
125
131
  const pendingScripts = [];
132
+ let deferredResolversComplete = Promise.resolve();
126
133
  let shellChunkStatus = ShellChunkStatus.START;
127
- const emitShell = (destination, buffered, markerIndex)=>{
134
+ const emitShell = (buffered, markerIndex)=>{
128
135
  const beforeMark = lifecycle.completedBody(buffered.subarray(0, markerIndex).toString('utf8'), 'shell');
129
136
  const completedShellBefore = createReplaceHelemt(getHelmetData(extenders))(shellBefore);
130
137
  shellChunkStatus = ShellChunkStatus.FINISH;
131
138
  chunks.length = 0;
132
- destination.push(`${completedShellBefore}${beforeMark}${shellAfter}`);
139
+ bufferedLength = 0;
140
+ coordinator.writeReact(`${completedShellBefore}${beforeMark}${shellAfter}`);
133
141
  const afterMark = buffered.subarray(markerIndex + marker.length);
134
- if (afterMark.length > 0) destination.push(afterMark);
135
- for (const script of pendingScripts)destination.push(script);
142
+ if (afterMark.length > 0) coordinator.writeReact(decoder.write(afterMark));
143
+ coordinator.markShellFinished();
144
+ for (const script of pendingScripts)coordinator.enqueueResolver(script);
136
145
  pendingScripts.length = 0;
137
146
  };
138
147
  const body = new Transform({
139
148
  transform (chunk, _encoding, callback) {
140
149
  try {
141
- if (shellChunkStatus === ShellChunkStatus.FINISH) this.push(chunk);
150
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
151
+ if (shellChunkStatus === ShellChunkStatus.FINISH) coordinator.writeReact(decoder.write(bytes));
142
152
  else {
143
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
144
- const buffered = Buffer.concat(chunks);
145
- const markerIndex = buffered.indexOf(marker);
146
- if (-1 !== markerIndex) emitShell(this, buffered, markerIndex);
153
+ const window = Buffer.concat([
154
+ markerTail,
155
+ bytes
156
+ ]);
157
+ const windowIndex = window.indexOf(marker);
158
+ const windowStart = bufferedLength - markerTail.length;
159
+ chunks.push(bytes);
160
+ bufferedLength += bytes.length;
161
+ if (-1 === windowIndex) markerTail = window.subarray(Math.max(0, window.length - marker.length + 1));
162
+ else emitShell(Buffer.concat(chunks, bufferedLength), windowStart + windowIndex);
147
163
  }
148
164
  callback();
149
165
  } catch (error) {
@@ -154,14 +170,27 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
154
170
  try {
155
171
  if (shellChunkStatus !== ShellChunkStatus.FINISH) {
156
172
  const buffered = Buffer.concat(chunks);
157
- emitShell(this, buffered, buffered.length);
173
+ emitShell(buffered, buffered.length);
158
174
  }
159
- callback();
175
+ const trailing = decoder.end();
176
+ if (trailing) coordinator.writeReact(trailing);
177
+ deferredResolversComplete.then(()=>{
178
+ try {
179
+ coordinator.finish();
180
+ callback();
181
+ } catch (error) {
182
+ callback(error instanceof Error ? error : new Error(String(error)));
183
+ }
184
+ }, (error)=>callback(error instanceof Error ? error : new Error(String(error))));
160
185
  } catch (error) {
161
186
  callback(error instanceof Error ? error : new Error(String(error)));
162
187
  }
163
188
  }
164
189
  });
190
+ const coordinator = new DeferredScriptOutputCoordinator((content)=>{
191
+ body.push(content);
192
+ });
193
+ body.once('close', ()=>coordinator.abort());
165
194
  own(body);
166
195
  const passThrough = new PassThrough();
167
196
  let processedStream = own(passThrough);
@@ -172,11 +201,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
172
201
  deliver(Readable.toWeb(body));
173
202
  reactStream.pipe(passThrough);
174
203
  try {
175
- const activeDeferreds = storage.useContext?.()?.activeDeferreds;
204
+ const storageContext = storage.useContext?.();
205
+ const activeDeferreds = storageContext?.activeDeferreds;
206
+ const deferredScriptKeys = storageContext?.deferredScriptKeys;
176
207
  const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
177
- enqueueFromEntries(entries, config.nonce, (script)=>{
208
+ if (entries.length > 0) deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, (script)=>{
178
209
  if (failed || body.destroyed || body.writableEnded) return;
179
- if (shellChunkStatus === ShellChunkStatus.FINISH) body.write(script);
210
+ if (shellChunkStatus === ShellChunkStatus.FINISH) coordinator.enqueueResolver(script);
180
211
  else pendingScripts.push(script);
181
212
  });
182
213
  } catch (error) {
@@ -213,6 +244,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
213
244
  reactStream = renderToPipeableStream(processedRootElement, {
214
245
  nonce: config.nonce,
215
246
  identifierPrefix: SSR_HYDRATION_ID_PREFIX,
247
+ progressiveChunkSize: SHELL_PROGRESSIVE_CHUNK_SIZE,
216
248
  [onReady] () {
217
249
  startOutput().catch(fail);
218
250
  },