@modern-js/runtime 3.9.1 → 3.9.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 (45) hide show
  1. package/dist/cjs/cli/template.server.js +1 -2
  2. package/dist/cjs/core/browser/hydrate.js +13 -11
  3. package/dist/cjs/core/server/requestHandler.js +2 -0
  4. package/dist/cjs/core/server/stream/beforeTemplate.js +3 -3
  5. package/dist/cjs/core/server/stream/createReadableStream.js +51 -8
  6. package/dist/cjs/core/server/stream/createReadableStream.worker.js +37 -15
  7. package/dist/cjs/core/server/stream/deferredScript.js +12 -6
  8. package/dist/cjs/core/server/stream/deferredScriptOutputCoordinator.js +370 -0
  9. package/dist/cjs/core/server/string/loadable.js +4 -4
  10. package/dist/cjs/router/cli/code/nestedRoutes.js +1 -2
  11. package/dist/cjs/router/cli/code/templates.js +1 -1
  12. package/dist/cjs/router/cli/code/utils.js +2 -2
  13. package/dist/cjs/router/runtime/DeferredDataScripts.node.js +13 -10
  14. package/dist/cjs/router/runtime/plugin.node.js +1 -1
  15. package/dist/esm/cli/template.server.mjs +1 -2
  16. package/dist/esm/core/browser/hydrate.mjs +13 -11
  17. package/dist/esm/core/server/requestHandler.mjs +2 -0
  18. package/dist/esm/core/server/stream/beforeTemplate.mjs +3 -3
  19. package/dist/esm/core/server/stream/createReadableStream.mjs +51 -8
  20. package/dist/esm/core/server/stream/createReadableStream.worker.mjs +37 -15
  21. package/dist/esm/core/server/stream/deferredScript.mjs +12 -6
  22. package/dist/esm/core/server/stream/deferredScriptOutputCoordinator.mjs +332 -0
  23. package/dist/esm/core/server/string/loadable.mjs +4 -4
  24. package/dist/esm/router/cli/code/nestedRoutes.mjs +1 -2
  25. package/dist/esm/router/cli/code/templates.mjs +1 -1
  26. package/dist/esm/router/cli/code/utils.mjs +2 -2
  27. package/dist/esm/router/runtime/DeferredDataScripts.node.mjs +13 -10
  28. package/dist/esm/router/runtime/plugin.node.mjs +1 -1
  29. package/dist/esm-node/cli/template.server.mjs +1 -2
  30. package/dist/esm-node/core/browser/hydrate.mjs +13 -11
  31. package/dist/esm-node/core/server/requestHandler.mjs +2 -0
  32. package/dist/esm-node/core/server/stream/beforeTemplate.mjs +3 -3
  33. package/dist/esm-node/core/server/stream/createReadableStream.mjs +51 -8
  34. package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +37 -15
  35. package/dist/esm-node/core/server/stream/deferredScript.mjs +12 -6
  36. package/dist/esm-node/core/server/stream/deferredScriptOutputCoordinator.mjs +333 -0
  37. package/dist/esm-node/core/server/string/loadable.mjs +4 -4
  38. package/dist/esm-node/router/cli/code/nestedRoutes.mjs +1 -2
  39. package/dist/esm-node/router/cli/code/templates.mjs +1 -1
  40. package/dist/esm-node/router/cli/code/utils.mjs +2 -2
  41. package/dist/esm-node/router/runtime/DeferredDataScripts.node.mjs +13 -10
  42. package/dist/esm-node/router/runtime/plugin.node.mjs +1 -1
  43. package/dist/types/core/server/stream/deferredScript.d.ts +1 -1
  44. package/dist/types/core/server/stream/deferredScriptOutputCoordinator.d.ts +72 -0
  45. package/package.json +12 -12
@@ -13,7 +13,8 @@ const DeferredDataScripts = (props)=>{
13
13
  }, []);
14
14
  const deferredScripts = useMemo(()=>{
15
15
  if (!staticContext) return null;
16
- const activeDeferreds = storage.useContext().activeDeferreds;
16
+ const requestStorage = storage.useContext();
17
+ const activeDeferreds = requestStorage.activeDeferreds;
17
18
  const _ROUTER_DATA = {
18
19
  loaderData: staticContext.loaderData,
19
20
  errors: serializeErrors(staticContext.errors)
@@ -26,6 +27,7 @@ const DeferredDataScripts = (props)=>{
26
27
  const deferredDataScripts = [];
27
28
  const initialScripts = Array.from(activeDeferreds.entries()).map(([routeId, deferredData])=>{
28
29
  const pendingKeys = new Set(deferredData.pendingKeys);
30
+ requestStorage.deferredScriptKeys?.set(routeId, Array.from(pendingKeys));
29
31
  const { deferredKeys } = deferredData;
30
32
  const deferredKeyPromiseManifests = deferredKeys.map((key)=>{
31
33
  if (pendingKeys.has(key)) return {
@@ -36,7 +38,7 @@ const DeferredDataScripts = (props)=>{
36
38
  serializeJson(key)
37
39
  ]
38
40
  };
39
- {
41
+ else {
40
42
  const trackedPromise = deferredData.data[key];
41
43
  if (void 0 !== trackedPromise._error) {
42
44
  const error = {
@@ -51,15 +53,16 @@ const DeferredDataScripts = (props)=>{
51
53
  serializeJson(error)
52
54
  ]
53
55
  };
56
+ } else {
57
+ if (void 0 === trackedPromise._data) throw new Error(`The deferred data for ${key} was not resolved, did you forget to return data from a deferred promise`);
58
+ return {
59
+ key,
60
+ routerDataFnName: 'p',
61
+ routerDataFnArgs: [
62
+ serializeJson(trackedPromise._data)
63
+ ]
64
+ };
54
65
  }
55
- if (void 0 === trackedPromise._data) throw new Error(`The deferred data for ${key} was not resolved, did you forget to return data from a deferred promise`);
56
- return {
57
- key,
58
- routerDataFnName: 'p',
59
- routerDataFnArgs: [
60
- serializeJson(trackedPromise._data)
61
- ]
62
- };
63
66
  }
64
67
  });
65
68
  return {
@@ -106,7 +106,7 @@ const routerPlugin = (userConfig = {})=>({
106
106
  }) : /*#__PURE__*/ jsx(RSCStaticRouter, {
107
107
  basename: basename
108
108
  });
109
- {
109
+ else {
110
110
  const routerWrapper = /*#__PURE__*/ jsxs(Fragment, {
111
111
  children: [
112
112
  /*#__PURE__*/ jsx(StaticRouterProvider, {
@@ -109,8 +109,7 @@ function genHandlerCode({ mode, metaName, customServerEntry, srcDirectory, inter
109
109
  return `
110
110
  export * from '${realEntryPath}';
111
111
  export { default as requestHandler } from '${realEntryPath}'`;
112
- }
113
- {
112
+ } else {
114
113
  const entrySource = enableRsc ? SERVER_ENTRY_RSC : SERVER_ENTRY;
115
114
  const serverEntry = transformServerEntry(entrySource, {
116
115
  metaName: metaName || 'modern-js',
@@ -27,7 +27,7 @@ function hydrate_hydrateRoot(App, context, ModernRender, ModernHydrate) {
27
27
  if ('stream' === renderMode) return streamSSRHydrate();
28
28
  function streamSSRHydrate() {
29
29
  if (renderLevel !== RenderLevel.SERVER_RENDER) return ModernRender(wrapRuntimeContextProvider(App, context));
30
- {
30
+ else {
31
31
  const SSRApp = ()=>/*#__PURE__*/ jsx(WithCallback, {
32
32
  callback: callback,
33
33
  children: App
@@ -38,19 +38,21 @@ function hydrate_hydrateRoot(App, context, ModernRender, ModernHydrate) {
38
38
  return stringSSRHydrate();
39
39
  function stringSSRHydrate() {
40
40
  if (renderLevel === RenderLevel.CLIENT_RENDER) return ModernRender(wrapRuntimeContextProvider(App, context));
41
- if (renderLevel === RenderLevel.SERVER_RENDER) return new Promise((resolve)=>{
42
- loadableReady(()=>{
43
- const SSRApp = ()=>/*#__PURE__*/ jsx(WithCallback, {
44
- callback: callback,
45
- children: App
41
+ else {
42
+ if (renderLevel === RenderLevel.SERVER_RENDER) return new Promise((resolve)=>{
43
+ loadableReady(()=>{
44
+ const SSRApp = ()=>/*#__PURE__*/ jsx(WithCallback, {
45
+ callback: callback,
46
+ children: App
47
+ });
48
+ ModernHydrate(wrapRuntimeContextProvider(/*#__PURE__*/ jsx(SSRApp, {}), hydrateContext)).then((root)=>{
49
+ resolve(root);
46
50
  });
47
- ModernHydrate(wrapRuntimeContextProvider(/*#__PURE__*/ jsx(SSRApp, {}), hydrateContext)).then((root)=>{
48
- resolve(root);
49
51
  });
50
52
  });
51
- });
52
- console.warn(`unknow render level: ${renderLevel}, execute render()`);
53
- return ModernRender(wrapRuntimeContextProvider(App, context));
53
+ console.warn(`unknow render level: ${renderLevel}, execute render()`);
54
+ return ModernRender(wrapRuntimeContextProvider(App, context));
55
+ }
54
56
  }
55
57
  }
56
58
  export { hydrateWithReact, hydrate_hydrateRoot as hydrateRoot };
@@ -102,12 +102,14 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
102
102
  status: -1
103
103
  };
104
104
  const activeDeferreds = new Map();
105
+ const deferredScriptKeys = new Map();
105
106
  return storage.run({
106
107
  headers: headersData,
107
108
  request,
108
109
  monitors: options.monitors,
109
110
  responseProxy,
110
111
  activeDeferreds,
112
+ deferredScriptKeys,
111
113
  serverPayload: void 0
112
114
  }, async ()=>{
113
115
  const Root = createRoot();
@@ -16,8 +16,8 @@ const readAsset = async (chunk)=>{
16
16
  };
17
17
  const checkIsInline = (chunk, enableInline)=>{
18
18
  if ('production' !== process.env.NODE_ENV) return false;
19
- if (enableInline instanceof RegExp) return enableInline.test(chunk);
20
- return Boolean(enableInline);
19
+ else if (enableInline instanceof RegExp) return enableInline.test(chunk);
20
+ else return Boolean(enableInline);
21
21
  };
22
22
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
23
23
  const { config, runtimeContext, styledComponentsStyleTags, entryName } = options;
@@ -58,7 +58,7 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
58
58
  const styles = await Promise.all(cssChunks.map(async (chunk)=>{
59
59
  const link = `<link href="${chunk}" rel="stylesheet" />`;
60
60
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
61
- return link;
61
+ else return link;
62
62
  }));
63
63
  return `${styles.join('')}`;
64
64
  }
@@ -1,5 +1,6 @@
1
1
  import "node:module";
2
2
  import { PassThrough, Readable, Transform } from "stream";
3
+ import { StringDecoder } from "string_decoder";
3
4
  import { storage } from "@modern-js/runtime-utils/node";
4
5
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
5
6
  import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
@@ -7,6 +8,7 @@ import { RenderLevel } from "../../constants.mjs";
7
8
  import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
8
9
  import { getMonitors } from "../../context/monitors/index.mjs";
9
10
  import { enqueueFromEntries } from "./deferredScript.mjs";
11
+ import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
10
12
  import { ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
11
13
  import { getTemplates } from "./template.mjs";
12
14
  const defaultExtender = {
@@ -55,7 +57,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
55
57
  entryName,
56
58
  styledComponentsStyleTags
57
59
  }).then(({ shellAfter, shellBefore })=>{
60
+ const decoder = new StringDecoder('utf8');
58
61
  const pendingScripts = [];
62
+ const outputState = {};
63
+ let deferredResolversComplete = Promise.resolve();
64
+ const writeReact = (content)=>{
65
+ if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
66
+ outputState.coordinator.writeReact(content);
67
+ };
59
68
  const body = new Transform({
60
69
  transform (chunk, _encoding, callback) {
61
70
  try {
@@ -67,17 +76,49 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
67
76
  const beforeMark = concatedChunk.slice(0, markerIndex);
68
77
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
69
78
  shellChunkStatus = ShellChunkStatus.FINISH;
70
- this.push(`${shellBefore}${beforeMark}${shellAfter}`);
71
- if (afterMark) this.push(afterMark);
72
- if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
79
+ writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
80
+ if (afterMark) writeReact(afterMark);
81
+ if (outputState.coordinator) {
82
+ outputState.coordinator.markShellFinished();
83
+ for (const script of pendingScripts)outputState.coordinator.enqueueResolver(script);
84
+ pendingScripts.length = 0;
85
+ }
73
86
  }
74
- } else this.push(chunk);
87
+ } else {
88
+ const decodedChunk = decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
89
+ writeReact(decodedChunk);
90
+ }
75
91
  callback();
76
92
  } catch (e) {
77
93
  e instanceof Error ? callback(e) : callback(new Error('Received unknown error when streaming'));
78
94
  }
95
+ },
96
+ flush (callback) {
97
+ try {
98
+ writeReact(decoder.end());
99
+ } catch (error) {
100
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
101
+ return;
102
+ }
103
+ deferredResolversComplete.then(()=>{
104
+ try {
105
+ outputState.coordinator?.finish();
106
+ callback();
107
+ } catch (error) {
108
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
109
+ }
110
+ }, (error)=>{
111
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
112
+ });
79
113
  }
80
114
  });
115
+ const coordinator = new DeferredScriptOutputCoordinator((content)=>{
116
+ body.push(content);
117
+ });
118
+ outputState.coordinator = coordinator;
119
+ body.once('close', ()=>{
120
+ coordinator.abort();
121
+ });
81
122
  const passThrough = new PassThrough();
82
123
  const stream = Readable.toWeb(body);
83
124
  resolve(stream);
@@ -85,23 +126,25 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
85
126
  extenders.forEach((extender)=>{
86
127
  if (extender.processStream) processedStream = extender.processStream(processedStream);
87
128
  });
88
- reactStreamingPipe(passThrough);
89
- processedStream.pipe(body);
90
129
  try {
91
130
  const storageContext = storage.useContext?.();
92
131
  const activeDeferreds = storageContext?.activeDeferreds;
132
+ const deferredScriptKeys = storageContext?.deferredScriptKeys;
93
133
  const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
94
134
  if (entries.length > 0) {
95
135
  const enqueueScript = (s)=>{
96
- if (shellChunkStatus === ShellChunkStatus.FINISH) body.write(s);
136
+ if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
137
+ if (shellChunkStatus === ShellChunkStatus.FINISH) outputState.coordinator.enqueueResolver(s);
97
138
  else pendingScripts.push(s);
98
139
  };
99
- enqueueFromEntries(entries, config.nonce, (s)=>enqueueScript(s));
140
+ deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, enqueueScript);
100
141
  }
101
142
  } catch (err) {
102
143
  const monitors = getMonitors();
103
144
  monitors.error("cannot inject router data script", err);
104
145
  }
146
+ reactStreamingPipe(passThrough);
147
+ processedStream.pipe(body);
105
148
  });
106
149
  },
107
150
  onShellError (error) {
@@ -4,6 +4,7 @@ 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
6
  import { enqueueFromEntries } from "./deferredScript.mjs";
7
+ import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
7
8
  import { ShellChunkStatus, encodeForWebStream, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
8
9
  import { getTemplates } from "./template.mjs";
9
10
  const createReadableStreamFromElement = async (request, rootElement, options)=>{
@@ -36,10 +37,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
36
37
  const { waitForAllReady } = resolveStreamingMode(request, forceStreamToString);
37
38
  if (waitForAllReady) await readableOriginal.allReady;
38
39
  const reader = readableOriginal.getReader();
40
+ let coordinator;
39
41
  const stream = new ReadableStream({
40
42
  start (controller) {
43
+ const decoder = new TextDecoder();
41
44
  const pendingScripts = [];
42
45
  let isClosed = false;
46
+ let deferredResolversComplete = Promise.resolve();
43
47
  const safeEnqueue = (chunk)=>{
44
48
  if (isClosed) return;
45
49
  try {
@@ -56,38 +60,55 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
56
60
  } catch {}
57
61
  }
58
62
  };
59
- const flushPendingScripts = ()=>{
60
- for (const s of pendingScripts)safeEnqueue(encodeForWebStream(s));
61
- pendingScripts.length = 0;
62
- };
63
- const enqueueScript = (script)=>{
64
- if (shellChunkStatus === ShellChunkStatus.FINISH) safeEnqueue(encodeForWebStream(script));
65
- else pendingScripts.push(script);
66
- };
63
+ coordinator = new DeferredScriptOutputCoordinator((content)=>{
64
+ safeEnqueue(encodeForWebStream(content));
65
+ });
67
66
  const storageContext = storage.useContext?.();
68
67
  const activeDeferreds = storageContext?.activeDeferreds;
68
+ const deferredScriptKeys = storageContext?.deferredScriptKeys;
69
69
  const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
70
- if (entries.length > 0) enqueueFromEntries(entries, config.nonce, enqueueScript);
70
+ if (entries.length > 0) deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, (script)=>{
71
+ if (!coordinator) throw new Error("Deferred script coordinator is not ready");
72
+ if (shellChunkStatus === ShellChunkStatus.FINISH) coordinator.enqueueResolver(script);
73
+ else pendingScripts.push(script);
74
+ });
71
75
  async function push() {
72
76
  try {
73
77
  const { done, value } = await reader.read();
74
- if (done) return void closeController();
78
+ if (done) {
79
+ const trailingText = decoder.decode();
80
+ if (trailingText) coordinator?.writeReact(trailingText);
81
+ await deferredResolversComplete;
82
+ coordinator?.finish();
83
+ closeController();
84
+ return;
85
+ }
75
86
  if (isClosed) return;
76
87
  if (shellChunkStatus !== ShellChunkStatus.FINISH) {
77
- chunkVec.push(new TextDecoder().decode(value));
88
+ chunkVec.push(decoder.decode(value, {
89
+ stream: true
90
+ }));
78
91
  const concatedChunk = chunkVec.join('');
79
92
  const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
80
93
  if (-1 !== markerIndex) {
81
94
  const beforeMark = concatedChunk.slice(0, markerIndex);
82
95
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
83
96
  shellChunkStatus = ShellChunkStatus.FINISH;
84
- safeEnqueue(encodeForWebStream(`${shellBefore}${beforeMark}${shellAfter}`));
85
- if (afterMark) safeEnqueue(encodeForWebStream(afterMark));
86
- flushPendingScripts();
97
+ coordinator?.writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
98
+ if (afterMark) coordinator?.writeReact(afterMark);
99
+ coordinator?.markShellFinished();
100
+ for (const script of pendingScripts)coordinator?.enqueueResolver(script);
101
+ pendingScripts.length = 0;
87
102
  }
88
- } else safeEnqueue(value);
103
+ } else {
104
+ const decodedChunk = decoder.decode(value, {
105
+ stream: true
106
+ });
107
+ if (decodedChunk) coordinator?.writeReact(decodedChunk);
108
+ }
89
109
  if (!isClosed) push();
90
110
  } catch (error) {
111
+ coordinator?.abort();
91
112
  if (!isClosed) {
92
113
  isClosed = true;
93
114
  try {
@@ -99,6 +120,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
99
120
  push();
100
121
  },
101
122
  cancel (reason) {
123
+ coordinator?.abort();
102
124
  reader.cancel(reason).catch(()=>{});
103
125
  }
104
126
  });
@@ -41,23 +41,29 @@ function buildDeferredDataScript(nonce, args) {
41
41
  const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
42
42
  return `<script async${nonceAttr} data-fn-name="r" data-script-src="modern-run-router-data-fn" data-fn-args='${escaped}' suppressHydrationWarning>${runRouterDataFnStr}</script>`;
43
43
  }
44
- function enqueueFromEntries(entries, nonce, emit) {
44
+ function enqueueFromEntries(entries, deferredScriptKeys, nonce, emit) {
45
+ const pendingResolvers = [];
45
46
  entries.forEach(([routeId, value])=>{
46
47
  if (!isDeferredDataLike(value)) return;
47
- const pendingKeys = new Set(value.pendingKeys ?? []);
48
- pendingKeys.forEach((key)=>{
48
+ const serializedKeys = deferredScriptKeys ? deferredScriptKeys.get(routeId) ?? [] : value.pendingKeys ?? [];
49
+ serializedKeys.forEach((key)=>{
49
50
  const tracked = value.data?.[key];
50
- if (isPromiseLike(tracked)) tracked.then((val)=>emit(buildDeferredDataScript(nonce, [
51
+ if (isPromiseLike(tracked)) pendingResolvers.push(Promise.resolve(tracked).then((val)=>{
52
+ emit(buildDeferredDataScript(nonce, [
51
53
  routeId,
52
54
  key,
53
55
  val
54
- ])), (err)=>emit(buildDeferredDataScript(nonce, [
56
+ ]));
57
+ }, (err)=>{
58
+ emit(buildDeferredDataScript(nonce, [
55
59
  routeId,
56
60
  key,
57
61
  void 0,
58
62
  toErrorInfo(err)
59
- ])));
63
+ ]));
64
+ }));
60
65
  });
61
66
  });
67
+ return Promise.all(pendingResolvers).then(()=>void 0);
62
68
  }
63
69
  export { buildDeferredDataScript, enqueueFromEntries, escapeAttr, isDeferredDataLike, isPromiseLike, isRecord, toErrorInfo };