@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.
Files changed (30) hide show
  1. package/dist/cjs/core/browser/index.js +4 -1
  2. package/dist/cjs/core/server/stream/createReadableStream.js +2 -0
  3. package/dist/cjs/core/server/string/index.js +4 -1
  4. package/dist/cjs/router/cli/code/index.js +7 -3
  5. package/dist/cjs/router/cli/code/templates.js +37 -9
  6. package/dist/cjs/router/cli/code/utils.js +5 -5
  7. package/dist/cjs/router/runtime/rsc-router.js +119 -55
  8. package/dist/cjs/router/runtime/utils.js +5 -0
  9. package/dist/esm/core/browser/index.mjs +4 -1
  10. package/dist/esm/core/server/stream/createReadableStream.mjs +2 -0
  11. package/dist/esm/core/server/string/index.mjs +4 -1
  12. package/dist/esm/router/cli/code/index.mjs +8 -4
  13. package/dist/esm/router/cli/code/templates.mjs +38 -10
  14. package/dist/esm/router/cli/code/utils.mjs +2 -2
  15. package/dist/esm/router/runtime/rsc-router.mjs +119 -55
  16. package/dist/esm/router/runtime/utils.mjs +5 -0
  17. package/dist/esm-node/core/browser/index.mjs +4 -1
  18. package/dist/esm-node/core/server/stream/createReadableStream.mjs +2 -0
  19. package/dist/esm-node/core/server/string/index.mjs +4 -1
  20. package/dist/esm-node/router/cli/code/index.mjs +8 -4
  21. package/dist/esm-node/router/cli/code/templates.mjs +38 -10
  22. package/dist/esm-node/router/cli/code/utils.mjs +2 -2
  23. package/dist/esm-node/router/runtime/rsc-router.mjs +119 -55
  24. package/dist/esm-node/router/runtime/utils.mjs +5 -0
  25. package/dist/types/core/context/serverPayload/index.server.d.ts +5 -5
  26. package/dist/types/router/cli/code/templates.d.ts +4 -2
  27. package/dist/types/router/cli/code/utils.d.ts +1 -1
  28. package/dist/types/router/runtime/rsc-router.d.ts +8 -2
  29. package/dist/types/router/runtime/types.d.ts +17 -0
  30. 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
- isRscClient: (0, utils_namespaceObject.isUseRsc)(config)
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
- isRscClient: false
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.getServerCombinedModueFile)(internalDirectory, entryName);
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
- const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry, entryName, internalDirectory, splitRouteChunks = true, isRscClient = false })=>{
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, isRscClient)=>{
158
+ const traverseRouteTree = async (route, isRscClientBundle)=>{
140
159
  let children;
141
- if ('children' in route && route.children) children = route?.children?.map((child)=>traverseRouteTree(child, isRscClient));
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, isRscClient);
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
- ${!isRscClient ? importComponentsCode : ''}
334
+ ${!isRscClientBundle ? importComponentsCode : ''}
307
335
  ${importRuntimeRouterCode}
308
- ${!isRscClient ? rootLayoutCode : ''}
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
- getServerCombinedModueFile: ()=>getServerCombinedModueFile,
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 getServerCombinedModueFile = (internalDirectory, entryName)=>external_path_default().join(internalDirectory, entryName, 'server-loader-combined.js');
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.getServerCombinedModueFile = __webpack_exports__.getServerCombinedModueFile;
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
- "getServerCombinedModueFile",
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 = (promise)=>{
49
- if ('function' == typeof external_react_default().use) return external_react_default().use(promise);
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 element = match.route.element;
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?.[match.route.id],
81
- actionData: routerContext?.actionData?.[match.route.id],
93
+ loaderData: routerContext?.loaderData?.[route.id],
94
+ actionData: routerContext?.actionData?.[route.id],
82
95
  params: match.params,
83
- matches: routerContext.matches.map((m)=>{
84
- const { route, pathname, params } = m;
85
- return {
86
- id: route.id,
87
- pathname,
88
- params,
89
- data: routerContext?.loaderData?.[route.id],
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
- const isLeafRoute = index === routerContext.matches.length - 1;
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: match.route.errorElement,
103
- handle: match.route.handle,
104
- hasAction: !!match.route.action,
105
- hasErrorBoundary: !!match.route.hasErrorBoundary,
106
- hasLoader: !!match.route.loader,
107
- hasClientLoader: !!match.route.hasClientLoader,
108
- id: match.route.id,
109
- index: match.route.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 || match.route.parentId,
112
- path: match.route.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 && void 0 !== component._init && void 0 !== component._payload;
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
- if ('lazyImport' in route && isLazyComponent(route.component)) route.component = (await route.lazyImport()).default;
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
- if (origRoute.id && routesMap.has(origRoute.id)) {
153
- const matchedRoute = routesMap.get(origRoute.id);
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: origRoute.hasClientLoader ? origRoute.loader : void 0,
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 (context)=>{
192
- const { request, matches } = context;
265
+ dataStrategy: async ({ request, matches })=>{
193
266
  const results = {};
194
- const clientMatches = matches.filter((match)=>match.route.hasClientLoader);
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 = clientMatches.length > 0 ? Promise.all(clientMatches.map(async (clientMatch)=>{
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: 'redirect',
280
+ type: 'data',
216
281
  result: (0, router_namespaceObject.redirect)(redirectLocation)
217
282
  };
218
283
  });
219
284
  return results;
220
285
  }
221
- const [clientLoaderResults] = await Promise.all([
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) router.patchRoutes(matchedRoute.parentId, [
235
- matchedRoute
236
- ], true);
237
- if (serverPayload.loaderData?.[routeId]) results[routeId] = {
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 { getServerCombinedModueFile, getServerLoadersFile } from "./utils.mjs";
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
- isRscClient: isUseRsc(config)
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
- isRscClient: false
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 = getServerCombinedModueFile(internalDirectory, entryName);
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');