前端工程化的总结
实现完里程碑2,以下是我的一些理解和分享。
这次最大的收获,不只是“会用 webpack”,而是开始真正理解前端工程化背后的运行机制。
在以往项目中,我大多数是通过 CLI 脚手架快速启动。
包括 loader、plugin、开发环境热更新(HMR)这些能力,很多时候都是脚手架默认提供。
虽然我之前也手动配置过部分插件,写过一些插件 demo,但对“热更新是怎么跑起来的”“多线程打包为什么能提速”这类底层问题,理解并不系统。
这次里程碑2,我把重点放在了打包链路本身:解析编译 -> 模块分包 -> 压缩优化还有开发环境热更新上。
即使现在浏览器对 ES Module 支持越来越好、很多配置被封装,打包工具也具备devServer功能不需要配置热更新。理解这些底层原理依然很有价值,尤其在排查性能问题和面试表达时非常有帮助。
1. Loader 和 Plugin 的理解
先分享一下 webpack 的 loader、plugin 配置(开发环境基础能力)。
module: {
rules: [{
test: /\.vue$/,
use: {
loader: 'vue-loader'
}
}, {
test: /\.js$/,
include: [path.resolve(process.cwd(), './app/pages')], // 只对业务代码 babel
use: {
loader: 'babel-loader'
}
}, {
test: /\.(png|jpe?g|gif)(\?.+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 300,
esModule: false
}
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}, {
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
use: 'file-loader'
}]
}
这套规则的核心目的,是把 .vue/.js/.css/.less/图片/字体 等不同资源都纳入 webpack 的模块系统,统一处理依赖关系和产物引用。
2. 生产环境增强:CSS 抽离 + 多线程
生产环境我增加了 CSS 抽离和多线程打包。
module: {
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'happypack/loader?id=css'
]
}, {
test: /\.js$/,
include: [path.resolve(process.cwd(), './app/pages')], // 只对业务代码 babel
use: {
loader: 'happypack/loader?id=js'
}
}]
}
在我的理解里:
MiniCssExtractPlugin:把样式从 JS 中抽离成独立 CSS 文件,利于缓存与首屏加载策略。happypack:并行处理 JS/CSS,减少构建耗时(当前也可用thread-loader替代,思路一致)。
另外还用到了常见插件:
HtmlWebpackPluginCleanWebpackPluginTerserPlugin
这些插件在实际工程和面试场景里都很常见,属于必须掌握的 webpack 基础能力。
3. 模块分包策略(splitChunks)
分包上我采用三类:
vendor:node_modules里的第三方依赖common:被复用两次以上的公共模块entry:页面业务代码
配置如下:
splitChunks: {
chunks: 'all', // 对同步和异步模块都进行分割
maxAsyncRequests: 10, // 每次异步加载最大并行请求数
maxInitialRequests: 10, // 入口点最大并行请求数
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor', // 模块名称
priority: 20, // 优先级,数字越大越高,默认 0
enforce: true, // 强制执行
reuseExistingChunk: true // 复用已有的公共 chunk
},
common: {
name: 'common',
minChunks: 2, // 被两处引用即为公共模块
minSize: 1, // 最小分割文件大小(1 byte)
priority: 10,
reuseExistingChunk: true // 复用已有的公共 chunk
}
}
}
这类拆分的主要收益是:把“变动频率不同”的代码分开,提升浏览器缓存命中率,避免每次上线都让用户重复下载所有资源。
4. HMR 热更新实现理解
我这次自己实践了 HMR,基于:
webpack-dev-middlewarewebpack-hot-middleware
理解如下:
webpack-dev-middleware:将构建产物写入内存,而不是磁盘,浏览器直接读取内存产物。webpack-hot-middleware:建立客户端与构建结果的通信,变更时通知浏览器做局部更新。webpack.HotModuleReplacementPlugin:开启模块热替换能力。
所以整体流程是:
监听代码变更 -> 增量编译到内存 -> 通知客户端 -> 局部替换模块。
这也是当前大多数 devServer 热更新机制的核心思路。