前端工程化建设:整理 Webpack 多入口和构建配置

这段时间整理了一下 elpis-core 的前端构建配置。平时写 Vue 页面时,组件、样式和图片最后都会变成浏览器能访问的文件;Webpack 的配置主要就是告诉它从哪里开始找文件、怎么处理,以及最终输出到哪里。

大致流程可以这么看:

1
入口文件 → 加载和转换模块 → 生成与拆分 chunk → 压缩等优化 → 输出文件

实际编译过程中插件会参与不同阶段,上面只是方便理解的简化流程。

多入口和模板

项目里使用 entry.*.js 作为页面入口,构建时扫描内核和业务目录,分别生成入口配置。每个入口再对应一个 HtmlWebpackPlugin 实例,生成包含相应资源引用的模板文件。

1
2
3
4
5
6
7
8
9
10
11
// 示意:一个入口对应一个模板
entry: {
'entry.dashboard': './app/pages/dashboard/entry.dashboard.js',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'entry.dashboard.tpl',
template: './app/view/entry.tpl',
chunks: ['entry.dashboard'],
}),
]

这个例子只展示关系,实际项目会按目录扫描生成。模板由服务端使用,再由浏览器加载其中引用的 JS、CSS。服务端渲染模板不等于 Vue 组件已经做了服务端渲染(SSR),这两个概念之前容易混在一起。

Loader 和插件

不同类型的文件需要对应的处理方式:.vue 用 vue-loader,JavaScript 用 babel-loader,样式经过 CSS 或 Less 相关 loader,图片和字体也有各自的处理规则。

VueLoaderPlugin 会配合 vue-loader,把已有规则应用到 Vue 单文件组件的相应部分。例如组件里的 <script>、<style> 也能走对应处理规则。ProvidePlugin 可以按配置自动提供模块;DefinePlugin 用来在编译时替换常量。它们各自做的事情不一样。

拆包和缓存

optimization.splitChunks 可以按规则提取公共模块。例如把第三方依赖和多个页面复用的代码单独拆出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
priority: 10,
reuseExistingChunk: true,
},
common: {
minChunks: 2,
name: 'common',
reuseExistingChunk: true,
},
},
},
}

这也是简化示例,实际是否拆出某个 chunk,还受大小、引用次数和其他配置影响。拆包主要是为了更好地组织资源和利用缓存,并不是拆得越多越好:包太碎也会增加请求和管理成本。

输出文件名可以带 hash,让内容变化后浏览器请求新文件;同时也要注意 HTML 或模板里的资源引用和缓存策略,否则文件更新了,页面仍可能引用旧地址。

开发和生产环境

开发环境更关心反馈速度:项目通过 Express 配合 webpack-dev-middleware 提供构建资源,再用 webpack-hot-middleware 处理热更新。这里配置了模板文件写入磁盘,其他构建资源由中间件提供。改完页面后能较快看到结果,排查样式和交互也方便。

生产环境更关心最终产物:提取 CSS、压缩 JS 和 CSS、清理旧文件、配置资源路径等。比如 MiniCssExtractPlugin 负责提取 CSS;TerserPlugin 用于压缩 JavaScript,是否移除 console 要看具体配置;CssMinimizerPlugin 用于压缩和优化 CSS,它本身不等于清理所有未使用的样式。

多线程处理也不是一定更快。小项目可能因为进程启动和通信成本看不到收益,最好结合实际构建耗时再决定是否使用。