@modern-js/runtime 3.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/browser/index.js +4 -1
- package/dist/cjs/core/server/stream/createReadableStream.js +2 -0
- package/dist/cjs/core/server/string/index.js +4 -1
- package/dist/cjs/router/cli/code/index.js +7 -3
- package/dist/cjs/router/cli/code/templates.js +37 -9
- package/dist/cjs/router/cli/code/utils.js +5 -5
- package/dist/cjs/router/runtime/rsc-router.js +119 -55
- package/dist/cjs/router/runtime/utils.js +5 -0
- package/dist/esm/core/browser/index.mjs +4 -1
- package/dist/esm/core/server/stream/createReadableStream.mjs +2 -0
- package/dist/esm/core/server/string/index.mjs +4 -1
- package/dist/esm/router/cli/code/index.mjs +8 -4
- package/dist/esm/router/cli/code/templates.mjs +38 -10
- package/dist/esm/router/cli/code/utils.mjs +2 -2
- package/dist/esm/router/runtime/rsc-router.mjs +119 -55
- package/dist/esm/router/runtime/utils.mjs +5 -0
- package/dist/esm-node/core/browser/index.mjs +4 -1
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +2 -0
- package/dist/esm-node/core/server/string/index.mjs +4 -1
- package/dist/esm-node/router/cli/code/index.mjs +8 -4
- package/dist/esm-node/router/cli/code/templates.mjs +38 -10
- package/dist/esm-node/router/cli/code/utils.mjs +2 -2
- package/dist/esm-node/router/runtime/rsc-router.mjs +119 -55
- package/dist/esm-node/router/runtime/utils.mjs +5 -0
- package/dist/types/core/context/serverPayload/index.server.d.ts +5 -5
- package/dist/types/router/cli/code/templates.d.ts +4 -2
- package/dist/types/router/cli/code/utils.d.ts +1 -1
- package/dist/types/router/runtime/rsc-router.d.ts +8 -2
- package/dist/types/router/runtime/types.d.ts +17 -0
- package/package.json +9 -9
|
@@ -39,6 +39,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
39
39
|
renderWithReact18: ()=>renderWithReact18,
|
|
40
40
|
renderWithReact17: ()=>renderWithReact17
|
|
41
41
|
});
|
|
42
|
+
const constants_namespaceObject = require("@modern-js/utils/universal/constants");
|
|
42
43
|
const external_cookie_namespaceObject = require("cookie");
|
|
43
44
|
var external_cookie_default = /*#__PURE__*/ __webpack_require__.n(external_cookie_namespaceObject);
|
|
44
45
|
const index_js_namespaceObject = require("../context/index.js");
|
|
@@ -128,7 +129,9 @@ async function renderWithReact17(App, rootElement) {
|
|
|
128
129
|
}
|
|
129
130
|
async function hydrateWithReact18(App, rootElement) {
|
|
130
131
|
const ReactDOM = await import("react-dom/client");
|
|
131
|
-
const root = ReactDOM.hydrateRoot(rootElement, App
|
|
132
|
+
const root = ReactDOM.hydrateRoot(rootElement, App, {
|
|
133
|
+
identifierPrefix: constants_namespaceObject.SSR_HYDRATION_ID_PREFIX
|
|
134
|
+
});
|
|
132
135
|
return root;
|
|
133
136
|
}
|
|
134
137
|
async function hydrateWithReact17(App, rootElement, callback) {
|
|
@@ -28,6 +28,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
28
|
});
|
|
29
29
|
const external_stream_namespaceObject = require("stream");
|
|
30
30
|
const node_namespaceObject = require("@modern-js/runtime-utils/node");
|
|
31
|
+
const constants_namespaceObject = require("@modern-js/utils/universal/constants");
|
|
31
32
|
const external_common_js_namespaceObject = require("../../../common.js");
|
|
32
33
|
const external_constants_js_namespaceObject = require("../../constants.js");
|
|
33
34
|
const index_js_namespaceObject = require("../../context/index.js");
|
|
@@ -65,6 +66,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
65
66
|
return new Promise((resolve)=>{
|
|
66
67
|
const { pipe: reactStreamingPipe } = renderToPipeableStream(processedRootElement, {
|
|
67
68
|
nonce: config.nonce,
|
|
69
|
+
identifierPrefix: constants_namespaceObject.SSR_HYDRATION_ID_PREFIX,
|
|
68
70
|
[onReady] () {
|
|
69
71
|
let styledComponentsStyleTags = '';
|
|
70
72
|
extenders.forEach((extender)=>{
|
|
@@ -36,6 +36,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
36
36
|
renderString: ()=>renderString
|
|
37
37
|
});
|
|
38
38
|
const time_namespaceObject = require("@modern-js/runtime-utils/time");
|
|
39
|
+
const constants_namespaceObject = require("@modern-js/utils/universal/constants");
|
|
39
40
|
const server_namespaceObject = require("react-dom/server");
|
|
40
41
|
var server_default = /*#__PURE__*/ __webpack_require__.n(server_namespaceObject);
|
|
41
42
|
const external_react_helmet_namespaceObject = require("react-helmet");
|
|
@@ -104,7 +105,9 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
|
|
|
104
105
|
const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
|
|
105
106
|
try {
|
|
106
107
|
const end = (0, time_namespaceObject.time)();
|
|
107
|
-
html = server_default().renderToString(finalApp
|
|
108
|
+
html = server_default().renderToString(finalApp, {
|
|
109
|
+
identifierPrefix: constants_namespaceObject.SSR_HYDRATION_ID_PREFIX
|
|
110
|
+
});
|
|
108
111
|
chunkSet.renderLevel = external_constants_js_namespaceObject.RenderLevel.SERVER_RENDER;
|
|
109
112
|
helmetData = external_react_helmet_default().renderStatic();
|
|
110
113
|
const cost = end();
|
|
@@ -130,7 +130,9 @@ const generateCode = async (appContext, config, entrypoints, api)=>{
|
|
|
130
130
|
entryName: entrypoint.entryName,
|
|
131
131
|
internalDirectory,
|
|
132
132
|
splitRouteChunks: config?.output?.splitRouteChunks,
|
|
133
|
-
|
|
133
|
+
isRscClientBundle: (0, utils_namespaceObject.isUseRsc)(config),
|
|
134
|
+
srcDirectory,
|
|
135
|
+
internalSrcAlias: appContext.internalSrcAlias
|
|
134
136
|
})
|
|
135
137
|
});
|
|
136
138
|
if (entrypoint.nestedRoutesEntry && ((0, utils_namespaceObject.isUseSSRBundle)(config) || (0, utils_namespaceObject.isUseRsc)(config))) {
|
|
@@ -150,13 +152,15 @@ const generateCode = async (appContext, config, entrypoints, api)=>{
|
|
|
150
152
|
entryName: entrypoint.entryName,
|
|
151
153
|
internalDirectory,
|
|
152
154
|
splitRouteChunks: config?.output?.splitRouteChunks,
|
|
153
|
-
|
|
155
|
+
isRscClientBundle: false,
|
|
156
|
+
srcDirectory,
|
|
157
|
+
internalSrcAlias: appContext.internalSrcAlias
|
|
154
158
|
});
|
|
155
159
|
await utils_namespaceObject.fs.outputFile(external_path_default().resolve(internalDirectory, `./${entryName}/routes.server.js`), serverRoutesCode, 'utf8');
|
|
156
160
|
}
|
|
157
161
|
const serverLoaderCombined = external_templates_js_namespaceObject.ssrLoaderCombinedModule(entrypoints, entrypoint, config, appContext);
|
|
158
162
|
if (serverLoaderCombined) {
|
|
159
|
-
const serverLoaderFile = (0, external_utils_js_namespaceObject.
|
|
163
|
+
const serverLoaderFile = (0, external_utils_js_namespaceObject.getServerCombinedModuleFile)(internalDirectory, entryName);
|
|
160
164
|
await utils_namespaceObject.fs.outputFile(serverLoaderFile, serverLoaderCombined);
|
|
161
165
|
}
|
|
162
166
|
await utils_namespaceObject.fs.outputFile(external_path_default().resolve(internalDirectory, `./${entryName}/${external_constants_js_namespaceObject.FILE_SYSTEM_ROUTES_FILE_NAME}`), code, 'utf8');
|
|
@@ -38,6 +38,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
38
38
|
routesForServer: ()=>routesForServer,
|
|
39
39
|
fileSystemRoutes: ()=>fileSystemRoutes
|
|
40
40
|
});
|
|
41
|
+
const promises_namespaceObject = require("node:fs/promises");
|
|
41
42
|
const external_path_namespaceObject = require("path");
|
|
42
43
|
var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
|
|
43
44
|
const utils_namespaceObject = require("@modern-js/utils");
|
|
@@ -111,7 +112,25 @@ const routesForServer = ({ routesForServerLoaderMatches })=>{
|
|
|
111
112
|
`;
|
|
112
113
|
};
|
|
113
114
|
const createMatchReg = (keyword)=>new RegExp(`("${keyword}":\\s)"([^\n]+)"`, 'g');
|
|
114
|
-
|
|
115
|
+
async function hasUseClientDirective(componentPath, srcDirectory, internalSrcAlias) {
|
|
116
|
+
let realPath = componentPath;
|
|
117
|
+
if (internalSrcAlias && srcDirectory && realPath.startsWith(internalSrcAlias)) realPath = external_path_default().join(srcDirectory, realPath.slice(internalSrcAlias.length));
|
|
118
|
+
const filePath = (0, utils_namespaceObject.findExists)(utils_namespaceObject.JS_EXTENSIONS.map((ext)=>`${realPath}${ext}`));
|
|
119
|
+
if (!filePath) return false;
|
|
120
|
+
let fh;
|
|
121
|
+
try {
|
|
122
|
+
fh = await (0, promises_namespaceObject.open)(filePath, 'r');
|
|
123
|
+
const buf = Buffer.alloc(64);
|
|
124
|
+
const { bytesRead } = await fh.read(buf, 0, 64, 0);
|
|
125
|
+
const content = buf.toString('utf-8', 0, bytesRead).trimStart();
|
|
126
|
+
return content.startsWith("'use client'") || content.startsWith('"use client"');
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
} finally{
|
|
130
|
+
await fh?.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry, entryName, internalDirectory, splitRouteChunks = true, isRscClientBundle = false, srcDirectory, internalSrcAlias })=>{
|
|
115
134
|
const components = [];
|
|
116
135
|
const loadings = [];
|
|
117
136
|
const errors = [];
|
|
@@ -136,9 +155,9 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
136
155
|
const importOptions = webpackChunkName ? `/* webpackChunkName: "${routeId}" */ ` : eager ? '/* webpackMode: "eager" */ ' : '';
|
|
137
156
|
return `() => import(${importOptions}'${componentPath}').then(routeModule => handleRouteModule(routeModule, "${routeId}")).catch(handleRouteModuleError)`;
|
|
138
157
|
};
|
|
139
|
-
const traverseRouteTree = (route,
|
|
158
|
+
const traverseRouteTree = async (route, isRscClientBundle)=>{
|
|
140
159
|
let children;
|
|
141
|
-
if ('children' in route && route.children) children = route
|
|
160
|
+
if ('children' in route && route.children) children = await Promise.all(route.children.map((child)=>traverseRouteTree(child, isRscClientBundle)));
|
|
142
161
|
let loading;
|
|
143
162
|
let error;
|
|
144
163
|
let loader;
|
|
@@ -210,6 +229,8 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
210
229
|
components.push(route._component);
|
|
211
230
|
component = `component_${components.length - 1}`;
|
|
212
231
|
}
|
|
232
|
+
const isClientComponent = 'nested' === route.type && Boolean(route._component) && Boolean(srcDirectory) && Boolean(internalSrcAlias) && await hasUseClientDirective(route._component, srcDirectory, internalSrcAlias);
|
|
233
|
+
const shouldIncludeClientBundle = !isRscClientBundle || isClientComponent;
|
|
213
234
|
const finalRoute = {
|
|
214
235
|
...route,
|
|
215
236
|
loading,
|
|
@@ -217,10 +238,17 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
217
238
|
action,
|
|
218
239
|
config,
|
|
219
240
|
error,
|
|
220
|
-
children
|
|
241
|
+
children,
|
|
242
|
+
...isClientComponent && {
|
|
243
|
+
isClientComponent: true
|
|
244
|
+
},
|
|
245
|
+
...shouldIncludeClientBundle && {
|
|
246
|
+
lazyImport
|
|
247
|
+
},
|
|
248
|
+
...shouldIncludeClientBundle && route._component && {
|
|
249
|
+
component
|
|
250
|
+
}
|
|
221
251
|
};
|
|
222
|
-
if (!isRscClient) finalRoute.lazyImport = lazyImport;
|
|
223
|
-
if (route._component && !isRscClient) finalRoute.component = component;
|
|
224
252
|
if ('nested' === route.type && route._component && (route.loader || route.data)) finalRoute.shouldRevalidate = `createShouldRevalidate("${route.id}")`;
|
|
225
253
|
return finalRoute;
|
|
226
254
|
};
|
|
@@ -228,7 +256,7 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
228
256
|
export const routes = [
|
|
229
257
|
`;
|
|
230
258
|
for (const route of routes)if ('type' in route) {
|
|
231
|
-
const newRoute = traverseRouteTree(route,
|
|
259
|
+
const newRoute = await traverseRouteTree(route, isRscClientBundle);
|
|
232
260
|
const routeStr = JSON.stringify(newRoute, null, 2);
|
|
233
261
|
const keywords = [
|
|
234
262
|
'component',
|
|
@@ -303,9 +331,9 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
303
331
|
`;
|
|
304
332
|
return `
|
|
305
333
|
${importLazyCode}
|
|
306
|
-
${!
|
|
334
|
+
${!isRscClientBundle ? importComponentsCode : ''}
|
|
307
335
|
${importRuntimeRouterCode}
|
|
308
|
-
${!
|
|
336
|
+
${!isRscClientBundle ? rootLayoutCode : ''}
|
|
309
337
|
${importLoadingCode}
|
|
310
338
|
${importErrorComponentsCode}
|
|
311
339
|
${importLoadersCode}
|
|
@@ -34,10 +34,10 @@ var __webpack_exports__ = {};
|
|
|
34
34
|
__webpack_require__.r(__webpack_exports__);
|
|
35
35
|
__webpack_require__.d(__webpack_exports__, {
|
|
36
36
|
parseModule: ()=>parseModule,
|
|
37
|
+
getServerCombinedModuleFile: ()=>getServerCombinedModuleFile,
|
|
37
38
|
hasLoader: ()=>hasLoader,
|
|
38
|
-
isPageComponentFile: ()=>isPageComponentFile,
|
|
39
39
|
getPathWithoutExt: ()=>getPathWithoutExt,
|
|
40
|
-
|
|
40
|
+
isPageComponentFile: ()=>isPageComponentFile,
|
|
41
41
|
getServerLoadersFile: ()=>getServerLoadersFile,
|
|
42
42
|
walkDirectory: ()=>walkDirectory,
|
|
43
43
|
hasAction: ()=>hasAction,
|
|
@@ -112,13 +112,13 @@ const hasAction = async (filename, source)=>{
|
|
|
112
112
|
return false;
|
|
113
113
|
};
|
|
114
114
|
const getServerLoadersFile = (internalDirectory, entryName)=>external_path_default().join(internalDirectory, entryName, 'route-server-loaders.js');
|
|
115
|
-
const
|
|
115
|
+
const getServerCombinedModuleFile = (internalDirectory, entryName)=>external_path_default().join(internalDirectory, entryName, 'server-loader-combined.js');
|
|
116
116
|
const getPathWithoutExt = (filename)=>{
|
|
117
117
|
const extname = external_path_default().extname(filename);
|
|
118
118
|
return extname ? filename.slice(0, -extname.length) : filename;
|
|
119
119
|
};
|
|
120
120
|
exports.getPathWithoutExt = __webpack_exports__.getPathWithoutExt;
|
|
121
|
-
exports.
|
|
121
|
+
exports.getServerCombinedModuleFile = __webpack_exports__.getServerCombinedModuleFile;
|
|
122
122
|
exports.getServerLoadersFile = __webpack_exports__.getServerLoadersFile;
|
|
123
123
|
exports.hasAction = __webpack_exports__.hasAction;
|
|
124
124
|
exports.hasLoader = __webpack_exports__.hasLoader;
|
|
@@ -128,7 +128,7 @@ exports.replaceWithAlias = __webpack_exports__.replaceWithAlias;
|
|
|
128
128
|
exports.walkDirectory = __webpack_exports__.walkDirectory;
|
|
129
129
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
130
130
|
"getPathWithoutExt",
|
|
131
|
-
"
|
|
131
|
+
"getServerCombinedModuleFile",
|
|
132
132
|
"getServerLoadersFile",
|
|
133
133
|
"hasAction",
|
|
134
134
|
"hasLoader",
|
|
@@ -45,8 +45,9 @@ const router_namespaceObject = require("@modern-js/runtime-utils/router");
|
|
|
45
45
|
const external_react_namespaceObject = require("react");
|
|
46
46
|
var external_react_default = /*#__PURE__*/ __webpack_require__.n(external_react_namespaceObject);
|
|
47
47
|
const external_CSSLinks_js_namespaceObject = require("./CSSLinks.js");
|
|
48
|
-
const safeUse = (
|
|
49
|
-
|
|
48
|
+
const safeUse = (value)=>{
|
|
49
|
+
const reactUse = external_react_default().use;
|
|
50
|
+
if ('function' == typeof reactUse) return reactUse(value);
|
|
50
51
|
return null;
|
|
51
52
|
};
|
|
52
53
|
function collectCssFilesFromRoutes(matches, routes) {
|
|
@@ -64,6 +65,17 @@ function collectCssFilesFromRoutes(matches, routes) {
|
|
|
64
65
|
}
|
|
65
66
|
const createServerPayload = (routerContext, routes)=>{
|
|
66
67
|
const cssFiles = collectCssFilesFromRoutes(routerContext.matches, routes);
|
|
68
|
+
let cssInjectionIndex = -1;
|
|
69
|
+
if (cssFiles.length > 0) {
|
|
70
|
+
for(let i = routerContext.matches.length - 1; i >= 0; i--){
|
|
71
|
+
const matchRoute = findRouteInTree(routes, routerContext.matches[i].route.id);
|
|
72
|
+
if (matchRoute && !matchRoute.isClientComponent) {
|
|
73
|
+
cssInjectionIndex = i;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (-1 === cssInjectionIndex) cssInjectionIndex = routerContext.matches.length - 1;
|
|
78
|
+
}
|
|
67
79
|
return {
|
|
68
80
|
type: 'render',
|
|
69
81
|
actionData: routerContext.actionData,
|
|
@@ -71,45 +83,42 @@ const createServerPayload = (routerContext, routes)=>{
|
|
|
71
83
|
loaderData: routerContext.loaderData,
|
|
72
84
|
location: routerContext.location,
|
|
73
85
|
routes: routerContext.matches.map((match, index, matches)=>{
|
|
74
|
-
const
|
|
86
|
+
const route = match.route;
|
|
87
|
+
const element = route.element;
|
|
75
88
|
const parentMatch = index > 0 ? matches[index - 1] : void 0;
|
|
76
89
|
let processedElement;
|
|
77
90
|
if (element) {
|
|
78
91
|
const ElementComponent = element.type;
|
|
79
92
|
const elementProps = {
|
|
80
|
-
loaderData: routerContext?.loaderData?.[
|
|
81
|
-
actionData: routerContext?.actionData?.[
|
|
93
|
+
loaderData: routerContext?.loaderData?.[route.id],
|
|
94
|
+
actionData: routerContext?.actionData?.[route.id],
|
|
82
95
|
params: match.params,
|
|
83
|
-
matches: routerContext.matches.map((m)=>{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
handle: route.handle
|
|
91
|
-
};
|
|
92
|
-
})
|
|
96
|
+
matches: routerContext.matches.map((m)=>({
|
|
97
|
+
id: m.route.id,
|
|
98
|
+
pathname: m.pathname,
|
|
99
|
+
params: m.params,
|
|
100
|
+
data: routerContext?.loaderData?.[m.route.id],
|
|
101
|
+
handle: m.route.handle
|
|
102
|
+
}))
|
|
93
103
|
};
|
|
94
104
|
const routeElement = /*#__PURE__*/ external_react_default().createElement(ElementComponent, elementProps);
|
|
95
|
-
|
|
96
|
-
processedElement = isLeafRoute && cssFiles.length > 0 ? /*#__PURE__*/ external_react_default().createElement(external_react_default().Fragment, null, /*#__PURE__*/ external_react_default().createElement(external_CSSLinks_js_namespaceObject.CSSLinks, {
|
|
105
|
+
processedElement = index === cssInjectionIndex ? /*#__PURE__*/ external_react_default().createElement(external_react_default().Fragment, null, /*#__PURE__*/ external_react_default().createElement(external_CSSLinks_js_namespaceObject.CSSLinks, {
|
|
97
106
|
cssFiles
|
|
98
107
|
}), routeElement) : routeElement;
|
|
99
108
|
}
|
|
100
109
|
return {
|
|
101
110
|
element: processedElement,
|
|
102
|
-
errorElement:
|
|
103
|
-
handle:
|
|
104
|
-
hasAction: !!
|
|
105
|
-
hasErrorBoundary: !!
|
|
106
|
-
hasLoader: !!
|
|
107
|
-
hasClientLoader: !!
|
|
108
|
-
id:
|
|
109
|
-
index:
|
|
111
|
+
errorElement: route.errorElement,
|
|
112
|
+
handle: route.handle,
|
|
113
|
+
hasAction: !!route.action,
|
|
114
|
+
hasErrorBoundary: !!route.hasErrorBoundary,
|
|
115
|
+
hasLoader: !!route.loader,
|
|
116
|
+
hasClientLoader: !!route.hasClientLoader,
|
|
117
|
+
id: route.id,
|
|
118
|
+
index: route.index,
|
|
110
119
|
params: match.params,
|
|
111
|
-
parentId: parentMatch?.route.id ||
|
|
112
|
-
path:
|
|
120
|
+
parentId: parentMatch?.route.id || route.parentId,
|
|
121
|
+
path: route.path,
|
|
113
122
|
pathname: match.pathname,
|
|
114
123
|
pathnameBase: match.pathnameBase
|
|
115
124
|
};
|
|
@@ -129,10 +138,11 @@ const handleRSCRedirect = (headers, basename, status)=>{
|
|
|
129
138
|
});
|
|
130
139
|
};
|
|
131
140
|
const prepareRSCRoutes = async (routes)=>{
|
|
132
|
-
const isLazyComponent = (component)=>component && 'object' == typeof component &&
|
|
141
|
+
const isLazyComponent = (component)=>null != component && 'object' == typeof component && '_init' in component && '_payload' in component;
|
|
133
142
|
const processRoutes = async (routesList)=>{
|
|
134
143
|
await Promise.all(routesList.map(async (route)=>{
|
|
135
|
-
|
|
144
|
+
const modernRoute = route;
|
|
145
|
+
if ('lazyImport' in modernRoute && isLazyComponent(modernRoute.component)) modernRoute.component = (await modernRoute.lazyImport()).default;
|
|
136
146
|
if (route.children && Array.isArray(route.children)) await processRoutes(route.children);
|
|
137
147
|
}));
|
|
138
148
|
};
|
|
@@ -149,12 +159,14 @@ const mergeRoutes = (routes, originalRoutes)=>{
|
|
|
149
159
|
};
|
|
150
160
|
buildRoutesMap(routes);
|
|
151
161
|
const mergeRoutesRecursive = (origRoutes)=>origRoutes.map((origRoute)=>{
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
const modernOrig = origRoute;
|
|
163
|
+
if (modernOrig.id && routesMap.has(modernOrig.id)) {
|
|
164
|
+
const matchedRoute = routesMap.get(modernOrig.id);
|
|
154
165
|
const result = {
|
|
155
|
-
loader:
|
|
166
|
+
loader: modernOrig.hasClientLoader ? modernOrig.loader : void 0,
|
|
156
167
|
...matchedRoute
|
|
157
168
|
};
|
|
169
|
+
if (modernOrig.isClientComponent) result.isClientComponent = true;
|
|
158
170
|
if (origRoute.children && Array.isArray(origRoute.children)) result.children = mergeRoutesRecursive(origRoute.children);
|
|
159
171
|
return result;
|
|
160
172
|
}
|
|
@@ -172,6 +184,68 @@ const findRouteInTree = (routes, routeId)=>{
|
|
|
172
184
|
}
|
|
173
185
|
return null;
|
|
174
186
|
};
|
|
187
|
+
function getChangedMatches(matches, currentMatches) {
|
|
188
|
+
const currentById = new Map();
|
|
189
|
+
for (const m of currentMatches)if (m.route?.id) currentById.set(m.route.id, m);
|
|
190
|
+
return matches.filter((match)=>{
|
|
191
|
+
const current = currentById.get(match.route?.id);
|
|
192
|
+
return !current || JSON.stringify(current.params) !== JSON.stringify(match.params);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function canSkipRscFetch(matches, routerState) {
|
|
196
|
+
const changedMatches = getChangedMatches(matches, routerState?.matches || []);
|
|
197
|
+
return changedMatches.length > 0 && changedMatches.every((m)=>{
|
|
198
|
+
const route = m.route;
|
|
199
|
+
return route.isClientComponent && !(route.hasLoader && !route.hasClientLoader);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function injectRouteCss(routeId) {
|
|
203
|
+
if ("u" < typeof window) return;
|
|
204
|
+
const cssAssets = window._MODERNJS_ROUTE_MANIFEST?.routeAssets?.[routeId]?.referenceCssAssets;
|
|
205
|
+
if (!cssAssets) return;
|
|
206
|
+
const publicPath = window.__webpack_public_path__ || '/';
|
|
207
|
+
for (const css of cssAssets){
|
|
208
|
+
const href = css.startsWith('http') || css.startsWith('/') ? css : publicPath + css;
|
|
209
|
+
if (!document.querySelector(`link[href="${CSS.escape(href)}"]`)) {
|
|
210
|
+
const link = document.createElement('link');
|
|
211
|
+
link.rel = 'stylesheet';
|
|
212
|
+
link.href = href;
|
|
213
|
+
document.head.appendChild(link);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function ensureClientComponent(route, originalRoutes) {
|
|
218
|
+
if (route.isClientComponent && !route.Component) {
|
|
219
|
+
const origRoute = findRouteInTree(originalRoutes, route.id);
|
|
220
|
+
if (origRoute?.Component) {
|
|
221
|
+
route.Component = origRoute.Component;
|
|
222
|
+
delete route.element;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function resolveClientLoaders(matches, originalRoutes) {
|
|
227
|
+
const clientMatches = matches.filter((m)=>m.route.hasClientLoader);
|
|
228
|
+
if (0 === clientMatches.length) return Promise.resolve([]);
|
|
229
|
+
return Promise.all(clientMatches.map(async (clientMatch)=>{
|
|
230
|
+
const origRoute = findRouteInTree(originalRoutes, clientMatch.route.id);
|
|
231
|
+
clientMatch.route.loader = origRoute?.loader;
|
|
232
|
+
const result = await clientMatch.resolve();
|
|
233
|
+
return {
|
|
234
|
+
routeId: clientMatch.route.id,
|
|
235
|
+
result
|
|
236
|
+
};
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
function applyLoaderResults(results, loaderResults) {
|
|
240
|
+
for (const { routeId, result } of loaderResults)results[routeId] = result;
|
|
241
|
+
}
|
|
242
|
+
async function resolveClientOnlyNavigation(matches, results, originalRoutes, routerState) {
|
|
243
|
+
const changedMatches = getChangedMatches(matches, routerState?.matches || []);
|
|
244
|
+
for (const match of changedMatches)ensureClientComponent(match.route, originalRoutes);
|
|
245
|
+
applyLoaderResults(results, await resolveClientLoaders(changedMatches, originalRoutes));
|
|
246
|
+
for (const match of changedMatches)if (match.route.id) injectRouteCss(match.route.id);
|
|
247
|
+
return results;
|
|
248
|
+
}
|
|
175
249
|
const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>{
|
|
176
250
|
const processedRoutes = payload.routes.reduceRight((previous, route)=>{
|
|
177
251
|
if (previous.length > 0) return [
|
|
@@ -188,42 +262,28 @@ const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>
|
|
|
188
262
|
const router = (0, router_namespaceObject.createBrowserRouter)(mergedRoutes, {
|
|
189
263
|
hydrationData: payload,
|
|
190
264
|
basename: basename,
|
|
191
|
-
dataStrategy: async (
|
|
192
|
-
const { request, matches } = context;
|
|
265
|
+
dataStrategy: async ({ request, matches })=>{
|
|
193
266
|
const results = {};
|
|
194
|
-
|
|
267
|
+
if (canSkipRscFetch(matches, router.state)) return resolveClientOnlyNavigation(matches, results, originalRoutes, router.state);
|
|
195
268
|
const fetchPromise = fetch(request.url, {
|
|
196
269
|
headers: {
|
|
197
270
|
'x-rsc-tree': 'true'
|
|
198
271
|
}
|
|
199
272
|
});
|
|
200
|
-
const clientLoadersPromise =
|
|
201
|
-
const foundRoute = findRouteInTree(originalRoutes, clientMatch.route.id);
|
|
202
|
-
clientMatch.route.loader = foundRoute?.loader;
|
|
203
|
-
const res = await clientMatch.resolve();
|
|
204
|
-
return {
|
|
205
|
-
routeId: clientMatch.route.id,
|
|
206
|
-
result: res
|
|
207
|
-
};
|
|
208
|
-
})) : Promise.resolve([]);
|
|
273
|
+
const clientLoadersPromise = resolveClientLoaders(matches, originalRoutes);
|
|
209
274
|
const res = await fetchPromise;
|
|
210
275
|
const redirectLocation = res.headers.get('X-Modernjs-Redirect');
|
|
211
276
|
if (redirectLocation) {
|
|
212
277
|
matches.forEach((match)=>{
|
|
213
278
|
const routeId = match.route.id;
|
|
214
279
|
if (routeId) results[routeId] = {
|
|
215
|
-
type: '
|
|
280
|
+
type: 'data',
|
|
216
281
|
result: (0, router_namespaceObject.redirect)(redirectLocation)
|
|
217
282
|
};
|
|
218
283
|
});
|
|
219
284
|
return results;
|
|
220
285
|
}
|
|
221
|
-
|
|
222
|
-
clientLoadersPromise
|
|
223
|
-
]);
|
|
224
|
-
clientLoaderResults.forEach(({ routeId, result })=>{
|
|
225
|
-
results[routeId] = result;
|
|
226
|
-
});
|
|
286
|
+
applyLoaderResults(results, await clientLoadersPromise);
|
|
227
287
|
if (!res.body) throw new Error('Response body is null');
|
|
228
288
|
const payload = await (0, client_namespaceObject.createFromReadableStream)(res.body);
|
|
229
289
|
if ('object' != typeof payload || null === payload || void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
@@ -231,10 +291,14 @@ const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>
|
|
|
231
291
|
matches.forEach((match)=>{
|
|
232
292
|
const routeId = match.route.id;
|
|
233
293
|
const matchedRoute = serverPayload.routes.find((route)=>route.id === routeId);
|
|
234
|
-
if (matchedRoute)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
294
|
+
if (matchedRoute) {
|
|
295
|
+
const modernMatch = match.route;
|
|
296
|
+
router.patchRoutes(matchedRoute.parentId ?? null, [
|
|
297
|
+
matchedRoute
|
|
298
|
+
], true);
|
|
299
|
+
if (modernMatch.isClientComponent && modernMatch.Component) delete modernMatch.Component;
|
|
300
|
+
}
|
|
301
|
+
if (serverPayload.loaderData && routeId in serverPayload.loaderData) results[routeId] = {
|
|
238
302
|
type: 'data',
|
|
239
303
|
result: serverPayload.loaderData[routeId]
|
|
240
304
|
};
|
|
@@ -114,7 +114,12 @@ function getRouteObjects(routes, { globalApp, ssrMode, props }) {
|
|
|
114
114
|
...'object' == typeof route.config ? route.config?.handle : {}
|
|
115
115
|
},
|
|
116
116
|
index: route.index,
|
|
117
|
+
hasLoader: !!route.loader,
|
|
117
118
|
hasClientLoader: !!route.clientData,
|
|
119
|
+
hasAction: !!route.action,
|
|
120
|
+
...route.isClientComponent ? {
|
|
121
|
+
isClientComponent: true
|
|
122
|
+
} : {},
|
|
118
123
|
Component: route.component ? route.component : void 0,
|
|
119
124
|
errorElement: route.error ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(route.error, {}) : void 0,
|
|
120
125
|
children: route.children ? route.children.map((child)=>getRouteObjects([
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
|
|
1
2
|
import cookie from "cookie";
|
|
2
3
|
import { getGlobalInternalRuntimeContext } from "../context/index.mjs";
|
|
3
4
|
import { getInitialContext } from "../context/runtime.mjs";
|
|
@@ -86,7 +87,9 @@ async function renderWithReact17(App, rootElement) {
|
|
|
86
87
|
}
|
|
87
88
|
async function hydrateWithReact18(App, rootElement) {
|
|
88
89
|
const ReactDOM = await import("react-dom/client");
|
|
89
|
-
const root = ReactDOM.hydrateRoot(rootElement, App
|
|
90
|
+
const root = ReactDOM.hydrateRoot(rootElement, App, {
|
|
91
|
+
identifierPrefix: SSR_HYDRATION_ID_PREFIX
|
|
92
|
+
});
|
|
90
93
|
return root;
|
|
91
94
|
}
|
|
92
95
|
async function hydrateWithReact17(App, rootElement, callback) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PassThrough, Readable, Transform } from "stream";
|
|
2
2
|
import { storage } from "@modern-js/runtime-utils/node";
|
|
3
|
+
import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
|
|
3
4
|
import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
|
|
4
5
|
import { RenderLevel } from "../../constants.mjs";
|
|
5
6
|
import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
|
|
@@ -37,6 +38,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
37
38
|
return new Promise((resolve)=>{
|
|
38
39
|
const { pipe: reactStreamingPipe } = renderToPipeableStream(processedRootElement, {
|
|
39
40
|
nonce: config.nonce,
|
|
41
|
+
identifierPrefix: SSR_HYDRATION_ID_PREFIX,
|
|
40
42
|
[onReady] () {
|
|
41
43
|
let styledComponentsStyleTags = '';
|
|
42
44
|
extenders.forEach((extender)=>{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { time } from "@modern-js/runtime-utils/time";
|
|
2
|
+
import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
|
|
2
3
|
import server from "react-dom/server";
|
|
3
4
|
import react_helmet from "react-helmet";
|
|
4
5
|
import { RenderLevel } from "../../constants.mjs";
|
|
@@ -65,7 +66,9 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
|
|
|
65
66
|
const finalApp = collectors.reduce((pre, creator)=>creator.collect?.(pre) || pre, App);
|
|
66
67
|
try {
|
|
67
68
|
const end = time();
|
|
68
|
-
html = server.renderToString(finalApp
|
|
69
|
+
html = server.renderToString(finalApp, {
|
|
70
|
+
identifierPrefix: SSR_HYDRATION_ID_PREFIX
|
|
71
|
+
});
|
|
69
72
|
chunkSet.renderLevel = RenderLevel.SERVER_RENDER;
|
|
70
73
|
helmetData = react_helmet.renderStatic();
|
|
71
74
|
const cost = end();
|
|
@@ -6,7 +6,7 @@ import { resolveSSRMode } from "../../../cli/ssr/mode.mjs";
|
|
|
6
6
|
import { FILE_SYSTEM_ROUTES_FILE_NAME } from "../constants.mjs";
|
|
7
7
|
import { walk } from "./nestedRoutes.mjs";
|
|
8
8
|
import { fileSystemRoutes, routesForServer, ssrLoaderCombinedModule } from "./templates.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { getServerCombinedModuleFile, getServerLoadersFile } from "./utils.mjs";
|
|
10
10
|
async function generateRoutesForEntry(entrypoint, appContext) {
|
|
11
11
|
const routes = [];
|
|
12
12
|
if (entrypoint.nestedRoutesEntry) {
|
|
@@ -89,7 +89,9 @@ const generateCode = async (appContext, config, entrypoints, api)=>{
|
|
|
89
89
|
entryName: entrypoint.entryName,
|
|
90
90
|
internalDirectory,
|
|
91
91
|
splitRouteChunks: config?.output?.splitRouteChunks,
|
|
92
|
-
|
|
92
|
+
isRscClientBundle: isUseRsc(config),
|
|
93
|
+
srcDirectory,
|
|
94
|
+
internalSrcAlias: appContext.internalSrcAlias
|
|
93
95
|
})
|
|
94
96
|
});
|
|
95
97
|
if (entrypoint.nestedRoutesEntry && (isUseSSRBundle(config) || isUseRsc(config))) {
|
|
@@ -109,13 +111,15 @@ const generateCode = async (appContext, config, entrypoints, api)=>{
|
|
|
109
111
|
entryName: entrypoint.entryName,
|
|
110
112
|
internalDirectory,
|
|
111
113
|
splitRouteChunks: config?.output?.splitRouteChunks,
|
|
112
|
-
|
|
114
|
+
isRscClientBundle: false,
|
|
115
|
+
srcDirectory,
|
|
116
|
+
internalSrcAlias: appContext.internalSrcAlias
|
|
113
117
|
});
|
|
114
118
|
await fs.outputFile(path.resolve(internalDirectory, `./${entryName}/routes.server.js`), serverRoutesCode, 'utf8');
|
|
115
119
|
}
|
|
116
120
|
const serverLoaderCombined = ssrLoaderCombinedModule(entrypoints, entrypoint, config, appContext);
|
|
117
121
|
if (serverLoaderCombined) {
|
|
118
|
-
const serverLoaderFile =
|
|
122
|
+
const serverLoaderFile = getServerCombinedModuleFile(internalDirectory, entryName);
|
|
119
123
|
await fs.outputFile(serverLoaderFile, serverLoaderCombined);
|
|
120
124
|
}
|
|
121
125
|
await fs.outputFile(path.resolve(internalDirectory, `./${entryName}/${FILE_SYSTEM_ROUTES_FILE_NAME}`), code, 'utf8');
|