@modern-js/runtime 2.8.0 → 2.10.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/cjs/router/runtime/DeferredDataScripts.js +2 -140
  3. package/dist/cjs/router/runtime/DeferredDataScripts.node.js +165 -0
  4. package/dist/cjs/ssr/serverRender/index.js +8 -0
  5. package/dist/cjs/ssr/serverRender/renderToStream/buildTemplate.after.js +8 -1
  6. package/dist/cjs/ssr/serverRender/renderToStream/bulidTemplate.before.js +2 -2
  7. package/dist/cjs/ssr/serverRender/renderToStream/renderToPipe.worker.js +25 -9
  8. package/dist/cjs/ssr/serverRender/renderToStream/template.js +2 -3
  9. package/dist/cjs/ssr/serverRender/utils.js +3 -0
  10. package/dist/esm/router/runtime/DeferredDataScripts.js +2 -164
  11. package/dist/esm/router/runtime/DeferredDataScripts.node.js +166 -0
  12. package/dist/esm/ssr/serverRender/index.js +6 -1
  13. package/dist/esm/ssr/serverRender/renderToStream/buildTemplate.after.js +5 -1
  14. package/dist/esm/ssr/serverRender/renderToStream/bulidTemplate.before.js +1 -1
  15. package/dist/esm/ssr/serverRender/renderToStream/renderToPipe.worker.js +24 -11
  16. package/dist/esm/ssr/serverRender/renderToStream/template.js +2 -3
  17. package/dist/esm/ssr/serverRender/utils.js +2 -1
  18. package/dist/esm-node/router/runtime/DeferredDataScripts.js +2 -144
  19. package/dist/esm-node/router/runtime/DeferredDataScripts.node.js +148 -0
  20. package/dist/esm-node/ssr/serverRender/index.js +8 -0
  21. package/dist/esm-node/ssr/serverRender/renderToStream/buildTemplate.after.js +8 -1
  22. package/dist/esm-node/ssr/serverRender/renderToStream/bulidTemplate.before.js +1 -1
  23. package/dist/esm-node/ssr/serverRender/renderToStream/renderToPipe.worker.js +25 -9
  24. package/dist/esm-node/ssr/serverRender/renderToStream/template.js +2 -3
  25. package/dist/esm-node/ssr/serverRender/utils.js +2 -0
  26. package/dist/types/router/runtime/DeferredDataScripts.d.ts +2 -7
  27. package/dist/types/router/runtime/DeferredDataScripts.node.d.ts +8 -0
  28. package/dist/types/router/runtime/index.d.ts +1 -1
  29. package/dist/types/ssr/serverRender/renderToStream/buildTemplate.after.d.ts +2 -4
  30. package/dist/types/ssr/serverRender/renderToStream/renderToPipe.worker.d.ts +1 -1
  31. package/dist/types/ssr/serverRender/utils.d.ts +1 -0
  32. package/package.json +12 -12
@@ -0,0 +1,148 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Suspense, useEffect, useRef, useMemo, useContext } from "react";
3
+ import {
4
+ Await,
5
+ UNSAFE_DataRouterContext as DataRouterContext,
6
+ useAsyncError
7
+ } from "react-router-dom";
8
+ import { serializeJson } from "@modern-js/utils/serialize";
9
+ import { JSX_SHELL_STREAM_END_MARK } from "../../common";
10
+ import { serializeErrors } from "./utils";
11
+ const setupFnStr = `function s(r,e){_ROUTER_DATA.r=_ROUTER_DATA.r||{},_ROUTER_DATA.r[r]=_ROUTER_DATA.r[r]||{};return new Promise((function(A,R){_ROUTER_DATA.r[r][e]={resolve:A,reject:R}}))};`;
12
+ const resolveFnStr = `function r(e,r,o,A){A?_ROUTER_DATA.r[e][r].reject(A):_ROUTER_DATA.r[e][r].resolve(o)};`;
13
+ const preResolvedFnStr = `function p(e,r){return void 0!==r?Promise.reject(new Error(r.message)):Promise.resolve(e)};`;
14
+ const DeferredDataScripts = () => {
15
+ const context = useContext(DataRouterContext);
16
+ const { staticContext } = context || {};
17
+ const hydratedRef = useRef(false);
18
+ useEffect(() => {
19
+ hydratedRef.current = true;
20
+ }, []);
21
+ const deferredScripts = useMemo(() => {
22
+ if (!staticContext) {
23
+ return null;
24
+ }
25
+ const activeDeferreds = staticContext.activeDeferreds || [];
26
+ const _ROUTER_DATA = {
27
+ loaderData: staticContext.loaderData,
28
+ errors: serializeErrors(staticContext.errors)
29
+ };
30
+ let initialScripts = [
31
+ `_ROUTER_DATA = ${serializeJson(_ROUTER_DATA)};`,
32
+ `_ROUTER_DATA.s = ${setupFnStr}`,
33
+ `_ROUTER_DATA.r = ${resolveFnStr}`,
34
+ `_ROUTER_DATA.p = ${preResolvedFnStr}`
35
+ ].join("\n");
36
+ const deferredDataScripts = [];
37
+ initialScripts += Object.entries(activeDeferreds).map(([routeId, deferredData]) => {
38
+ const pendingKeys = new Set(deferredData.pendingKeys);
39
+ const { deferredKeys } = deferredData;
40
+ const deferredKeyPromiseStr = deferredKeys.map((key) => {
41
+ if (pendingKeys.has(key)) {
42
+ deferredDataScripts.push(
43
+ /* @__PURE__ */ jsx(
44
+ DeferredDataScript,
45
+ {
46
+ data: deferredData.data[key],
47
+ dataKey: key,
48
+ routeId
49
+ },
50
+ `${routeId} | ${key}`
51
+ )
52
+ );
53
+ return `${JSON.stringify(key)}: _ROUTER_DATA.s(${JSON.stringify(
54
+ routeId
55
+ )},${JSON.stringify(key)}) `;
56
+ } else {
57
+ const trackedPromise = deferredData.data[key];
58
+ if (typeof trackedPromise._error !== "undefined") {
59
+ const error = {
60
+ message: trackedPromise._error.message,
61
+ stack: process.env.NODE_ENV !== "production" ? trackedPromise._error.stack : void 0
62
+ };
63
+ return `${JSON.stringify(
64
+ key
65
+ )}: _ROUTER_DATA.p(${void 0}, ${serializeJson(error)})`;
66
+ } else {
67
+ if (typeof trackedPromise._data === "undefined") {
68
+ throw new Error(
69
+ `The deferred data for ${key} was not resolved, did you forget to return data from a deferred promise`
70
+ );
71
+ }
72
+ return `${JSON.stringify(key)}: _ROUTER_DATA.p(${serializeJson(
73
+ trackedPromise._data
74
+ )})`;
75
+ }
76
+ }
77
+ }).join(",\n");
78
+ return `Object.assign(_ROUTER_DATA.loaderData[${JSON.stringify(
79
+ routeId
80
+ )}], {${deferredKeyPromiseStr}});`;
81
+ }).join("\n");
82
+ return [initialScripts, deferredDataScripts];
83
+ }, []);
84
+ if (!deferredScripts) {
85
+ return null;
86
+ }
87
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
88
+ !hydratedRef.current && /* @__PURE__ */ jsx(
89
+ "script",
90
+ {
91
+ async: true,
92
+ suppressHydrationWarning: true,
93
+ dangerouslySetInnerHTML: { __html: deferredScripts[0] }
94
+ }
95
+ ),
96
+ !hydratedRef.current && deferredScripts[1],
97
+ JSX_SHELL_STREAM_END_MARK
98
+ ] });
99
+ };
100
+ const DeferredDataScript = ({
101
+ data,
102
+ routeId,
103
+ dataKey
104
+ }) => {
105
+ return /* @__PURE__ */ jsx(Suspense, { children: typeof document === "undefined" && data && dataKey && routeId ? /* @__PURE__ */ jsx(
106
+ Await,
107
+ {
108
+ resolve: data,
109
+ errorElement: /* @__PURE__ */ jsx(ErrorDeferredDataScript, { routeId, dataKey }),
110
+ children: (data2) => /* @__PURE__ */ jsx(
111
+ "script",
112
+ {
113
+ async: true,
114
+ suppressHydrationWarning: true,
115
+ dangerouslySetInnerHTML: {
116
+ __html: `_ROUTER_DATA.r(${JSON.stringify(
117
+ routeId
118
+ )}, ${JSON.stringify(dataKey)}, ${serializeJson(data2)});`
119
+ }
120
+ }
121
+ )
122
+ }
123
+ ) : null });
124
+ };
125
+ const ErrorDeferredDataScript = ({
126
+ routeId,
127
+ dataKey
128
+ }) => {
129
+ const error = useAsyncError();
130
+ return /* @__PURE__ */ jsx(
131
+ "script",
132
+ {
133
+ suppressHydrationWarning: true,
134
+ dangerouslySetInnerHTML: {
135
+ __html: `_ROUTER_DATA.r(${JSON.stringify(routeId)}, ${JSON.stringify(
136
+ dataKey
137
+ )}, ${void 0}, ${serializeJson({
138
+ message: error.message,
139
+ stack: error.stack
140
+ })});`
141
+ }
142
+ }
143
+ );
144
+ };
145
+ var DeferredDataScripts_node_default = DeferredDataScripts;
146
+ export {
147
+ DeferredDataScripts_node_default as default
148
+ };
@@ -1,5 +1,13 @@
1
1
  import { isReact18 } from "../utils";
2
+ import { CSS_CHUNKS_PLACEHOLDER } from "./utils";
2
3
  async function serverRender(options) {
4
+ var _a, _b;
5
+ if ((_a = options.context.ssrContext) == null ? void 0 : _a.template) {
6
+ options.context.ssrContext.template = (_b = options.context.ssrContext) == null ? void 0 : _b.template.replace(
7
+ "</head>",
8
+ `${CSS_CHUNKS_PLACEHOLDER}</head>`
9
+ );
10
+ }
3
11
  if (isReact18() && options.config.mode === "stream") {
4
12
  const pipe = await require("./renderToStream").render(options);
5
13
  return pipe;
@@ -7,12 +7,19 @@ function buildShellAfterTemplate(afterAppTemplate, options) {
7
7
  const ssrDataScript = buildSSRDataScript();
8
8
  return template.replace("<!--<?- SSRDataScript ?>-->", ssrDataScript);
9
9
  function buildSSRDataScript() {
10
- const { ssrContext, renderLevel } = options;
10
+ const {
11
+ context: { ssrContext, initialData, __i18nData__ },
12
+ renderLevel
13
+ } = options;
11
14
  const { request, enableUnsafeCtx } = ssrContext;
12
15
  const unsafeContext = {
13
16
  headers: request.headers
14
17
  };
15
18
  const SSRData = {
19
+ data: {
20
+ initialData,
21
+ i18nData: __i18nData__
22
+ },
16
23
  context: {
17
24
  request: {
18
25
  params: request.params,
@@ -1,11 +1,11 @@
1
1
  import ReactHelmet from "react-helmet";
2
2
  import { matchRoutes } from "react-router-dom";
3
3
  import helmetReplace from "../helmet";
4
+ import { CSS_CHUNKS_PLACEHOLDER } from "../utils";
4
5
  import {
5
6
  HEAD_REG_EXP,
6
7
  buildTemplate
7
8
  } from "./buildTemplate.share";
8
- const CSS_CHUNKS_PLACEHOLDER = "<!--<?- chunksMap.css ?>-->";
9
9
  function getHeadTemplate(beforeEntryTemplate, context) {
10
10
  const callbacks = [
11
11
  (headTemplate2) => {
@@ -1,9 +1,16 @@
1
1
  import { RenderLevel } from "../types";
2
+ import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common";
2
3
  import { getTemplates } from "./template";
4
+ var ShellChunkStatus = /* @__PURE__ */ ((ShellChunkStatus2) => {
5
+ ShellChunkStatus2[ShellChunkStatus2["IDLE"] = 0] = "IDLE";
6
+ ShellChunkStatus2[ShellChunkStatus2["START"] = 1] = "START";
7
+ ShellChunkStatus2[ShellChunkStatus2["FINIESH"] = 2] = "FINIESH";
8
+ return ShellChunkStatus2;
9
+ })(ShellChunkStatus || {});
3
10
  function renderToPipe(rootElement, context, options) {
4
- let isShellStream = true;
11
+ let shellChunkStatus = 0 /* IDLE */;
5
12
  const { ssrContext } = context;
6
- const forUserPipe = async (stream) => {
13
+ const forUserPipe = async () => {
7
14
  let renderToReadableStream;
8
15
  try {
9
16
  ({ renderToReadableStream } = require("react-dom/server"));
@@ -35,11 +42,20 @@ function renderToPipe(rootElement, context, options) {
35
42
  controller.close();
36
43
  return;
37
44
  }
38
- if (isShellStream) {
39
- controller.enqueue(encodeForWebStream(shellBefore));
40
- controller.enqueue(value);
41
- controller.enqueue(encodeForWebStream(shellAfter));
42
- isShellStream = false;
45
+ if (shellChunkStatus !== 2 /* FINIESH */) {
46
+ let concatedChunk = new TextDecoder().decode(value);
47
+ if (shellChunkStatus === 0 /* IDLE */) {
48
+ concatedChunk = `${shellBefore}${concatedChunk}`;
49
+ shellChunkStatus = 1 /* START */;
50
+ }
51
+ if (shellChunkStatus === 1 /* START */ && concatedChunk.endsWith(ESCAPED_SHELL_STREAM_END_MARK)) {
52
+ concatedChunk = concatedChunk.replace(
53
+ ESCAPED_SHELL_STREAM_END_MARK,
54
+ shellAfter
55
+ );
56
+ shellChunkStatus = 2 /* FINIESH */;
57
+ }
58
+ controller.enqueue(encodeForWebStream(concatedChunk));
43
59
  } else {
44
60
  controller.enqueue(value);
45
61
  }
@@ -48,7 +64,7 @@ function renderToPipe(rootElement, context, options) {
48
64
  push();
49
65
  }
50
66
  });
51
- return readableOriginal(injectableStream).readableOriginal(stream);
67
+ return injectableStream;
52
68
  } catch (err) {
53
69
  ssrContext.metrics.emitCounter("app.render.streaming.shell.error", 1);
54
70
  const { shellAfter: shellAfter2, shellBefore: shellBefore2 } = getTemplates(
@@ -59,7 +75,7 @@ function renderToPipe(rootElement, context, options) {
59
75
  return fallbackHtml;
60
76
  }
61
77
  };
62
- return forUserPipe;
78
+ return forUserPipe();
63
79
  }
64
80
  let encoder;
65
81
  function encodeForWebStream(thing) {
@@ -2,15 +2,14 @@ import { buildShellAfterTemplate } from "./buildTemplate.after";
2
2
  import { buildShellBeforeTemplate } from "./bulidTemplate.before";
3
3
  const HTML_SEPARATOR = "<!--<?- html ?>-->";
4
4
  const getTemplates = (context, renderLevel) => {
5
- const { ssrContext, routerContext } = context;
5
+ const { ssrContext } = context;
6
6
  const [beforeAppTemplate = "", afterAppHtmlTemplate = ""] = ssrContext.template.split(HTML_SEPARATOR) || [];
7
7
  const builtBeforeTemplate = buildShellBeforeTemplate(
8
8
  beforeAppTemplate,
9
9
  context
10
10
  );
11
11
  const builtAfterTemplate = buildShellAfterTemplate(afterAppHtmlTemplate, {
12
- ssrContext,
13
- routerContext,
12
+ context,
14
13
  renderLevel
15
14
  });
16
15
  return {
@@ -1,3 +1,4 @@
1
+ const CSS_CHUNKS_PLACEHOLDER = "<!--<?- chunksMap.css ?>-->";
1
2
  function getLoadableScripts(extractor) {
2
3
  const check = (scripts2) => (scripts2 || "").includes("__LOADABLE_REQUIRED_CHUNKS___ext");
3
4
  const scripts = extractor.getScriptTags();
@@ -7,5 +8,6 @@ function getLoadableScripts(extractor) {
7
8
  return scripts.split("</script>").slice(0, 2).map((i) => `${i}</script>`).join("");
8
9
  }
9
10
  export {
11
+ CSS_CHUNKS_PLACEHOLDER,
10
12
  getLoadableScripts
11
13
  };
@@ -1,8 +1,3 @@
1
- /// <reference types="react" />
1
+ declare const _default: () => null;
2
2
 
3
- /**
4
- * DeferredDataScripts only renders in server side,
5
- * it doesn't need to be hydrated in client side.
6
- */
7
- declare const DeferredDataScripts: () => JSX.Element | null;
8
- export default DeferredDataScripts;
3
+ export default _default;
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+
3
+ /**
4
+ * DeferredDataScripts only renders in server side,
5
+ * it doesn't need to be hydrated in client side.
6
+ */
7
+ declare const DeferredDataScripts: () => JSX.Element | null;
8
+ export default DeferredDataScripts;
@@ -4,6 +4,6 @@ export type { SingleRouteConfig, RouterConfig };
4
4
  export default routerPlugin;
5
5
  export { modifyRoutes } from './plugin';
6
6
  export * from './withRouter';
7
- export type { FormEncType, FormMethod, GetScrollRestorationKeyFunction, ParamKeyValuePair, SubmitOptions, URLSearchParamsInit, FetcherWithComponents, ActionFunction, ActionFunctionArgs, AwaitProps, unstable_Blocker, unstable_BlockerFunction, DataRouteMatch, DataRouteObject, Fetcher, Hash, IndexRouteObject, IndexRouteProps, JsonFunction, LayoutRouteProps, LoaderFunction, LoaderFunctionArgs, Location, MemoryRouterProps, NavigateFunction, NavigateOptions, NavigateProps, Navigation, Navigator, NavLinkProps, NonIndexRouteObject, OutletProps, Params, ParamParseKey, Path, PathMatch, Pathname, PathPattern, PathRouteProps, RedirectFunction, RelativeRoutingType, RouteMatch, RouteObject, RouteProps, RouterProps, RouterProviderProps, RoutesProps, Search, ShouldRevalidateFunction, To } from 'react-router-dom';
7
+ export type { FormEncType, FormMethod, GetScrollRestorationKeyFunction, ParamKeyValuePair, SubmitOptions, URLSearchParamsInit, FetcherWithComponents, BrowserRouterProps, HashRouterProps, HistoryRouterProps, LinkProps, NavLinkProps, FormProps, ScrollRestorationProps, SubmitFunction, ActionFunction, ActionFunctionArgs, AwaitProps, unstable_Blocker, unstable_BlockerFunction, DataRouteMatch, DataRouteObject, Fetcher, Hash, IndexRouteObject, IndexRouteProps, JsonFunction, LayoutRouteProps, LoaderFunction, LoaderFunctionArgs, Location, MemoryRouterProps, NavigateFunction, NavigateOptions, NavigateProps, Navigation, Navigator, NonIndexRouteObject, OutletProps, Params, ParamParseKey, Path, PathMatch, Pathname, PathPattern, PathRouteProps, RedirectFunction, RelativeRoutingType, RouteMatch, RouteObject, RouteProps, RouterProps, RouterProviderProps, RoutesProps, Search, ShouldRevalidateFunction, To } from 'react-router-dom';
8
8
  export { createBrowserRouter, createHashRouter, createMemoryRouter, RouterProvider, BrowserRouter, HashRouter, MemoryRouter, Router, Await, Form, Link, NavLink, Navigate, Outlet, Route, Routes, ScrollRestoration, useActionData, useAsyncError, useAsyncValue, useBeforeUnload, useFetcher, useFetchers, useFormAction, useHref, useInRouterContext, useLinkClickHandler, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes, useSearchParams, useSubmit, createRoutesFromChildren, createRoutesFromElements, createSearchParams, generatePath, isRouteErrorResponse, matchPath, matchRoutes, renderMatches, resolvePath } from 'react-router-dom';
9
9
  export { defer, json, redirect } from '@modern-js/utils/remix-router';
@@ -1,8 +1,6 @@
1
- import type { StaticHandlerContext } from '@modern-js/utils/remix-router';
2
- import { RenderLevel, SSRServerContext } from '../types';
1
+ import { RenderLevel, RuntimeContext } from '../types';
3
2
  type BuildShellAfterTemplateOptions = {
4
- ssrContext: SSRServerContext;
5
- routerContext: StaticHandlerContext;
3
+ context: RuntimeContext;
6
4
  renderLevel: RenderLevel;
7
5
  };
8
6
  export declare function buildShellAfterTemplate(afterAppTemplate: string, options: BuildShellAfterTemplateOptions): string;
@@ -4,5 +4,5 @@ import type { Writable } from 'stream';
4
4
  import type { RenderToReadableStreamOptions } from 'react-dom/server';
5
5
  import { RuntimeContext } from '../types';
6
6
  export type Pipe<T extends Writable> = (output: T) => Promise<T | string>;
7
- declare function renderToPipe(rootElement: React.ReactElement, context: RuntimeContext, options?: RenderToReadableStreamOptions): Pipe<Writable>;
7
+ declare function renderToPipe(rootElement: React.ReactElement, context: RuntimeContext, options?: RenderToReadableStreamOptions): Promise<string | ReadableStream<any>>;
8
8
  export default renderToPipe;
@@ -1,2 +1,3 @@
1
1
  import type { ChunkExtractor } from '@loadable/server';
2
+ export declare const CSS_CHUNKS_PLACEHOLDER = "<!--<?- chunksMap.css ?>-->";
2
3
  export declare function getLoadableScripts(extractor: ChunkExtractor): string;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "modern",
12
12
  "modern.js"
13
13
  ],
14
- "version": "2.8.0",
14
+ "version": "2.10.0",
15
15
  "engines": {
16
16
  "node": ">=14.17.6"
17
17
  },
@@ -158,9 +158,9 @@
158
158
  "react-side-effect": "^2.1.1",
159
159
  "redux-logger": "^3.0.6",
160
160
  "styled-components": "^5.3.1",
161
- "@modern-js/plugin": "2.8.0",
162
- "@modern-js/types": "2.8.0",
163
- "@modern-js/utils": "2.8.0"
161
+ "@modern-js/plugin": "2.10.0",
162
+ "@modern-js/types": "2.10.0",
163
+ "@modern-js/utils": "2.10.0"
164
164
  },
165
165
  "peerDependencies": {
166
166
  "react": ">=17",
@@ -172,20 +172,20 @@
172
172
  "@testing-library/react": "^13.4.0",
173
173
  "@testing-library/react-hooks": "^8.0.1",
174
174
  "@types/invariant": "^2.2.30",
175
- "@types/jest": "^27",
175
+ "@types/jest": "^29",
176
176
  "@types/loadable__webpack-plugin": "^5.7.3",
177
177
  "@types/node": "^14",
178
178
  "@types/react-side-effect": "^1.1.1",
179
- "jest": "^27",
179
+ "jest": "^29",
180
180
  "react": "^18",
181
181
  "react-dom": "^18",
182
- "ts-jest": "^27.0.4",
182
+ "ts-jest": "^29.0.5",
183
183
  "typescript": "^4",
184
- "@modern-js/app-tools": "2.8.0",
185
- "@modern-js/core": "2.8.0",
186
- "@modern-js/server-core": "2.8.0",
187
- "@scripts/jest-config": "2.8.0",
188
- "@scripts/build": "2.8.0"
184
+ "@modern-js/app-tools": "2.10.0",
185
+ "@modern-js/core": "2.10.0",
186
+ "@modern-js/server-core": "2.10.0",
187
+ "@scripts/build": "2.10.0",
188
+ "@scripts/jest-config": "2.10.0"
189
189
  },
190
190
  "sideEffects": false,
191
191
  "modernConfig": {},