1. 本地 Egg 项目启动
- 首先执行
node index.js
或者npm run dev
启动 Egg 应用 - 在 Egg Agent 里面启动 koa 服务, 同时在 koa 服务里面启动 Webpack 编译服务
- 挂载 Webpack 内存文件读取方法覆盖本地文件读取的逻辑
app.react.render = (name, locals, options) => {const filePath = path.isAbsolute(name) ? name : path.join(app.config.view.root[0], name);
const promise = app.webpack.fileSystem.readWebpackMemoryFile(filePath, name);
return co(function* () {
const code = yield promise;
if (!code) {throw new Error(`read webpack memory file[${filePath}] content is empty, please check if the file exists`);
}
// dynamic execute javascript
const wrapper = NativeModule.wrap(code);
vm.runInThisContext(wrapper)(exports, require, module, __filename, __dirname);
const reactClass = module.exports;
if (options && options.markup) {return Promise.resolve(app.react.renderToStaticMarkup(reactClass, locals));
}
return Promise.resolve(app.react.renderToString(reactClass, locals));
});
};
- Worker 监听 Webpack 编译状态, 检测 Webpack 编译是否完成, 如果未完成, 显示 Webpack 编译 Loading, 如果编译完成, 自动打开浏览器
- Webpack 编译完成, Agent 发送消息给 Worker, Worker 检测到编译完成, 自动打开浏览器, Egg 服务正式可用
2. 本地服务端渲染页面访问
- npm run dev
- 浏览器输入 URL 请求地址, 然后 Egg 接收到请求, 然后进入 Controller
- Node 层获取数据后 (Node 通过 http/rpc 方式调用 Java 后端 API 数据接口), 进入模板 render 流程
- 进入 render 流程后, 通过 worker 进程通过调用
app.messenger.sendToAgent
发送文件名给 Agent 进程, 同时通过app.messenger.on
启动监听监听 agent 发送过来的消 - Agent 进程获取到文件名后, 从 Webpack 编译内存里面获取文件内容, 然后 Agent 通过
agent.messenger.sendToApp
把文件内容发送给 Worker 进程 - Worker 进程获取到内容以后, 进行 React 编译 HTML, 编译成 HTML 后, 进入 jss/css 资源依赖流程
- 如果启动代理模式 (见 easywebpack 的 setProxy), HTML 直接注入相对路径的 JS/CSS, 如下:
页面可以直接使用 /public/client/js/vendor.js
相对路径, /public/client/js/vendor.js
由后端框架代理转发到 webpack 编译服务, 然后返回内容给后端框架, 这里涉及两个应用通信. 如下:
<link rel="stylesheet" href="/public/client/css/home/android/home.css">
<script type="text/javascript" src="/public/client/js/vendor.js"></script>
<script type="text/javascript" src="/public/client/js/home.js"></script>
- 如果非代理模式 (见 easywebpack 的 setProxy), HTML 直接注入必须是绝对路径的 JS/CSS, 如下:
页面必须使用 http://127.0.0.1:9000/public/client/js/vendor.js
绝对路径
<link rel="stylesheet" href="http://127.0.0.1:9000/public/client/css/home/android/home.css">
<script type="text/javascript" src="http://127.0.0.1:9000/public/client/js/vendor.js"></script>
<script type="text/javascript" src="http://127.0.0.1:9000/public/client/js/home.js"></script>
其中 http://127.0.0.1:9000 是 Agent 里面启动的 Webpack 编译服务地址, 与 Egg 应用地址是两回事
- 最后, 模板渲染完成, 服务器输出 HTML 内容给浏览器.
3. 正式环境发布模式构建流程和运行模式
- Webpack 通过本地构建或者 ci 直接构建好服务端文件和客户端资源文件到磁盘
- Egg render 直接读取本地文件, 然后渲染成 HTML
- 根据
manfifest.json
文件注入 jss/css 资源依赖注入 - 模板渲染完成, 服务器输出 HTML 内容给浏览器.
4. 关于 Egg 框架中的 Agent 和 Worker
- 我们利用本地开发修改 Node 层代码修复重启时, 只会重启 Worker 进程, 不会重启 Agent 进程, 我们可以在 Agent 里面启动 Webpack 编译服务解决 Webpack compiler 实例问题.
- 因为 Egg App 进程 和 Agent 进程是两个进程, 当 url 访问时, 我们通过 worker 发送消息给 agent 进程, 获取服务端渲染的文件内容, 然后 Agent 再发送消息给 Worker 解决文件读取问题.
- 本地开发 webpack 热更新内存存储读取和线上应用本机文件读取逻辑分离功能, 我们通过本地开发模式时, 通过读取 Webpack 内存内容覆盖本地文件读取的逻辑, 这样在开发模式和发布模式可以无缝对接.
- worker 和 agent 通信机制: https://eggjs.org/zh-cn/core/cluster-and-ipc.html
- 实现 egg 项目服务器代码修改项目自动重启的功能可以使用 egg-development 插件.
5. 项目和插件
- egg-react-webpack-boilerplate 基于 easywebpack-react 和 egg-view-react(ssr) 插件的工程骨架项目
- easywebpack-react Webpack React 构建工程化方案.
- easywebpack-cli Webpack 构建工程化脚手架.
- egg-view-react-ssr react ssr 解决方案.
- egg-webpack 本地开发热更新使用.
- egg-webpack-react 本地开发渲染内存读取辅助插件