@umijs/server 4.0.0-rc.7 → 4.0.0

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/index.d.ts CHANGED
@@ -1 +1,2 @@
1
+ export { createServerRoutes } from './routes';
1
2
  export * from './server';
package/dist/index.js CHANGED
@@ -14,4 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createServerRoutes = void 0;
18
+ var routes_1 = require("./routes");
19
+ Object.defineProperty(exports, "createServerRoutes", { enumerable: true, get: function () { return routes_1.createServerRoutes; } });
17
20
  __exportStar(require("./server"), exports);
package/dist/server.d.ts CHANGED
@@ -10,7 +10,8 @@ export interface IOpts {
10
10
  links?: Record<string, string>[];
11
11
  metas?: Record<string, string>[];
12
12
  styles?: (Record<string, string> | string)[];
13
- favicon?: string;
13
+ favicons?: string[];
14
+ title?: string;
14
15
  headScripts?: (Record<string, string> | string)[];
15
16
  scripts?: (Record<string, string> | string)[];
16
17
  mountElementId?: string;
@@ -18,6 +19,7 @@ export interface IOpts {
18
19
  modifyHTML?: (html: string, args: {
19
20
  path?: string;
20
21
  }) => Promise<string>;
22
+ historyType?: 'hash' | 'browser';
21
23
  }
22
24
  export declare function getMarkup(opts: Omit<IOpts, 'routes'> & {
23
25
  path?: string;
package/dist/server.js CHANGED
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
4
  };
@@ -15,54 +6,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
6
  exports.createRequestHandler = exports.getMarkup = void 0;
16
7
  const react_1 = __importDefault(require("react"));
17
8
  const server_1 = __importDefault(require("react-dom/server"));
18
- const react_router_dom_1 = require("react-router-dom");
19
- const routes_1 = require("./routes");
9
+ // import { matchRoutes } from 'react-router-dom';
10
+ // import { createServerRoutes } from './routes';
20
11
  const scripts_1 = require("./scripts");
21
12
  const styles_1 = require("./styles");
22
- function getMarkup(opts) {
23
- return __awaiter(this, void 0, void 0, function* () {
24
- // TODO: use real component
25
- let markup = server_1.default.renderToString(react_1.default.createElement('div', { id: opts.mountElementId || 'root' }));
26
- function propsToString(opts) {
27
- return Object.keys(opts.props)
28
- .filter((key) => !(opts.filters || []).includes(key))
29
- .map((key) => `${key}=${JSON.stringify(opts.props[key])}`)
30
- .join(' ');
31
- }
32
- function getScriptContent(script) {
33
- const attrs = propsToString({
34
- props: script,
35
- filters: ['src', 'content'],
36
- });
37
- return script.src
38
- ? `<script${opts.esmScript ? ' type="module"' : ''} ${attrs} src="${script.src}"></script>`
39
- : `<script${opts.esmScript ? ' type="module"' : ''} ${attrs}>${script.content}</script>`;
40
- }
41
- function getStyleContent(style) {
42
- const attrs = propsToString({
43
- props: style,
44
- filters: ['src', 'content'],
45
- });
46
- return style.src
47
- ? `<link rel="stylesheet" ${attrs} href="${style.src}" />`
48
- : `<style ${attrs}>${style.content}</style>`;
49
- }
50
- function getTagContent(opts) {
51
- const attrs = propsToString({
52
- props: opts.attrs,
53
- });
54
- return `<${opts.tagName} ${attrs} />`;
55
- }
56
- const favicon = opts.favicon
57
- ? `<link rel="shortcut icon" href="${opts.favicon}">`
58
- : '';
59
- const metas = (opts.metas || []).map((meta) => getTagContent({ attrs: meta, tagName: 'meta' }));
60
- const links = (opts.links || []).map((link) => getTagContent({ attrs: link, tagName: 'link' }));
61
- const styles = (0, styles_1.normalizeStyles)(opts.styles || []).map(getStyleContent);
62
- const headScripts = (0, scripts_1.normalizeScripts)(opts.headScripts || []).map(getScriptContent);
63
- const scripts = (0, scripts_1.normalizeScripts)(opts.scripts || []).map(getScriptContent);
64
- markup = [
65
- `<!DOCTYPE html>
13
+ async function getMarkup(opts) {
14
+ // TODO: use real component
15
+ let markup = server_1.default.renderToString(react_1.default.createElement('div', { id: opts.mountElementId || 'root' }));
16
+ function propsToString(opts) {
17
+ return Object.keys(opts.props)
18
+ .filter((key) => !(opts.filters || []).includes(key))
19
+ .map((key) => `${key}=${JSON.stringify(opts.props[key])}`)
20
+ .join(' ');
21
+ }
22
+ function getScriptContent(script) {
23
+ const attrs = propsToString({
24
+ props: script,
25
+ filters: ['src', 'content'],
26
+ });
27
+ return script.src
28
+ ? `<script${opts.esmScript ? ' type="module"' : ''} ${attrs} src="${script.src}"></script>`
29
+ : `<script${opts.esmScript ? ' type="module"' : ''} ${attrs}>${script.content}</script>`;
30
+ }
31
+ function getStyleContent(style) {
32
+ const attrs = propsToString({
33
+ props: style,
34
+ filters: ['src', 'content'],
35
+ });
36
+ return style.src
37
+ ? `<link rel="stylesheet" ${attrs} href="${style.src}" />`
38
+ : `<style ${attrs}>${style.content}</style>`;
39
+ }
40
+ function getTagContent(opts) {
41
+ const attrs = propsToString({
42
+ props: opts.attrs,
43
+ });
44
+ return `<${opts.tagName} ${attrs} />`;
45
+ }
46
+ const favicons = [];
47
+ if (Array.isArray(opts.favicons)) {
48
+ opts.favicons.forEach((e) => {
49
+ favicons.push(`<link rel="shortcut icon" href="${e}">`);
50
+ });
51
+ }
52
+ const title = opts.title ? `<title>${opts.title}</title>` : '';
53
+ const metas = (opts.metas || []).map((meta) => getTagContent({ attrs: meta, tagName: 'meta' }));
54
+ const links = (opts.links || []).map((link) => getTagContent({ attrs: link, tagName: 'link' }));
55
+ const styles = (0, styles_1.normalizeStyles)(opts.styles || []).map(getStyleContent);
56
+ const headScripts = (0, scripts_1.normalizeScripts)(opts.headScripts || []).map(getScriptContent);
57
+ const scripts = (0, scripts_1.normalizeScripts)(opts.scripts || []).map(getScriptContent);
58
+ markup = [
59
+ `<!DOCTYPE html>
66
60
  <html>
67
61
  <head>
68
62
  <meta charset="UTF-8" />
@@ -71,43 +65,50 @@ function getMarkup(opts) {
71
65
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
72
66
  />
73
67
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />`,
74
- metas.join('\n'),
75
- favicon,
76
- links.join('\n'),
77
- styles.join('\n'),
78
- headScripts.join('\n'),
79
- `</head>
68
+ metas.join('\n'),
69
+ favicons.join('\n'),
70
+ title,
71
+ links.join('\n'),
72
+ styles.join('\n'),
73
+ headScripts.join('\n'),
74
+ `</head>
80
75
  <body>`,
81
- markup,
82
- scripts.join('\n'),
83
- `</body>
76
+ markup,
77
+ scripts.join('\n'),
78
+ `</body>
84
79
  </html>`,
85
- ]
86
- .filter(Boolean)
87
- .join('\n');
88
- if (opts.modifyHTML) {
89
- markup = yield opts.modifyHTML(markup, { path: opts.path });
90
- }
91
- return markup;
92
- });
80
+ ]
81
+ .filter(Boolean)
82
+ .join('\n');
83
+ if (opts.modifyHTML) {
84
+ markup = await opts.modifyHTML(markup, { path: opts.path });
85
+ }
86
+ return markup;
93
87
  }
94
88
  exports.getMarkup = getMarkup;
95
89
  function createRequestHandler(opts) {
96
- return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
97
- // 匹配路由,不匹配走 next()
98
- // TODO: cache
99
- const routes = (0, routes_1.createServerRoutes)({
100
- routesById: opts.routes,
101
- });
102
- const matches = (0, react_router_dom_1.matchRoutes)(routes, req.path, opts.base);
103
- if (matches) {
90
+ return async (req, res, next) => {
91
+ var _a;
92
+ if (opts.historyType === 'browser' &&
93
+ opts.base !== '/' &&
94
+ req.path === '/') {
95
+ // 如果是 browser,并且配置了非 / base,访问 / 时 redirect 到 base 路径
96
+ res.redirect(opts.base);
97
+ }
98
+ else if ((_a = req.headers.accept) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
99
+ // 匹配路由,不匹配走 next()
100
+ // const routes = createServerRoutes({
101
+ // routesById: opts.routes,
102
+ // });
103
+ // const matches = matchRoutes(routes, req.path, opts.base);
104
+ // 其他接受 HTML 的请求都兜底返回 HTML
104
105
  res.set('Content-Type', 'text/html');
105
- const markup = yield getMarkup(Object.assign(Object.assign({}, opts), { path: req.path }));
106
+ const markup = await getMarkup({ ...opts, path: req.path });
106
107
  res.end(markup);
107
108
  }
108
109
  else {
109
110
  next();
110
111
  }
111
- });
112
+ };
112
113
  }
113
114
  exports.createRequestHandler = createRequestHandler;
package/dist/ssr.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ interface RouteLoaders {
2
+ [key: string]: () => Promise<any>;
3
+ }
4
+ interface CreateRequestHandlerOptions {
5
+ routesWithServerLoader: RouteLoaders;
6
+ PluginManager: any;
7
+ manifest: (() => {
8
+ assets: Record<string, string>;
9
+ }) | {
10
+ assets: Record<string, string>;
11
+ };
12
+ getPlugins: () => any;
13
+ getValidKeys: () => any;
14
+ getRoutes: (PluginManager: any) => any;
15
+ getClientRootComponent: (PluginManager: any) => any;
16
+ }
17
+ export default function createRequestHandler(opts: CreateRequestHandlerOptions): (req: any, res: any, next: any) => Promise<any>;
18
+ export {};
package/dist/ssr.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const server_1 = require("react-dom/server");
4
+ const react_router_dom_1 = require("react-router-dom");
5
+ function createRequestHandler(opts) {
6
+ return async function (req, res, next) {
7
+ const { routesWithServerLoader, PluginManager, getPlugins, getValidKeys, getRoutes, } = opts;
8
+ // 切换路由场景下,会通过此 API 执行 server loader
9
+ if (req.url.startsWith('/__serverLoader') && req.query.route) {
10
+ const data = await executeLoader(req.query.route, routesWithServerLoader);
11
+ res.status(200).json(data);
12
+ return;
13
+ }
14
+ const pluginManager = PluginManager.create({
15
+ plugins: getPlugins(),
16
+ validKeys: getValidKeys(),
17
+ });
18
+ const { routes, routeComponents } = await getRoutes(pluginManager);
19
+ const matches = matchRoutesForSSR(req.url, routes);
20
+ if (matches.length === 0) {
21
+ return next();
22
+ }
23
+ const loaderData = {};
24
+ await Promise.all(matches
25
+ .filter((id) => routes[id].hasServerLoader)
26
+ .map((id) => new Promise(async (resolve) => {
27
+ loaderData[id] = await executeLoader(id, routesWithServerLoader);
28
+ resolve();
29
+ })));
30
+ const manifest = typeof opts.manifest === 'function' ? opts.manifest() : opts.manifest;
31
+ const context = {
32
+ routes,
33
+ routeComponents,
34
+ pluginManager,
35
+ location: req.url,
36
+ manifest,
37
+ loaderData,
38
+ };
39
+ const jsx = await opts.getClientRootComponent(context);
40
+ const stream = (0, server_1.renderToPipeableStream)(jsx, {
41
+ bootstrapScripts: [manifest.assets['umi.js'] || '/umi.js'],
42
+ onShellReady() {
43
+ res.setHeader('Content-type', 'text/html');
44
+ stream.pipe(res);
45
+ },
46
+ onError(x) {
47
+ console.error(x);
48
+ },
49
+ });
50
+ };
51
+ }
52
+ exports.default = createRequestHandler;
53
+ function matchRoutesForSSR(reqUrl, routesById) {
54
+ var _a;
55
+ return (((_a = (0, react_router_dom_1.matchRoutes)(createClientRoutes({ routesById }), reqUrl)) === null || _a === void 0 ? void 0 : _a.map((route) => route.route.id)) || []);
56
+ }
57
+ function createClientRoutes(opts) {
58
+ const { routesById, parentId } = opts;
59
+ return Object.keys(routesById)
60
+ .filter((id) => routesById[id].parentId === parentId)
61
+ .map((id) => {
62
+ const route = createClientRoute(routesById[id]);
63
+ const children = createClientRoutes({
64
+ routesById,
65
+ parentId: route.id,
66
+ });
67
+ if (children.length > 0) {
68
+ // @ts-ignore
69
+ route.children = children;
70
+ }
71
+ return route;
72
+ });
73
+ }
74
+ function createClientRoute(route) {
75
+ const { id, path, index } = route;
76
+ return {
77
+ id,
78
+ path,
79
+ index,
80
+ };
81
+ }
82
+ async function executeLoader(routeKey, routesWithServerLoader) {
83
+ const mod = await routesWithServerLoader[routeKey]();
84
+ if (!mod.serverLoader || typeof mod.serverLoader !== 'function') {
85
+ return;
86
+ }
87
+ // TODO: 处理错误场景
88
+ return await mod.serverLoader();
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umijs/server",
3
- "version": "4.0.0-rc.7",
3
+ "version": "4.0.0",
4
4
  "description": "@umijs/server",
5
5
  "homepage": "https://github.com/umijs/umi-next/tree/master/packages/server#readme",
6
6
  "bugs": "https://github.com/umijs/umi-next/issues",
@@ -16,15 +16,15 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "build": "pnpm tsc",
19
- "build:deps": "pnpm esno ../../scripts/bundleDeps.ts",
19
+ "build:deps": "umi-scripts bundleDeps",
20
20
  "dev": "pnpm build -- --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@umijs/bundler-utils": "4.0.0-rc.6",
23
+ "@umijs/bundler-utils": "4.0.0",
24
24
  "history": "5.3.0",
25
- "react": "17.0.2",
26
- "react-dom": "17.0.2",
27
- "react-router-dom": "6.2.2"
25
+ "react": "18.1.0",
26
+ "react-dom": "18.1.0",
27
+ "react-router-dom": "6.3.0"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"