@bleedingdev/modern-js-runtime 3.4.0-ultramodern.1 → 3.4.0-ultramodern.11
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/cli/index.js +5 -2
- package/dist/cjs/cli/template.server.js +12 -1
- package/dist/cjs/core/browser/index.js +1 -1
- package/dist/cjs/core/server/stream/beforeTemplate.js +2 -2
- package/dist/cjs/core/server/stream/beforeTemplate.worker.js +2 -2
- package/dist/cjs/core/server/string/loadable.js +2 -6
- package/dist/cjs/router/cli/code/templates.js +8 -8
- package/dist/cjs/router/cli/index.js +7 -7
- package/dist/cjs/router/cli/nestedRoutesSpec.js +114 -0
- package/dist/cjs/router/runtime/routerHelper.js +48 -6
- package/dist/cjs/router/runtime/rsc-router.js +4 -3
- package/dist/esm/cli/index.mjs +2 -2
- package/dist/esm/cli/template.server.mjs +12 -1
- package/dist/esm/core/browser/index.mjs +2 -2
- package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -2
- package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
- package/dist/esm/core/server/string/loadable.mjs +2 -6
- package/dist/esm/router/cli/code/templates.mjs +9 -9
- package/dist/esm/router/cli/index.mjs +4 -7
- package/dist/esm/router/cli/nestedRoutesSpec.mjs +66 -0
- package/dist/esm/router/runtime/routerHelper.mjs +44 -5
- package/dist/esm/router/runtime/rsc-router.mjs +4 -3
- package/dist/esm-node/cli/index.mjs +2 -2
- package/dist/esm-node/cli/template.server.mjs +12 -1
- package/dist/esm-node/core/browser/index.mjs +2 -2
- package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -2
- package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
- package/dist/esm-node/core/server/string/loadable.mjs +2 -6
- package/dist/esm-node/router/cli/code/templates.mjs +9 -9
- package/dist/esm-node/router/cli/index.mjs +4 -7
- package/dist/esm-node/router/cli/nestedRoutesSpec.mjs +67 -0
- package/dist/esm-node/router/runtime/routerHelper.mjs +44 -5
- package/dist/esm-node/router/runtime/rsc-router.mjs +4 -3
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/core/server/string/loadable.d.ts +0 -1
- package/dist/types/router/cli/index.d.ts +1 -0
- package/dist/types/router/cli/nestedRoutesSpec.d.ts +1 -0
- package/dist/types/router/runtime/routerHelper.d.ts +3 -2
- package/package.json +11 -11
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import node_path from "node:path";
|
|
2
|
-
import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer
|
|
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
|
-
|
|
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 =
|
|
4
|
-
if (routeModule
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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)
|
|
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)
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
307
|
-
const combinedModule = `export * from "${slash(serverLoaderRuntime)}"; export * from "${
|
|
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("${
|
|
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
|
|
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
|
-
|
|
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 =
|
|
5
|
-
if (routeModule
|
|
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
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
2
|
+
import type { ElementType } from 'react';
|
|
3
3
|
export declare const createShouldRevalidate: (routeId: string) => ShouldRevalidateFunction;
|
|
4
|
-
export declare const
|
|
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;
|