Skip to content

Commit

Permalink
support auto inject props for initialState
Browse files Browse the repository at this point in the history
  • Loading branch information
MrWangJustToDo committed Dec 31, 2021
1 parent 9ac3af5 commit be51a3e
Show file tree
Hide file tree
Showing 19 changed files with 1,716 additions and 1,556 deletions.
6 changes: 3 additions & 3 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ DEV_PORT=9000
WDS_PORT=9001
PROD_PORT=5000
PROD_HOST=localhost
PUBLIC_API_HOST=localhost:9000
PUBLIC_API_HOST=localhost:5000
CRYPTO_KEY=ad$cr3efW89ypg
SSR=true
UI=antd
MIDDLEWARE=false
UI=material
MIDDLEWARE=true
ANIMATE_ROUTER=false
SERVER_ENTRY=./src/server/entry.ts
CLIENT_ENTRY=./src/client/entry.tsx
46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-ssr",
"version": "1.1.4",
"version": "1.2.0",
"author": "mrwang",
"license": "MIT",
"scripts": {
Expand All @@ -15,8 +15,8 @@
"@emotion/styled": "^11.6.0",
"@loadable/component": "^5.15.2",
"@loadable/server": "^5.15.2",
"@mui/material": "^5.2.5",
"antd": "^4.17.4",
"@mui/material": "^5.2.6",
"antd": "^4.18.2",
"axios": "^0.24.0",
"chalk": "4",
"compression": "^1.7.4",
Expand Down Expand Up @@ -46,20 +46,20 @@
"zustand": "^3.6.8"
},
"devDependencies": {
"@babel/cli": "^7.16.0",
"@babel/core": "^7.16.5",
"@babel/plugin-proposal-class-properties": "^7.16.5",
"@babel/plugin-proposal-decorators": "^7.16.5",
"@babel/plugin-proposal-export-default-from": "^7.16.5",
"@babel/plugin-proposal-object-rest-spread": "^7.16.5",
"@babel/plugin-proposal-optional-chaining": "^7.16.5",
"@babel/plugin-proposal-private-methods": "^7.16.5",
"@babel/cli": "^7.16.7",
"@babel/core": "^7.16.7",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-decorators": "^7.16.7",
"@babel/plugin-proposal-export-default-from": "^7.16.7",
"@babel/plugin-proposal-object-rest-spread": "^7.16.7",
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@babel/plugin-proposal-private-methods": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.16.5",
"@babel/plugin-transform-runtime": "^7.16.5",
"@babel/preset-env": "^7.16.5",
"@babel/preset-react": "^7.16.5",
"@babel/preset-typescript": "^7.16.5",
"@babel/plugin-transform-modules-commonjs": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.7",
"@babel/preset-env": "^7.16.7",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@loadable/babel-plugin": "^5.13.2",
"@loadable/webpack-plugin": "^5.15.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
Expand All @@ -72,17 +72,17 @@
"@types/loadable__server": "^5.12.3",
"@types/lodash": "^4.14.178",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.4",
"@types/node": "^17.0.5",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-redux": "^7.1.21",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-middleware": "^5.0.2",
"@types/webpack-env": "^1.16.3",
"@types/webpack-hot-middleware": "^2.25.4",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"autoprefixer": "^10.2.4",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"autoprefixer": "^10.4.1",
"babel-loader": "^8.2.2",
"babel-plugin-import": "^1.13.3",
"clean-webpack-plugin": "^4.0.0",
Expand All @@ -100,21 +100,21 @@
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^6.5.0",
"mini-css-extract-plugin": "^2.4.5",
"node-sass": "^7.0.0",
"node-sass": "^7.0.1",
"nodemon": "^2.0.15",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
"prettier": "^2.5.1",
"react-refresh": "^0.11.0",
"sass": "^1.45.1",
"sass": "^1.45.2",
"sass-loader": "^12.4.0",
"style-loader": "^3.2.1",
"thread-loader": "^3.0.4",
"typescript": "^4.5.4",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.5.0",
"webpack-dev-middleware": "^5.3.0",
"webpack-dev-server": "^4.7.1",
"webpack-dev-server": "^4.7.2",
"webpack-hot-middleware": "^2.25.1",
"webpack-manifest-plugin": "^4.0.2",
"webpack-merge": "^5.7.3",
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ yarn run start

## 代码分割

## preLoad data 标准化预加载行为 通过返回配置对象进行精细的跳转控制: getInitialState 方法返回 {error, redirect, headers(server), cookies(client)} more usage for getInitialState
## preLoad data 标准化预加载行为 通过返回配置对象进行精细的跳转控制: getInitialState 方法返回 {error, redirect, headers(server), cookies(client), props: {auto inject data}} more usage for getInitialState

## sass

Expand All @@ -60,7 +60,7 @@ yarn run start

## multiple UI (Material-UI, antd, chakra-ui)

## 数据自动注入原理上比较难以实现 改用 redux connect
## 数据自动注入 getInitialState: () => ({props: xxx}) see pages/Great.tsx

## middleware develop 适合手机等远程调试

Expand Down
17 changes: 15 additions & 2 deletions src/client/entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@ import { createUniversalStore } from "store";
import { log } from "utils/log";
import { safeData } from "utils/safeData";
import { StoreState } from "types/store";
import { preLoad } from "utils/preLoad";
import { allRoutes } from "router/routes";

const place = document.querySelector("#__content__");

const preLoadEnvElement = document.querySelector("script#__preload_env__");

const preLoadPropsElement = document.querySelector("script#__preload_props__");

const preLoadStateElement = document.querySelector("script#__preload_state__");

const store = createUniversalStore({ initialState: JSON.parse(preLoadStateElement?.innerHTML || "{}") as StoreState });

window.__ENV__ = safeData(JSON.parse(preLoadEnvElement?.innerHTML || "{}"));
window.__ENV__ = JSON.parse(preLoadEnvElement?.innerHTML || "{}");

window.__INITIAL_PROPS_SSR__ = JSON.parse(preLoadPropsElement?.innerHTML || "{}");

safeData(window.__ENV__);

safeData(window as unknown as Record<string, unknown>, "__ENV__");

safeData(window.__INITIAL_PROPS_SSR__);

safeData(window as unknown as Record<string, unknown>, "__INITIAL_PROPS_SSR__");

let Root = ({ store: _store }: { store: ReturnType<typeof createUniversalStore> }) => <></>;

// multiple UI component
Expand All @@ -37,7 +49,8 @@ if (__UI__ === "material") {
}

if (!window.__ENV__.SSR) {
loadableReady(() => render(<Root store={store} />, place));
// for client side render, we need load component props first
preLoad(allRoutes, location.pathname, store).then(() => loadableReady(() => render(<Root store={store} />, place)));
} else {
if (__DEVELOPMENT__ && __MIDDLEWARE__) {
log("not hydrate render on client", "warn");
Expand Down
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,6 @@ interface Window {
__cache: unknown;
__request: unknown;
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
__INITIAL_PROPS_SSR__: { [key: string]: any };
__ENV__: { PUBLIC_API_HOST: string; ANIMATE_ROUTER: boolean; SSR: boolean; CRYPTO_KEY: string; LANG: string; UI: "antd" | "material" | "chakra" };
}
9 changes: 8 additions & 1 deletion src/hooks/usePreLoad.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useStore } from "react-redux";
import cookie from "js-cookie";
import shallow from "zustand/shallow";
import { useLocation, useNavigate } from "react-router";
import { log } from "utils/log";
import { hydrateLoad } from "utils/preLoad";
import { useChangeLoadingWithoutRedux } from "./useLoadingBar";
import { UsePreLoadType } from "types/hooks";

Expand All @@ -25,6 +26,12 @@ const usePreLoad: UsePreLoadType = ({ routes, preLoad }) => {
const storeRef = useRef(store);
const [loadedLocation, setLoadedLocation] = useState(location);

// hydrate to auto inject props
useMemo(() => {
hydrateLoad(routes, location.pathname);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

loadingPath.current = location.pathname;

loadedPath.current = loadedLocation.pathname;
Expand Down
11 changes: 4 additions & 7 deletions src/pages/Great.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import React from "react";
import { connect } from "react-redux";
import { apiName } from "config/api";
import { getDataAction_Server } from "store/reducer/server/share/action";

import type { StoreState } from "types/store";
import type { GetInitialStateType, PreLoadComponentType } from "types/components";

const Great: PreLoadComponentType<{ blog: string[] }> = (props) => {
console.log(props, "666");
return <div>Great rt</div>;
console.log(props, "test auto inject");
return <div>Great rt, {props?.blog?.join(", ")}</div>;
};

export const getInitialState: GetInitialStateType = async ({ store }) => {
console.log("开始222");
await store.dispatch(getDataAction_Server({ name: apiName.blog }));
console.log(store.sagaTask?.isRunning(), store.sagaTask?.isCancelled());
console.log("dispatch done");
return { props: { blog: [1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 0] } };
};

export default connect((state: StoreState) => ({
blog: state.server.blog.data,
}))(Great);
export default Great;
2 changes: 1 addition & 1 deletion src/router/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (__UI__ === "chakra") {
LoadAble_UI = loadable<unknown>(() => import("../components/chakraComponent"));
}

export const routes: PreLoadRouteConfig[] = [
const routes: PreLoadRouteConfig[] = [
{ path: "/", element: <UI />, Component: UI },
{ path: "/i18n", element: <LoadAble_I18n />, Component: LoadAble_I18n },
{ path: __UI__ === "antd" ? "/antd" : __UI__ === "material" ? "/material" : "chakra", element: <LoadAble_UI />, Component: LoadAble_UI },
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; serverSideProps?: { [key: string]: any } };

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

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

const { headers, error, redirect } = await preLoad(allRoutes, req.url, store, { req, lang });
if (headers) {
Object.keys(headers).forEach((key) => res.setHeader(key, headers[key]));
}
const { error, redirect, serverSideProps } = await preLoad(allRoutes, req.url, store, { req, lang });

// console.log(serverSideProps);

if (error) {
throw new ServerError(error, 403);
Expand All @@ -23,6 +22,7 @@ export const loadStore: Middleware = (next) => async (args) => {
res.writeHead(302, { Location: redirect });
res.end();
} else {
args.serverSideProps = serverSideProps;
await next(args);
}
};
8 changes: 4 additions & 4 deletions src/server/middleware/renderPage/renderSSR/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import { ServerError } from "server/utils/error";
import { AnyAction, composeRender } from "../compose";
import { globalEnv, initLang, initStore, loadLang, loadStore } from "../middleware";

const targetRender: AnyAction = async ({ req, res, store, lang, env }) => {
const targetRender: AnyAction = async ({ req, res, store, lang, env, serverSideProps = {} }) => {
if (!store || !lang || !env) {
throw new ServerError("初始化失败", 500);
} else {
if (__UI__ === "antd") {
const { targetRender } = require("./renderAntDesign");
return targetRender({ req, res, store, lang, env });
return targetRender({ req, res, store, lang, env, serverSideProps });
}
if (__UI__ === "material") {
const { targetRender } = require("./renderMaterial");
return targetRender({ req, res, store, lang, env });
return targetRender({ req, res, store, lang, env, serverSideProps });
}
if (__UI__ === "chakra") {
const { targetRender } = require("./renderChakra");
return targetRender({ req, res, store, lang, env });
return targetRender({ req, res, store, lang, env, serverSideProps });
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { HTML } from "template/Html";
import { App } from "components/App";
import { SafeAction } from "../compose";

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

const content = (
Expand Down Expand Up @@ -43,6 +43,7 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
script={scriptElements}
helmetContext={helmetContext}
link={linkElements.concat(styleElements)}
serverSideProps={JSON.stringify(serverSideProps)}
reduxInitialState={JSON.stringify(store.getState())}
>
{body}
Expand Down
3 changes: 2 additions & 1 deletion src/server/middleware/renderPage/renderSSR/renderChakra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { HTML } from "template/Html";
import { manifestLoadable } from "utils/manifest";
import { SafeAction } from "../compose";

export const targetRender: SafeAction = async ({ req, res, store, lang, env }) => {
export const targetRender: SafeAction = async ({ req, res, store, lang, env, serverSideProps }) => {
const helmetContext = {};
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
Expand Down Expand Up @@ -58,6 +58,7 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
helmetContext={helmetContext}
emotionChunks={emotionChunks}
link={linkElements.concat(styleElements)}
serverSideProps={JSON.stringify(serverSideProps)}
reduxInitialState={JSON.stringify(store.getState())}
>
{body}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { theme } from "config/materialTheme";
import { createEmotionCache } from "config/createEmotionCache";
import { SafeAction } from "../compose";

export const targetRender: SafeAction = async ({ req, res, store, lang, env }) => {
export const targetRender: SafeAction = async ({ req, res, store, lang, env, serverSideProps }) => {
const helmetContext = {};
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
Expand Down Expand Up @@ -60,6 +60,7 @@ export const targetRender: SafeAction = async ({ req, res, store, lang, env }) =
helmetContext={helmetContext}
emotionChunks={emotionChunks}
link={linkElements.concat(styleElements)}
serverSideProps={JSON.stringify(serverSideProps)}
reduxInitialState={JSON.stringify(store.getState())}
>
{body}
Expand Down
19 changes: 15 additions & 4 deletions src/template/Html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,23 @@ type HTMLProps = {
children?: string;
link?: React.ReactElement[];
script?: React.ReactElement[];
serverSideProps?: string;
reduxInitialState?: string;
emotionChunks?: EmotionCriticalToChunks;
helmetContext?: { helmet?: HelmetData };
};

export const HTML = ({ lang, children, link = [], script = [], reduxInitialState = "{}", helmetContext = {}, emotionChunks, env = "{}" }: HTMLProps) => {
export const HTML = ({
lang,
children,
link = [],
script = [],
serverSideProps = "{}",
reduxInitialState = "{}",
helmetContext = {},
emotionChunks,
env = "{}",
}: HTMLProps) => {
const { helmet } = helmetContext;

return (
Expand All @@ -34,9 +45,9 @@ export const HTML = ({ lang, children, link = [], script = [], reduxInitialState
{emotionChunks?.styles.map((style) => (
<style data-server data-emotion={`${style.key} ${style.ids.join(" ")}`} key={style.key} dangerouslySetInnerHTML={{ __html: style.css }} />
))}
<script id="__preload_env__inject" dangerouslySetInnerHTML={{ __html: `window.__ENV__ = ${env}` }} />
<script id="__preload_env__" type="text/json" dangerouslySetInnerHTML={{ __html: `${env}` }} />
<script id="__preload_state__" type="text/json" dangerouslySetInnerHTML={{ __html: `${reduxInitialState}` }} />
<script id="__preload_env__" type="application/json" dangerouslySetInnerHTML={{ __html: `${env}` }} />
<script id="__preload_props__" type="application/json" dangerouslySetInnerHTML={{ __html: `${serverSideProps}` }} />
<script id="__preload_state__" type="application/json" dangerouslySetInnerHTML={{ __html: `${reduxInitialState}` }} />
</head>
<body>
<div id="__content__" dangerouslySetInnerHTML={{ __html: children || "" }} />
Expand Down
Loading

0 comments on commit be51a3e

Please sign in to comment.