@bleedingdev/modern-js-runtime 3.4.0-ultramodern.1 → 3.4.0-ultramodern.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 (39) hide show
  1. package/dist/cjs/cli/index.js +5 -2
  2. package/dist/cjs/cli/template.server.js +12 -1
  3. package/dist/cjs/core/browser/index.js +1 -1
  4. package/dist/cjs/core/server/stream/beforeTemplate.js +2 -2
  5. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +2 -2
  6. package/dist/cjs/core/server/string/loadable.js +2 -6
  7. package/dist/cjs/router/cli/code/templates.js +8 -8
  8. package/dist/cjs/router/cli/index.js +7 -7
  9. package/dist/cjs/router/cli/nestedRoutesSpec.js +114 -0
  10. package/dist/cjs/router/runtime/routerHelper.js +48 -6
  11. package/dist/cjs/router/runtime/rsc-router.js +4 -3
  12. package/dist/esm/cli/index.mjs +2 -2
  13. package/dist/esm/cli/template.server.mjs +12 -1
  14. package/dist/esm/core/browser/index.mjs +2 -2
  15. package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -2
  16. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
  17. package/dist/esm/core/server/string/loadable.mjs +2 -6
  18. package/dist/esm/router/cli/code/templates.mjs +9 -9
  19. package/dist/esm/router/cli/index.mjs +4 -7
  20. package/dist/esm/router/cli/nestedRoutesSpec.mjs +66 -0
  21. package/dist/esm/router/runtime/routerHelper.mjs +44 -5
  22. package/dist/esm/router/runtime/rsc-router.mjs +4 -3
  23. package/dist/esm-node/cli/index.mjs +2 -2
  24. package/dist/esm-node/cli/template.server.mjs +12 -1
  25. package/dist/esm-node/core/browser/index.mjs +2 -2
  26. package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -2
  27. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
  28. package/dist/esm-node/core/server/string/loadable.mjs +2 -6
  29. package/dist/esm-node/router/cli/code/templates.mjs +9 -9
  30. package/dist/esm-node/router/cli/index.mjs +4 -7
  31. package/dist/esm-node/router/cli/nestedRoutesSpec.mjs +67 -0
  32. package/dist/esm-node/router/runtime/routerHelper.mjs +44 -5
  33. package/dist/esm-node/router/runtime/rsc-router.mjs +4 -3
  34. package/dist/types/cli/index.d.ts +1 -1
  35. package/dist/types/core/server/string/loadable.d.ts +0 -1
  36. package/dist/types/router/cli/index.d.ts +1 -0
  37. package/dist/types/router/cli/nestedRoutesSpec.d.ts +1 -0
  38. package/dist/types/router/runtime/routerHelper.d.ts +3 -2
  39. package/package.json +11 -11
@@ -1,8 +1,9 @@
1
1
  import node_path from "node:path";
2
- import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer, fs } from "@modern-js/utils";
2
+ import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer } from "@modern-js/utils";
3
3
  import { NESTED_ROUTES_DIR } from "./constants.mjs";
4
4
  import { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, isRouteEntry } from "./entry.mjs";
5
5
  import { handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints } from "./handler.mjs";
6
+ import { updateNestedRoutesSpec } from "./nestedRoutesSpec.mjs";
6
7
  function isBuiltInRouteEntrypoint(entrypoint) {
7
8
  const entrypointRoutesOwner = getEntrypointRoutesOwner(entrypoint);
8
9
  if (entrypointRoutesOwner) return entrypointRoutesOwner === BUILT_IN_ROUTES_OWNER;
@@ -87,11 +88,7 @@ const routerPlugin = ()=>({
87
88
  if (isBuiltInRouteEntrypoint(entrypoint)) {
88
89
  const { distDirectory } = api.getAppContext();
89
90
  const nestedRoutesSpecPath = node_path.resolve(distDirectory, NESTED_ROUTE_SPEC_FILE);
90
- const existingNestedRoutes = await fs.pathExists(nestedRoutesSpecPath) ? await fs.readJSON(nestedRoutesSpecPath) : {};
91
- await fs.outputJSON(nestedRoutesSpecPath, {
92
- ...existingNestedRoutes,
93
- ...nestedRoutesForServer
94
- });
91
+ await updateNestedRoutesSpec(nestedRoutesSpecPath, nestedRoutesForServer);
95
92
  }
96
93
  return {
97
94
  entrypoint,
@@ -102,4 +99,4 @@ const routerPlugin = ()=>({
102
99
  });
103
100
  const cli = routerPlugin;
104
101
  export default cli;
105
- export { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin };
102
+ export { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin, updateNestedRoutesSpec };
@@ -0,0 +1,66 @@
1
+ import node_path from "node:path";
2
+ import { setTimeout as promises_setTimeout } from "node:timers/promises";
3
+ import { fs } from "@modern-js/utils";
4
+ const lockPollIntervalMs = 25;
5
+ const staleLockAgeMs = 120000;
6
+ const pendingUpdates = new Map();
7
+ let tempFileCounter = 0;
8
+ async function acquireSpecLock(specPath) {
9
+ const lockDir = `${specPath}.lock`;
10
+ await fs.ensureDir(node_path.dirname(specPath));
11
+ while(true){
12
+ try {
13
+ await fs.mkdir(lockDir);
14
+ return async ()=>{
15
+ await fs.remove(lockDir);
16
+ };
17
+ } catch (error) {
18
+ if ('EEXIST' !== error.code) throw error;
19
+ }
20
+ try {
21
+ const stat = await fs.stat(lockDir);
22
+ if (performance.timeOrigin + performance.now() - stat.mtimeMs > staleLockAgeMs) {
23
+ await fs.remove(lockDir);
24
+ continue;
25
+ }
26
+ } catch (error) {
27
+ if ('ENOENT' !== error.code) throw error;
28
+ continue;
29
+ }
30
+ await promises_setTimeout(lockPollIntervalMs);
31
+ }
32
+ }
33
+ async function writeJSONAtomically(filePath, value) {
34
+ const directory = node_path.dirname(filePath);
35
+ const tempPath = node_path.join(directory, `.${node_path.basename(filePath)}.${process.pid}.${tempFileCounter += 1}.tmp`);
36
+ try {
37
+ await fs.writeFile(tempPath, `${JSON.stringify(value)}\n`);
38
+ await fs.rename(tempPath, filePath);
39
+ } catch (error) {
40
+ await fs.remove(tempPath).catch(()=>{});
41
+ throw error;
42
+ }
43
+ }
44
+ async function updateNestedRoutesSpec(specPath, nextRoutes) {
45
+ const resolvedSpecPath = node_path.resolve(specPath);
46
+ const previousUpdate = pendingUpdates.get(resolvedSpecPath) ?? Promise.resolve();
47
+ const currentUpdate = previousUpdate.catch(()=>void 0).then(async ()=>{
48
+ const releaseLock = await acquireSpecLock(resolvedSpecPath);
49
+ try {
50
+ const existingRoutes = await fs.pathExists(resolvedSpecPath) ? await fs.readJSON(resolvedSpecPath) : {};
51
+ await writeJSONAtomically(resolvedSpecPath, {
52
+ ...existingRoutes,
53
+ ...nextRoutes
54
+ });
55
+ } finally{
56
+ await releaseLock();
57
+ }
58
+ });
59
+ pendingUpdates.set(resolvedSpecPath, currentUpdate);
60
+ try {
61
+ await currentUpdate;
62
+ } finally{
63
+ if (pendingUpdates.get(resolvedSpecPath) === currentUpdate) pendingUpdates.delete(resolvedSpecPath);
64
+ }
65
+ }
66
+ export { updateNestedRoutesSpec };
@@ -1,15 +1,54 @@
1
1
  import { ROUTE_MODULES } from "@modern-js/utils/universal/constants";
2
+ const isObjectLike = (value)=>'object' == typeof value && null !== value || 'function' == typeof value;
3
+ const isRouteComponent = (value)=>'function' == typeof value || isObjectLike(value) && '$$typeof' in value;
4
+ const getRouteModules = ()=>{
5
+ if ("u" < typeof window) return;
6
+ return window[ROUTE_MODULES];
7
+ };
8
+ const storeRouteModule = (routeModule, routeId)=>{
9
+ if ("u" < typeof document) return;
10
+ const routeModules = getRouteModules();
11
+ if (void 0 !== routeModules) routeModules[routeId] = routeModule;
12
+ };
13
+ const unwrapRspackAsyncModule = (routeModule)=>{
14
+ if (!isObjectLike(routeModule)) return routeModule;
15
+ const rspackExportsSymbol = Object.getOwnPropertySymbols(routeModule).find((symbol)=>'rspack exports' === symbol.description);
16
+ if (void 0 !== rspackExportsSymbol) return routeModule[rspackExportsSymbol];
17
+ if ('__webpack_exports__' in routeModule) return routeModule.__webpack_exports__;
18
+ return routeModule;
19
+ };
2
20
  const createShouldRevalidate = (routeId)=>(arg)=>{
3
- const routeModule = "u" > typeof window ? window?.[ROUTE_MODULES]?.[routeId] : void 0;
4
- if (routeModule && 'function' == typeof routeModule.shouldRevalidate) return routeModule.shouldRevalidate(arg);
21
+ const routeModule = getRouteModules()?.[routeId];
22
+ if (isObjectLike(routeModule)) {
23
+ const shouldRevalidate = routeModule.shouldRevalidate;
24
+ if ('function' == typeof shouldRevalidate) return shouldRevalidate(arg);
25
+ }
5
26
  return arg.defaultShouldRevalidate;
6
27
  };
28
+ const pickRouteModuleComponent = (routeModule, seen = new Set())=>{
29
+ const unwrappedRouteModule = unwrapRspackAsyncModule(routeModule);
30
+ if (isRouteComponent(unwrappedRouteModule)) return unwrappedRouteModule;
31
+ if (!isObjectLike(unwrappedRouteModule) || seen.has(unwrappedRouteModule)) return;
32
+ seen.add(unwrappedRouteModule);
33
+ const componentModule = unwrappedRouteModule;
34
+ for (const candidate of [
35
+ componentModule.default,
36
+ componentModule.Component
37
+ ]){
38
+ const component = pickRouteModuleComponent(candidate, seen);
39
+ if (void 0 !== component) return component;
40
+ }
41
+ };
42
+ const resolveRouteComponent = (routeModule)=>pickRouteModuleComponent(routeModule) ?? routeModule;
7
43
  const handleRouteModule = (routeModule, routeId)=>{
8
- if ("u" > typeof document) window[ROUTE_MODULES][routeId] = routeModule;
9
- return routeModule;
44
+ storeRouteModule(routeModule, routeId);
45
+ const component = pickRouteModuleComponent(routeModule);
46
+ return void 0 !== component ? {
47
+ default: component
48
+ } : routeModule;
10
49
  };
11
50
  const handleRouteModuleError = (error)=>{
12
51
  console.error(error);
13
52
  return null;
14
53
  };
15
- export { createShouldRevalidate, handleRouteModule, handleRouteModuleError };
54
+ export { createShouldRevalidate, handleRouteModule, handleRouteModuleError, resolveRouteComponent };
@@ -44,10 +44,10 @@ const createServerPayload = (routerContext, routes)=>{
44
44
  routes: routerContext.matches.map((match, index, matches)=>{
45
45
  const route = match.route;
46
46
  const element = route.element;
47
+ const Component = route.Component;
47
48
  const parentMatch = index > 0 ? matches[index - 1] : void 0;
48
49
  let processedElement;
49
- if (element) {
50
- const ElementComponent = element.type;
50
+ if (element || Component) {
51
51
  const elementProps = {
52
52
  loaderData: routerContext?.loaderData?.[route.id],
53
53
  actionData: routerContext?.actionData?.[route.id],
@@ -60,7 +60,8 @@ const createServerPayload = (routerContext, routes)=>{
60
60
  handle: m.route.handle
61
61
  }))
62
62
  };
63
- const routeElement = /*#__PURE__*/ react.createElement(ElementComponent, elementProps);
63
+ const RouteComponent = Component;
64
+ const routeElement = element ? /*#__PURE__*/ react.cloneElement(element, elementProps) : /*#__PURE__*/ react.createElement(RouteComponent, elementProps);
64
65
  processedElement = index === cssInjectionIndex ? /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(CSSLinks, {
65
66
  cssFiles
66
67
  }), routeElement) : routeElement;
@@ -3,7 +3,7 @@ const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/
3
3
  import { cleanRequireCache, isReact18 as utils_isReact18 } from "@modern-js/utils";
4
4
  import path_0 from "path";
5
5
  import { documentPlugin } from "../document/cli/index.mjs";
6
- import { getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin } from "../router/cli/index.mjs";
6
+ import { getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin, updateNestedRoutesSpec } from "../router/cli/index.mjs";
7
7
  import { builderPluginAlias } from "./alias.mjs";
8
8
  import { generateCode } from "./code.mjs";
9
9
  import { ENTRY_BOOTSTRAP_FILE_NAME, ENTRY_POINT_FILE_NAME } from "./constants.mjs";
@@ -94,4 +94,4 @@ const cli = runtimePlugin;
94
94
  export { makeLegalIdentifier } from "../router/cli/code/makeLegalIdentifier.mjs";
95
95
  export { getPathWithoutExt } from "../router/cli/code/utils.mjs";
96
96
  export default cli;
97
- export { documentPlugin, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, isRuntimeEntry, routerPlugin, runtimePlugin, ssrPlugin };
97
+ export { documentPlugin, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, isRuntimeEntry, routerPlugin, runtimePlugin, ssrPlugin, updateNestedRoutesSpec };
@@ -25,7 +25,7 @@ import {
25
25
  createRequestHandler,
26
26
  } from '@#metaName/runtime/ssr/server';
27
27
  import { RSCServerSlot } from '@#metaName/runtime/rsc/client';
28
- import { renderRsc } from '@#metaName/runtime/rsc/server';
28
+ import { renderCSRWithRSC, renderRsc } from '@#metaName/runtime/rsc/server';
29
29
  export { handleAction } from '@#metaName/runtime/rsc/server';
30
30
 
31
31
  const handleRequest = async (request, ServerRoot, options) => {
@@ -52,6 +52,17 @@ export const requestHandler = createRequestHandler(handleRequest, {
52
52
  enableRsc: true
53
53
  });
54
54
 
55
+ const handleCSRRender = async (request, ServerRoot, options) => {
56
+ return renderCSRWithRSC({
57
+ html: options.html,
58
+ rscRoot: options.rscRoot,
59
+ });
60
+ }
61
+
62
+ export const renderRscStreamHandler = createRequestHandler(handleCSRRender, {
63
+ enableRsc: true
64
+ });
65
+
55
66
  const handleRSCRequest = async (request, ServerRoot, options) => {
56
67
  const { serverPayload } = options;
57
68
  const stream = renderRsc({
@@ -1,6 +1,6 @@
1
1
  import "node:module";
2
2
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
3
- import { parse } from "cookie";
3
+ import { parseCookie } from "cookie";
4
4
  import { getGlobalInternalRuntimeContext } from "../context/index.mjs";
5
5
  import { getInitialContext } from "../context/runtime.mjs";
6
6
  import { wrapRuntimeContextProvider } from "../react/wrapper.mjs";
@@ -12,7 +12,7 @@ const getQuery = ()=>window.location.search.substring(1).split('&').reduce((res,
12
12
  return res;
13
13
  }, {});
14
14
  const getCookieMap = ()=>{
15
- const parsed = parse(document.cookie || '');
15
+ const parsed = parseCookie(document.cookie || '');
16
16
  return Object.fromEntries(Object.entries(parsed).filter((entry)=>'string' == typeof entry[1]));
17
17
  };
18
18
  function getSSRData() {
@@ -38,8 +38,8 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
38
38
  return safeReplace(template, CHUNK_CSS_PLACEHOLDER, css);
39
39
  async function getCssChunks() {
40
40
  const { routeManifest, routerContext, routes } = runtimeContext;
41
- if (!routeManifest) return '';
42
- const { routeAssets } = routeManifest;
41
+ const routeAssets = routeManifest?.routeAssets;
42
+ if (!routeAssets) return '';
43
43
  let matchedRouteManifests = [];
44
44
  const matchedRouteIds = getRouterMatchedRouteIds(runtimeContext);
45
45
  if (matchedRouteIds?.length) matchedRouteManifests = matchedRouteIds.map((routeId)=>routeAssets[routeId]).filter(Boolean);
@@ -29,8 +29,8 @@ async function buildShellBeforeTemplate(beforeAppTemplate, options) {
29
29
  return safeReplace(template, CHUNK_CSS_PLACEHOLDER, css);
30
30
  async function getCssChunks() {
31
31
  const { routeManifest, routerContext, routes } = runtimeContext;
32
- if (!routeManifest) return '';
33
- const { routeAssets } = routeManifest;
32
+ const routeAssets = routeManifest?.routeAssets;
33
+ if (!routeAssets) return '';
34
34
  let matchedRouteManifests = [];
35
35
  const matchedRouteIds = getRouterMatchedRouteIds(runtimeContext);
36
36
  if (matchedRouteIds?.length) matchedRouteManifests = matchedRouteIds.map((routeId)=>routeAssets[routeId]).filter(Boolean);
@@ -32,10 +32,6 @@ const readAsset = async (chunk)=>{
32
32
  return fs.readFile(filepath, 'utf-8');
33
33
  };
34
34
  class LoadableCollector {
35
- get existsAssets() {
36
- const { routeManifest, entryName } = this.options;
37
- return routeManifest?.routeAssets?.[entryName]?.assets;
38
- }
39
35
  getMatchedRouteChunks() {
40
36
  const { routeManifest, runtimeContext } = this.options;
41
37
  if (!routeManifest) return [];
@@ -102,7 +98,7 @@ class LoadableCollector {
102
98
  const matchs = template.matchAll(jsScriptRegExp);
103
99
  const existedScript = [];
104
100
  for (const match of matchs)existedScript.push(match[1]);
105
- const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
101
+ const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url)).map(async (chunk)=>{
106
102
  const script = `<script${attributes} src="${chunk.url}"></script>`;
107
103
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<script>${content}</script>`).catch((_)=>script);
108
104
  return script;
@@ -113,7 +109,7 @@ class LoadableCollector {
113
109
  const { template, chunkSet, config, moduleFederationCssAssets } = this.options;
114
110
  const { inlineStyles } = config;
115
111
  const atrributes = attributesToString(this.generateAttributes());
116
- const emittedChunks = chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path));
112
+ const emittedChunks = chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url));
117
113
  const css = await Promise.all(emittedChunks.map(async (chunk)=>{
118
114
  const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
119
115
  if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
@@ -5,7 +5,7 @@ import { JS_EXTENSIONS, findExists, formatImportPath, fs, getEntryOptions, isSSG
5
5
  import { ROUTE_MODULES } from "@modern-js/utils/universal/constants";
6
6
  import path from "path";
7
7
  import { APP_INIT_EXPORTED, TEMP_LOADERS_DIR } from "../constants.mjs";
8
- import { getPathWithoutExt, getServerLoadersFile, parseModule, replaceWithAlias } from "./utils.mjs";
8
+ import { getPathWithoutExt, parseModule, replaceWithAlias } from "./utils.mjs";
9
9
  const routesForServer = ({ routesForServerLoaderMatches })=>{
10
10
  const loaders = [];
11
11
  const actions = [];
@@ -171,14 +171,14 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
171
171
  routeId: route.id,
172
172
  webpackChunkName: true
173
173
  });
174
- component = 'string' === ssrMode ? `loadable(${lazyImport})` : `lazy(${lazyImport})`;
174
+ component = 'string' === ssrMode ? `loadable(${lazyImport}, { resolveComponent: resolveRouteComponent })` : `lazy(${lazyImport})`;
175
175
  } else {
176
176
  components.push(route._component);
177
177
  component = `component_${components.length - 1}`;
178
178
  }
179
179
  } else if (route._component) if (splitRouteChunks) {
180
180
  lazyImport = `() => import('${route._component}')`;
181
- component = `loadable(${lazyImport})`;
181
+ component = `loadable(${lazyImport}, { resolveComponent: resolveRouteComponent })`;
182
182
  } else {
183
183
  components.push(route._component);
184
184
  component = `component_${components.length - 1}`;
@@ -226,7 +226,7 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
226
226
  const newRouteStr = regs.reduce((acc, reg)=>acc.replace(reg, '$1$2'), routeStr).replace(/"(RootLayout)"/g, '$1').replace(/\\"/g, '"');
227
227
  routeComponentsCode += `${newRouteStr},`;
228
228
  } else {
229
- const component = `loadable(() => import('${route._component}'))`;
229
+ const component = `loadable(() => import('${route._component}'), { resolveComponent: resolveRouteComponent })`;
230
230
  const finalRoute = {
231
231
  ...route,
232
232
  component
@@ -276,7 +276,7 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
276
276
  await fs.ensureFile(loadersMapFile);
277
277
  await fs.writeJSON(loadersMapFile, loadersMap);
278
278
  const importRuntimeRouterCode = `
279
- import { createShouldRevalidate, handleRouteModule, handleRouteModuleError} from '@${metaName}/runtime/routerHelper';
279
+ import { createShouldRevalidate, handleRouteModule, handleRouteModuleError, resolveRouteComponent } from '@${metaName}/runtime/routerHelper';
280
280
  `;
281
281
  const routeModulesCode = `
282
282
  if(typeof document !== 'undefined'){
@@ -298,19 +298,19 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
298
298
  };
299
299
  function ssrLoaderCombinedModule(entrypoints, entrypoint, config, appContext) {
300
300
  const { entryName, isMainEntry } = entrypoint;
301
- const { packageName, internalDirectory } = appContext;
301
+ const { packageName } = appContext;
302
302
  const ssr = getEntryOptions(entryName, isMainEntry, config.server.ssr, config.server.ssrByEntries, packageName);
303
303
  const ssg = isSSGEntry(config, entryName, entrypoints);
304
304
  if (entrypoint.nestedRoutesEntry && (ssr || ssg)) {
305
305
  const serverLoaderRuntime = require.resolve('@modern-js/plugin-data-loader/runtime');
306
- const serverLoadersFile = getServerLoadersFile(internalDirectory, entryName);
307
- const combinedModule = `export * from "${slash(serverLoaderRuntime)}"; export * from "${slash(serverLoadersFile)}"`;
306
+ const serverLoadersFile = './route-server-loaders.js';
307
+ const combinedModule = `export * from "${slash(serverLoaderRuntime)}"; export * from "${serverLoadersFile}"`;
308
308
  if (!config.source.enableAsyncEntry) return combinedModule;
309
309
  return `
310
310
  async function loadModules() {
311
311
  const [moduleA, moduleB] = await Promise.all([
312
312
  import("${slash(serverLoaderRuntime)}"),
313
- import("${slash(serverLoadersFile)}")
313
+ import("${serverLoadersFile}")
314
314
  ]);
315
315
 
316
316
  return {
@@ -1,9 +1,10 @@
1
1
  import "node:module";
2
2
  import node_path from "node:path";
3
- import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer, fs } from "@modern-js/utils";
3
+ import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer } from "@modern-js/utils";
4
4
  import { NESTED_ROUTES_DIR } from "./constants.mjs";
5
5
  import { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, isRouteEntry } from "./entry.mjs";
6
6
  import { handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints } from "./handler.mjs";
7
+ import { updateNestedRoutesSpec } from "./nestedRoutesSpec.mjs";
7
8
  import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
8
9
  import { dirname as __rspack_dirname } from "node:path";
9
10
  var cli_dirname = __rspack_dirname(__rspack_fileURLToPath(import.meta.url));
@@ -91,11 +92,7 @@ const routerPlugin = ()=>({
91
92
  if (isBuiltInRouteEntrypoint(entrypoint)) {
92
93
  const { distDirectory } = api.getAppContext();
93
94
  const nestedRoutesSpecPath = node_path.resolve(distDirectory, NESTED_ROUTE_SPEC_FILE);
94
- const existingNestedRoutes = await fs.pathExists(nestedRoutesSpecPath) ? await fs.readJSON(nestedRoutesSpecPath) : {};
95
- await fs.outputJSON(nestedRoutesSpecPath, {
96
- ...existingNestedRoutes,
97
- ...nestedRoutesForServer
98
- });
95
+ await updateNestedRoutesSpec(nestedRoutesSpecPath, nestedRoutesForServer);
99
96
  }
100
97
  return {
101
98
  entrypoint,
@@ -106,4 +103,4 @@ const routerPlugin = ()=>({
106
103
  });
107
104
  const cli = routerPlugin;
108
105
  export default cli;
109
- export { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin };
106
+ export { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, routerPlugin, updateNestedRoutesSpec };
@@ -0,0 +1,67 @@
1
+ import "node:module";
2
+ import node_path from "node:path";
3
+ import { setTimeout as promises_setTimeout } from "node:timers/promises";
4
+ import { fs } from "@modern-js/utils";
5
+ const lockPollIntervalMs = 25;
6
+ const staleLockAgeMs = 120000;
7
+ const pendingUpdates = new Map();
8
+ let tempFileCounter = 0;
9
+ async function acquireSpecLock(specPath) {
10
+ const lockDir = `${specPath}.lock`;
11
+ await fs.ensureDir(node_path.dirname(specPath));
12
+ while(true){
13
+ try {
14
+ await fs.mkdir(lockDir);
15
+ return async ()=>{
16
+ await fs.remove(lockDir);
17
+ };
18
+ } catch (error) {
19
+ if ('EEXIST' !== error.code) throw error;
20
+ }
21
+ try {
22
+ const stat = await fs.stat(lockDir);
23
+ if (performance.timeOrigin + performance.now() - stat.mtimeMs > staleLockAgeMs) {
24
+ await fs.remove(lockDir);
25
+ continue;
26
+ }
27
+ } catch (error) {
28
+ if ('ENOENT' !== error.code) throw error;
29
+ continue;
30
+ }
31
+ await promises_setTimeout(lockPollIntervalMs);
32
+ }
33
+ }
34
+ async function writeJSONAtomically(filePath, value) {
35
+ const directory = node_path.dirname(filePath);
36
+ const tempPath = node_path.join(directory, `.${node_path.basename(filePath)}.${process.pid}.${tempFileCounter += 1}.tmp`);
37
+ try {
38
+ await fs.writeFile(tempPath, `${JSON.stringify(value)}\n`);
39
+ await fs.rename(tempPath, filePath);
40
+ } catch (error) {
41
+ await fs.remove(tempPath).catch(()=>{});
42
+ throw error;
43
+ }
44
+ }
45
+ async function updateNestedRoutesSpec(specPath, nextRoutes) {
46
+ const resolvedSpecPath = node_path.resolve(specPath);
47
+ const previousUpdate = pendingUpdates.get(resolvedSpecPath) ?? Promise.resolve();
48
+ const currentUpdate = previousUpdate.catch(()=>void 0).then(async ()=>{
49
+ const releaseLock = await acquireSpecLock(resolvedSpecPath);
50
+ try {
51
+ const existingRoutes = await fs.pathExists(resolvedSpecPath) ? await fs.readJSON(resolvedSpecPath) : {};
52
+ await writeJSONAtomically(resolvedSpecPath, {
53
+ ...existingRoutes,
54
+ ...nextRoutes
55
+ });
56
+ } finally{
57
+ await releaseLock();
58
+ }
59
+ });
60
+ pendingUpdates.set(resolvedSpecPath, currentUpdate);
61
+ try {
62
+ await currentUpdate;
63
+ } finally{
64
+ if (pendingUpdates.get(resolvedSpecPath) === currentUpdate) pendingUpdates.delete(resolvedSpecPath);
65
+ }
66
+ }
67
+ export { updateNestedRoutesSpec };
@@ -1,16 +1,55 @@
1
1
  import "node:module";
2
2
  import { ROUTE_MODULES } from "@modern-js/utils/universal/constants";
3
+ const isObjectLike = (value)=>'object' == typeof value && null !== value || 'function' == typeof value;
4
+ const isRouteComponent = (value)=>'function' == typeof value || isObjectLike(value) && '$$typeof' in value;
5
+ const getRouteModules = ()=>{
6
+ if ("u" < typeof window) return;
7
+ return window[ROUTE_MODULES];
8
+ };
9
+ const storeRouteModule = (routeModule, routeId)=>{
10
+ if ("u" < typeof document) return;
11
+ const routeModules = getRouteModules();
12
+ if (void 0 !== routeModules) routeModules[routeId] = routeModule;
13
+ };
14
+ const unwrapRspackAsyncModule = (routeModule)=>{
15
+ if (!isObjectLike(routeModule)) return routeModule;
16
+ const rspackExportsSymbol = Object.getOwnPropertySymbols(routeModule).find((symbol)=>'rspack exports' === symbol.description);
17
+ if (void 0 !== rspackExportsSymbol) return routeModule[rspackExportsSymbol];
18
+ if ('__webpack_exports__' in routeModule) return routeModule.__webpack_exports__;
19
+ return routeModule;
20
+ };
3
21
  const createShouldRevalidate = (routeId)=>(arg)=>{
4
- const routeModule = "u" > typeof window ? window?.[ROUTE_MODULES]?.[routeId] : void 0;
5
- if (routeModule && 'function' == typeof routeModule.shouldRevalidate) return routeModule.shouldRevalidate(arg);
22
+ const routeModule = getRouteModules()?.[routeId];
23
+ if (isObjectLike(routeModule)) {
24
+ const shouldRevalidate = routeModule.shouldRevalidate;
25
+ if ('function' == typeof shouldRevalidate) return shouldRevalidate(arg);
26
+ }
6
27
  return arg.defaultShouldRevalidate;
7
28
  };
29
+ const pickRouteModuleComponent = (routeModule, seen = new Set())=>{
30
+ const unwrappedRouteModule = unwrapRspackAsyncModule(routeModule);
31
+ if (isRouteComponent(unwrappedRouteModule)) return unwrappedRouteModule;
32
+ if (!isObjectLike(unwrappedRouteModule) || seen.has(unwrappedRouteModule)) return;
33
+ seen.add(unwrappedRouteModule);
34
+ const componentModule = unwrappedRouteModule;
35
+ for (const candidate of [
36
+ componentModule.default,
37
+ componentModule.Component
38
+ ]){
39
+ const component = pickRouteModuleComponent(candidate, seen);
40
+ if (void 0 !== component) return component;
41
+ }
42
+ };
43
+ const resolveRouteComponent = (routeModule)=>pickRouteModuleComponent(routeModule) ?? routeModule;
8
44
  const handleRouteModule = (routeModule, routeId)=>{
9
- if ("u" > typeof document) window[ROUTE_MODULES][routeId] = routeModule;
10
- return routeModule;
45
+ storeRouteModule(routeModule, routeId);
46
+ const component = pickRouteModuleComponent(routeModule);
47
+ return void 0 !== component ? {
48
+ default: component
49
+ } : routeModule;
11
50
  };
12
51
  const handleRouteModuleError = (error)=>{
13
52
  console.error(error);
14
53
  return null;
15
54
  };
16
- export { createShouldRevalidate, handleRouteModule, handleRouteModuleError };
55
+ export { createShouldRevalidate, handleRouteModule, handleRouteModuleError, resolveRouteComponent };
@@ -45,10 +45,10 @@ const createServerPayload = (routerContext, routes)=>{
45
45
  routes: routerContext.matches.map((match, index, matches)=>{
46
46
  const route = match.route;
47
47
  const element = route.element;
48
+ const Component = route.Component;
48
49
  const parentMatch = index > 0 ? matches[index - 1] : void 0;
49
50
  let processedElement;
50
- if (element) {
51
- const ElementComponent = element.type;
51
+ if (element || Component) {
52
52
  const elementProps = {
53
53
  loaderData: routerContext?.loaderData?.[route.id],
54
54
  actionData: routerContext?.actionData?.[route.id],
@@ -61,7 +61,8 @@ const createServerPayload = (routerContext, routes)=>{
61
61
  handle: m.route.handle
62
62
  }))
63
63
  };
64
- const routeElement = /*#__PURE__*/ react.createElement(ElementComponent, elementProps);
64
+ const RouteComponent = Component;
65
+ const routeElement = element ? /*#__PURE__*/ react.cloneElement(element, elementProps) : /*#__PURE__*/ react.createElement(RouteComponent, elementProps);
65
66
  processedElement = index === cssInjectionIndex ? /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(CSSLinks, {
66
67
  cssFiles
67
68
  }), routeElement) : routeElement;
@@ -2,7 +2,7 @@ import type { AppTools, CliPlugin } from '@modern-js/app-tools';
2
2
  import { documentPlugin } from '../document/cli';
3
3
  import { routerPlugin } from '../router/cli';
4
4
  import { ssrPlugin } from './ssr';
5
- export { getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, } from '../router/cli';
5
+ export { getEntrypointRoutesDir, getEntrypointRoutesOwner, handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, isRouteEntry, updateNestedRoutesSpec, } from '../router/cli';
6
6
  export { makeLegalIdentifier } from '../router/cli/code/makeLegalIdentifier';
7
7
  export { getPathWithoutExt } from '../router/cli/code/utils';
8
8
  export { isRuntimeEntry } from './entry';
@@ -30,7 +30,6 @@ export declare class LoadableCollector implements Collector {
30
30
  private options;
31
31
  private extractor?;
32
32
  constructor(options: LoadableCollectorOptions);
33
- private get existsAssets();
34
33
  private getMatchedRouteChunks;
35
34
  collect(comopnent: ReactElement): ReactElement;
36
35
  effect(): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import type { AppTools, CliPlugin } from '@modern-js/app-tools';
2
2
  export { BUILT_IN_ROUTES_OWNER, getEntrypointRoutesDir, getEntrypointRoutesOwner, isRouteEntry, } from './entry';
3
3
  export { handleFileChange, handleGeneratorEntryCode, handleModifyEntrypoints, } from './handler';
4
+ export { updateNestedRoutesSpec } from './nestedRoutesSpec';
4
5
  export declare const routerPlugin: () => CliPlugin<AppTools>;
5
6
  export default routerPlugin;
@@ -0,0 +1 @@
1
+ export declare function updateNestedRoutesSpec(specPath: string, nextRoutes: Record<string, unknown>): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import type { ShouldRevalidateFunction } from '@modern-js/runtime-utils/router';
2
- import type Module from 'module';
2
+ import type { ElementType } from 'react';
3
3
  export declare const createShouldRevalidate: (routeId: string) => ShouldRevalidateFunction;
4
- export declare const handleRouteModule: (routeModule: Module, routeId: string) => Module;
4
+ export declare const resolveRouteComponent: (routeModule: unknown) => ElementType<Record<string, unknown>>;
5
+ export declare const handleRouteModule: (routeModule: unknown, routeId: string) => unknown;
5
6
  export declare const handleRouteModuleError: (error: Error) => null;