# 正文

上一节我们完成了生产环境的配置,从本节开始一起来做一些“装修”的工作,让应用骨架的开发体验更好,同时也是解决我们之前留下的坑。

良好的开发体验可以大大提高我们的开发效率,目前有一个明显的缺陷,开发环境中需要手动刷新页面来看效果。

而我们要做的就是 - 搞定他!

使用webpack --watch来动态监听前端文件的改变并实时打包,输出新bundle.js文件,这样文件多了之后打包速度会很慢,此外这样的打包方式不能做到hot replace,即每次webpack编译之后需要手动刷新浏览器。

所以本节我们就是用热更新机制来解决这个问题。

# 热更新

对于热更,相信大家都有所了解,一种是热重载,一种是模块热替换。

我们这里要实现的是模块热替换。

# webpack-dev-server

因为该模块已经内置了热更新机制,只需要进行一些简单的配置就能将这个特性继承到我们的项目中。

所以开发环境中实现热更新推荐使用webpack-dev-server

ps:webpack-dev-server后面都使用wds简称来代替

该库的作用主要是用来提供静态资源文件的更新和访问。其内部会启动一个基于expresshttp服务器。 该http服务和浏览器之间使用websocket进行实时通讯。当原始文件作出改动后,wds会实时编译,但是最后的构建文件并没有输出到硬盘,而是全部载入内存,省去了每次对磁盘的 io,所以性能上也较以往的方式有很大的提升。

启动webpack-dev-server服务有2种方式:

  1. 通过 cmd line
  2. 通过Node.js API

为了可以更加灵活控制,我们采用API方式来启动。

另外还需要结合react-hot-loader库来实现 react 开发环境下的热更新。

# 具体实施

# 端口约定

我们约定webpack-dev-server服务端口为9002,应用骨架的node server启动端口为9001,保持不变。

ps:这些端口可以根据自己的喜好来调整。

# 安装模块

npm i webpack-dev-server -D

react-hot-loader 可以结合 wds 实现热更新过程中保存 react 组件的状态不丢失

npm i react-hot-loader -D

# react-hot-loader 配置

.babelrc` 内增加 `react-hot-loader/babel
// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}
  • 根组件使用热导出
// src/client/app/layout.js

import { hot } from 'react-hot-loader/root';
class Index extends React.Component{
//...
}
export default hot(Index);
  • 安装@hot-loader/react-dom

它替换了同一版本的react-dom包,使用了额外的补丁来支持热重新加载。

直接使用react-dom,控制台会出现下面的警告

image-20210214215636524

  • webpack配置入口entry调整
//webpack.dev.config
module.exports = {
  entry: {
        main:['react-hot-loader/patch',resolvePath('../src/client/app/index.js'), //入口文件
    }

# wds 配置

通过 api 方式启动 wds 服务时,会忽略webpack.dev.config内的配置,需要将配置作为参数传递给WebpackDevServer

创建一个 wds 配置文件

// webpack/scripts/webpack-dev-server.config.js

const path  = require('path');

module.exports = function (port,publicPath) {
    return {
        quiet: true,//不显示构建日志
        contentBase: path.resolve(__dirname, '../../dist/static'),
        publicPath: publicPath,//必须和 webpack.dev.config 配置一致
        hot: true,
        progress:true,
        open: false,
        compress: true,
        watchOptions: {
            ignored: /node_modules/,
            //当第一个文件更改,会在重新构建前增加延迟。
            //这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
            aggregateTimeout: 500,
            //指定毫秒为单位进行轮询
            poll: 500
        }
    }
}

# 相关 api 调用

webpack/scripts下创建wds-start.js文件,用于启动 wds 服务。

  1. 获得 webpack compiler

wds 需要和 webpack 相结合才能工作

//webpack dev 环境配置
const clientConfig = require('../webpack.dev.config');

// 获得webpack compiler
function getWebPackCompiler() {
    return webpack(clientConfig);
}
  1. 创建 wds 服务

这里的服务指的是 wds 内部会启动一个 http server,用来支持 socket 和 资源访问。

//wds 配置
const getWdsConfig = require('./webpack-dev-server.config');

//创建 wds 服务
function createWdsServer() {

    let compiler = getWebPackCompiler();

     compiler.hooks.done.tap('done', function (data) {
        console.log('\n wds server compile done'); //编译完成时的提示
    });

    return new WebpackDevServer(compiler, getWdsConfig(clientConfig.output.publicPath));
}
  1. 启动 wds 服务

createWdsServer会返回一个 http server 实例,这里启动的端口设置为9002

// 启动 WebpackDevServer

function runWdsServer() {

    let devServer = createWdsServer();
    let port=9002;//wds 服务端口
    devServer.listen(port,'localhost',err => {
        if (err) {
            return console.log(err);
        }
        console.log(chalk.cyan('Starting the development node server...\n'));

        console.log('🚀 started');
    });

}

runWdsServer();

# 关键 - 配置 publicPath

wdswebpack.dev.configpublicPath必须一致,否则热更新会无效,会默认请求到node server,导致静态资源访问无效。

下面对webpack.dev.config.js进行调整,端口指向的是wds的端口9002,因为所有的静态资源现在是由wds提供的。

// webpack.dev.config.js

 output: {
     //...
    publicPath: 'http://localhost:9002/'
}

# 配置 npm scripts 命令

"wds:watch": "BABEL_ENV=development node ./webpack/scripts/wds-start.js",
npm run wds:watch

image-20210214215704693

从上图可以看出编译已经通过,而且支持实时编译,但是热更新还看不出来。

# 调整开发环境启动命令

// webpack/start.js

//移除 webpack -watch 监听
- spawn('npm', ['run', 'fe:watch'], { stdio: 'inherit' });

//增加 wds 监听
+ spawn('npm', ['run', 'wds:watch'], { stdio: 'inherit' });

# 客户端渲染入口代码配置

// src/client/app/index.js

//只有在开发环境才启用热更新
if (process.env.NODE_ENV==='development' &&  module.hot) {
    module.hot.accept();
}

# 更改 node serverjs 脚本地址

因为我们采用的是双服务模式

  • wds 服务提供静态资源服务
  • node server 提供 ssr 能力

所以我们需要将静态资源的地址指向 wds 服务的9002端口

// src/server/common/assets.js

- let devHost = '//localhost:9001';
+ let devHost = '//localhost:9002';

//...

# 测试 - 启动热更新

执行 npm run dev

看下图,socket已成功建立链接,后面只要更新了文件就会自动更新页面,且页面不会刷新,组件的状态也不会丢失。

image-20210214215733222

# 小结

本地开发服务启动后,会启动三个进程,其中包含两个服务,也就是我们最开始说的双服务,wds服务和node server,在这两个服务的配合下完成了开发环境的热更新开发体验。

  1. wds服务进程,提供静态资源访问和热更新功能
  2. 服务端代码监听进程
  3. node http server 提供react ssr能力

node server 会从wds 9002端口服务上加载静态资源。

好了,本小节到此结束,快来试试热更新吧。

本节代码已上传

github.com/Bigerfe/koa… (opens new window)

阅读全文