1. 背景

在用 Node.js + Webpack 构建的方式进行开发时, 我们希望能实现修改代码能实时刷新页面UI的效果. 这个特性 Webpack本身是支持的, 而且基于koa也有现成的 koa-webpack-hot-middleware 和 koa-webpack-dev-middleware 封装好的组件支持.

不过这里如果需要支持Node.js服务器端修改代码自动重启webpack自动编译功能该如何实现呢, 主要存在以下几个问题:

  • 如何解决 Node.js 服务器端代码修改应用重启避免 Webpack 重新编译.
  • 如何访问 js, css, image 等静态资源.
  • 服务端渲染时, Node 层如何读取 Webpack 内存编译的内容
  • 如何处理本地开发 Webpack 热更新内存存储读取和线上应用本机文件读取逻辑分离.

2.构建热更新实现

前端渲染和服务端渲染构建热更新实现

在koa项目中, 通过 koa-webpack-dev-middleware 和 koa-webpack-hot-middleware 可以实现 Webpack 编译内存存储和热更新功能, 代码如下:

const compiler = webpack(webpackConfig);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, options);
const hotMiddleware = require('koa-webpack-hot-middleware')(compiler, options);
app.use(devMiddleware);
app.use(hotMiddleware);

如果按照上面实现, 可以满足修改修改客户端代码实现webpack自动变编译和UI界面热更新的功能.

但如果是修改 Node.js 服务器端代码重启后就会发现webpack会重新编译, 这不是我们要的效果.

原因是因为 middleware 是依赖 app 的生命周期, 当 app 销毁时, 对应 Webpack compiler 实例也就没有了, 重启时会重新执行middleware 初始化工作.

那有没有办法保持 Webpack 编译实例呢? 针对这个我们可以通过 Egg 框架已经内置了 worke r和 agent 机制来实现 Webpack内存编译功能.

3. Egg 框架中 Webpack 构建

3.1 解决思路

  • 我们利用本地开发修改 Node 层代码修复重启时, 只会重启 Worker 进程, 不会重启 Agent 进程, 我们可以在 Agent 里面启动 Webpack 编译服务解决 Webpack compiler 实例问题.
  • 因为 Egg App 进程 和 Agent 进程是两个进程, 当 url 访问时, 我们通过 Worker 发送消息给 Agent 进程, 获取服务端渲染的文件内容, 然后 Agent 再发送消息给 Worker 解决文件读取问题.
  • 本地开发 Webpack 热更新内存存储读取和线上应用本机文件读取逻辑分离功能, 我们通过本地开发模式时, 通过读取Webpack 内存内容覆盖本地文件读取的逻辑, 这样在开发模式和发布模式可以无缝对接.

3.2 本地开发模式

3.2.1 Egg项目启动

  • 首先执行node index.js 或者 npm run dev 启动 Egg 应用
  • 在 Egg Agent 里面启动 koa 服务, 同时在koa服务里面启动 Webpack 编译服务
  • 挂载 Webpack 内存文件读取方法覆盖本地文件读取的逻辑
app.vue.renderBundle = (name, context, 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 content = yield promise;
    if (!content) {
      throw new Error(`read webpack memory file[${filePath}] content is empty, please check if the file exists`);
    }
    return renderBundle.bind(app.vue)(content, context, options);
  });
};
  • Worker 监听 Webpack 编译状态, 检测 Webpack 编译是否完成, 如果未完成, 显示 Webpack 编译 Loading, 如果编译完成, 自动打开浏览器
  • Webpack 编译完成, Agent 发送消息给 Worker, Worker 检测到编译完成, 自动打开浏览器, Egg 服务正式可用

3.2.2 本地开发服务端渲染页面访问

  • 浏览器输入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进程获取到内容以后, 进行Vue编译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">

  • ```javascript ```
  • 如果非代理模式, HTML直接注入必须是绝对路径的 JS/CSS , 如下:

    页面必须使用 http://127.0.0.1:9001/public/client/js/vendor.js 绝对路径


  • ```html ```

  • ```javascript ```
  • 其中 http://127.0.0.1:9001 是 Agent里面启动的Webpack编译服务地址, 与Egg应用地址是两回事

  • 最后, 模板渲染完成, 服务器输出HTML内容给浏览器.

4. 发布模式构建流程和运行模式

  • easywebpack 通过本地构建或者ci直接构建好服务端和客户端渲染文件到磁盘, 命令是 easy build prod
  • Egg render 直接读取本地文件, 然后渲染成 HTML.
  • 根据 manfifest.json 文件注入 jss/css 资源依赖注入.
  • 模板渲染完成, 服务器输出HTML内容给浏览器.

解决方案


Author: sky
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source sky !
 Previous
Android 与 Flutter 入门开发实践指引 Android 与 Flutter 入门开发实践指引
项目集成Android 集成 Flutter 项目通过 Android Studio 创建 Andriod 原生应用 AndroidApp通过 Android Studio 创建 New Flutter Project, 选择 Flutter Module,创建 Flutter Module ...
2019-12-23 sky
Next 
Webpack 热更新实现原理分析 Webpack 热更新实现原理分析
概述在使用 Webpack 构建开发期时,Webpack 提供热更新功能为开发带来良好的体验和开发效率。Webpack 热更新可以做到页面无刷新局部更新能力。Webpack 热更新机制相比传统的直接自动刷新浏览器,可以保持元素当前状态,特别是在编写 css 样式调整布局的时候优势明显,不用反复...
2019-12-23 sky