Webpack 是前端最流行的编译工具之一,而webpack-dev-server是 Webpack 的开发环境服务器,它的底层依赖的就是服务器框架 Express。下面使用 Express 的实现一个简单的webpack-dev-server。
webpack 官方和社区提供了两个非常好用的中间件库让我们可以在 Node 中很好的使用 Webpack,我们实现webpack-dev-server也需要借助这两个 库。它们就是webpack-dev-middleware和webpack-hot-middleware。
webpack-dev-middleware:开发环境中间件webpack-hot-middleware:热重载中间件
首先安装所有的依赖
yarn add express webpack webpack-dev-middleware webpack-hot-middleware html-webpack-plugin -D创建配置文件webpack.config.js(这个配置文件和普通配置文件没什么区别),编写下面代码
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
devtool: "cheap-module-eval-source-map",
entry: path.resolve(__dirname, "src/index.js"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "template.html"),
filename: "index.html",
}),
new webpack.HotModuleReplacementPlugin(), // 注意:这里需要设置模块热重载插件。
],
};在根目录的src文件夹下编写下面的文件
模块文件button.js
export function generateBtn(params) {
const button = document.createElement("button");
button.id = "btn";
button.innerText = "click me";
button.addEventListener("click", function () {
alert(this.innerText);
});
document.body.appendChild(button);
}入口文件index.js
import { generateBtn } from "./button";
generateBtn();Express 服务器文件server.js
const express = require("express");
const webpack = require("webpack");
const webpackConfig = require("./webpack.config");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackHotMiddleware = require("webpack-hot-middleware");
// 这里只处理 entry 选项是 string 和 array 的情况
if (typeof webpackConfig.entry === "string") {
const entry = webpackConfig.entry;
webpackConfig.entry = [entry];
}
webpackConfig.entry.push("webpack-hot-middleware/client"); // 引入 client
const app = express();
const compiler = webpack(webpackConfig); // 生成 webpack 编译器
// 使用中间件,都需要传入 webpack 编译器作为参数
app.use(
webpackDevMiddleware(compiler, {
noInfo: true,
})
);
app.use(webpackHotMiddleware(compiler));
const PORT = process.env.PORT || 3000;
app.listen(PORT, (error) => {
if (error) throw error;
console.log("Server running at http://127.0.0.1:3000");
});这里采用的是最简单的配置,只实现基本的功能。
首先通过 Express 创建一个服务器,然后分别调用webpack-dev-middleware和webpack-hot-middleware这 两个中间件。这两个中间件都需要传入compiler参数,而这个参数是通过调用webpack并传入配置文件编译出来的。
另外,为了实现模块的热重载功能,还需要在入口entry中引入webpack-hot-middleware/client这个文件,这里只处理了入口entry的值是字符串和数组这两种类型,多个页面入口的情况暂时不考虑。
然后执行命令启动 Express 服务器
node server.js发现 Webpack 编译打包成功。页面显示正常,模块文件button.js里面的点击事件监听器也能正常调用。
然后我们修改下模块button中元素的内容,比如把innerText的内容click me改成click me123,重新查看页面,发现页面并没有热重载,然后发现控制台中有警告信息,页面热重载实现失败,页面内容没有更新。不过尝试刷新下页面,浏览器重新渲染页面后,页面内容更新了。
给webpack-hot-middleware/client添加参数,在热重载失败的时候自动刷新页面。
webpackConfig.entry.push(
// 'webpack-hot-middleware/client'
"webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true"
);这种传参数的形式有点类似于 HTTP 请求时 URL 中的query字符串。其中reload=true参数表示热重载失败后会自动刷新页面。
重新启动服务器,刷新浏览器页面,再次修改innerText的值,发现页面热重载还是失败,不过浏览器自动刷新页面,重新渲染了页面的内容。其实到这一步,已经基本能满足一般的开发需求了。
如果还需要实现热重载的话,需要编写特定的热重载代码,在入口文件index.js中,增加下面代码
if (module.hot) {
module.hot.accept("./button", () => {
document.body.removeChild(document.getElementById("btn"));
generateBtn();
});
}重新启动服务器,刷新浏览器页面,再修改innerText的值,发现页面热重载已经生效。可以通过查看控制台打印的信息确认页面热重载是否生效。
在上面的代码中,需要手动的编写模块module的热重载代码。这些代码表示在模块热重载时,需要先删除掉原来的元素,然后再执行函数generateBtn重新生成新的元素。如果不删除原来的原生,页面中就会出现两个button元素,这样就和预期不相符。
在实际生产环境中,我们一般是使用各种 Loader 去解析模块的,而在这些模块内部已经实现了这样类似的代码,所以不需要我们手动去编写这些代码。