Skip to content

Commit a1fda3c

Browse files
committed
feat: support dynamic router preload
1 parent 737e031 commit a1fda3c

22 files changed

+354
-220
lines changed

package.json

+16-16
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
},
1717
"dependencies": {
1818
"@chakra-ui/react": "^2.2.1",
19-
"@emotion/react": "^11.9.0",
19+
"@emotion/react": "^11.9.3",
2020
"@emotion/server": "^11.4.0",
21-
"@emotion/styled": "^11.8.1",
21+
"@emotion/styled": "^11.9.3",
2222
"axios": "^0.27.2",
2323
"chalk": "4",
2424
"compression": "^1.7.4",
@@ -28,13 +28,13 @@
2828
"express": "^4.18.1",
2929
"express-session": "^1.17.3",
3030
"framer-motion": "^6.3.11",
31-
"immer": "^9.0.14",
31+
"immer": "^9.0.15",
3232
"js-cookie": "^3.0.1",
3333
"lodash": "^4.17.21",
3434
"multer": "^1.4.4",
3535
"pretty-error": "^4.0.0",
36-
"react": "^18.1.0",
37-
"react-dom": "^18.1.0",
36+
"react": "^18.2.0",
37+
"react-dom": "^18.2.0",
3838
"react-helmet-async": "^1.3.0",
3939
"react-intl": "^6.0.4",
4040
"react-redux": "^8.0.2",
@@ -49,7 +49,7 @@
4949
},
5050
"devDependencies": {
5151
"@babel/cli": "^7.17.10",
52-
"@babel/core": "^7.18.2",
52+
"@babel/core": "^7.18.5",
5353
"@babel/plugin-proposal-class-properties": "^7.17.12",
5454
"@babel/plugin-proposal-decorators": "^7.18.2",
5555
"@babel/plugin-proposal-export-default-from": "^7.17.12",
@@ -58,7 +58,7 @@
5858
"@babel/plugin-proposal-private-methods": "^7.17.12",
5959
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
6060
"@babel/plugin-transform-modules-commonjs": "^7.18.2",
61-
"@babel/plugin-transform-runtime": "^7.18.2",
61+
"@babel/plugin-transform-runtime": "^7.18.5",
6262
"@babel/preset-env": "^7.18.2",
6363
"@babel/preset-react": "^7.17.12",
6464
"@babel/preset-typescript": "^7.17.12",
@@ -72,20 +72,20 @@
7272
"@types/js-cookie": "^3.0.2",
7373
"@types/lodash": "^4.14.182",
7474
"@types/multer": "^1.4.7",
75-
"@types/node": "^17.0.42",
75+
"@types/node": "^18.0.0",
7676
"@types/react": "^18.0.12",
7777
"@types/react-dom": "^18.0.5",
7878
"@types/webpack": "^5.28.0",
7979
"@types/webpack-env": "^1.17.0",
8080
"@types/webpack-hot-middleware": "^2.25.6",
81-
"@typescript-eslint/eslint-plugin": "^5.27.1",
82-
"@typescript-eslint/parser": "^5.27.1",
81+
"@typescript-eslint/eslint-plugin": "^5.28.0",
82+
"@typescript-eslint/parser": "^5.28.0",
8383
"autoprefixer": "^10.4.7",
8484
"babel-jest": "^28.1.1",
8585
"babel-loader": "^8.2.5",
8686
"babel-plugin-import": "^1.13.5",
8787
"clean-webpack-plugin": "^4.0.0",
88-
"core-js": "^3.22.8",
88+
"core-js": "^3.23.1",
8989
"cross-env": "^7.0.3",
9090
"css-loader": "^6.7.1",
9191
"css-minimizer-webpack-plugin": "^4.0.0",
@@ -96,25 +96,25 @@
9696
"eslint-plugin-jsx-a11y": "^6.5.1",
9797
"eslint-plugin-prettier": "^4.0.0",
9898
"eslint-plugin-react": "^7.30.0",
99-
"eslint-plugin-react-hooks": "^4.5.0",
99+
"eslint-plugin-react-hooks": "^4.6.0",
100100
"eslint-webpack-plugin": "^3.1.1",
101101
"file-loader": "^6.2.0",
102102
"fork-ts-checker-webpack-plugin": "^7.2.11",
103103
"identity-obj-proxy": "^3.0.0",
104104
"jest": "^28.1.1",
105-
"mini-css-extract-plugin": "^2.6.0",
105+
"mini-css-extract-plugin": "^2.6.1",
106106
"nodemon": "^2.0.15",
107107
"postcss": "^8.4.14",
108108
"postcss-loader": "^7.0.0",
109-
"prettier": "^2.6.2",
110-
"react-refresh": "^0.13.0",
109+
"prettier": "^2.7.0",
110+
"react-refresh": "^0.14.0",
111111
"sass": "^1.52.3",
112112
"sass-loader": "^13.0.0",
113113
"style-loader": "^3.3.1",
114114
"thread-loader": "^3.0.4",
115115
"typescript": "^4.7.3",
116116
"webpack-bundle-analyzer": "^4.5.0",
117-
"webpack-cli": "^4.9.2",
117+
"webpack-cli": "^4.10.0",
118118
"webpack-dev-middleware": "^5.3.3",
119119
"webpack-dev-server": "^4.9.2",
120120
"webpack-hot-middleware": "^2.25.1",

script/dynamic.js

+28
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ class DynamicRouter {
1313
getRouterConfig = (prePath, dirName) => {
1414
return new Promise((resolve) => {
1515
const routes = [];
16+
let indexPath = 0;
1617
let dynamicPath = 0;
18+
let fallbackPath = 0;
19+
const chunkPrePath = prePath.replaceAll("/", "-");
1720
fs.promises
1821
.readdir(dirName, { withFileTypes: true })
1922
.then((files) =>
@@ -29,11 +32,30 @@ class DynamicRouter {
2932
dynamicPath++;
3033
const [, params] = Array.from(dynamicPathReg.exec(fileName));
3134
config.path = `${prePath}:${params}`;
35+
config.chunkName = `${chunkPrePath}-dynamic_${params}`.toLowerCase();
3236
} else {
3337
throw new Error(`file router dynamicPath duplicate`);
3438
}
39+
} else if (fileName.toLowerCase() === "index") {
40+
// 默认路由, 同一级只应该有一个
41+
if (indexPath === 0) {
42+
indexPath++;
43+
config.path = `${prePath}`;
44+
config.chunkName = `${chunkPrePath}-index`.toLowerCase();
45+
} else {
46+
throw new Error("file router default path duplicate");
47+
}
48+
} else if (fileName === "404") {
49+
if (prePath === "/") {
50+
fallbackPath++;
51+
config.path = `${prePath}*`;
52+
config.chunkName = `${chunkPrePath}-${fileName}`.toLowerCase();
53+
} else {
54+
throw new Error(`can not add 404 page on the ${prePath}`);
55+
}
3556
} else {
3657
config.path = `${prePath}${fileName}`;
58+
config.chunkName = `${chunkPrePath}-${fileName}`.toLowerCase();
3759
}
3860
config.componentPath = `${prePath.slice(1)}${fileName}`;
3961
// 文件名字重复
@@ -54,6 +76,12 @@ class DynamicRouter {
5476
// 如果存在动态路由 进行排序放在当前层级最后面
5577
routes.sort((_, t) => (/^\[(.*)\]$/.test(t.path) ? -1 : 0));
5678
}
79+
if (indexPath === 1) {
80+
routes.sort((_, t) => (t.path.endsWith("/") ? 1 : 0));
81+
}
82+
if (fallbackPath === 1) {
83+
routes.sort((_, b) => (b.path === "/*" ? -1 : 0));
84+
}
5785
})
5886
.then(() => resolve(routes))
5987
.catch((e) => console.log(chalk.red(`file router error, ${e.toString()}`)));

src/components/Layout.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ export const Layout: PreLoadComponentType = () => {
1313
<div className={style.container}>
1414
<Header />
1515
<main className={style.content}>
16-
<Suspense>
17-
<Outlet />
18-
</Suspense>
16+
<Outlet />
1917
<hr />
2018
</main>
2119
<Suspense>

src/components/UI.tsx

-7
This file was deleted.

src/module/Bar/index.module.scss

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.c {
2+
border: 1px solid red;
3+
color: blue;
4+
}

src/module/Bar/index.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import style from "./index.module.scss";
2+
3+
export default function Index() {
4+
return <div className={style.c}>this is module Bar file</div>;
5+
}

src/pages/ChakraUi.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function ChakraComponent() {
2121
</Box>
2222
</Fade>
2323

24-
<Menu>
24+
<Menu isLazy>
2525
{({ isOpen }) => (
2626
<>
2727
<MenuButton isActive={isOpen} as={Button} rightIcon={<span>{1234}</span>}>

src/pages/Home.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
import { lazy, Suspense } from "react";
2+
3+
const BB = lazy(() => import("../module/Bar"));
4+
15
export default function Home() {
2-
return <div>home page</div>;
6+
return (
7+
<div>
8+
home
9+
<BB />
10+
<Suspense></Suspense>
11+
</div>
12+
);
313
}

src/pages/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Index() {
2+
return <div>home page</div>;
3+
}

src/router/dynamicRoutes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
/* do not editor this template */
33
import type { DynamicRouteConfig } from "types/router";
44

5-
export const dynamicRouteConfig: DynamicRouteConfig[] = [{"path":"/404","componentPath":"404"},{"path":"/ChakraUi","componentPath":"ChakraUi"},{"path":"/Great","componentPath":"Great"},{"path":"/Home","componentPath":"Home"},{"path":"/I18n","componentPath":"I18n"},{"path":"/Tcc","componentPath":"Tcc"},{"path":"/Tdd","componentPath":"Tdd"},{"path":"/Foo/:id","componentPath":"Foo/:id"},{"path":"/Foo/Gff","componentPath":"Foo/Gff"}];
5+
export const dynamicRouteConfig: DynamicRouteConfig[] = [{"path":"/ChakraUi","chunkName":"--chakraui","componentPath":"ChakraUi"},{"path":"/Great","chunkName":"--great","componentPath":"Great"},{"path":"/Home","chunkName":"--home","componentPath":"Home"},{"path":"/I18n","chunkName":"--i18n","componentPath":"I18n"},{"path":"/Tcc","chunkName":"--tcc","componentPath":"Tcc"},{"path":"/Tdd","chunkName":"--tdd","componentPath":"Tdd"},{"path":"/","chunkName":"--index","componentPath":"index"},{"path":"/Foo/:id","chunkName":"-foo--dynamic_id","componentPath":"Foo/:id"},{"path":"/Foo/Gff","chunkName":"-foo--gff","componentPath":"Foo/Gff"},{"path":"/*","chunkName":"--404","componentPath":"404"}];

src/router/routes.tsx

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { lazy } from "react";
22

33
import { Layout } from "components/Layout";
4-
import { UI } from "components/UI";
54
import { AutoInjectInitialProps } from "utils/preLoad";
65

76
import { dynamicRouteConfig } from "./dynamicRoutes";
@@ -13,25 +12,23 @@ const baseRouter: PreLoadRouteConfig = {
1312
element: <Layout />,
1413
};
1514

16-
const routes: PreLoadRouteConfig[] = [{ path: "/", element: <UI /> }];
17-
1815
const dynamicRoutes = dynamicRouteConfig
1916
.map((it) => ({
20-
path: it.componentPath === "404" ? "/*" : it.path,
17+
path: it.path,
2118
preLoad: () =>
2219
import(
2320
/* webpackMode: "lazy" */
2421
/* webpackPrefetch: true */
2522
/* webpackPreload: true */
26-
/* webpackChunkName: "[request]" */
23+
/* webpackChunkName: "page-[request]" */
2724
`../pages/${it.componentPath}`
2825
),
2926
component: lazy(() =>
3027
import(
3128
/* webpackMode: "lazy" */
3229
/* webpackPrefetch: true */
3330
/* webpackPreload: true */
34-
/* webpackChunkName: "[request]" */
31+
/* webpackChunkName: "page-[request]" */
3532
`../pages/${it.componentPath}`
3633
).then((module) => ({
3734
default: AutoInjectInitialProps(module.default),
@@ -40,8 +37,6 @@ const dynamicRoutes = dynamicRouteConfig
4037
}))
4138
.map(({ path, component: Component, preLoad }) => ({ path: path, preLoad, element: <Component /> }));
4239

43-
baseRouter.children = filter(routes.concat(dynamicRoutes) || [])
44-
.sort((a) => (a.path === "/*" ? 1 : 0))
45-
.sort((_, b) => (b.path === "/*" ? -1 : 0));
40+
baseRouter.children = filter(dynamicRoutes || []);
4641

4742
export const allRoutes = [baseRouter];

src/server/middleware/renderError.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ const renderError: RenderErrorType = ({ res, code, e }) =>
1616
<hr />
1717
<div style={{ fontSize: "18px", color: "red" }}>
1818
error code:
19-
<b>${code}</b>
19+
<b> {code}</b>
2020
<br />
2121
<br />
2222
<pre style={{ fontSize: "18px", color: "red" }}>{e.stack}</pre>
2323
</div>
24-
<script dangerouslySetInnerHTML={{ __html: `console.error(${pre.render(e, true, false)})` }} />
24+
<script dangerouslySetInnerHTML={{ __html: `console.error(\`${pre.render(e, true, false)}\`)` }} />
2525
</HTML>
2626
)
2727
);

src/server/middleware/renderPage/compose.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import type { Request, Response } from "express";
44
import type { SagaStore } from "types/store";
55

6-
type BaseArgs = { req: Request; res: Response; store?: SagaStore; env?: { [p: string]: unknown }; lang?: string };
6+
type BaseArgs = { req: Request; res: Response; store?: SagaStore; env?: { [p: string]: unknown }; lang?: string; page?: string[] };
77

88
export type OverrideBase<T = unknown> = BaseArgs & T;
99

src/server/middleware/renderPage/middleware/loadStore.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const loadStore: Middleware = (next) => async (args) => {
1111
throw new ServerError(`server 初始化失败 lang: ${lang}, store: ${store}`, 500);
1212
}
1313

14-
const { error, redirect, cookies } = (await preLoad(allRoutes, req.path, new URLSearchParams(req.url.split("?")[1]), store)) || {};
14+
const { error, redirect, cookies, page } = (await preLoad(allRoutes, req.path, new URLSearchParams(req.url.split("?")[1]), store)) || {};
15+
16+
args.page = page;
1517

1618
if (cookies) {
1719
Object.keys(cookies).forEach((key) => {

src/server/middleware/renderPage/renderSSR/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { targetRender as ChakraTargetRender } from "./renderChakra";
77

88
import type { AnyAction } from "../compose";
99

10-
const targetRender: AnyAction = async ({ req, res, store, lang, env }) => {
11-
if (!store || !lang || !env) {
10+
const targetRender: AnyAction = async ({ req, res, store, lang, env, page }) => {
11+
if (!store || !lang || !env || !page) {
1212
throw new ServerError("初始化失败", 500);
1313
} else {
14-
return ChakraTargetRender({ req, res, store, lang, env });
14+
return ChakraTargetRender({ req, res, store, lang, env, page });
1515
}
1616
};
1717

src/server/middleware/renderPage/renderSSR/renderChakra.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,29 @@ import {
1515
runtimeScriptsPath,
1616
generateStyleElements,
1717
generatePreloadScriptElements,
18+
manifestDepsFile,
19+
getDynamicPagePath,
20+
dynamicPageStylesPath,
21+
dynamicPageScriptsPath,
1822
} from "utils/manifest";
1923

2024
import type { SafeAction } from "../compose";
2125

22-
export const targetRender: SafeAction = async ({ req, res, store, lang, env }) => {
26+
export const targetRender: SafeAction = async ({ req, res, store, lang, env, page }) => {
2327
const helmetContext = {};
2428

2529
const cookieStore = createCookieStorageManager("chakra-ui-color-mode", store.getState().server.cookie.data);
2630

2731
const stateFileContent = await getAllStateFileContent(manifestStateFile("client"));
2832

33+
const depsFileContent = await getAllStateFileContent(manifestDepsFile("client"));
34+
35+
const dynamicPage = getDynamicPagePath(depsFileContent, page);
36+
37+
const dynamicStylesPath = dynamicPageStylesPath(stateFileContent, dynamicPage);
38+
39+
const dynamicScriptsPath = dynamicPageScriptsPath(stateFileContent, dynamicPage);
40+
2941
const mainStyles = mainStylesPath(stateFileContent);
3042

3143
const runtimeScripts = runtimeScriptsPath(stateFileContent);
@@ -39,8 +51,8 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
3951
env={JSON.stringify(env)}
4052
lang={JSON.stringify(lang)}
4153
helmetContext={helmetContext}
42-
link={generateStyleElements(mainStyles)}
43-
preLoad={generatePreloadScriptElements(mainScripts)}
54+
link={generateStyleElements(mainStyles.concat(dynamicStylesPath))}
55+
preLoad={generatePreloadScriptElements(mainScripts.concat(runtimeScripts).concat(dynamicScriptsPath))}
4456
reduxInitialState={JSON.stringify(store.getState())}
4557
>
4658
<ChakraProvider resetCSS theme={theme} colorModeManager={cookieStore}>
@@ -54,7 +66,7 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
5466
</ChakraProvider>
5567
</HTML>,
5668
{
57-
bootstrapScripts: [...runtimeScripts, ...mainScripts],
69+
bootstrapScripts: [...runtimeScripts, ...mainScripts, ...dynamicScriptsPath],
5870
onShellReady() {
5971
// The content above all Suspense boundaries is ready.
6072
// If something errored before we started streaming, we set the error code appropriately.

src/types/router/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export interface TransformType {
1313

1414
export interface DynamicRouteConfig {
1515
path: string;
16+
chunkName: string;
1617
componentPath?: string;
1718
}

0 commit comments

Comments
 (0)