Skip to content

Commit 4425fd6

Browse files
fix: resolve worker SSR some bug, more detail see Conversation (#3694)
* fix: add worker ssr ctx * fix: delete useless function * chore: update the input and output options
1 parent 906bd94 commit 4425fd6

File tree

4 files changed

+159
-64
lines changed

4 files changed

+159
-64
lines changed

.changeset/quick-colts-travel.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@modern-js/plugin-worker': patch
3+
'@modern-js/app-tools': patch
4+
'@modern-js/prod-server': patch
5+
---
6+
7+
fix: worker ssr context lack some fields & worker ssr must have routerManifest.json
8+
fix: worker ssr 上下文缺少一些字段,worker ssr 必须有 routerManifest.json 文件

packages/server/plugin-worker/src/code.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
export const worker = () => `
22
// work entry code
3-
import { createHandler, handleUrl } from "@modern-js/prod-server/worker";
3+
import { createHandler } from "@modern-js/prod-server/worker";
44
import loadableStats from "../loadable-stats.json";
55
import routeManifest from "../routes-manifest.json";
66
import { manifest } from "./manifest";
7+
78
async function handleRequest(request) {
8-
const context = {
9-
request: {
10-
...request,
11-
url: request.url,
12-
},
13-
url: handleUrl(request.url),
14-
body: null,
9+
const options = {
10+
request,
1511
loadableStats,
1612
routeManifest,
17-
};
13+
}
1814
const handler = createHandler(manifest);
19-
await handler(context);
20-
return new Response(context.body, {
21-
headers: {
22-
'content-type': 'text/html;charset=UTF-8',
23-
},
15+
16+
const { body, status, headers } = await handler(options);
17+
18+
return new Response(body, {
19+
status,
20+
headers,
2421
});
2522
}
2623
Lines changed: 138 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,63 @@
1+
import { BaseSSRServerContext } from '@modern-js/types';
12
import { Logger, LoggerInterface } from './libs/logger';
23
import { ModernRouteInterface, RouteMatchManager } from './libs/route';
34
import { metrics as defaultMetrics } from './libs/metrics';
45

56
export type Context = Record<string, any>;
67

7-
export interface UrlQuery {
8-
[key: string]: string;
8+
export interface HandlerOptions {
9+
request: Request;
10+
loadableStats: Record<string, any>;
11+
routeManifest: Record<string, any>;
12+
}
13+
14+
export class ReturnResponse {
15+
body: string;
16+
17+
status: number;
18+
19+
headers: Headers;
20+
21+
constructor(body: string, status: number, headers: Record<string, any> = {}) {
22+
this.body = body;
23+
this.status = status;
24+
this.headers = new Headers(headers);
25+
this.headers.set('content-type', 'text/html;charset=UTF-8');
26+
}
27+
28+
/**
29+
* Iterate a Object
30+
* 1. adds the value if the key does not already exist.
31+
* 2. append the value if the key does already exist.
32+
*
33+
* more detail follow: https://developer.mozilla.org/en-US/docs/Web/API/Headers/append
34+
* @param headers
35+
* @returns
36+
*/
37+
appendHeaders(headers: Record<string, any>): this {
38+
Object.entries(headers).forEach(([key, value]) => {
39+
this.headers.append(key, value.toString() as string);
40+
});
41+
42+
return this;
43+
}
44+
45+
/**
46+
* Iterate a Object
47+
* 1. adds the value if the key does not already exist.
48+
* 2. modify the value if the key does already exist.
49+
*
50+
* more detail follow: https://developer.mozilla.org/en-US/docs/Web/API/Headers/set
51+
* @param headers
52+
* @returns
53+
*/
54+
setHeaders(headers: Record<string, any>): this {
55+
Object.entries(headers).forEach(([key, value]) => {
56+
this.headers.set(key, value.toString() as string);
57+
});
58+
59+
return this;
60+
}
961
}
1062

1163
export type Manifest = {
@@ -20,67 +72,104 @@ export type Manifest = {
2072
routes: ModernRouteInterface[];
2173
};
2274

23-
export const handleUrl = (url: string) => {
24-
return url.replace(/^https?:\/\/.*?\//gi, '/');
25-
};
75+
const RESPONSE_NOTFOUND = new ReturnResponse('404: Page not found', 404);
2676

2777
export const createHandler = (manifest: Manifest) => {
2878
const routeMgr = new RouteMatchManager();
2979
const { pages, routes } = manifest;
3080
routeMgr.reset(routes);
31-
return async (ctx: Context) => {
32-
const pageMatch = routeMgr.match(ctx.url);
81+
return async (options: HandlerOptions): Promise<ReturnResponse> => {
82+
const { request, loadableStats, routeManifest } = options;
83+
// eslint-disable-next-line node/no-unsupported-features/node-builtins, node/prefer-global/url
84+
const url = new URL(request.url);
85+
const pageMatch = routeMgr.match(url.pathname);
3386
if (!pageMatch) {
34-
ctx.body = '404: Page not found';
35-
ctx.status = 404;
36-
return;
87+
return RESPONSE_NOTFOUND;
3788
}
3889
const page = pages[pageMatch.spec.urlPath];
39-
ctx.request.query ??= ctx.query;
40-
ctx.request.pathname ??= ctx.pathname;
41-
ctx.request.params ??= ctx.params;
42-
const params = pageMatch.parseURLParams(ctx.url);
4390
if (page.serverRender) {
4491
try {
45-
ctx.body = await page.serverRender({
46-
entryName: page.entryName,
92+
const responseLike = {
93+
headers: {} as Record<string, any>,
94+
statusCode: 200,
95+
locals: {} as Record<string, any>,
96+
setHeader(key: string, value: string) {
97+
this.headers[key] = value;
98+
},
99+
status(code: number) {
100+
this.statusCode = code;
101+
},
102+
};
103+
const params = pageMatch.parseURLParams(url.pathname) || {};
104+
105+
const serverRenderContext: BaseSSRServerContext = {
106+
request: createServerRequest(url, request, params),
107+
response: responseLike,
108+
loadableStats,
109+
routeManifest,
110+
redirection: {},
47111
template: page.template,
48-
query: ctx.query,
49-
request: ctx.request,
50-
response: ctx.response,
51-
pathname: ctx.pathname,
52-
req: ctx.request,
53-
res: ctx.response,
54-
params: ctx.params || params || {},
55-
logger:
56-
ctx.logger ||
57-
(new Logger({
58-
level: 'warn',
59-
}) as Logger & LoggerInterface),
60-
metrics: ctx.metrics || defaultMetrics,
61-
loadableStats: ctx.loadableStats,
62-
routeManifest: ctx.routeManifest,
63-
});
64-
ctx.status = 200;
65-
return;
112+
entryName: page.entryName,
113+
logger: new Logger({
114+
level: 'warn',
115+
}) as Logger & LoggerInterface,
116+
metrics: defaultMetrics as any,
117+
// FIXME: pass correctly req & res
118+
req: request as any,
119+
res: responseLike as any,
120+
};
121+
122+
const body = await page.serverRender(serverRenderContext);
123+
124+
return new ReturnResponse(
125+
body,
126+
responseLike.statusCode,
127+
responseLike.headers,
128+
);
66129
} catch (e) {
67-
if (page.template) {
68-
ctx.body = page.template;
69-
ctx.status = 200;
70-
return;
71-
} else {
72-
ctx.body = '404: not found';
73-
ctx.status = 404;
74-
return;
75-
}
130+
console.warn(
131+
`page(${pageMatch.spec.urlPath}) serverRender occur error: `,
132+
);
133+
console.warn(e);
134+
135+
return createResponse(page.template);
76136
}
77137
}
78-
if (page.template) {
79-
ctx.body = page.template;
80-
ctx.status = 200;
81-
return;
138+
139+
console.warn(`Can't not page(${pageMatch.spec.urlPath}) serverRender`);
140+
141+
return createResponse(page.template);
142+
143+
function createServerRequest(
144+
// eslint-disable-next-line node/no-unsupported-features/node-builtins, node/prefer-global/url
145+
url: URL,
146+
request: Request,
147+
params: Record<string, string>,
148+
) {
149+
const { pathname, host, searchParams } = url;
150+
const { headers: rawHeaders } = request;
151+
const headers = {} as Record<string, any>;
152+
rawHeaders.forEach((value, key) => {
153+
headers[key] = value;
154+
});
155+
// eslint-disable-next-line node/no-unsupported-features/es-builtins
156+
const query = Object.fromEntries(searchParams);
157+
158+
return {
159+
pathname,
160+
host,
161+
headers,
162+
params,
163+
query,
164+
};
82165
}
83-
ctx.body = '404: not found';
84-
ctx.status = 404;
85166
};
86167
};
168+
169+
function createResponse(template?: string) {
170+
if (template) {
171+
return new ReturnResponse(template, 200);
172+
} else {
173+
return RESPONSE_NOTFOUND;
174+
}
175+
}

packages/solutions/app-tools/src/builder/shared/builderPlugins/adapterSSR.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,10 @@ function applyRouterPlugin<B extends Bundler>(
123123

124124
const routerConfig: any = normalizedConfig?.runtime?.router;
125125
const routerManifest = Boolean(routerConfig?.manifest);
126+
const workerSSR = Boolean(normalizedConfig.deploy.worker?.ssr);
126127

127128
// for ssr mode
128-
if (existNestedRoutes || routerManifest) {
129+
if (existNestedRoutes || routerManifest || workerSSR) {
129130
chain.plugin('route-plugin').use(RouterPlugin);
130131
}
131132
}

0 commit comments

Comments
 (0)