@modern-js/runtime 3.0.1 → 3.0.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.
- package/dist/cjs/cli/index.js +4 -1
- package/dist/cjs/cli/template.js +6 -1
- package/dist/cjs/cli/template.server.js +5 -26
- package/dist/cjs/core/server/requestHandler.js +21 -15
- package/dist/cjs/core/server/stream/createReadableStream.worker.js +1 -2
- package/dist/cjs/exports/head.js +12 -27
- package/dist/cjs/router/runtime/CSSLinks.js +46 -0
- package/dist/cjs/router/runtime/rsc-router.js +32 -8
- package/dist/esm/cli/index.mjs +4 -1
- package/dist/esm/cli/template.mjs +6 -1
- package/dist/esm/cli/template.server.mjs +5 -26
- package/dist/esm/core/server/requestHandler.mjs +21 -15
- package/dist/esm/core/server/stream/createReadableStream.worker.mjs +1 -2
- package/dist/esm/exports/head.mjs +3 -3
- package/dist/esm/router/runtime/CSSLinks.mjs +12 -0
- package/dist/esm/router/runtime/rsc-router.mjs +32 -8
- package/dist/esm-node/cli/index.mjs +4 -1
- package/dist/esm-node/cli/template.mjs +6 -1
- package/dist/esm-node/cli/template.server.mjs +5 -26
- package/dist/esm-node/core/server/requestHandler.mjs +21 -15
- package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +1 -2
- package/dist/esm-node/exports/head.mjs +3 -3
- package/dist/esm-node/router/runtime/CSSLinks.mjs +13 -0
- package/dist/esm-node/router/runtime/rsc-router.mjs +32 -8
- package/dist/types/core/context/serverPayload/index.server.d.ts +1 -0
- package/dist/types/exports/head.d.ts +2 -1
- package/dist/types/router/runtime/CSSLinks.d.ts +9 -0
- package/package.json +18 -11
package/dist/cjs/cli/index.js
CHANGED
|
@@ -103,7 +103,10 @@ const runtimePlugin = (params)=>({
|
|
|
103
103
|
source: {
|
|
104
104
|
globalVars: {
|
|
105
105
|
'process.env.IS_REACT18': process.env.IS_REACT18
|
|
106
|
-
}
|
|
106
|
+
},
|
|
107
|
+
include: [
|
|
108
|
+
new RegExp(`[\\\\/]node_modules[\\\\/]@${metaName}[\\\\/]runtime[\\\\/].*[\\\\/]head\\.[jt]sx?$`)
|
|
109
|
+
]
|
|
107
110
|
},
|
|
108
111
|
tools: {
|
|
109
112
|
bundlerChain: (chain)=>{
|
package/dist/cjs/cli/template.js
CHANGED
|
@@ -60,7 +60,9 @@ const genRenderCode = ({ srcDirectory, internalSrcAlias, metaName, entry, custom
|
|
|
60
60
|
return `import { createRoot } from '@${metaName}/runtime/react';
|
|
61
61
|
import { render } from '@${metaName}/runtime/browser';
|
|
62
62
|
|
|
63
|
-
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
63
|
+
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer, setServerCallback } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
64
|
+
|
|
65
|
+
${enableRsc ? "setServerCallback(callServer);" : ''}
|
|
64
66
|
|
|
65
67
|
${enableRsc ? `const data = createFromReadableStream(rscStream, {
|
|
66
68
|
callServer: callServer,
|
|
@@ -85,9 +87,12 @@ const entryForCSRWithRSC = ({ metaName, entryName, urlPath = '/', mountId = 'roo
|
|
|
85
87
|
isRedirectResponse,
|
|
86
88
|
rscStream,
|
|
87
89
|
callServer,
|
|
90
|
+
setServerCallback,
|
|
88
91
|
createFromReadableStream
|
|
89
92
|
} from '@${metaName}/runtime/rsc/client';
|
|
90
93
|
|
|
94
|
+
setServerCallback(callServer);
|
|
95
|
+
|
|
91
96
|
const handleRedirectResponse = (res: Response) => {
|
|
92
97
|
const { headers } = res;
|
|
93
98
|
const location = headers.get('X-Modernjs-Redirect');
|
|
@@ -82,8 +82,7 @@ export const requestHandler = createRequestHandler(handleRequest, {
|
|
|
82
82
|
const handleRSCRequest = async (request, ServerRoot, options) => {
|
|
83
83
|
const { serverPayload } = options;
|
|
84
84
|
const stream = renderRsc({
|
|
85
|
-
element: options.rscRoot
|
|
86
|
-
clientManifest: options.rscClientManifest!,
|
|
85
|
+
element: options.rscRoot
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
return new Response(stream);
|
|
@@ -106,32 +105,13 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
106
105
|
import {
|
|
107
106
|
createRequestHandler,
|
|
108
107
|
} from '@${metaName}/runtime/ssr/server';
|
|
109
|
-
import {
|
|
108
|
+
import { renderCSRWithRSC, renderRsc } from '@${metaName}/runtime/rsc/server';
|
|
110
109
|
export { handleAction } from '@${metaName}/runtime/rsc/server';
|
|
111
110
|
|
|
112
111
|
const handleCSRRender = async (request, ServerRoot, options) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
const stream = new ReadableStream({
|
|
118
|
-
start(controller) {
|
|
119
|
-
const encoder = new TextEncoder();
|
|
120
|
-
|
|
121
|
-
controller.enqueue(encoder.encode(options.html));
|
|
122
|
-
|
|
123
|
-
processRSCStream(rscPayloadStream, controller, encoder)
|
|
124
|
-
.catch(err => {
|
|
125
|
-
controller.error(err);
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
return new Response(stream, {
|
|
131
|
-
status: 200,
|
|
132
|
-
headers: new Headers({
|
|
133
|
-
'content-type': 'text/html; charset=UTF-8',
|
|
134
|
-
}),
|
|
112
|
+
return renderCSRWithRSC({
|
|
113
|
+
html: options.html,
|
|
114
|
+
rscRoot: options.rscRoot,
|
|
135
115
|
});
|
|
136
116
|
}
|
|
137
117
|
|
|
@@ -142,7 +122,6 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
142
122
|
const handleRequest = async (request, ServerRoot, options) => {
|
|
143
123
|
const stream = renderRsc({
|
|
144
124
|
element: options.rscRoot,
|
|
145
|
-
clientManifest: options.rscClientManifest!,
|
|
146
125
|
});
|
|
147
126
|
|
|
148
127
|
return new Response(stream);
|
|
@@ -40,6 +40,7 @@ const node_namespaceObject = require("@modern-js/runtime-utils/node");
|
|
|
40
40
|
const request_namespaceObject = require("@modern-js/runtime-utils/universal/request");
|
|
41
41
|
const external_react_namespaceObject = require("react");
|
|
42
42
|
var external_react_default = /*#__PURE__*/ __webpack_require__.n(external_react_namespaceObject);
|
|
43
|
+
const rsc_router_js_namespaceObject = require("../../router/runtime/rsc-router.js");
|
|
43
44
|
const index_js_namespaceObject = require("../context/index.js");
|
|
44
45
|
const runtime_js_namespaceObject = require("../context/runtime.js");
|
|
45
46
|
const serverPayload_index_js_namespaceObject = require("../context/serverPayload/index.js");
|
|
@@ -65,6 +66,14 @@ async function handleRSCRequest(request, Root, context, options, handleRequest)
|
|
|
65
66
|
runtimeContext: context
|
|
66
67
|
});
|
|
67
68
|
}
|
|
69
|
+
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
70
|
+
const processRedirect = (headers, status, ctx)=>{
|
|
71
|
+
if (ctx.enableRsc && ctx.isRSCNavigation) return (0, rsc_router_js_namespaceObject.handleRSCRedirect)(headers, ctx.basename, status);
|
|
72
|
+
return new Response(null, {
|
|
73
|
+
status,
|
|
74
|
+
headers
|
|
75
|
+
});
|
|
76
|
+
};
|
|
68
77
|
function createSSRContext(request, options) {
|
|
69
78
|
const { config, loaderContext, onError, onTiming, locals, resource, params, responseProxy, reporter } = options;
|
|
70
79
|
const { nonce, useJsonScript } = config;
|
|
@@ -155,26 +164,22 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
155
164
|
ssrContext,
|
|
156
165
|
isBrowser: false
|
|
157
166
|
});
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const { ssrContext } = context;
|
|
163
|
-
if (ssrContext) return new Response(null, {
|
|
164
|
-
status,
|
|
165
|
-
headers: {
|
|
166
|
-
Location: redirectUrl
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
167
|
+
const redirectCtx = {
|
|
168
|
+
enableRsc: !!createRequestOptions?.enableRsc,
|
|
169
|
+
isRSCNavigation: 'true' === request.headers.get('x-rsc-tree'),
|
|
170
|
+
basename: ssrContext.baseUrl || '/'
|
|
170
171
|
};
|
|
171
172
|
const beforeRenderResult = await runBeforeRender(context);
|
|
172
173
|
if (context.routerContext?.statusCode && context.routerContext?.statusCode !== 200) context.ssrContext?.response.status(context.routerContext?.statusCode);
|
|
173
174
|
const errors = Object.values(context.routerContext?.errors || {});
|
|
174
175
|
if (errors.length > 0) options.onError(errors[0], external_tracer_js_namespaceObject.SSRErrors.LOADER_ERROR);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
if ("u" > typeof Response && beforeRenderResult instanceof Response && isRedirectStatus(beforeRenderResult.status)) {
|
|
177
|
+
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
178
|
+
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
179
|
+
return processRedirect(new Headers({
|
|
180
|
+
Location: redirectUrl
|
|
181
|
+
}), beforeRenderResult.status, redirectCtx);
|
|
182
|
+
}
|
|
178
183
|
if (!createRequestOptions?.enableRsc) {
|
|
179
184
|
const { htmlTemplate } = options.resource;
|
|
180
185
|
options.resource.htmlTemplate = htmlTemplate.replace('</head>', `${external_constants_js_namespaceObject.CHUNK_CSS_PLACEHOLDER}</head>`);
|
|
@@ -184,6 +189,7 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
184
189
|
...options,
|
|
185
190
|
runtimeContext: context
|
|
186
191
|
});
|
|
192
|
+
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
187
193
|
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
188
194
|
response.headers.set(key, value);
|
|
189
195
|
});
|
|
@@ -48,10 +48,9 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
48
48
|
try {
|
|
49
49
|
const readableOriginal = await (0, ssr_namespaceObject.renderSSRStream)(rootElement, {
|
|
50
50
|
request,
|
|
51
|
-
clientManifest: options.rscClientManifest,
|
|
52
|
-
ssrManifest: options.rscSSRManifest,
|
|
53
51
|
nonce: config.nonce,
|
|
54
52
|
rscRoot: rscRoot,
|
|
53
|
+
routes: runtimeContext.routes,
|
|
55
54
|
onError (error) {
|
|
56
55
|
options.onError?.(error);
|
|
57
56
|
}
|
package/dist/cjs/exports/head.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
module.exports = require("react-helmet");
|
|
5
|
-
}
|
|
6
|
-
};
|
|
7
|
-
var __webpack_module_cache__ = {};
|
|
8
|
-
function __webpack_require__(moduleId) {
|
|
9
|
-
var cachedModule = __webpack_module_cache__[moduleId];
|
|
10
|
-
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
11
|
-
var module = __webpack_module_cache__[moduleId] = {
|
|
12
|
-
exports: {}
|
|
13
|
-
};
|
|
14
|
-
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
15
|
-
return module.exports;
|
|
16
|
-
}
|
|
2
|
+
"use client";
|
|
3
|
+
var __webpack_require__ = {};
|
|
17
4
|
(()=>{
|
|
18
5
|
__webpack_require__.n = (module)=>{
|
|
19
6
|
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
@@ -45,20 +32,18 @@ function __webpack_require__(moduleId) {
|
|
|
45
32
|
};
|
|
46
33
|
})();
|
|
47
34
|
var __webpack_exports__ = {};
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
58
|
-
const __rspack_default_export = react_helmet__rspack_import_0_default();
|
|
59
|
-
})();
|
|
35
|
+
__webpack_require__.r(__webpack_exports__);
|
|
36
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
37
|
+
Helmet: ()=>external_react_helmet_namespaceObject.Helmet,
|
|
38
|
+
default: ()=>head
|
|
39
|
+
});
|
|
40
|
+
const external_react_helmet_namespaceObject = require("react-helmet");
|
|
41
|
+
var external_react_helmet_default = /*#__PURE__*/ __webpack_require__.n(external_react_helmet_namespaceObject);
|
|
42
|
+
const head = external_react_helmet_default();
|
|
43
|
+
exports.Helmet = __webpack_exports__.Helmet;
|
|
60
44
|
exports["default"] = __webpack_exports__["default"];
|
|
61
45
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
46
|
+
"Helmet",
|
|
62
47
|
"default"
|
|
63
48
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
64
49
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
CSSLinks: ()=>CSSLinks
|
|
28
|
+
});
|
|
29
|
+
const jsx_runtime_namespaceObject = require("react/jsx-runtime");
|
|
30
|
+
require("react");
|
|
31
|
+
function CSSLinks({ cssFiles }) {
|
|
32
|
+
if (0 === cssFiles.length) return null;
|
|
33
|
+
return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(jsx_runtime_namespaceObject.Fragment, {
|
|
34
|
+
children: cssFiles.map((css)=>/*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("link", {
|
|
35
|
+
href: css,
|
|
36
|
+
rel: "stylesheet"
|
|
37
|
+
}, css))
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
exports.CSSLinks = __webpack_exports__.CSSLinks;
|
|
41
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
42
|
+
"CSSLinks"
|
|
43
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
44
|
+
Object.defineProperty(exports, '__esModule', {
|
|
45
|
+
value: true
|
|
46
|
+
});
|
|
@@ -44,11 +44,27 @@ const client_namespaceObject = require("@modern-js/render/client");
|
|
|
44
44
|
const router_namespaceObject = require("@modern-js/runtime-utils/router");
|
|
45
45
|
const external_react_namespaceObject = require("react");
|
|
46
46
|
var external_react_default = /*#__PURE__*/ __webpack_require__.n(external_react_namespaceObject);
|
|
47
|
+
const external_CSSLinks_js_namespaceObject = require("./CSSLinks.js");
|
|
47
48
|
const safeUse = (promise)=>{
|
|
48
49
|
if ('function' == typeof external_react_default().use) return external_react_default().use(promise);
|
|
49
50
|
return null;
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
+
function collectCssFilesFromRoutes(matches, routes) {
|
|
53
|
+
const cssFiles = [];
|
|
54
|
+
for (const match of matches){
|
|
55
|
+
const route = findRouteInTree(routes, match.route.id);
|
|
56
|
+
if (!route) continue;
|
|
57
|
+
const component = route.Component;
|
|
58
|
+
if (component && 'function' == typeof component && 'entryCssFiles' in component) {
|
|
59
|
+
const css = component.entryCssFiles;
|
|
60
|
+
if (Array.isArray(css)) cssFiles.push(...css);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Array.from(new Set(cssFiles));
|
|
64
|
+
}
|
|
65
|
+
const createServerPayload = (routerContext, routes)=>{
|
|
66
|
+
const cssFiles = collectCssFilesFromRoutes(routerContext.matches, routes);
|
|
67
|
+
return {
|
|
52
68
|
type: 'render',
|
|
53
69
|
actionData: routerContext.actionData,
|
|
54
70
|
errors: routerContext.errors,
|
|
@@ -60,7 +76,7 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
60
76
|
let processedElement;
|
|
61
77
|
if (element) {
|
|
62
78
|
const ElementComponent = element.type;
|
|
63
|
-
|
|
79
|
+
const elementProps = {
|
|
64
80
|
loaderData: routerContext?.loaderData?.[match.route.id],
|
|
65
81
|
actionData: routerContext?.actionData?.[match.route.id],
|
|
66
82
|
params: match.params,
|
|
@@ -74,7 +90,12 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
74
90
|
handle: route.handle
|
|
75
91
|
};
|
|
76
92
|
})
|
|
77
|
-
}
|
|
93
|
+
};
|
|
94
|
+
const routeElement = /*#__PURE__*/ external_react_default().createElement(ElementComponent, elementProps);
|
|
95
|
+
const isLeafRoute = index === routerContext.matches.length - 1;
|
|
96
|
+
processedElement = isLeafRoute && cssFiles.length > 0 ? /*#__PURE__*/ external_react_default().createElement(external_react_default().Fragment, null, /*#__PURE__*/ external_react_default().createElement(external_CSSLinks_js_namespaceObject.CSSLinks, {
|
|
97
|
+
cssFiles
|
|
98
|
+
}), routeElement) : routeElement;
|
|
78
99
|
}
|
|
79
100
|
return {
|
|
80
101
|
element: processedElement,
|
|
@@ -93,7 +114,8 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
93
114
|
pathnameBase: match.pathnameBase
|
|
94
115
|
};
|
|
95
116
|
})
|
|
96
|
-
}
|
|
117
|
+
};
|
|
118
|
+
};
|
|
97
119
|
const handleRSCRedirect = (headers, basename, status)=>{
|
|
98
120
|
const newHeaders = new Headers(headers);
|
|
99
121
|
let redirectUrl = headers.get('Location');
|
|
@@ -202,17 +224,19 @@ const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>
|
|
|
202
224
|
clientLoaderResults.forEach(({ routeId, result })=>{
|
|
203
225
|
results[routeId] = result;
|
|
204
226
|
});
|
|
227
|
+
if (!res.body) throw new Error('Response body is null');
|
|
205
228
|
const payload = await (0, client_namespaceObject.createFromReadableStream)(res.body);
|
|
206
|
-
if (void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
229
|
+
if ('object' != typeof payload || null === payload || void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
230
|
+
const serverPayload = payload;
|
|
207
231
|
matches.forEach((match)=>{
|
|
208
232
|
const routeId = match.route.id;
|
|
209
|
-
const matchedRoute =
|
|
233
|
+
const matchedRoute = serverPayload.routes.find((route)=>route.id === routeId);
|
|
210
234
|
if (matchedRoute) router.patchRoutes(matchedRoute.parentId, [
|
|
211
235
|
matchedRoute
|
|
212
236
|
], true);
|
|
213
|
-
if (
|
|
237
|
+
if (serverPayload.loaderData?.[routeId]) results[routeId] = {
|
|
214
238
|
type: 'data',
|
|
215
|
-
result:
|
|
239
|
+
result: serverPayload.loaderData[routeId]
|
|
216
240
|
};
|
|
217
241
|
});
|
|
218
242
|
return results;
|
package/dist/esm/cli/index.mjs
CHANGED
|
@@ -60,7 +60,10 @@ const runtimePlugin = (params)=>({
|
|
|
60
60
|
source: {
|
|
61
61
|
globalVars: {
|
|
62
62
|
'process.env.IS_REACT18': process.env.IS_REACT18
|
|
63
|
-
}
|
|
63
|
+
},
|
|
64
|
+
include: [
|
|
65
|
+
new RegExp(`[\\\\/]node_modules[\\\\/]@${metaName}[\\\\/]runtime[\\\\/].*[\\\\/]head\\.[jt]sx?$`)
|
|
66
|
+
]
|
|
64
67
|
},
|
|
65
68
|
tools: {
|
|
66
69
|
bundlerChain: (chain)=>{
|
|
@@ -15,7 +15,9 @@ const genRenderCode = ({ srcDirectory, internalSrcAlias, metaName, entry, custom
|
|
|
15
15
|
return `import { createRoot } from '@${metaName}/runtime/react';
|
|
16
16
|
import { render } from '@${metaName}/runtime/browser';
|
|
17
17
|
|
|
18
|
-
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
18
|
+
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer, setServerCallback } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
19
|
+
|
|
20
|
+
${enableRsc ? "setServerCallback(callServer);" : ''}
|
|
19
21
|
|
|
20
22
|
${enableRsc ? `const data = createFromReadableStream(rscStream, {
|
|
21
23
|
callServer: callServer,
|
|
@@ -40,9 +42,12 @@ const entryForCSRWithRSC = ({ metaName, entryName, urlPath = '/', mountId = 'roo
|
|
|
40
42
|
isRedirectResponse,
|
|
41
43
|
rscStream,
|
|
42
44
|
callServer,
|
|
45
|
+
setServerCallback,
|
|
43
46
|
createFromReadableStream
|
|
44
47
|
} from '@${metaName}/runtime/rsc/client';
|
|
45
48
|
|
|
49
|
+
setServerCallback(callServer);
|
|
50
|
+
|
|
46
51
|
const handleRedirectResponse = (res: Response) => {
|
|
47
52
|
const { headers } = res;
|
|
48
53
|
const location = headers.get('X-Modernjs-Redirect');
|
|
@@ -53,8 +53,7 @@ export const requestHandler = createRequestHandler(handleRequest, {
|
|
|
53
53
|
const handleRSCRequest = async (request, ServerRoot, options) => {
|
|
54
54
|
const { serverPayload } = options;
|
|
55
55
|
const stream = renderRsc({
|
|
56
|
-
element: options.rscRoot
|
|
57
|
-
clientManifest: options.rscClientManifest!,
|
|
56
|
+
element: options.rscRoot
|
|
58
57
|
});
|
|
59
58
|
|
|
60
59
|
return new Response(stream);
|
|
@@ -77,32 +76,13 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
77
76
|
import {
|
|
78
77
|
createRequestHandler,
|
|
79
78
|
} from '@${metaName}/runtime/ssr/server';
|
|
80
|
-
import {
|
|
79
|
+
import { renderCSRWithRSC, renderRsc } from '@${metaName}/runtime/rsc/server';
|
|
81
80
|
export { handleAction } from '@${metaName}/runtime/rsc/server';
|
|
82
81
|
|
|
83
82
|
const handleCSRRender = async (request, ServerRoot, options) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
const stream = new ReadableStream({
|
|
89
|
-
start(controller) {
|
|
90
|
-
const encoder = new TextEncoder();
|
|
91
|
-
|
|
92
|
-
controller.enqueue(encoder.encode(options.html));
|
|
93
|
-
|
|
94
|
-
processRSCStream(rscPayloadStream, controller, encoder)
|
|
95
|
-
.catch(err => {
|
|
96
|
-
controller.error(err);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return new Response(stream, {
|
|
102
|
-
status: 200,
|
|
103
|
-
headers: new Headers({
|
|
104
|
-
'content-type': 'text/html; charset=UTF-8',
|
|
105
|
-
}),
|
|
83
|
+
return renderCSRWithRSC({
|
|
84
|
+
html: options.html,
|
|
85
|
+
rscRoot: options.rscRoot,
|
|
106
86
|
});
|
|
107
87
|
}
|
|
108
88
|
|
|
@@ -113,7 +93,6 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
113
93
|
const handleRequest = async (request, ServerRoot, options) => {
|
|
114
94
|
const stream = renderRsc({
|
|
115
95
|
element: options.rscRoot,
|
|
116
|
-
clientManifest: options.rscClientManifest!,
|
|
117
96
|
});
|
|
118
97
|
|
|
119
98
|
return new Response(stream);
|
|
@@ -2,6 +2,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { storage } from "@modern-js/runtime-utils/node";
|
|
3
3
|
import { getPathname, parseCookie, parseHeaders, parseQuery } from "@modern-js/runtime-utils/universal/request";
|
|
4
4
|
import react, { Fragment } from "react";
|
|
5
|
+
import { handleRSCRedirect } from "../../router/runtime/rsc-router.mjs";
|
|
5
6
|
import { getGlobalInternalRuntimeContext, getGlobalRSCRoot } from "../context/index.mjs";
|
|
6
7
|
import { getInitialContext } from "../context/runtime.mjs";
|
|
7
8
|
import { getServerPayload } from "../context/serverPayload/index.mjs";
|
|
@@ -27,6 +28,14 @@ async function handleRSCRequest(request, Root, context, options, handleRequest)
|
|
|
27
28
|
runtimeContext: context
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
32
|
+
const processRedirect = (headers, status, ctx)=>{
|
|
33
|
+
if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
|
|
34
|
+
return new Response(null, {
|
|
35
|
+
status,
|
|
36
|
+
headers
|
|
37
|
+
});
|
|
38
|
+
};
|
|
30
39
|
function createSSRContext(request, options) {
|
|
31
40
|
const { config, loaderContext, onError, onTiming, locals, resource, params, responseProxy, reporter } = options;
|
|
32
41
|
const { nonce, useJsonScript } = config;
|
|
@@ -117,26 +126,22 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
117
126
|
ssrContext,
|
|
118
127
|
isBrowser: false
|
|
119
128
|
});
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const { ssrContext } = context;
|
|
125
|
-
if (ssrContext) return new Response(null, {
|
|
126
|
-
status,
|
|
127
|
-
headers: {
|
|
128
|
-
Location: redirectUrl
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
129
|
+
const redirectCtx = {
|
|
130
|
+
enableRsc: !!createRequestOptions?.enableRsc,
|
|
131
|
+
isRSCNavigation: 'true' === request.headers.get('x-rsc-tree'),
|
|
132
|
+
basename: ssrContext.baseUrl || '/'
|
|
132
133
|
};
|
|
133
134
|
const beforeRenderResult = await runBeforeRender(context);
|
|
134
135
|
if (context.routerContext?.statusCode && context.routerContext?.statusCode !== 200) context.ssrContext?.response.status(context.routerContext?.statusCode);
|
|
135
136
|
const errors = Object.values(context.routerContext?.errors || {});
|
|
136
137
|
if (errors.length > 0) options.onError(errors[0], SSRErrors.LOADER_ERROR);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
if ("u" > typeof Response && beforeRenderResult instanceof Response && isRedirectStatus(beforeRenderResult.status)) {
|
|
139
|
+
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
140
|
+
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
141
|
+
return processRedirect(new Headers({
|
|
142
|
+
Location: redirectUrl
|
|
143
|
+
}), beforeRenderResult.status, redirectCtx);
|
|
144
|
+
}
|
|
140
145
|
if (!createRequestOptions?.enableRsc) {
|
|
141
146
|
const { htmlTemplate } = options.resource;
|
|
142
147
|
options.resource.htmlTemplate = htmlTemplate.replace('</head>', `${CHUNK_CSS_PLACEHOLDER}</head>`);
|
|
@@ -146,6 +151,7 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
146
151
|
...options,
|
|
147
152
|
runtimeContext: context
|
|
148
153
|
});
|
|
154
|
+
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
149
155
|
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
150
156
|
response.headers.set(key, value);
|
|
151
157
|
});
|
|
@@ -20,10 +20,9 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
20
20
|
try {
|
|
21
21
|
const readableOriginal = await renderSSRStream(rootElement, {
|
|
22
22
|
request,
|
|
23
|
-
clientManifest: options.rscClientManifest,
|
|
24
|
-
ssrManifest: options.rscSSRManifest,
|
|
25
23
|
nonce: config.nonce,
|
|
26
24
|
rscRoot: rscRoot,
|
|
25
|
+
routes: runtimeContext.routes,
|
|
27
26
|
onError (error) {
|
|
28
27
|
options.onError?.(error);
|
|
29
28
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import react_helmet, { Helmet } from "react-helmet";
|
|
3
3
|
const head = react_helmet;
|
|
4
|
-
export { head as default };
|
|
4
|
+
export { Helmet, head as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
function CSSLinks({ cssFiles }) {
|
|
4
|
+
if (0 === cssFiles.length) return null;
|
|
5
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
6
|
+
children: cssFiles.map((css)=>/*#__PURE__*/ jsx("link", {
|
|
7
|
+
href: css,
|
|
8
|
+
rel: "stylesheet"
|
|
9
|
+
}, css))
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export { CSSLinks };
|
|
@@ -2,11 +2,27 @@ import { Fragment, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { ElementsContext, createFromReadableStream } from "@modern-js/render/client";
|
|
3
3
|
import { StaticRouterProvider, createBrowserRouter, createStaticRouter, redirect } from "@modern-js/runtime-utils/router";
|
|
4
4
|
import react from "react";
|
|
5
|
+
import { CSSLinks } from "./CSSLinks.mjs";
|
|
5
6
|
const safeUse = (promise)=>{
|
|
6
7
|
if ('function' == typeof react.use) return react.use(promise);
|
|
7
8
|
return null;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
function collectCssFilesFromRoutes(matches, routes) {
|
|
11
|
+
const cssFiles = [];
|
|
12
|
+
for (const match of matches){
|
|
13
|
+
const route = findRouteInTree(routes, match.route.id);
|
|
14
|
+
if (!route) continue;
|
|
15
|
+
const component = route.Component;
|
|
16
|
+
if (component && 'function' == typeof component && 'entryCssFiles' in component) {
|
|
17
|
+
const css = component.entryCssFiles;
|
|
18
|
+
if (Array.isArray(css)) cssFiles.push(...css);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return Array.from(new Set(cssFiles));
|
|
22
|
+
}
|
|
23
|
+
const createServerPayload = (routerContext, routes)=>{
|
|
24
|
+
const cssFiles = collectCssFilesFromRoutes(routerContext.matches, routes);
|
|
25
|
+
return {
|
|
10
26
|
type: 'render',
|
|
11
27
|
actionData: routerContext.actionData,
|
|
12
28
|
errors: routerContext.errors,
|
|
@@ -18,7 +34,7 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
18
34
|
let processedElement;
|
|
19
35
|
if (element) {
|
|
20
36
|
const ElementComponent = element.type;
|
|
21
|
-
|
|
37
|
+
const elementProps = {
|
|
22
38
|
loaderData: routerContext?.loaderData?.[match.route.id],
|
|
23
39
|
actionData: routerContext?.actionData?.[match.route.id],
|
|
24
40
|
params: match.params,
|
|
@@ -32,7 +48,12 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
32
48
|
handle: route.handle
|
|
33
49
|
};
|
|
34
50
|
})
|
|
35
|
-
}
|
|
51
|
+
};
|
|
52
|
+
const routeElement = /*#__PURE__*/ react.createElement(ElementComponent, elementProps);
|
|
53
|
+
const isLeafRoute = index === routerContext.matches.length - 1;
|
|
54
|
+
processedElement = isLeafRoute && cssFiles.length > 0 ? /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(CSSLinks, {
|
|
55
|
+
cssFiles
|
|
56
|
+
}), routeElement) : routeElement;
|
|
36
57
|
}
|
|
37
58
|
return {
|
|
38
59
|
element: processedElement,
|
|
@@ -51,7 +72,8 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
51
72
|
pathnameBase: match.pathnameBase
|
|
52
73
|
};
|
|
53
74
|
})
|
|
54
|
-
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
55
77
|
const handleRSCRedirect = (headers, basename, status)=>{
|
|
56
78
|
const newHeaders = new Headers(headers);
|
|
57
79
|
let redirectUrl = headers.get('Location');
|
|
@@ -160,17 +182,19 @@ const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>
|
|
|
160
182
|
clientLoaderResults.forEach(({ routeId, result })=>{
|
|
161
183
|
results[routeId] = result;
|
|
162
184
|
});
|
|
185
|
+
if (!res.body) throw new Error('Response body is null');
|
|
163
186
|
const payload = await createFromReadableStream(res.body);
|
|
164
|
-
if (void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
187
|
+
if ('object' != typeof payload || null === payload || void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
188
|
+
const serverPayload = payload;
|
|
165
189
|
matches.forEach((match)=>{
|
|
166
190
|
const routeId = match.route.id;
|
|
167
|
-
const matchedRoute =
|
|
191
|
+
const matchedRoute = serverPayload.routes.find((route)=>route.id === routeId);
|
|
168
192
|
if (matchedRoute) router.patchRoutes(matchedRoute.parentId, [
|
|
169
193
|
matchedRoute
|
|
170
194
|
], true);
|
|
171
|
-
if (
|
|
195
|
+
if (serverPayload.loaderData?.[routeId]) results[routeId] = {
|
|
172
196
|
type: 'data',
|
|
173
|
-
result:
|
|
197
|
+
result: serverPayload.loaderData[routeId]
|
|
174
198
|
};
|
|
175
199
|
});
|
|
176
200
|
return results;
|
|
@@ -62,7 +62,10 @@ const runtimePlugin = (params)=>({
|
|
|
62
62
|
source: {
|
|
63
63
|
globalVars: {
|
|
64
64
|
'process.env.IS_REACT18': process.env.IS_REACT18
|
|
65
|
-
}
|
|
65
|
+
},
|
|
66
|
+
include: [
|
|
67
|
+
new RegExp(`[\\\\/]node_modules[\\\\/]@${metaName}[\\\\/]runtime[\\\\/].*[\\\\/]head\\.[jt]sx?$`)
|
|
68
|
+
]
|
|
66
69
|
},
|
|
67
70
|
tools: {
|
|
68
71
|
bundlerChain: (chain)=>{
|
|
@@ -16,7 +16,9 @@ const genRenderCode = ({ srcDirectory, internalSrcAlias, metaName, entry, custom
|
|
|
16
16
|
return `import { createRoot } from '@${metaName}/runtime/react';
|
|
17
17
|
import { render } from '@${metaName}/runtime/browser';
|
|
18
18
|
|
|
19
|
-
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
19
|
+
${enableRsc ? `import { RscClientRoot, createFromReadableStream, rscStream, callServer, setServerCallback } from '@${metaName}/runtime/rsc/client';` : ''}
|
|
20
|
+
|
|
21
|
+
${enableRsc ? "setServerCallback(callServer);" : ''}
|
|
20
22
|
|
|
21
23
|
${enableRsc ? `const data = createFromReadableStream(rscStream, {
|
|
22
24
|
callServer: callServer,
|
|
@@ -41,9 +43,12 @@ const entryForCSRWithRSC = ({ metaName, entryName, urlPath = '/', mountId = 'roo
|
|
|
41
43
|
isRedirectResponse,
|
|
42
44
|
rscStream,
|
|
43
45
|
callServer,
|
|
46
|
+
setServerCallback,
|
|
44
47
|
createFromReadableStream
|
|
45
48
|
} from '@${metaName}/runtime/rsc/client';
|
|
46
49
|
|
|
50
|
+
setServerCallback(callServer);
|
|
51
|
+
|
|
47
52
|
const handleRedirectResponse = (res: Response) => {
|
|
48
53
|
const { headers } = res;
|
|
49
54
|
const location = headers.get('X-Modernjs-Redirect');
|
|
@@ -54,8 +54,7 @@ export const requestHandler = createRequestHandler(handleRequest, {
|
|
|
54
54
|
const handleRSCRequest = async (request, ServerRoot, options) => {
|
|
55
55
|
const { serverPayload } = options;
|
|
56
56
|
const stream = renderRsc({
|
|
57
|
-
element: options.rscRoot
|
|
58
|
-
clientManifest: options.rscClientManifest!,
|
|
57
|
+
element: options.rscRoot
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
return new Response(stream);
|
|
@@ -78,32 +77,13 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
78
77
|
import {
|
|
79
78
|
createRequestHandler,
|
|
80
79
|
} from '@${metaName}/runtime/ssr/server';
|
|
81
|
-
import {
|
|
80
|
+
import { renderCSRWithRSC, renderRsc } from '@${metaName}/runtime/rsc/server';
|
|
82
81
|
export { handleAction } from '@${metaName}/runtime/rsc/server';
|
|
83
82
|
|
|
84
83
|
const handleCSRRender = async (request, ServerRoot, options) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
const stream = new ReadableStream({
|
|
90
|
-
start(controller) {
|
|
91
|
-
const encoder = new TextEncoder();
|
|
92
|
-
|
|
93
|
-
controller.enqueue(encoder.encode(options.html));
|
|
94
|
-
|
|
95
|
-
processRSCStream(rscPayloadStream, controller, encoder)
|
|
96
|
-
.catch(err => {
|
|
97
|
-
controller.error(err);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return new Response(stream, {
|
|
103
|
-
status: 200,
|
|
104
|
-
headers: new Headers({
|
|
105
|
-
'content-type': 'text/html; charset=UTF-8',
|
|
106
|
-
}),
|
|
84
|
+
return renderCSRWithRSC({
|
|
85
|
+
html: options.html,
|
|
86
|
+
rscRoot: options.rscRoot,
|
|
107
87
|
});
|
|
108
88
|
}
|
|
109
89
|
|
|
@@ -114,7 +94,6 @@ const entryForCSRWithRSC = ({ metaName, entryName })=>`
|
|
|
114
94
|
const handleRequest = async (request, ServerRoot, options) => {
|
|
115
95
|
const stream = renderRsc({
|
|
116
96
|
element: options.rscRoot,
|
|
117
|
-
clientManifest: options.rscClientManifest!,
|
|
118
97
|
});
|
|
119
98
|
|
|
120
99
|
return new Response(stream);
|
|
@@ -3,6 +3,7 @@ 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 { handleRSCRedirect } from "../../router/runtime/rsc-router.mjs";
|
|
6
7
|
import { getGlobalInternalRuntimeContext, getGlobalRSCRoot } from "../context/index.mjs";
|
|
7
8
|
import { getInitialContext } from "../context/runtime.mjs";
|
|
8
9
|
import { getServerPayload } from "../context/serverPayload/index.mjs";
|
|
@@ -28,6 +29,14 @@ async function handleRSCRequest(request, Root, context, options, handleRequest)
|
|
|
28
29
|
runtimeContext: context
|
|
29
30
|
});
|
|
30
31
|
}
|
|
32
|
+
const isRedirectStatus = (status)=>status >= 300 && status <= 399;
|
|
33
|
+
const processRedirect = (headers, status, ctx)=>{
|
|
34
|
+
if (ctx.enableRsc && ctx.isRSCNavigation) return handleRSCRedirect(headers, ctx.basename, status);
|
|
35
|
+
return new Response(null, {
|
|
36
|
+
status,
|
|
37
|
+
headers
|
|
38
|
+
});
|
|
39
|
+
};
|
|
31
40
|
function createSSRContext(request, options) {
|
|
32
41
|
const { config, loaderContext, onError, onTiming, locals, resource, params, responseProxy, reporter } = options;
|
|
33
42
|
const { nonce, useJsonScript } = config;
|
|
@@ -118,26 +127,22 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
118
127
|
ssrContext,
|
|
119
128
|
isBrowser: false
|
|
120
129
|
});
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const { ssrContext } = context;
|
|
126
|
-
if (ssrContext) return new Response(null, {
|
|
127
|
-
status,
|
|
128
|
-
headers: {
|
|
129
|
-
Location: redirectUrl
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
}
|
|
130
|
+
const redirectCtx = {
|
|
131
|
+
enableRsc: !!createRequestOptions?.enableRsc,
|
|
132
|
+
isRSCNavigation: 'true' === request.headers.get('x-rsc-tree'),
|
|
133
|
+
basename: ssrContext.baseUrl || '/'
|
|
133
134
|
};
|
|
134
135
|
const beforeRenderResult = await runBeforeRender(context);
|
|
135
136
|
if (context.routerContext?.statusCode && context.routerContext?.statusCode !== 200) context.ssrContext?.response.status(context.routerContext?.statusCode);
|
|
136
137
|
const errors = Object.values(context.routerContext?.errors || {});
|
|
137
138
|
if (errors.length > 0) options.onError(errors[0], SSRErrors.LOADER_ERROR);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if ("u" > typeof Response && beforeRenderResult instanceof Response && isRedirectStatus(beforeRenderResult.status)) {
|
|
140
|
+
if (beforeRenderResult.headers.has('X-Modernjs-Redirect')) return beforeRenderResult;
|
|
141
|
+
const redirectUrl = beforeRenderResult.headers.get('Location') || '/';
|
|
142
|
+
return processRedirect(new Headers({
|
|
143
|
+
Location: redirectUrl
|
|
144
|
+
}), beforeRenderResult.status, redirectCtx);
|
|
145
|
+
}
|
|
141
146
|
if (!createRequestOptions?.enableRsc) {
|
|
142
147
|
const { htmlTemplate } = options.resource;
|
|
143
148
|
options.resource.htmlTemplate = htmlTemplate.replace('</head>', `${CHUNK_CSS_PLACEHOLDER}</head>`);
|
|
@@ -147,6 +152,7 @@ const createRequestHandler = async (handleRequest, createRequestOptions)=>{
|
|
|
147
152
|
...options,
|
|
148
153
|
runtimeContext: context
|
|
149
154
|
});
|
|
155
|
+
if (-1 !== responseProxy.status && isRedirectStatus(responseProxy.status) && responseProxy.headers.Location) return processRedirect(new Headers(responseProxy.headers), responseProxy.status, redirectCtx);
|
|
150
156
|
Object.entries(responseProxy.headers).forEach(([key, value])=>{
|
|
151
157
|
response.headers.set(key, value);
|
|
152
158
|
});
|
|
@@ -21,10 +21,9 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
21
21
|
try {
|
|
22
22
|
const readableOriginal = await renderSSRStream(rootElement, {
|
|
23
23
|
request,
|
|
24
|
-
clientManifest: options.rscClientManifest,
|
|
25
|
-
ssrManifest: options.rscSSRManifest,
|
|
26
24
|
nonce: config.nonce,
|
|
27
25
|
rscRoot: rscRoot,
|
|
26
|
+
routes: runtimeContext.routes,
|
|
28
27
|
onError (error) {
|
|
29
28
|
options.onError?.(error);
|
|
30
29
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import "react";
|
|
4
|
+
function CSSLinks({ cssFiles }) {
|
|
5
|
+
if (0 === cssFiles.length) return null;
|
|
6
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
7
|
+
children: cssFiles.map((css)=>/*#__PURE__*/ jsx("link", {
|
|
8
|
+
href: css,
|
|
9
|
+
rel: "stylesheet"
|
|
10
|
+
}, css))
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export { CSSLinks };
|
|
@@ -3,11 +3,27 @@ import { Fragment, jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { ElementsContext, createFromReadableStream } from "@modern-js/render/client";
|
|
4
4
|
import { StaticRouterProvider, createBrowserRouter, createStaticRouter, redirect } from "@modern-js/runtime-utils/router";
|
|
5
5
|
import react from "react";
|
|
6
|
+
import { CSSLinks } from "./CSSLinks.mjs";
|
|
6
7
|
const safeUse = (promise)=>{
|
|
7
8
|
if ('function' == typeof react.use) return react.use(promise);
|
|
8
9
|
return null;
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
+
function collectCssFilesFromRoutes(matches, routes) {
|
|
12
|
+
const cssFiles = [];
|
|
13
|
+
for (const match of matches){
|
|
14
|
+
const route = findRouteInTree(routes, match.route.id);
|
|
15
|
+
if (!route) continue;
|
|
16
|
+
const component = route.Component;
|
|
17
|
+
if (component && 'function' == typeof component && 'entryCssFiles' in component) {
|
|
18
|
+
const css = component.entryCssFiles;
|
|
19
|
+
if (Array.isArray(css)) cssFiles.push(...css);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return Array.from(new Set(cssFiles));
|
|
23
|
+
}
|
|
24
|
+
const createServerPayload = (routerContext, routes)=>{
|
|
25
|
+
const cssFiles = collectCssFilesFromRoutes(routerContext.matches, routes);
|
|
26
|
+
return {
|
|
11
27
|
type: 'render',
|
|
12
28
|
actionData: routerContext.actionData,
|
|
13
29
|
errors: routerContext.errors,
|
|
@@ -19,7 +35,7 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
19
35
|
let processedElement;
|
|
20
36
|
if (element) {
|
|
21
37
|
const ElementComponent = element.type;
|
|
22
|
-
|
|
38
|
+
const elementProps = {
|
|
23
39
|
loaderData: routerContext?.loaderData?.[match.route.id],
|
|
24
40
|
actionData: routerContext?.actionData?.[match.route.id],
|
|
25
41
|
params: match.params,
|
|
@@ -33,7 +49,12 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
33
49
|
handle: route.handle
|
|
34
50
|
};
|
|
35
51
|
})
|
|
36
|
-
}
|
|
52
|
+
};
|
|
53
|
+
const routeElement = /*#__PURE__*/ react.createElement(ElementComponent, elementProps);
|
|
54
|
+
const isLeafRoute = index === routerContext.matches.length - 1;
|
|
55
|
+
processedElement = isLeafRoute && cssFiles.length > 0 ? /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(CSSLinks, {
|
|
56
|
+
cssFiles
|
|
57
|
+
}), routeElement) : routeElement;
|
|
37
58
|
}
|
|
38
59
|
return {
|
|
39
60
|
element: processedElement,
|
|
@@ -52,7 +73,8 @@ const createServerPayload = (routerContext, routes)=>({
|
|
|
52
73
|
pathnameBase: match.pathnameBase
|
|
53
74
|
};
|
|
54
75
|
})
|
|
55
|
-
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
56
78
|
const handleRSCRedirect = (headers, basename, status)=>{
|
|
57
79
|
const newHeaders = new Headers(headers);
|
|
58
80
|
let redirectUrl = headers.get('Location');
|
|
@@ -161,17 +183,19 @@ const createClientRouterFromPayload = (payload, originalRoutes, basename = '')=>
|
|
|
161
183
|
clientLoaderResults.forEach(({ routeId, result })=>{
|
|
162
184
|
results[routeId] = result;
|
|
163
185
|
});
|
|
186
|
+
if (!res.body) throw new Error('Response body is null');
|
|
164
187
|
const payload = await createFromReadableStream(res.body);
|
|
165
|
-
if (void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
188
|
+
if ('object' != typeof payload || null === payload || void 0 === payload.type || 'render' !== payload.type) throw new Error('Unexpected payload type');
|
|
189
|
+
const serverPayload = payload;
|
|
166
190
|
matches.forEach((match)=>{
|
|
167
191
|
const routeId = match.route.id;
|
|
168
|
-
const matchedRoute =
|
|
192
|
+
const matchedRoute = serverPayload.routes.find((route)=>route.id === routeId);
|
|
169
193
|
if (matchedRoute) router.patchRoutes(matchedRoute.parentId, [
|
|
170
194
|
matchedRoute
|
|
171
195
|
], true);
|
|
172
|
-
if (
|
|
196
|
+
if (serverPayload.loaderData?.[routeId]) results[routeId] = {
|
|
173
197
|
type: 'data',
|
|
174
|
-
result:
|
|
198
|
+
result: serverPayload.loaderData[routeId]
|
|
175
199
|
};
|
|
176
200
|
});
|
|
177
201
|
return results;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import head from 'react-helmet';
|
|
2
2
|
export default head;
|
|
3
|
-
export
|
|
3
|
+
export { Helmet } from 'react-helmet';
|
|
4
|
+
export type { HelmetTags, HelmetProps, HelmetPropsToState, HelmetData, HelmetDatum, HelmetHTMLBodyDatum, HelmetHTMLElementDatum, } from 'react-helmet';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Server Component that renders CSS link tags
|
|
4
|
+
* These will be serialized into RSC payload and automatically handled by React on client
|
|
5
|
+
* React will automatically hoist these link tags to the head
|
|
6
|
+
*/
|
|
7
|
+
export declare function CSSLinks({ cssFiles }: {
|
|
8
|
+
cssFiles: string[];
|
|
9
|
+
}): React.JSX.Element | null;
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"modern",
|
|
16
16
|
"modern.js"
|
|
17
17
|
],
|
|
18
|
-
"version": "3.0.
|
|
18
|
+
"version": "3.0.3",
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"react-server": "./dist/esm/react-server.mjs",
|
|
29
29
|
"default": "./dist/esm/index.mjs"
|
|
30
30
|
},
|
|
31
|
+
"./react-server": {
|
|
32
|
+
"types": "./dist/types/react-server.d.ts",
|
|
33
|
+
"default": "./dist/esm/react-server.mjs"
|
|
34
|
+
},
|
|
31
35
|
"./internal": {
|
|
32
36
|
"types": "./dist/types/internal.d.ts",
|
|
33
37
|
"default": "./dist/esm/internal.mjs"
|
|
@@ -131,6 +135,9 @@
|
|
|
131
135
|
".": [
|
|
132
136
|
"./dist/types/index.d.ts"
|
|
133
137
|
],
|
|
138
|
+
"react-server": [
|
|
139
|
+
"./dist/types/react-server.d.ts"
|
|
140
|
+
],
|
|
134
141
|
"internal": [
|
|
135
142
|
"./dist/types/internal.d.ts"
|
|
136
143
|
],
|
|
@@ -197,7 +204,7 @@
|
|
|
197
204
|
"@loadable/component": "5.16.7",
|
|
198
205
|
"@loadable/server": "5.16.7",
|
|
199
206
|
"@swc/helpers": "^0.5.17",
|
|
200
|
-
"@swc/plugin-loadable-components": "^11.
|
|
207
|
+
"@swc/plugin-loadable-components": "^11.5.0",
|
|
201
208
|
"@types/loadable__component": "^5.13.10",
|
|
202
209
|
"@types/react-helmet": "^6.1.11",
|
|
203
210
|
"cookie": "0.7.2",
|
|
@@ -208,12 +215,12 @@
|
|
|
208
215
|
"isbot": "3.8.0",
|
|
209
216
|
"react-helmet": "^6.1.0",
|
|
210
217
|
"react-is": "^18.3.1",
|
|
211
|
-
"@modern-js/plugin": "3.0.
|
|
212
|
-
"@modern-js/
|
|
213
|
-
"@modern-js/
|
|
214
|
-
"@modern-js/runtime-utils": "3.0.
|
|
215
|
-
"@modern-js/types": "3.0.
|
|
216
|
-
"@modern-js/utils": "3.0.
|
|
218
|
+
"@modern-js/plugin": "3.0.3",
|
|
219
|
+
"@modern-js/plugin-data-loader": "3.0.3",
|
|
220
|
+
"@modern-js/render": "3.0.3",
|
|
221
|
+
"@modern-js/runtime-utils": "3.0.3",
|
|
222
|
+
"@modern-js/types": "3.0.3",
|
|
223
|
+
"@modern-js/utils": "3.0.3"
|
|
217
224
|
},
|
|
218
225
|
"peerDependencies": {
|
|
219
226
|
"react": ">=17.0.2",
|
|
@@ -221,8 +228,8 @@
|
|
|
221
228
|
},
|
|
222
229
|
"devDependencies": {
|
|
223
230
|
"@remix-run/web-fetch": "^4.1.3",
|
|
224
|
-
"@rsbuild/core": "2.0.0-beta.
|
|
225
|
-
"@rslib/core": "0.19.
|
|
231
|
+
"@rsbuild/core": "2.0.0-beta.4",
|
|
232
|
+
"@rslib/core": "0.19.6",
|
|
226
233
|
"@testing-library/dom": "^10.4.1",
|
|
227
234
|
"@testing-library/react": "^16.3.2",
|
|
228
235
|
"@types/cookie": "0.6.0",
|
|
@@ -233,7 +240,7 @@
|
|
|
233
240
|
"react-dom": "^19.2.4",
|
|
234
241
|
"ts-node": "^10.9.2",
|
|
235
242
|
"typescript": "^5",
|
|
236
|
-
"@modern-js/app-tools": "3.0.
|
|
243
|
+
"@modern-js/app-tools": "3.0.3",
|
|
237
244
|
"@modern-js/rslib": "2.68.10",
|
|
238
245
|
"@scripts/rstest-config": "2.66.0"
|
|
239
246
|
},
|