@umijs/server 4.0.0-canary.20240321.1 → 4.0.0-canary.20240402.1

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/ssr.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ /// <reference lib="webworker" />
2
+ import type { RequestHandler } from '@umijs/bundler-utils/compiled/express';
1
3
  import React from 'react';
2
- import type { UmiRequest } from './types';
4
+ import type { ITplOpts, UmiRequest } from './types';
3
5
  interface RouteLoaders {
4
6
  [key: string]: () => Promise<any>;
5
7
  }
@@ -25,9 +27,19 @@ interface CreateRequestHandlerOptions extends CreateRequestServerlessOptions {
25
27
  createHistory: (opts: any) => any;
26
28
  helmetContext?: any;
27
29
  ServerInsertedHTMLContext: React.Context<ServerInsertedHTMLHook | null>;
30
+ tplOpts: ITplOpts;
31
+ renderFromRoot: boolean;
32
+ mountElementId: string;
28
33
  }
29
34
  export declare function createMarkupGenerator(opts: CreateRequestHandlerOptions): (url: string) => Promise<unknown>;
30
- export default function createRequestHandler(opts: CreateRequestHandlerOptions): (req: any, res: any, next: any) => Promise<any>;
35
+ declare type IExpressRequestHandlerArgs = Parameters<RequestHandler>;
36
+ declare type IWorkerRequestHandlerArgs = [
37
+ ev: FetchEvent,
38
+ opts?: {
39
+ modifyResponse?: (res: Response) => Promise<Response> | Response;
40
+ }
41
+ ];
42
+ export default function createRequestHandler(opts: CreateRequestHandlerOptions): (...args: IExpressRequestHandlerArgs | IWorkerRequestHandlerArgs) => Promise<void>;
31
43
  export declare function createUmiHandler(opts: CreateRequestHandlerOptions): (req: UmiRequest, params?: CreateRequestHandlerOptions) => Promise<NodeJS.ReadableStream>;
32
44
  export declare function createUmiServerLoader(opts: CreateRequestHandlerOptions): (req: UmiRequest) => Promise<any>;
33
45
  export {};
package/dist/ssr.js CHANGED
@@ -39,7 +39,8 @@ var import_react = __toESM(require("react"));
39
39
  var ReactDomServer = __toESM(require("react-dom/server"));
40
40
  var import_react_router_dom = require("react-router-dom");
41
41
  var import_stream = require("stream");
42
- var createJSXProvider = (Provider, serverInsertedHTMLCallbacks) => {
42
+ var createJSXProvider = (Provider) => {
43
+ const serverInsertedHTMLCallbacks = /* @__PURE__ */ new Set();
43
44
  const JSXProvider = (props) => {
44
45
  const addInsertedHtml = import_react.default.useCallback(
45
46
  (handler) => {
@@ -52,7 +53,7 @@ var createJSXProvider = (Provider, serverInsertedHTMLCallbacks) => {
52
53
  value: addInsertedHtml
53
54
  });
54
55
  };
55
- return JSXProvider;
56
+ return [JSXProvider, serverInsertedHTMLCallbacks];
56
57
  };
57
58
  function createJSXGenerator(opts) {
58
59
  return async (url, serverLoaderArgs) => {
@@ -84,7 +85,6 @@ function createJSXGenerator(opts) {
84
85
  return;
85
86
  }
86
87
  const loaderData = {};
87
- const metadata = {};
88
88
  await Promise.all(
89
89
  matches.filter((id) => routes[id].hasServerLoader).map(
90
90
  (id) => new Promise(async (resolve) => {
@@ -94,15 +94,19 @@ function createJSXGenerator(opts) {
94
94
  serverLoaderArgs
95
95
  });
96
96
  if (routes[id].hasMetadataLoader) {
97
- Object.assign(
98
- metadata,
99
- await executeMetadataLoader({
100
- routesWithServerLoader,
101
- routeKey: id,
102
- serverLoaderArgs,
103
- serverLoaderData: loaderData[id]
104
- })
105
- );
97
+ const metadataLoaderData = await executeMetadataLoader({
98
+ routesWithServerLoader,
99
+ routeKey: id,
100
+ serverLoaderArgs,
101
+ serverLoaderData: loaderData[id]
102
+ });
103
+ Object.entries(metadataLoaderData).forEach(([k, v]) => {
104
+ if (Array.isArray(v)) {
105
+ opts.tplOpts[k] = (opts.tplOpts[k] || []).concat(v);
106
+ } else {
107
+ opts.tplOpts[k] = v;
108
+ }
109
+ });
106
110
  }
107
111
  resolve();
108
112
  })
@@ -116,7 +120,9 @@ function createJSXGenerator(opts) {
116
120
  location: url,
117
121
  manifest,
118
122
  loaderData,
119
- metadata
123
+ tplOpts: opts.tplOpts,
124
+ renderFromRoot: opts.renderFromRoot,
125
+ mountElementId: opts.mountElementId
120
126
  };
121
127
  const element = await opts.getClientRootComponent(
122
128
  context
@@ -127,13 +133,19 @@ function createJSXGenerator(opts) {
127
133
  };
128
134
  };
129
135
  }
130
- var getGenerateStaticHTML = (serverInsertedHTMLCallbacks) => {
136
+ var SERVER_INSERTED_HTML = "umi-server-inserted-html";
137
+ var getGenerateStaticHTML = (serverInsertedHTMLCallbacks, opts) => {
138
+ const children = import_react.default.createElement(import_react.default.Fragment, {
139
+ children: Array.from(serverInsertedHTMLCallbacks || []).map(
140
+ (callback) => callback()
141
+ )
142
+ });
131
143
  return ReactDomServer.renderToString(
132
- import_react.default.createElement(import_react.default.Fragment, {
133
- children: Array.from(serverInsertedHTMLCallbacks || []).map(
134
- (callback) => callback()
135
- )
136
- })
144
+ (opts == null ? void 0 : opts.wrapper) ? import_react.default.createElement(
145
+ "div",
146
+ { id: SERVER_INSERTED_HTML, hidden: true },
147
+ children
148
+ ) : children
137
149
  ) || "";
138
150
  };
139
151
  function createMarkupGenerator(opts) {
@@ -142,10 +154,8 @@ function createMarkupGenerator(opts) {
142
154
  const jsx = await jsxGeneratorDeferrer(url);
143
155
  if (jsx) {
144
156
  return new Promise(async (resolve, reject) => {
145
- const serverInsertedHTMLCallbacks = /* @__PURE__ */ new Set();
146
- const JSXProvider = createJSXProvider(
147
- opts.ServerInsertedHTMLContext.Provider,
148
- serverInsertedHTMLCallbacks
157
+ const [JSXProvider, serverInsertedHTMLCallbacks] = createJSXProvider(
158
+ opts.ServerInsertedHTMLContext.Provider
149
159
  );
150
160
  let chunks = [];
151
161
  const writable = new import_stream.Writable();
@@ -155,7 +165,10 @@ function createMarkupGenerator(opts) {
155
165
  };
156
166
  writable.on("finish", async () => {
157
167
  let html = Buffer.concat(chunks).toString("utf8");
158
- html += await getGenerateStaticHTML(serverInsertedHTMLCallbacks);
168
+ const serverHTML = getGenerateStaticHTML(serverInsertedHTMLCallbacks);
169
+ if (serverHTML) {
170
+ html = html.replace(/<\/head>/, `${serverHTML}</head>`);
171
+ }
159
172
  if (opts.helmetContext) {
160
173
  html = html.replace(
161
174
  /(<\/head>)/,
@@ -187,8 +200,136 @@ function createMarkupGenerator(opts) {
187
200
  }
188
201
  function createRequestHandler(opts) {
189
202
  const jsxGeneratorDeferrer = createJSXGenerator(opts);
190
- return async function(req, res, next) {
191
- if (req.url.startsWith("/__serverLoader") && req.query.route) {
203
+ const normalizeHandlerArgs = (...args) => {
204
+ var _a, _b;
205
+ let ret;
206
+ const replaceServerHTMLScript = `<script>!function(){var e=document.getElementById("${SERVER_INSERTED_HTML}");e&&(Array.from(e.children).forEach(e=>{document.head.appendChild(e)}),e.remove())}();</script>`;
207
+ if (typeof FetchEvent !== "undefined" && args[0] instanceof FetchEvent) {
208
+ const [ev, workerOpts] = args;
209
+ const { pathname, searchParams } = new URL(ev.request.url);
210
+ ret = {
211
+ req: {
212
+ url: ev.request.url,
213
+ pathname,
214
+ headers: ev.request.headers,
215
+ query: {
216
+ route: searchParams.get("route"),
217
+ url: searchParams.get("url")
218
+ }
219
+ },
220
+ async sendServerLoader(data) {
221
+ let res = new Response(JSON.stringify(data), {
222
+ headers: {
223
+ "content-type": "application/json; charset=utf-8"
224
+ },
225
+ status: 200
226
+ });
227
+ if (workerOpts == null ? void 0 : workerOpts.modifyResponse) {
228
+ res = await workerOpts.modifyResponse(res);
229
+ }
230
+ ev.respondWith(res);
231
+ },
232
+ async sendPage(jsx) {
233
+ const [JSXProvider, serverInsertedHTMLCallbacks] = createJSXProvider(
234
+ opts.ServerInsertedHTMLContext.Provider
235
+ );
236
+ const stream = await ReactDomServer.renderToReadableStream(
237
+ import_react.default.createElement(JSXProvider, void 0, jsx.element),
238
+ {
239
+ bootstrapScripts: [jsx.manifest.assets["umi.js"] || "/umi.js"],
240
+ onError(x) {
241
+ console.error(x);
242
+ }
243
+ }
244
+ );
245
+ const transformStream = new TransformStream({
246
+ flush(controller) {
247
+ if (serverInsertedHTMLCallbacks.size) {
248
+ const serverHTML = getGenerateStaticHTML(
249
+ serverInsertedHTMLCallbacks,
250
+ { wrapper: true }
251
+ );
252
+ controller.enqueue(serverHTML);
253
+ controller.enqueue(replaceServerHTMLScript);
254
+ }
255
+ }
256
+ });
257
+ stream.pipeThrough(transformStream);
258
+ let res = new Response(stream, {
259
+ headers: {
260
+ "content-type": "text/html; charset=utf-8"
261
+ },
262
+ status: 200
263
+ });
264
+ if (workerOpts == null ? void 0 : workerOpts.modifyResponse) {
265
+ res = await workerOpts.modifyResponse(res);
266
+ }
267
+ ev.respondWith(res);
268
+ },
269
+ otherwise() {
270
+ throw new Error("no page resource");
271
+ }
272
+ };
273
+ } else {
274
+ const [req, res, next] = args;
275
+ ret = {
276
+ req: {
277
+ url: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
278
+ pathname: req.url,
279
+ headers: req.headers,
280
+ query: {
281
+ route: (_a = req.query.route) == null ? void 0 : _a.toString(),
282
+ url: (_b = req.query.url) == null ? void 0 : _b.toString()
283
+ }
284
+ },
285
+ sendServerLoader(data) {
286
+ res.status(200).json(data);
287
+ },
288
+ async sendPage(jsx) {
289
+ const [JSXProvider, serverInsertedHTMLCallbacks] = createJSXProvider(
290
+ opts.ServerInsertedHTMLContext.Provider
291
+ );
292
+ const writable = new import_stream.Writable();
293
+ res.type("html");
294
+ writable._write = (chunk, _encoding, cb) => {
295
+ res.write(chunk);
296
+ cb();
297
+ };
298
+ writable.on("finish", async () => {
299
+ if (serverInsertedHTMLCallbacks.size) {
300
+ res.write(
301
+ getGenerateStaticHTML(serverInsertedHTMLCallbacks, {
302
+ wrapper: true
303
+ })
304
+ );
305
+ res.write(replaceServerHTMLScript);
306
+ }
307
+ res.end();
308
+ });
309
+ const stream = ReactDomServer.renderToPipeableStream(
310
+ import_react.default.createElement(JSXProvider, void 0, jsx.element),
311
+ {
312
+ bootstrapScripts: [jsx.manifest.assets["umi.js"] || "/umi.js"],
313
+ onShellReady() {
314
+ stream.pipe(writable);
315
+ },
316
+ onError(x) {
317
+ console.error(x);
318
+ }
319
+ }
320
+ );
321
+ },
322
+ otherwise: next
323
+ };
324
+ }
325
+ return ret;
326
+ };
327
+ return async function unifiedRequestHandler(...args) {
328
+ let jsx;
329
+ const { req, sendServerLoader, sendPage, otherwise } = normalizeHandlerArgs(
330
+ ...args
331
+ );
332
+ if (req.pathname.startsWith("/__serverLoader") && req.query.route && req.query.url) {
192
333
  const serverLoaderRequest = new Request(req.query.url, {
193
334
  headers: req.headers
194
335
  });
@@ -197,38 +338,27 @@ function createRequestHandler(opts) {
197
338
  routesWithServerLoader: opts.routesWithServerLoader,
198
339
  serverLoaderArgs: { request: serverLoaderRequest }
199
340
  });
200
- res.status(200).json(data);
201
- return;
341
+ await sendServerLoader(data);
342
+ } else if (jsx = await jsxGeneratorDeferrer(req.pathname, {
343
+ request: new Request(req.url, {
344
+ headers: req.headers
345
+ })
346
+ })) {
347
+ await sendPage(jsx);
348
+ } else {
349
+ await otherwise();
202
350
  }
203
- const fullUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
204
- const request = new Request(fullUrl, {
205
- headers: req.headers
206
- });
207
- const jsx = await jsxGeneratorDeferrer(req.url, { request });
208
- if (!jsx)
209
- return next();
210
- const writable = new import_stream.Writable();
211
- writable._write = (chunk, _encoding, next2) => {
212
- res.write(chunk);
213
- next2();
214
- };
215
- writable.on("finish", async () => {
216
- res.write(await getGenerateStaticHTML());
217
- res.end();
218
- });
219
- const stream = await ReactDomServer.renderToPipeableStream(jsx.element, {
220
- bootstrapScripts: [jsx.manifest.assets["umi.js"] || "/umi.js"],
221
- onShellReady() {
222
- stream.pipe(writable);
223
- },
224
- onError(x) {
225
- console.error(x);
226
- }
227
- });
228
351
  };
229
352
  }
230
353
  function createUmiHandler(opts) {
354
+ let isWarned = false;
231
355
  return async function(req, params) {
356
+ if (!isWarned) {
357
+ console.warn(
358
+ "[umi] `renderRoot` is deprecated, please use `requestHandler` instead"
359
+ );
360
+ isWarned = true;
361
+ }
232
362
  const jsxGeneratorDeferrer = createJSXGenerator({
233
363
  ...opts,
234
364
  ...params
@@ -247,7 +377,14 @@ function createUmiHandler(opts) {
247
377
  };
248
378
  }
249
379
  function createUmiServerLoader(opts) {
380
+ let isWarned = false;
250
381
  return async function(req) {
382
+ if (!isWarned) {
383
+ console.warn(
384
+ "[umi] `serverLoader` is deprecated, please use `requestHandler` instead"
385
+ );
386
+ isWarned = true;
387
+ }
251
388
  const query = Object.fromEntries(new URL(req.url).searchParams);
252
389
  const serverLoaderRequest = new Request(query.url, {
253
390
  headers: req.headers
@@ -296,19 +433,21 @@ async function executeLoader(params) {
296
433
  return mod.serverLoader(serverLoaderArgs);
297
434
  }
298
435
  async function executeMetadataLoader(params) {
299
- const {
300
- routesWithServerLoader,
301
- routeKey,
302
- serverLoaderArgs,
303
- serverLoaderData
304
- } = params;
436
+ const { routesWithServerLoader, routeKey, serverLoaderData } = params;
305
437
  const mod = await routesWithServerLoader[routeKey]();
306
438
  if (!mod.serverLoader || typeof mod.serverLoader !== "function") {
307
439
  return;
308
440
  }
309
- return mod.metadataLoader(
310
- serverLoaderData,
311
- serverLoaderArgs
441
+ const result = mod.metadataLoader(
442
+ serverLoaderData
443
+ );
444
+ return ["title", "description", "keywords", "lang", "metas"].reduce(
445
+ (acc, key) => {
446
+ if (Object.prototype.hasOwnProperty.call(result, key))
447
+ acc[key] = result[key];
448
+ return acc;
449
+ },
450
+ {}
312
451
  );
313
452
  }
314
453
  // Annotate the CommonJS export names for ESM import in node:
package/dist/types.d.ts CHANGED
@@ -1,3 +1,29 @@
1
+ export interface IOpts {
2
+ base: string;
3
+ routes: Record<string, {
4
+ path: string;
5
+ file: string;
6
+ id: string;
7
+ parentId?: string;
8
+ }>;
9
+ links?: Record<string, string>[];
10
+ metas?: Record<string, string>[];
11
+ styles?: (Record<string, string> | string)[];
12
+ favicons?: string[];
13
+ title?: string;
14
+ headScripts?: (Record<string, string> | string)[];
15
+ scripts?: (Record<string, string> | string)[];
16
+ mountElementId?: string;
17
+ esmScript?: boolean;
18
+ modifyHTML?: (html: string, args: {
19
+ path?: string;
20
+ }) => Promise<string>;
21
+ historyType?: 'hash' | 'browser';
22
+ }
23
+ export declare type IUserExtraRoute = string | {
24
+ path: string;
25
+ prerender: boolean;
26
+ };
1
27
  export interface IRoute {
2
28
  id: string;
3
29
  path?: string;
@@ -29,6 +55,24 @@ export interface IMetadata {
29
55
  */
30
56
  lang?: string;
31
57
  metas?: IMetaTag[];
58
+ headScripts?: (Record<string, string> | string)[];
59
+ links?: Record<string, string>[];
60
+ styles?: string[];
61
+ favicons?: string[];
62
+ scripts?: (Record<string, string> | string)[];
63
+ [key: string]: any;
64
+ }
65
+ export interface ITplOpts {
66
+ title?: string;
67
+ description?: string;
68
+ keywords?: string[];
69
+ metas?: IMetaTag[];
70
+ headScripts?: (Record<string, string> | string)[];
71
+ links?: Record<string, string>[];
72
+ styles?: string[];
73
+ favicons?: string[];
74
+ scripts?: (Record<string, string> | string)[];
75
+ [key: string]: any;
32
76
  }
33
77
  export declare type MetadataLoader<T = any> = (serverLoaderData: T, req?: IServerLoaderArgs) => LoaderReturn<IMetadata>;
34
78
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umijs/server",
3
- "version": "4.0.0-canary.20240321.1",
3
+ "version": "4.0.0-canary.20240402.1",
4
4
  "description": "@umijs/server",
5
5
  "homepage": "https://github.com/umijs/umi/tree/master/packages/server#readme",
6
6
  "bugs": "https://github.com/umijs/umi/issues",
@@ -19,7 +19,7 @@
19
19
  "react": "18.1.0",
20
20
  "react-dom": "18.1.0",
21
21
  "react-router-dom": "6.3.0",
22
- "@umijs/bundler-utils": "4.0.0-canary.20240321.1"
22
+ "@umijs/bundler-utils": "4.0.0-canary.20240402.1"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"