前端工程化设计与实现(WebPack)
前端工程化是一个老生常谈的话题,但大多都是讨论使用 webpack 还是 vite ,亦或者是 rspack 。其实工程化使用什么工具并不是主要的,而是了解工程化的意义,理解每一个配置是在做什么,这样即可一通百通,而工程化也不仅仅是项目的构建打包工具而已,还要包括Eslint、CI/CD等一系列的东西,但本文篇幅有限,仅介绍 webpack 相关配置与环境搭建。
为什么选择webpack
可能有小伙伴会问,vite 比 webpack 快很多啊,而且配置也简单,为啥不用 vite ,而还是选用 webpack 呢?还是像前面说的,使用什么工具不重要,重要的是理解概念,而 vite 封装程度比较高,配置简单,不太利于深入理解各项配置,因此选用 webpack 来介绍。
webpack 配置
entry
entry 是入口配置,主要配置页面的入口文件路径。
loader
loader 的作用是将不同的文件转义为浏览器可执行的文件,在 module.rules 中配置。
vue-loader
- 允许以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。
- 允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在
<style>的部分使用 Sass 和在<template>的部分使用 Pug。 - 为每个组件模拟出 scoped CSS。
需配合 VueLoaderPlugin 插件使用。
babel-loader
- 将 ES6 代码转义为 5 的代码,有更好的兼容性。
- 可以使用
@babel/preset-typescript,并配置allExtensions: true来支持解析TS文件与 vue 文件中的 TS 代码。
css-loader
css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样。
需与 style-loader 或 MiniCssExtractPlugin.loader 一起使用
style-loader
使用多个 <style></style> 将 CSS 插入到 DOM 中,通常在开发环境中与 css-loader 一起使用。
MiniCssExtractPlugin.loader
提取 CSS,创建单独的 css 文件,以便以后能够使用 CSS/JS 资源的并行加载。通常在生产环境中与 css-loader 一起使用。
sass-loader
加载 Sass/SCSS 文件并将他们编译为 CSS。
thread-loader
开启多进程打包,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。应仅在耗时的操作中使用此 loader !
webpack5资源模块
webpack5 提供了资源模块,使用type: 'asset',将以往需要使用对应 loader 处理的静态资源文件改为使用资源模块来处理:
asset/resource发送一个单独的文件并导出 URL。之前通过使用file-loader实现。asset/inline导出一个资源的 data URI。之前通过使用url-loader实现。asset会根据文件的类型选择使用asset/resource或asset/inline
output
output 配置产物输出的路径。
resolve
resolve 配置模块解析的具体行为(定义 webpack 在打包时,如何找到并解析具体模块的路径)如:@ 等快捷路径。
plugins
plugins 配置配置 webpack 插件,通过社区丰富的插件来强化 webpack 的能力,如 VueLoaderPlugin 处理 vue 文件、 CleanWebpackPlugin 每次 build 前, 自动清空 pubilc/dist 目录、 MiniCssExtractPlugin 提取 css 的公共部分,有效利用缓存、 CssMinimizerPlugin 优化并压缩 css 资源, AutoImport 自动引入 API , Components 自动引入组件等等。
optimization
optimization 配置打包输出优化 (代码分割、模块合并、缓存、压缩、Tree Shaking等)。
splitChunks
代码分割配置,将代码分割成多个代码块,充分利用浏览器缓存机制,以减少加载时间。
/**
* 分包策略
* 把 js 文件打包成3种类型
* 1. 第三方库 node_modules 中的库
* 2. 业务代码差异部分: 经常改动
* 3. 公共代码 common: 业务组件代码抽离出的公共部分
*/
splitChunks: {
chunks: "all", // 对同步和异步模块都切割
maxAsyncRequests: 10, // 最大异步请求数
maxInitialRequests: 10, // 最大初始请求数
cacheGroups: {
vendors: { // 第三方库
test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录下的文件
name: "vendors", // 指定名称
priority: 20, // 优先级
enforce: true, // 强制执行
reuseExistingChunk: true, // 如果模块已经存在,则不重新创建
},
common: { // 公共代码
name: "common", // 指定名称
priority: 10, // 优先级
minChunks: 2, // 最小引用次数(被两处引用即为公共模块)
minSize: 0, // 最小切割大小
},
},
},
// 将 webpack 代码抽离成单独的文件 runtime.js
runtimeChunk: true,
minimizer
使用 TerserPlugin 的并发和缓存, 提升压缩阶段的性能。
minimize: true,
minimizer: [
new TerserPlugin({
// 开启多进程并行压缩
parallel: true,
terserOptions: {
compress: {
// 删除代码中的 console.log 语句
drop_console: true
}
}
}),
],
实现HMR
HMR 即热更新,有两种实现方式,一种是通过 devserver 配置和插件 HotModuleReplacementPlugin 实现,一种是通过在自定义开发服务下,使用插件webpack-dev-middleware和webpack-Hot-middleware配合实现 HMR。本篇介绍第二种方式,使用 express 搭建自定义开发服务器。
webpack-dev-middleware
webpack-dev-middleware 是一个容器wrapper ,它可以把 webpack 处理后的文件传递给一个服务器server ,实现监听文件改动并重新编译。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。
webpack-Hot-middleware
用来进行页面的热更新,刷新浏览器
示例代码
// 本地开发启动 devServe
import express from 'express';
import path from 'path';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import { DEV_SERVER_CONFIG } from './config/hmr-config';
// 从 webpack.dev 中获取配置
import webpackDevConfig from './config/webpack.dev';
const app = express();
const { HOST, PORT, HMR_PATH } = DEV_SERVER_CONFIG
const compiler = webpack(webpackDevConfig);
// 指定静态文件目录
app.use(express.static(path.join(__dirname, '../public/dist')));
// 引用 divMiddleware 中间件 (监控文件改动)
app.use(webpackDevMiddleware(compiler, {
// 落地文件(不用打包)
writeToDisk: (filePath) => {
return filePath.endsWith('.tpl')
},
// 资源路径
publicPath: webpackDevConfig.output!.publicPath!,
// headers 配置(允许跨域)
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With',
},
// 日志输出
stats: {
colors: true,
}
}));
// 引用 hotMiddleware 中间件 (实现热更新)
app.use(webpackHotMiddleware(compiler, {
path: `/${HMR_PATH}`,
log: () => {}
}));
console.log("构建中......");
// 启动 devServer
app.listen(PORT, HOST, () => {
console.log(`Server is running on http://${HOST}:${PORT}`);
});