Skip to content

Commit

Permalink
feat: support dynamic router preload
Browse files Browse the repository at this point in the history
  • Loading branch information
hengwangsay committed Jun 16, 2022
1 parent 737e031 commit a1fda3c
Show file tree
Hide file tree
Showing 22 changed files with 354 additions and 220 deletions.
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
},
"dependencies": {
"@chakra-ui/react": "^2.2.1",
"@emotion/react": "^11.9.0",
"@emotion/react": "^11.9.3",
"@emotion/server": "^11.4.0",
"@emotion/styled": "^11.8.1",
"@emotion/styled": "^11.9.3",
"axios": "^0.27.2",
"chalk": "4",
"compression": "^1.7.4",
Expand All @@ -28,13 +28,13 @@
"express": "^4.18.1",
"express-session": "^1.17.3",
"framer-motion": "^6.3.11",
"immer": "^9.0.14",
"immer": "^9.0.15",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"multer": "^1.4.4",
"pretty-error": "^4.0.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-intl": "^6.0.4",
"react-redux": "^8.0.2",
Expand All @@ -49,7 +49,7 @@
},
"devDependencies": {
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.2",
"@babel/core": "^7.18.5",
"@babel/plugin-proposal-class-properties": "^7.17.12",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/plugin-proposal-export-default-from": "^7.17.12",
Expand All @@ -58,7 +58,7 @@
"@babel/plugin-proposal-private-methods": "^7.17.12",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.18.2",
"@babel/plugin-transform-runtime": "^7.18.2",
"@babel/plugin-transform-runtime": "^7.18.5",
"@babel/preset-env": "^7.18.2",
"@babel/preset-react": "^7.17.12",
"@babel/preset-typescript": "^7.17.12",
Expand All @@ -72,20 +72,20 @@
"@types/js-cookie": "^3.0.2",
"@types/lodash": "^4.14.182",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.42",
"@types/node": "^18.0.0",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.17.0",
"@types/webpack-hot-middleware": "^2.25.6",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"autoprefixer": "^10.4.7",
"babel-jest": "^28.1.1",
"babel-loader": "^8.2.5",
"babel-plugin-import": "^1.13.5",
"clean-webpack-plugin": "^4.0.0",
"core-js": "^3.22.8",
"core-js": "^3.23.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
Expand All @@ -96,25 +96,25 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.11",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.1.1",
"mini-css-extract-plugin": "^2.6.0",
"mini-css-extract-plugin": "^2.6.1",
"nodemon": "^2.0.15",
"postcss": "^8.4.14",
"postcss-loader": "^7.0.0",
"prettier": "^2.6.2",
"react-refresh": "^0.13.0",
"prettier": "^2.7.0",
"react-refresh": "^0.14.0",
"sass": "^1.52.3",
"sass-loader": "^13.0.0",
"style-loader": "^3.3.1",
"thread-loader": "^3.0.4",
"typescript": "^4.7.3",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack-cli": "^4.10.0",
"webpack-dev-middleware": "^5.3.3",
"webpack-dev-server": "^4.9.2",
"webpack-hot-middleware": "^2.25.1",
Expand Down
28 changes: 28 additions & 0 deletions script/dynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ class DynamicRouter {
getRouterConfig = (prePath, dirName) => {
return new Promise((resolve) => {
const routes = [];
let indexPath = 0;
let dynamicPath = 0;
let fallbackPath = 0;
const chunkPrePath = prePath.replaceAll("/", "-");
fs.promises
.readdir(dirName, { withFileTypes: true })
.then((files) =>
Expand All @@ -29,11 +32,30 @@ class DynamicRouter {
dynamicPath++;
const [, params] = Array.from(dynamicPathReg.exec(fileName));
config.path = `${prePath}:${params}`;
config.chunkName = `${chunkPrePath}-dynamic_${params}`.toLowerCase();
} else {
throw new Error(`file router dynamicPath duplicate`);
}
} else if (fileName.toLowerCase() === "index") {
// 默认路由, 同一级只应该有一个
if (indexPath === 0) {
indexPath++;
config.path = `${prePath}`;
config.chunkName = `${chunkPrePath}-index`.toLowerCase();
} else {
throw new Error("file router default path duplicate");
}
} else if (fileName === "404") {
if (prePath === "/") {
fallbackPath++;
config.path = `${prePath}*`;
config.chunkName = `${chunkPrePath}-${fileName}`.toLowerCase();
} else {
throw new Error(`can not add 404 page on the ${prePath}`);
}
} else {
config.path = `${prePath}${fileName}`;
config.chunkName = `${chunkPrePath}-${fileName}`.toLowerCase();
}
config.componentPath = `${prePath.slice(1)}${fileName}`;
// 文件名字重复
Expand All @@ -54,6 +76,12 @@ class DynamicRouter {
// 如果存在动态路由 进行排序放在当前层级最后面
routes.sort((_, t) => (/^\[(.*)\]$/.test(t.path) ? -1 : 0));
}
if (indexPath === 1) {
routes.sort((_, t) => (t.path.endsWith("/") ? 1 : 0));
}
if (fallbackPath === 1) {
routes.sort((_, b) => (b.path === "/*" ? -1 : 0));
}
})
.then(() => resolve(routes))
.catch((e) => console.log(chalk.red(`file router error, ${e.toString()}`)));
Expand Down
4 changes: 1 addition & 3 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ export const Layout: PreLoadComponentType = () => {
<div className={style.container}>
<Header />
<main className={style.content}>
<Suspense>
<Outlet />
</Suspense>
<Outlet />
<hr />
</main>
<Suspense>
Expand Down
7 changes: 0 additions & 7 deletions src/components/UI.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions src/module/Bar/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.c {
border: 1px solid red;
color: blue;
}
5 changes: 5 additions & 0 deletions src/module/Bar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import style from "./index.module.scss";

export default function Index() {
return <div className={style.c}>this is module Bar file</div>;
}
2 changes: 1 addition & 1 deletion src/pages/ChakraUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function ChakraComponent() {
</Box>
</Fade>

<Menu>
<Menu isLazy>
{({ isOpen }) => (
<>
<MenuButton isActive={isOpen} as={Button} rightIcon={<span>{1234}</span>}>
Expand Down
12 changes: 11 additions & 1 deletion src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import { lazy, Suspense } from "react";

const BB = lazy(() => import("../module/Bar"));

export default function Home() {
return <div>home page</div>;
return (
<div>
home
<BB />
<Suspense></Suspense>
</div>
);
}
3 changes: 3 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Index() {
return <div>home page</div>;
}
2 changes: 1 addition & 1 deletion src/router/dynamicRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/* do not editor this template */
import type { DynamicRouteConfig } from "types/router";

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"}];
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"}];
13 changes: 4 additions & 9 deletions src/router/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { lazy } from "react";

import { Layout } from "components/Layout";
import { UI } from "components/UI";
import { AutoInjectInitialProps } from "utils/preLoad";

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

const routes: PreLoadRouteConfig[] = [{ path: "/", element: <UI /> }];

const dynamicRoutes = dynamicRouteConfig
.map((it) => ({
path: it.componentPath === "404" ? "/*" : it.path,
path: it.path,
preLoad: () =>
import(
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
/* webpackChunkName: "[request]" */
/* webpackChunkName: "page-[request]" */
`../pages/${it.componentPath}`
),
component: lazy(() =>
import(
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
/* webpackChunkName: "[request]" */
/* webpackChunkName: "page-[request]" */
`../pages/${it.componentPath}`
).then((module) => ({
default: AutoInjectInitialProps(module.default),
Expand All @@ -40,8 +37,6 @@ const dynamicRoutes = dynamicRouteConfig
}))
.map(({ path, component: Component, preLoad }) => ({ path: path, preLoad, element: <Component /> }));

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

export const allRoutes = [baseRouter];
4 changes: 2 additions & 2 deletions src/server/middleware/renderError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const renderError: RenderErrorType = ({ res, code, e }) =>
<hr />
<div style={{ fontSize: "18px", color: "red" }}>
error code:
<b>${code}</b>
<b> {code}</b>
<br />
<br />
<pre style={{ fontSize: "18px", color: "red" }}>{e.stack}</pre>
</div>
<script dangerouslySetInnerHTML={{ __html: `console.error(${pre.render(e, true, false)})` }} />
<script dangerouslySetInnerHTML={{ __html: `console.error(\`${pre.render(e, true, false)}\`)` }} />
</HTML>
)
);
Expand Down
2 changes: 1 addition & 1 deletion src/server/middleware/renderPage/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { Request, Response } from "express";
import type { SagaStore } from "types/store";

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

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

Expand Down
4 changes: 3 additions & 1 deletion src/server/middleware/renderPage/middleware/loadStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const loadStore: Middleware = (next) => async (args) => {
throw new ServerError(`server 初始化失败 lang: ${lang}, store: ${store}`, 500);
}

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

args.page = page;

if (cookies) {
Object.keys(cookies).forEach((key) => {
Expand Down
6 changes: 3 additions & 3 deletions src/server/middleware/renderPage/renderSSR/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { targetRender as ChakraTargetRender } from "./renderChakra";

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

const targetRender: AnyAction = async ({ req, res, store, lang, env }) => {
if (!store || !lang || !env) {
const targetRender: AnyAction = async ({ req, res, store, lang, env, page }) => {
if (!store || !lang || !env || !page) {
throw new ServerError("初始化失败", 500);
} else {
return ChakraTargetRender({ req, res, store, lang, env });
return ChakraTargetRender({ req, res, store, lang, env, page });
}
};

Expand Down
20 changes: 16 additions & 4 deletions src/server/middleware/renderPage/renderSSR/renderChakra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@ import {
runtimeScriptsPath,
generateStyleElements,
generatePreloadScriptElements,
manifestDepsFile,
getDynamicPagePath,
dynamicPageStylesPath,
dynamicPageScriptsPath,
} from "utils/manifest";

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

export const targetRender: SafeAction = async ({ req, res, store, lang, env }) => {
export const targetRender: SafeAction = async ({ req, res, store, lang, env, page }) => {
const helmetContext = {};

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

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

const depsFileContent = await getAllStateFileContent(manifestDepsFile("client"));

const dynamicPage = getDynamicPagePath(depsFileContent, page);

const dynamicStylesPath = dynamicPageStylesPath(stateFileContent, dynamicPage);

const dynamicScriptsPath = dynamicPageScriptsPath(stateFileContent, dynamicPage);

const mainStyles = mainStylesPath(stateFileContent);

const runtimeScripts = runtimeScriptsPath(stateFileContent);
Expand All @@ -39,8 +51,8 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
env={JSON.stringify(env)}
lang={JSON.stringify(lang)}
helmetContext={helmetContext}
link={generateStyleElements(mainStyles)}
preLoad={generatePreloadScriptElements(mainScripts)}
link={generateStyleElements(mainStyles.concat(dynamicStylesPath))}
preLoad={generatePreloadScriptElements(mainScripts.concat(runtimeScripts).concat(dynamicScriptsPath))}
reduxInitialState={JSON.stringify(store.getState())}
>
<ChakraProvider resetCSS theme={theme} colorModeManager={cookieStore}>
Expand All @@ -54,7 +66,7 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
</ChakraProvider>
</HTML>,
{
bootstrapScripts: [...runtimeScripts, ...mainScripts],
bootstrapScripts: [...runtimeScripts, ...mainScripts, ...dynamicScriptsPath],
onShellReady() {
// The content above all Suspense boundaries is ready.
// If something errored before we started streaming, we set the error code appropriately.
Expand Down
1 change: 1 addition & 0 deletions src/types/router/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export interface TransformType {

export interface DynamicRouteConfig {
path: string;
chunkName: string;
componentPath?: string;
}
Loading

0 comments on commit a1fda3c

Please sign in to comment.