@bleedingdev/modern-js-runtime 3.5.0-ultramodern.30 → 3.5.0-ultramodern.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/react/wrapper.js +57 -0
- package/dist/cjs/core/server/federatedCss.js +7 -3
- package/dist/cjs/core/server/requestHandler.js +15 -45
- package/dist/cjs/core/server/requestResponse.js +83 -0
- package/dist/cjs/core/server/routerCleanup.js +21 -0
- package/dist/cjs/core/server/scriptOrder.js +13 -7
- package/dist/cjs/core/server/string/loadable.js +3 -11
- package/dist/cjs/router/cli/code/templates.js +3 -2
- package/dist/cjs/router/runtime/provider.js +1 -1
- package/dist/esm/core/react/wrapper.mjs +58 -1
- package/dist/esm/core/server/federatedCss.mjs +7 -3
- package/dist/esm/core/server/requestHandler.mjs +15 -45
- package/dist/esm/core/server/requestResponse.mjs +39 -0
- package/dist/esm/core/server/routerCleanup.mjs +16 -1
- package/dist/esm/core/server/scriptOrder.mjs +11 -8
- package/dist/esm/core/server/string/loadable.mjs +3 -8
- package/dist/esm/router/cli/code/templates.mjs +3 -2
- package/dist/esm/router/runtime/provider.mjs +1 -1
- package/dist/esm-node/core/react/wrapper.mjs +58 -1
- package/dist/esm-node/core/server/federatedCss.mjs +7 -3
- package/dist/esm-node/core/server/requestHandler.mjs +15 -45
- package/dist/esm-node/core/server/requestResponse.mjs +40 -0
- package/dist/esm-node/core/server/routerCleanup.mjs +16 -1
- package/dist/esm-node/core/server/scriptOrder.mjs +11 -8
- package/dist/esm-node/core/server/string/loadable.mjs +3 -8
- package/dist/esm-node/router/cli/code/templates.mjs +3 -2
- package/dist/esm-node/router/runtime/provider.mjs +1 -1
- package/dist/types/core/context/index.d.ts +1 -1
- package/dist/types/core/server/requestResponse.d.ts +15 -0
- package/dist/types/core/server/routerCleanup.d.ts +2 -0
- package/dist/types/core/server/scriptOrder.d.ts +1 -0
- package/dist/types/core/server/string/loadable.d.ts +0 -2
- package/package.json +8 -8
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getRouterServerSnapshot } from "../../router/runtime/lifecycle.mjs";
|
|
2
|
+
import { handleRSCRedirect } from "../../router/runtime/rsc-router.mjs";
|
|
3
|
+
import { SSRErrors } from "./tracer.mjs";
|
|
4
|
+
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
5
|
+
const isNullBodyStatus = (status)=>204 === status || 205 === status || 304 === status;
|
|
6
|
+
const processRedirect = (headers, status, ctx)=>{
|
|
7
|
+
if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
|
|
8
|
+
return new Response(null, {
|
|
9
|
+
status,
|
|
10
|
+
headers
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const applyRouterSnapshotResult = (context, onError)=>{
|
|
14
|
+
const routerServerSnapshot = getRouterServerSnapshot(context);
|
|
15
|
+
const routerStatusCode = routerServerSnapshot?.statusCode ?? context.routerContext?.statusCode;
|
|
16
|
+
if (routerStatusCode && 200 !== routerStatusCode) context.ssrContext?.response.status(routerStatusCode);
|
|
17
|
+
const errors = Object.values(routerServerSnapshot?.errors || context.routerContext?.errors || {});
|
|
18
|
+
if (errors.length > 0) onError(errors[0], SSRErrors.LOADER_ERROR);
|
|
19
|
+
};
|
|
20
|
+
const createLoaderRedirectResponse = (beforeRenderResult, redirectCtx)=>{
|
|
21
|
+
if (!beforeRenderResult || !isRedirectStatus(beforeRenderResult.status)) return;
|
|
22
|
+
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
23
|
+
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
24
|
+
return processRedirect(new Headers({
|
|
25
|
+
Location: redirectUrl
|
|
26
|
+
}), beforeRenderResult.status, redirectCtx);
|
|
27
|
+
};
|
|
28
|
+
const finalizeRenderResponse = (response, responseProxy, redirectCtx, routerCleanup)=>{
|
|
29
|
+
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
30
|
+
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
31
|
+
response.headers.set(key, value);
|
|
32
|
+
});
|
|
33
|
+
if (-1 !== responseProxy.status) return routerCleanup.deferUntilBodyDone(new Response(isNullBodyStatus(responseProxy.status) ? null : response.body, {
|
|
34
|
+
status: responseProxy.status,
|
|
35
|
+
headers: response.headers
|
|
36
|
+
}));
|
|
37
|
+
return routerCleanup.deferUntilBodyDone(response);
|
|
38
|
+
};
|
|
39
|
+
export { applyRouterSnapshotResult, createLoaderRedirectResponse, finalizeRenderResponse };
|
|
@@ -8,6 +8,21 @@ async function withRouterCleanup(runtimeContext, onError, callback) {
|
|
|
8
8
|
if (!routerCleanup.deferred) await routerCleanup.run();
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
async function runWithRouterCleanupOnError(routerCleanup, callback) {
|
|
12
|
+
try {
|
|
13
|
+
return await callback();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
await routerCleanup.run();
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function finishWithRouterCleanup(routerCleanup, callback) {
|
|
20
|
+
try {
|
|
21
|
+
return await callback();
|
|
22
|
+
} finally{
|
|
23
|
+
if (!routerCleanup.deferred) await routerCleanup.run();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
11
26
|
function createRouterCleanup(runtimeContext, onError) {
|
|
12
27
|
let deferred = false;
|
|
13
28
|
let finished = false;
|
|
@@ -63,4 +78,4 @@ function createRouterCleanup(runtimeContext, onError) {
|
|
|
63
78
|
deferUntilBodyDone
|
|
64
79
|
};
|
|
65
80
|
}
|
|
66
|
-
export { ROUTER_CLEANUP_ERROR, createRouterCleanup, withRouterCleanup };
|
|
81
|
+
export { ROUTER_CLEANUP_ERROR, createRouterCleanup, finishWithRouterCleanup, runWithRouterCleanupOnError, withRouterCleanup };
|
|
@@ -42,6 +42,10 @@ function getMatchedRouteAssets(runtimeContext, routeManifest) {
|
|
|
42
42
|
const matchedRouteIds = getRouterMatchedRouteIds(runtimeContext) ?? [];
|
|
43
43
|
return matchedRouteIds.flatMap((routeId)=>routeAssets[routeId]?.assets ?? []);
|
|
44
44
|
}
|
|
45
|
+
function getMatchedRouteChunks(runtimeContext, routeManifest, routeAssetToChunk) {
|
|
46
|
+
if (!routeManifest) return [];
|
|
47
|
+
return getMatchedRouteAssets(runtimeContext, routeManifest).map(routeAssetToChunk);
|
|
48
|
+
}
|
|
45
49
|
const orderHydrationScriptChunks = ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName })=>{
|
|
46
50
|
const asyncEntryScriptChunks = [];
|
|
47
51
|
const asyncEntryDependencyChunks = [];
|
|
@@ -72,13 +76,12 @@ function createRouteHydrationScriptTags(runtimeContext, entryName, options = {})
|
|
|
72
76
|
const { nonce, template } = options;
|
|
73
77
|
const routeAssets = getRouteAssets(runtimeContext);
|
|
74
78
|
if (void 0 === routeAssets) return '';
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
...
|
|
78
|
-
routeAssets[`async-${entryName}`]
|
|
79
|
-
].filter((
|
|
80
|
-
const jsAssets = Array.from(new Set(assetEntries.flatMap((entry)=>(entry.assets ?? []).filter((asset)=>asset.endsWith('.js')))));
|
|
79
|
+
const existingScriptSrcs = void 0 === template ? new Set() : new Set(getScriptTags(template).map(({ src })=>src));
|
|
80
|
+
const jsAssets = Array.from(new Set([
|
|
81
|
+
...getMatchedRouteAssets(runtimeContext),
|
|
82
|
+
...routeAssets[`async-${entryName}`]?.assets ?? []
|
|
83
|
+
].filter((asset)=>asset.endsWith('.js'))));
|
|
81
84
|
const nonceAttr = void 0 === nonce || '' === nonce ? '' : ` nonce="${nonce}"`;
|
|
82
|
-
return jsAssets.filter((asset)
|
|
85
|
+
return jsAssets.filter((asset)=>!existingScriptSrcs.has(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
|
|
83
86
|
}
|
|
84
|
-
export { createRouteHydrationScriptTags, getMatchedRouteAssets, injectBeforeHydrationEntryScript, orderHydrationScriptChunks, replaceChunkJsPlaceholder };
|
|
87
|
+
export { createRouteHydrationScriptTags, getMatchedRouteAssets, getMatchedRouteChunks, injectBeforeHydrationEntryScript, orderHydrationScriptChunks, replaceChunkJsPlaceholder };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChunkExtractor } from "@loadable/server";
|
|
2
2
|
import { createFederatedCssLinks } from "../federatedCss.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { getMatchedRouteChunks, orderHydrationScriptChunks } from "../scriptOrder.mjs";
|
|
4
4
|
import { attributesToString, checkIsNode, hasStylesheetLink } from "../utils.mjs";
|
|
5
5
|
const extname = (uri)=>{
|
|
6
6
|
if ('string' != typeof uri || !uri.includes('.')) return '';
|
|
@@ -28,11 +28,6 @@ const readAsset = async (chunk)=>{
|
|
|
28
28
|
return fs.readFile(filepath, 'utf-8');
|
|
29
29
|
};
|
|
30
30
|
class LoadableCollector {
|
|
31
|
-
getMatchedRouteChunks() {
|
|
32
|
-
const { routeManifest, runtimeContext } = this.options;
|
|
33
|
-
if (!routeManifest) return [];
|
|
34
|
-
return getMatchedRouteAssets(runtimeContext, routeManifest).map(routeAssetToChunk);
|
|
35
|
-
}
|
|
36
31
|
collect(comopnent) {
|
|
37
32
|
const { stats, entryName } = this.options;
|
|
38
33
|
if (!stats) return comopnent;
|
|
@@ -54,7 +49,7 @@ class LoadableCollector {
|
|
|
54
49
|
]));
|
|
55
50
|
} catch (e) {}
|
|
56
51
|
const collectedChunks = extractor ? extractor.getChunkAssets(extractor.chunks) : [];
|
|
57
|
-
const matchedRouteChunks =
|
|
52
|
+
const matchedRouteChunks = getMatchedRouteChunks(options.runtimeContext, options.routeManifest, routeAssetToChunk);
|
|
58
53
|
const orderedScriptChunks = orderHydrationScriptChunks({
|
|
59
54
|
asyncEntryChunks: asyncChunks,
|
|
60
55
|
collectedChunks,
|
|
@@ -132,4 +127,4 @@ class LoadableCollector {
|
|
|
132
127
|
this.options = options;
|
|
133
128
|
}
|
|
134
129
|
}
|
|
135
|
-
export { LoadableCollector
|
|
130
|
+
export { LoadableCollector };
|
|
@@ -182,7 +182,8 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
182
182
|
component = `component_${components.length - 1}`;
|
|
183
183
|
}
|
|
184
184
|
const isClientComponent = 'nested' === route.type && Boolean(route._component) && Boolean(srcDirectory) && Boolean(internalSrcAlias) && await hasUseClientDirective(route._component, srcDirectory, internalSrcAlias);
|
|
185
|
-
const
|
|
185
|
+
const shouldHydrateRscRoute = isRscClientBundle && !isClientComponent && !route.loader && !route.data;
|
|
186
|
+
const shouldIncludeClientBundle = !isRscClientBundle || isClientComponent || shouldHydrateRscRoute;
|
|
186
187
|
const finalRoute = {
|
|
187
188
|
...route,
|
|
188
189
|
loading,
|
|
@@ -285,7 +286,7 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
285
286
|
${importLazyCode}
|
|
286
287
|
${!isRscClientBundle ? importComponentsCode : ''}
|
|
287
288
|
${importRuntimeRouterCode}
|
|
288
|
-
${
|
|
289
|
+
${rootLayoutCode}
|
|
289
290
|
${importLoadingCode}
|
|
290
291
|
${importErrorComponentsCode}
|
|
291
292
|
${importLoadersCode}
|
|
@@ -40,7 +40,7 @@ function registerRouterProvider(name, factory, options = {}) {
|
|
|
40
40
|
}
|
|
41
41
|
function resolveRouterProvider(framework, options = {}) {
|
|
42
42
|
const registry = getRegistry();
|
|
43
|
-
const name = framework ||
|
|
43
|
+
const name = framework || options.localDefault?.name || registry.defaultProvider;
|
|
44
44
|
if (void 0 === name) throw new Error('[@modern-js/runtime] No default router provider is registered. This is a bug in the runtime setup.');
|
|
45
45
|
if (void 0 !== options.localDefault && name === options.localDefault.name) return options.localDefault.factory;
|
|
46
46
|
const factory = registry.providers.get(name);
|
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { HelmetProvider } from "react-helmet-async";
|
|
4
|
-
import { InternalRuntimeContext, RuntimeContext } from "../context/index.mjs";
|
|
4
|
+
import { InternalRuntimeContext, RuntimeContext, getGlobalEnableRsc } from "../context/index.mjs";
|
|
5
5
|
import { stripRuntimeContextExtensions } from "../context/extensions.mjs";
|
|
6
6
|
import { ensureHelmetContext } from "../context/helmetContext.mjs";
|
|
7
|
+
function toRscSafeRecord(value) {
|
|
8
|
+
if (!value || 'object' != typeof value) return value;
|
|
9
|
+
return Object.fromEntries(Object.entries(value));
|
|
10
|
+
}
|
|
11
|
+
function createRscSafeSsrContext(ssrContext) {
|
|
12
|
+
if (!ssrContext) return ssrContext;
|
|
13
|
+
const { request } = ssrContext;
|
|
14
|
+
return {
|
|
15
|
+
nonce: ssrContext.nonce,
|
|
16
|
+
useJsonScript: ssrContext.useJsonScript,
|
|
17
|
+
htmlModifiers: [],
|
|
18
|
+
baseUrl: ssrContext.baseUrl,
|
|
19
|
+
request: request ? {
|
|
20
|
+
url: request.url,
|
|
21
|
+
userAgent: request.userAgent,
|
|
22
|
+
cookie: request.cookie,
|
|
23
|
+
pathname: request.pathname,
|
|
24
|
+
query: toRscSafeRecord(request.query),
|
|
25
|
+
params: toRscSafeRecord(request.params),
|
|
26
|
+
headers: toRscSafeRecord(request.headers),
|
|
27
|
+
host: request.host,
|
|
28
|
+
referer: request.referer
|
|
29
|
+
} : request,
|
|
30
|
+
mode: ssrContext.mode,
|
|
31
|
+
loaderFailureMode: ssrContext.loaderFailureMode
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createRscSafeRequestContext(ssrContext) {
|
|
35
|
+
const safeSsrContext = createRscSafeSsrContext(ssrContext);
|
|
36
|
+
return {
|
|
37
|
+
request: safeSsrContext?.request || {},
|
|
38
|
+
response: {
|
|
39
|
+
locals: ssrContext?.response?.locals || {}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
7
43
|
function wrapRuntimeContextProvider(App, contextValue) {
|
|
8
44
|
const { isBrowser, initialData, routes, context, routeManifest, routerContext, unstable_getBlockNavState, ssrContext, _internalContext, _internalRouterBaseName, ...rest } = contextValue;
|
|
9
45
|
const internalContextValue = contextValue;
|
|
@@ -16,6 +52,27 @@ function wrapRuntimeContextProvider(App, contextValue) {
|
|
|
16
52
|
...rest
|
|
17
53
|
};
|
|
18
54
|
stripRuntimeContextExtensions(runtimeContextValue);
|
|
55
|
+
if (getGlobalEnableRsc() && !isBrowser) {
|
|
56
|
+
const rscSafeRequestContext = createRscSafeRequestContext(ssrContext);
|
|
57
|
+
const rscInternalContextValue = {
|
|
58
|
+
...internalContextValue,
|
|
59
|
+
context: rscSafeRequestContext,
|
|
60
|
+
requestContext: rscSafeRequestContext,
|
|
61
|
+
ssrContext: createRscSafeSsrContext(ssrContext)
|
|
62
|
+
};
|
|
63
|
+
const rscRuntimeContextValue = {
|
|
64
|
+
...runtimeContextValue,
|
|
65
|
+
context: rscSafeRequestContext,
|
|
66
|
+
requestContext: rscSafeRequestContext
|
|
67
|
+
};
|
|
68
|
+
return /*#__PURE__*/ jsx(InternalRuntimeContext.Provider, {
|
|
69
|
+
value: rscInternalContextValue,
|
|
70
|
+
children: /*#__PURE__*/ jsx(RuntimeContext.Provider, {
|
|
71
|
+
value: rscRuntimeContextValue,
|
|
72
|
+
children: App
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
}
|
|
19
76
|
return /*#__PURE__*/ jsx(InternalRuntimeContext.Provider, {
|
|
20
77
|
value: internalContextValue,
|
|
21
78
|
children: /*#__PURE__*/ jsx(RuntimeContext.Provider, {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { attributesToString } from "./utils.mjs";
|
|
3
|
+
const escapeHtmlAttribute = (value)=>value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
3
4
|
const createFederatedCssLinks = (assets, options)=>{
|
|
4
5
|
if (void 0 === assets || 0 === assets.length) return '';
|
|
5
6
|
const seen = new Set(options.existingAssets || []);
|
|
6
7
|
const attributes = attributesToString(options.attributes || {});
|
|
7
8
|
const links = [];
|
|
8
|
-
for (const asset of assets)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
for (const asset of assets){
|
|
10
|
+
const href = escapeHtmlAttribute(asset);
|
|
11
|
+
if (!('' === asset || seen.has(asset) || options.template.includes(asset) || options.template.includes(href))) {
|
|
12
|
+
seen.add(asset);
|
|
13
|
+
links.push(`<link${attributes} href="${href}" rel="stylesheet" />`);
|
|
14
|
+
}
|
|
11
15
|
}
|
|
12
16
|
return links.join('');
|
|
13
17
|
};
|
|
@@ -3,15 +3,13 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { storage } from "@modern-js/runtime-utils/node";
|
|
4
4
|
import { getPathname, parseCookie, parseHeaders, parseQuery } from "@modern-js/runtime-utils/universal/request";
|
|
5
5
|
import react, { Fragment } from "react";
|
|
6
|
-
import { getRouterServerSnapshot } from "../../router/runtime/lifecycle.mjs";
|
|
7
|
-
import { handleRSCRedirect } from "../../router/runtime/rsc-router.mjs";
|
|
8
6
|
import { getGlobalInternalRuntimeContext, getGlobalRSCRoot } from "../context/index.mjs";
|
|
9
7
|
import { getInitialContext } from "../context/runtime.mjs";
|
|
10
8
|
import { getServerPayload } from "../context/serverPayload/index.mjs";
|
|
11
9
|
import { createRoot } from "../react/index.mjs";
|
|
12
10
|
import { CHUNK_CSS_PLACEHOLDER } from "./constants.mjs";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { applyRouterSnapshotResult, createLoaderRedirectResponse, finalizeRenderResponse } from "./requestResponse.mjs";
|
|
12
|
+
import { createRouterCleanup, finishWithRouterCleanup, runWithRouterCleanupOnError } from "./routerCleanup.mjs";
|
|
15
13
|
import { getSSRConfigByEntry, getSSRMode } from "./utils.mjs";
|
|
16
14
|
async function handleRSCRequest(request, Root, context, options, handleRequest) {
|
|
17
15
|
const serverPayload = getServerPayload();
|
|
@@ -31,29 +29,6 @@ async function handleRSCRequest(request, Root, context, options, handleRequest)
|
|
|
31
29
|
runtimeContext: context
|
|
32
30
|
});
|
|
33
31
|
}
|
|
34
|
-
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
35
|
-
const processRedirect = (headers, status, ctx)=>{
|
|
36
|
-
if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
|
|
37
|
-
return new Response(null, {
|
|
38
|
-
status,
|
|
39
|
-
headers
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
const applyRouterSnapshotResult = (context, onError)=>{
|
|
43
|
-
const routerServerSnapshot = getRouterServerSnapshot(context);
|
|
44
|
-
const routerStatusCode = routerServerSnapshot?.statusCode ?? context.routerContext?.statusCode;
|
|
45
|
-
if (routerStatusCode && 200 !== routerStatusCode) context.ssrContext?.response.status(routerStatusCode);
|
|
46
|
-
const errors = Object.values(routerServerSnapshot?.errors || context.routerContext?.errors || {});
|
|
47
|
-
if (errors.length > 0) onError(errors[0], SSRErrors.LOADER_ERROR);
|
|
48
|
-
};
|
|
49
|
-
const createLoaderRedirectResponse = (beforeRenderResult, redirectCtx)=>{
|
|
50
|
-
if (!beforeRenderResult || !isRedirectStatus(beforeRenderResult.status)) return;
|
|
51
|
-
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
52
|
-
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
53
|
-
return processRedirect(new Headers({
|
|
54
|
-
Location: redirectUrl
|
|
55
|
-
}), beforeRenderResult.status, redirectCtx);
|
|
56
|
-
};
|
|
57
32
|
const renderRequest = async (request, Root, context, options, handleRequest, enableRsc)=>{
|
|
58
33
|
if (enableRsc) return handleRSCRequest(request, Root, context, options, handleRequest);
|
|
59
34
|
return handleRequest(request, Root, {
|
|
@@ -61,17 +36,6 @@ const renderRequest = async (request, Root, context, options, handleRequest, ena
|
|
|
61
36
|
runtimeContext: context
|
|
62
37
|
});
|
|
63
38
|
};
|
|
64
|
-
const finalizeRenderResponse = (response, responseProxy, redirectCtx, routerCleanup)=>{
|
|
65
|
-
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
66
|
-
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
67
|
-
response.headers.set(key, value);
|
|
68
|
-
});
|
|
69
|
-
if (-1 !== responseProxy.status) return routerCleanup.deferUntilBodyDone(new Response(response.body, {
|
|
70
|
-
status: responseProxy.status,
|
|
71
|
-
headers: response.headers
|
|
72
|
-
}));
|
|
73
|
-
return routerCleanup.deferUntilBodyDone(response);
|
|
74
|
-
};
|
|
75
39
|
function createSSRContext(request, options) {
|
|
76
40
|
const { config, loaderContext, onError, onTiming, locals, resource, params, responseProxy, reporter } = options;
|
|
77
41
|
const { nonce, useJsonScript } = config;
|
|
@@ -167,20 +131,26 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
167
131
|
isRSCNavigation: 'true' === request.headers.get('x-rsc-tree'),
|
|
168
132
|
basename: ssrContext.baseUrl || '/'
|
|
169
133
|
};
|
|
170
|
-
|
|
171
|
-
|
|
134
|
+
const routerCleanup = createRouterCleanup(context, options.onError);
|
|
135
|
+
const beforeRenderResult = await runWithRouterCleanupOnError(routerCleanup, ()=>runBeforeRender(context));
|
|
136
|
+
await runWithRouterCleanupOnError(routerCleanup, ()=>{
|
|
172
137
|
applyRouterSnapshotResult(context, options.onError);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
});
|
|
139
|
+
if ("u" > typeof Response) {
|
|
140
|
+
const redirectResponse = await runWithRouterCleanupOnError(routerCleanup, ()=>createLoaderRedirectResponse(beforeRenderResult, redirectCtx));
|
|
141
|
+
if (redirectResponse) {
|
|
142
|
+
await routerCleanup.run();
|
|
143
|
+
return redirectResponse;
|
|
176
144
|
}
|
|
145
|
+
}
|
|
146
|
+
await runWithRouterCleanupOnError(routerCleanup, ()=>{
|
|
177
147
|
if (!createRequestOptions?.enableRsc) {
|
|
178
148
|
const { htmlTemplate } = options.resource;
|
|
179
149
|
options.resource.htmlTemplate = htmlTemplate.replace('</head>', `${CHUNK_CSS_PLACEHOLDER}</head>`);
|
|
180
150
|
}
|
|
181
|
-
const response = await renderRequest(request, Root, context, options, handleRequest, !!createRequestOptions?.enableRsc);
|
|
182
|
-
return finalizeRenderResponse(response, responseProxy, redirectCtx, routerCleanup);
|
|
183
151
|
});
|
|
152
|
+
const response = await runWithRouterCleanupOnError(routerCleanup, ()=>renderRequest(request, Root, context, options, handleRequest, !!createRequestOptions?.enableRsc));
|
|
153
|
+
return finishWithRouterCleanup(routerCleanup, ()=>finalizeRenderResponse(response, responseProxy, redirectCtx, routerCleanup));
|
|
184
154
|
});
|
|
185
155
|
};
|
|
186
156
|
return requestHandler;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { getRouterServerSnapshot } from "../../router/runtime/lifecycle.mjs";
|
|
3
|
+
import { handleRSCRedirect } from "../../router/runtime/rsc-router.mjs";
|
|
4
|
+
import { SSRErrors } from "./tracer.mjs";
|
|
5
|
+
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
6
|
+
const isNullBodyStatus = (status)=>204 === status || 205 === status || 304 === status;
|
|
7
|
+
const processRedirect = (headers, status, ctx)=>{
|
|
8
|
+
if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
|
|
9
|
+
return new Response(null, {
|
|
10
|
+
status,
|
|
11
|
+
headers
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const applyRouterSnapshotResult = (context, onError)=>{
|
|
15
|
+
const routerServerSnapshot = getRouterServerSnapshot(context);
|
|
16
|
+
const routerStatusCode = routerServerSnapshot?.statusCode ?? context.routerContext?.statusCode;
|
|
17
|
+
if (routerStatusCode && 200 !== routerStatusCode) context.ssrContext?.response.status(routerStatusCode);
|
|
18
|
+
const errors = Object.values(routerServerSnapshot?.errors || context.routerContext?.errors || {});
|
|
19
|
+
if (errors.length > 0) onError(errors[0], SSRErrors.LOADER_ERROR);
|
|
20
|
+
};
|
|
21
|
+
const createLoaderRedirectResponse = (beforeRenderResult, redirectCtx)=>{
|
|
22
|
+
if (!beforeRenderResult || !isRedirectStatus(beforeRenderResult.status)) return;
|
|
23
|
+
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
24
|
+
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
25
|
+
return processRedirect(new Headers({
|
|
26
|
+
Location: redirectUrl
|
|
27
|
+
}), beforeRenderResult.status, redirectCtx);
|
|
28
|
+
};
|
|
29
|
+
const finalizeRenderResponse = (response, responseProxy, redirectCtx, routerCleanup)=>{
|
|
30
|
+
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
31
|
+
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
32
|
+
response.headers.set(key, value);
|
|
33
|
+
});
|
|
34
|
+
if (-1 !== responseProxy.status) return routerCleanup.deferUntilBodyDone(new Response(isNullBodyStatus(responseProxy.status) ? null : response.body, {
|
|
35
|
+
status: responseProxy.status,
|
|
36
|
+
headers: response.headers
|
|
37
|
+
}));
|
|
38
|
+
return routerCleanup.deferUntilBodyDone(response);
|
|
39
|
+
};
|
|
40
|
+
export { applyRouterSnapshotResult, createLoaderRedirectResponse, finalizeRenderResponse };
|
|
@@ -9,6 +9,21 @@ async function withRouterCleanup(runtimeContext, onError, callback) {
|
|
|
9
9
|
if (!routerCleanup.deferred) await routerCleanup.run();
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
async function runWithRouterCleanupOnError(routerCleanup, callback) {
|
|
13
|
+
try {
|
|
14
|
+
return await callback();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
await routerCleanup.run();
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function finishWithRouterCleanup(routerCleanup, callback) {
|
|
21
|
+
try {
|
|
22
|
+
return await callback();
|
|
23
|
+
} finally{
|
|
24
|
+
if (!routerCleanup.deferred) await routerCleanup.run();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
function createRouterCleanup(runtimeContext, onError) {
|
|
13
28
|
let deferred = false;
|
|
14
29
|
let finished = false;
|
|
@@ -64,4 +79,4 @@ function createRouterCleanup(runtimeContext, onError) {
|
|
|
64
79
|
deferUntilBodyDone
|
|
65
80
|
};
|
|
66
81
|
}
|
|
67
|
-
export { ROUTER_CLEANUP_ERROR, createRouterCleanup, withRouterCleanup };
|
|
82
|
+
export { ROUTER_CLEANUP_ERROR, createRouterCleanup, finishWithRouterCleanup, runWithRouterCleanupOnError, withRouterCleanup };
|
|
@@ -43,6 +43,10 @@ function getMatchedRouteAssets(runtimeContext, routeManifest) {
|
|
|
43
43
|
const matchedRouteIds = getRouterMatchedRouteIds(runtimeContext) ?? [];
|
|
44
44
|
return matchedRouteIds.flatMap((routeId)=>routeAssets[routeId]?.assets ?? []);
|
|
45
45
|
}
|
|
46
|
+
function getMatchedRouteChunks(runtimeContext, routeManifest, routeAssetToChunk) {
|
|
47
|
+
if (!routeManifest) return [];
|
|
48
|
+
return getMatchedRouteAssets(runtimeContext, routeManifest).map(routeAssetToChunk);
|
|
49
|
+
}
|
|
46
50
|
const orderHydrationScriptChunks = ({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName })=>{
|
|
47
51
|
const asyncEntryScriptChunks = [];
|
|
48
52
|
const asyncEntryDependencyChunks = [];
|
|
@@ -73,13 +77,12 @@ function createRouteHydrationScriptTags(runtimeContext, entryName, options = {})
|
|
|
73
77
|
const { nonce, template } = options;
|
|
74
78
|
const routeAssets = getRouteAssets(runtimeContext);
|
|
75
79
|
if (void 0 === routeAssets) return '';
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
...
|
|
79
|
-
routeAssets[`async-${entryName}`]
|
|
80
|
-
].filter((
|
|
81
|
-
const jsAssets = Array.from(new Set(assetEntries.flatMap((entry)=>(entry.assets ?? []).filter((asset)=>asset.endsWith('.js')))));
|
|
80
|
+
const existingScriptSrcs = void 0 === template ? new Set() : new Set(getScriptTags(template).map(({ src })=>src));
|
|
81
|
+
const jsAssets = Array.from(new Set([
|
|
82
|
+
...getMatchedRouteAssets(runtimeContext),
|
|
83
|
+
...routeAssets[`async-${entryName}`]?.assets ?? []
|
|
84
|
+
].filter((asset)=>asset.endsWith('.js'))));
|
|
82
85
|
const nonceAttr = void 0 === nonce || '' === nonce ? '' : ` nonce="${nonce}"`;
|
|
83
|
-
return jsAssets.filter((asset)
|
|
86
|
+
return jsAssets.filter((asset)=>!existingScriptSrcs.has(asset)).map((asset)=>`<script src=${asset}${nonceAttr}></script>`).join(' ');
|
|
84
87
|
}
|
|
85
|
-
export { createRouteHydrationScriptTags, getMatchedRouteAssets, injectBeforeHydrationEntryScript, orderHydrationScriptChunks, replaceChunkJsPlaceholder };
|
|
88
|
+
export { createRouteHydrationScriptTags, getMatchedRouteAssets, getMatchedRouteChunks, injectBeforeHydrationEntryScript, orderHydrationScriptChunks, replaceChunkJsPlaceholder };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { ChunkExtractor } from "@loadable/server";
|
|
3
3
|
import { createFederatedCssLinks } from "../federatedCss.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { getMatchedRouteChunks, orderHydrationScriptChunks } from "../scriptOrder.mjs";
|
|
5
5
|
import { attributesToString, checkIsNode, hasStylesheetLink } from "../utils.mjs";
|
|
6
6
|
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
7
7
|
import { dirname as __rspack_dirname } from "node:path";
|
|
@@ -32,11 +32,6 @@ const readAsset = async (chunk)=>{
|
|
|
32
32
|
return fs.readFile(filepath, 'utf-8');
|
|
33
33
|
};
|
|
34
34
|
class LoadableCollector {
|
|
35
|
-
getMatchedRouteChunks() {
|
|
36
|
-
const { routeManifest, runtimeContext } = this.options;
|
|
37
|
-
if (!routeManifest) return [];
|
|
38
|
-
return getMatchedRouteAssets(runtimeContext, routeManifest).map(routeAssetToChunk);
|
|
39
|
-
}
|
|
40
35
|
collect(comopnent) {
|
|
41
36
|
const { stats, entryName } = this.options;
|
|
42
37
|
if (!stats) return comopnent;
|
|
@@ -58,7 +53,7 @@ class LoadableCollector {
|
|
|
58
53
|
]));
|
|
59
54
|
} catch (e) {}
|
|
60
55
|
const collectedChunks = extractor ? extractor.getChunkAssets(extractor.chunks) : [];
|
|
61
|
-
const matchedRouteChunks =
|
|
56
|
+
const matchedRouteChunks = getMatchedRouteChunks(options.runtimeContext, options.routeManifest, routeAssetToChunk);
|
|
62
57
|
const orderedScriptChunks = orderHydrationScriptChunks({
|
|
63
58
|
asyncEntryChunks: asyncChunks,
|
|
64
59
|
collectedChunks,
|
|
@@ -136,4 +131,4 @@ class LoadableCollector {
|
|
|
136
131
|
this.options = options;
|
|
137
132
|
}
|
|
138
133
|
}
|
|
139
|
-
export { LoadableCollector
|
|
134
|
+
export { LoadableCollector };
|
|
@@ -184,7 +184,8 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
184
184
|
component = `component_${components.length - 1}`;
|
|
185
185
|
}
|
|
186
186
|
const isClientComponent = 'nested' === route.type && Boolean(route._component) && Boolean(srcDirectory) && Boolean(internalSrcAlias) && await hasUseClientDirective(route._component, srcDirectory, internalSrcAlias);
|
|
187
|
-
const
|
|
187
|
+
const shouldHydrateRscRoute = isRscClientBundle && !isClientComponent && !route.loader && !route.data;
|
|
188
|
+
const shouldIncludeClientBundle = !isRscClientBundle || isClientComponent || shouldHydrateRscRoute;
|
|
188
189
|
const finalRoute = {
|
|
189
190
|
...route,
|
|
190
191
|
loading,
|
|
@@ -287,7 +288,7 @@ const fileSystemRoutes = async ({ metaName, routes, ssrMode, nestedRoutesEntry,
|
|
|
287
288
|
${importLazyCode}
|
|
288
289
|
${!isRscClientBundle ? importComponentsCode : ''}
|
|
289
290
|
${importRuntimeRouterCode}
|
|
290
|
-
${
|
|
291
|
+
${rootLayoutCode}
|
|
291
292
|
${importLoadingCode}
|
|
292
293
|
${importErrorComponentsCode}
|
|
293
294
|
${importLoadersCode}
|
|
@@ -41,7 +41,7 @@ function registerRouterProvider(name, factory, options = {}) {
|
|
|
41
41
|
}
|
|
42
42
|
function resolveRouterProvider(framework, options = {}) {
|
|
43
43
|
const registry = getRegistry();
|
|
44
|
-
const name = framework ||
|
|
44
|
+
const name = framework || options.localDefault?.name || registry.defaultProvider;
|
|
45
45
|
if (void 0 === name) throw new Error('[@modern-js/runtime] No default router provider is registered. This is a bug in the runtime setup.');
|
|
46
46
|
if (void 0 !== options.localDefault && name === options.localDefault.name) return options.localDefault.factory;
|
|
47
47
|
const factory = registry.providers.get(name);
|
|
@@ -23,7 +23,7 @@ export { DefaultNotFound } from '../../router/runtime/DefaultNotFound';
|
|
|
23
23
|
export { modifyRoutes, onAfterCreateRouter, onAfterHydrateRouter, onBeforeCreateRouter, onBeforeCreateRoutes, onBeforeHydrateRouter, type RouterExtendsHooks, } from '../../router/runtime/hooks';
|
|
24
24
|
export { applyRouterRuntimeState, applyRouterServerPrepareResult, cleanupRouterRuntimeState, createRouterRuntimeState, createRouterServerSnapshot, getRouterHydrationScripts, getRouterMatchedRouteIds, getRouterRuntimeState, getRouterServerSnapshot, type RouterLifecycleContext, type RouterLifecyclePhase, } from '../../router/runtime/lifecycle';
|
|
25
25
|
export { type RouterProviderFactory, type RouterProviderPlugin, registerRouterProvider, resolveRouterProvider, routerProviderRegistryHooks, } from '../../router/runtime/provider';
|
|
26
|
-
export type { BuiltInRouterFramework, InternalRouterRuntimeState, InternalRouterServerSnapshot, RouterFramework, RouterRouteMatchSnapshot, RouterServerPrepareResult, } from '../../router/runtime/types';
|
|
26
|
+
export type { BuiltInRouterFramework, InternalRouterRuntimeState, InternalRouterServerSnapshot, LoaderFunction, LoaderFunctionArgs, ModernRoute, RouterFramework, RouterRouteMatchSnapshot, RouterServerPrepareResult, } from '../../router/runtime/types';
|
|
27
27
|
export { createRuntimeContextExtension, type RuntimeContextExtension, } from './extensions';
|
|
28
28
|
export { getInitialContext, InternalRuntimeContext, RuntimeContext, type TInternalRuntimeContext, type TRuntimeContext, } from './runtime';
|
|
29
29
|
export type { PayloadRoute, ServerPayload } from './serverPayload/index';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OnError } from '@modern-js/app-tools';
|
|
2
|
+
import type { TInternalRuntimeContext } from '../context';
|
|
3
|
+
import type { RouterCleanup } from './routerCleanup';
|
|
4
|
+
export type ResponseProxy = {
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
status: number;
|
|
7
|
+
};
|
|
8
|
+
export type RedirectContext = {
|
|
9
|
+
enableRsc: boolean;
|
|
10
|
+
isRSCNavigation: boolean;
|
|
11
|
+
basename: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const applyRouterSnapshotResult: (context: TInternalRuntimeContext, onError: OnError) => void;
|
|
14
|
+
export declare const createLoaderRedirectResponse: (beforeRenderResult: Response | undefined, redirectCtx: RedirectContext) => Response | undefined;
|
|
15
|
+
export declare const finalizeRenderResponse: (response: Response, responseProxy: ResponseProxy, redirectCtx: RedirectContext, routerCleanup: RouterCleanup) => Response;
|
|
@@ -25,4 +25,6 @@ export type RouterCleanup = {
|
|
|
25
25
|
deferUntilBodyDone: (response: Response) => Response;
|
|
26
26
|
};
|
|
27
27
|
export declare function withRouterCleanup<T>(runtimeContext: TInternalRuntimeContext, onError: OnError, callback: (routerCleanup: RouterCleanup) => Promise<T>): Promise<T>;
|
|
28
|
+
export declare function runWithRouterCleanupOnError<T>(routerCleanup: RouterCleanup, callback: () => Promise<T> | T): Promise<T>;
|
|
29
|
+
export declare function finishWithRouterCleanup<T>(routerCleanup: RouterCleanup, callback: () => Promise<T> | T): Promise<T>;
|
|
28
30
|
export declare function createRouterCleanup(runtimeContext: TInternalRuntimeContext, onError: OnError): RouterCleanup;
|
|
@@ -10,6 +10,7 @@ type ScriptChunkLike = {
|
|
|
10
10
|
url?: string;
|
|
11
11
|
};
|
|
12
12
|
export declare function getMatchedRouteAssets(runtimeContext: TInternalRuntimeContext, routeManifest?: RouteManifestLike): string[];
|
|
13
|
+
export declare function getMatchedRouteChunks<T>(runtimeContext: TInternalRuntimeContext, routeManifest: RouteManifestLike | undefined, routeAssetToChunk: (asset: string) => T): T[];
|
|
13
14
|
export declare const orderHydrationScriptChunks: <T extends ScriptChunkLike>({ asyncEntryChunks, collectedChunks, matchedRouteChunks, entryName, }: {
|
|
14
15
|
asyncEntryChunks: T[];
|
|
15
16
|
collectedChunks: T[];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { TInternalRuntimeContext } from '../../context';
|
|
3
3
|
import type { ChunkSet, Collector } from './types';
|
|
4
|
-
export { orderHydrationScriptChunks } from '../scriptOrder';
|
|
5
4
|
declare module '@loadable/server' {
|
|
6
5
|
interface ChunkExtractor {
|
|
7
6
|
chunks: string[];
|
|
@@ -30,7 +29,6 @@ export declare class LoadableCollector implements Collector {
|
|
|
30
29
|
private options;
|
|
31
30
|
private extractor?;
|
|
32
31
|
constructor(options: LoadableCollectorOptions);
|
|
33
|
-
private getMatchedRouteChunks;
|
|
34
32
|
collect(comopnent: ReactElement): ReactElement;
|
|
35
33
|
effect(): Promise<void>;
|
|
36
34
|
private emitLoadableScripts;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.5.0-ultramodern.
|
|
20
|
+
"version": "3.5.0-ultramodern.32",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -240,12 +240,12 @@
|
|
|
240
240
|
"react-helmet": "^6.1.0",
|
|
241
241
|
"react-helmet-async": "3.0.0",
|
|
242
242
|
"react-is": "^19.2.7",
|
|
243
|
-
"@modern-js/plugin
|
|
244
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.
|
|
245
|
-
"@modern-js/
|
|
246
|
-
"@modern-js/
|
|
247
|
-
"@modern-js/
|
|
248
|
-
"@modern-js/
|
|
243
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.32",
|
|
244
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.32",
|
|
245
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.32",
|
|
246
|
+
"@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.5.0-ultramodern.32",
|
|
247
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.32",
|
|
248
|
+
"@modern-js/render": "npm:@bleedingdev/modern-js-render@3.5.0-ultramodern.32"
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"react": "^19.2.7",
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
"react-dom": "^19.2.7",
|
|
266
266
|
"ts-node": "^10.9.2",
|
|
267
267
|
"typescript": "^6.0.3",
|
|
268
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.
|
|
268
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.32",
|
|
269
269
|
"@scripts/rstest-config": "2.66.0"
|
|
270
270
|
},
|
|
271
271
|
"sideEffects": false,
|