《打包系列|webpack配置篇》

webpack 配置

初始化webpack

通过npm i -g webpack webpack-cli下载webpack和webpack的命令行包。

js 配置

我们都知道es6语法在低版本浏览器内是不会被识别的, 另外有一些语法不会被浏览器支持。于是就有了
babel这个工具链: 将 ECMAScript 2015+ (又可称为ES6ES7ES8等)版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

Babel 主要做了如下三件事:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
  • 源码转换, 比如 JSX 等

关于babel的相关插件和预设(.babelrc是babel的配置文件):

  • @babel/core:
    babel的核心功能, 没有我下面的插件都别想用
  • @babel/preset-env:
    提供一个预设环境(其实就是各种语法翻译包),是很多语法能够被转换成es5(箭头语法, let, const等)
    但是有些es6后面的语法比如(includes等)是不会被翻译的. 另外babel的插件的使用都需要这个预设库
    支持。
  • @babel/preset-react:
    对react jsx等语法的翻译处理,另外可以使用js可选链操作
  • @babel/cli:
    一个内置的 CLI 命令行工具,可通过命令行编译文件
    比如:babel index.js -o ./dist/index.js
  • @babel/polyfill:
    包括core-js和一个自定义的regenerator runtime模块.
    解决了有些浏览器或者低版本NodeJs下(inclues, promise, async, from等)语法不支持的问题
  • @babel/plugin-transform-runtime:
    防止打包后的文件中出现重复声明的函数, 它可以重复使用 Babel 注入的 helpers 函数,达到节省代码大小的目的
  • @babel/runtime:
    @babel/plugin-transform-runtime这个插件需要@babel/runtime配合使用
    供业务代码引入模块化的 Babel helpers 函数

babel的运作流程如下:
source code -> @babel/core -> ast ->
@babel/traverse 和 @babel/types -> ast -> @babel/generator -> output

但是babel会把esm的import语法变成CommonJS语法,这是不会被浏览器识别的。所以需要用打包工具处理

另外@babel/polyfill这个包文件很大.
一般会用 babel-polyfill 结合 @babel/preset-env + useBuiltins(entry) + preset-env targets 的方案
@babel/preset-env提供插件需要的预设环境, useBuiltins实现polyfill的按需引用
于是就有了下面的.babelrc配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
],
// 支持 import('xx.js').then(({default: file}) => {})语法
"dynamic-import-webpack"
]
}

先关的js处理在webpack配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
const isProdMode = process.argv.indexOf("--mode=production") !== -1
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: "babel-loader",
options: {
cacheDirectory: true, // false是使用缓存, 所以就是对node_modules下的包进行缓存
cacheCompression: isProdMode // true是对每个Babel的输出将使用Gzip压缩, 开发环境当然不要压缩了
}
},
]

另外有时候在开发公共包的时候,团队协作可能就需要使用typescript了,typescript该怎么处理配置呢?

  • webpack的配置文件下添加如下处理:
1
2
3
4
5
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/,
},
  • 添加ts-loader的配置文件tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"compilerOptions": {
"outDir": "./dist/", // 重定向输出目录。
"allowSyntheticDefaultImports" : true, // 允许从没有设置默认导出的模块中默认导入。
"module": "es6", // 指定生成哪个模块系统代码
"target": "es5", // 指定ECMAScript目标版本
"jsx": "react",
"sourceMap": true,
"moduleResolution": "node",
"allowJs": true //允许ts中引入js
},
"include": [
"mock/**/*",
"src/**/*",
"config/**/*",
".umirc.ts",
"typings.d.ts"
],
"exclude": [
"node_modules",
"lib",
"es",
"dist",
"typings",
"**/__test__",
"test",
"docs",
"tests"
]
}

样式配置

关于css的处理,一般要处理各种浏览器的css样式兼容性,支持scss等。下面是各种loader和插件的解释:

  • require("mini-css-extract-plugin").loder:
    把css拆分出来用外链的形式引入css文件,该插件会将所有的css样式合并为一个css文件

  • style-loader:
    把css插入到style中

  • css-loader:
    @import,url()进行处理

  • sass-loader:
    把scss/sass 翻译成css

  • postcss-loader:
    使用postcss处理css

  • postcss:
    把css解析为一个抽象语法树, 调用插件处理抽象语法树并添加功能

  • autoprefixer:
    添加前缀的,解决浏览器兼容问题. 添加webkit, mozilla前缀

  • postcss-plugin-px2rem:
    根据跟节点fontsize设置,将px装成rem

在webpack中配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const isProdMode = process.argv.indexOf("--mode=production") !== -1;
const isDevMode = process.argv.indexOf("--mode=development") !== -1;

const getStyleLoaders = (cssOptions, preProcessors) => {
const loaders = [
isDevMode && "style-loader",
isProdMode && MiniCssExtractPlugin.loader,
{
loader: require.resolve("css-loader"),
options: cssOptions,
},
{
loader: require.resolve("postcss-loader"),
options: {
postcssOptions: {
plugins: [
require("postcss-preset-env")({
autoprefixer: {
overrideBrowderslist: "andoroid >= 4.3",
},
stage: 3,
}),
require("postcss-plugin-px2rem")({
rootValue: 75,
minPixelValue: 2, // 设置要替换的最小像素值
}),
],
},
},
},
].filter(Boolean); // loader 是在数组的从后往前的顺序先后处理

if (preProcessors) {
loaders.push({
loader: require.resolve(preProcessors),
});
}
return loaders;
}

{
test: /\.css$/,
use: getStyleLoaders({
importLoaders: 1,
}),
},
{
test: /\.(scss|sass)$/,
exclude: /\.module\.(scss|sass)$/,
// importLoaders 选项告知走该loader之前需要走几个loader
// 为了防止经过css-loader处理时没有被sass-loader和postcss-loader处理
use: getStyleLoaders(
{
importLoaders: 2,
},
"sass-loader"
),
},
{
test: /\.module\.(scss|sass)$/,
use: getStyleLoaders({
importLoaders: 2,
sourceMap: isProdMode,
modules: {
localIdentName: '[local]_[hash:base64:6]'
},
}, 'sass-loader')
},

关于sourceMap

通过sourceMap可以查看报错是出自哪个源文件而不是打包后的文件哪里出错
不过使用sourceMap会使得打包速度变慢
另外默认在module: 'development'环境中sourceMap是默认开启的
下面是各种sourceMap的解释:

  • devtool: ‘inline-source-map’ 将对应的map文件放到js文件中
  • devtool: ‘cheap-inline-source-map’ 告诉你具体哪一行出问题,不会精确到那一列出问题
  • devtool: ‘eval’ eval是打包速度最快的除了你设置devtool:none
  • devtool: ‘cheap-module-eval-source-map’ 推荐development环境使用
  • devtool: ‘cheap-module-source-map’ 推荐production环境使用

各种plugin

plugin 可以在webpack运行到某个时刻的时候帮你做一些事情,所以plugin有生命周期这个说法。
下面是一些常用的插件:
clean-webpack-plugin:
打包前清除上一次打包的文件

htmlWebpackPlugin:
打包结束后,自动生成一个html文件, 并把打包生成的js自动引入到这个html

MiniCssExtractPlugin:
把css拆分出来用外链的形式引入css文件,该插件会将所有的css样式合并为一个css文件
另外还可以对css进行代码分割

webpack-bundle-analyzer:
打包分析工具

HappyPack:
开启多进程Loader转换,不过还是别用,经常听到电脑风扇的转动声

HotModuleReplacementPlugin:
热更新的模块替换插件

copy-webpack-plugin:
拷贝静态资源

css-minimizer-webpack-plugin:
压缩css

terser-webpack-plugin:
压缩js

tree shaking

这是我写的一个tree shaking demo

去除无用代码, 只支持ESM, 不支持commonJS的require的引入,
因为CommonJS定义的模块化规范,只有在执行代码后,才能动态确定依赖模块,所以不支持tree shaking。毕竟tree shaking的本质是在在编译生成AST以后进行无用代码去除
默认production模式开启tree shaking(由类似terser-webpack-plugin的插件做的处理)
如果想在开发环境中设置tree shaing, 需要这么配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
// --webpack.config.js
optimization: {
usedExports: true,
}
// --package.json
'sideEffects': false
// 对所有模块做tree shaking, 不过一般我们不对css等进行tree shaking
// 所以要这么配置
"sideEffects": [
"*.css",
"*.scss",
"*.sass"
],

另外有时候我们的代码是作为公共库进行开发,可能在Node环境也要运行,而CommonJs又不支持
tree shaking. 所以一般我们会有下面的处理:

1
2
3
4
5
{
"name": "Library",
"main": "dist/index.cjs.js", // 打包出一份commonjs规范的bundle
"module": "dist/index.esm.js", // 打包出一份tree shaking后的ESM规范的bundle
}

代码分割(code splitting)

当重新载入文件的时候,因为代码分割到了不同文件,
只需要对变更代码的文件重新加载即可,所以提升了加载性能
在webpack中配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
optimization: {
// 把runtime部分的代码抽离出来单独打包
// runtime部分的代码是管理各个模块的连接(模块之间的引用关系)
runtimeChunk: {
name: "runtime",
},
splitChunks: {
// 对同步代码和异步代码都进行代码分割
// 如果是async那就是只对异步代码做代码分割
chunks: "all",
minSize: 30000, // 表示抽取出来的文件在压缩前的最小大小,默认为30000
maxSize: 0, // 表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
minChunks: 1, // 引入的模块最少引入次数,超过才打包
maxAsyncRequests: 6, // 同时加载的模块树
maxInitialRequests: 4,
automaticNameDelimiter: "~", // 组和文件名的连接符
cacheGroups: {
vendors: {
name: 'vendors', // 对这种打包文件放到vendors.js中
enforce: true, // 忽略默认参数
test: /[\\/]node_modules[\\/]/, // 打包的文件来自node_modules
priority: 20, // 不同组的优先级
reuseExistingChunk: true,
},
// MiniCssExtractPlugin会对生成的css进行代码分割
// 然后打包到styles.css的文件中
styles: {
name: 'styles',
test: /\.(scss|sass|css|less)$/,
chunks: 'all',
enforce: true,
priority: 10,
},
// 默认组
default: {
minChunks: 2,
priority: -20, // 同时满足多个组条件,谁的优先级高就用谁
reuseExistingChunk: true, // 如果一个模块已经被打包了,就用之前打包的模块做引用
},
},
},
},

最后

另外还需要对webpack进行

  1. 多环境配置
  2. 支持多页面入口打包配置
  3. 代码打包分析
    这是我写的一个webpack配置

谈谈我做的优化:

  • aliasextensions的配置减少了webpack搜索路径的查找和根据后缀查找文件
  • HappyPack开启多进程,是打包速度更快,减少了构建时间
  • cacheDirectory 开启缓存,将每次的编译结果写进硬盘文件,这样如果打包的模块没有发生变换直接使用缓存就可以
  • tree shaking 生成环境代码进行无用代码删除在压缩减少了代码的体积
  • split chunk 代码分割以及抽离第三方模块,只需要对变更代码的文件重新加载即可,所以提升了加载性能
  • @babel/plugin-transform-runtime 减少了代码的重复声明
  • dynamic-import-webpack 动态加载,按需引入
  • external cdn外联引入一些三方库, 防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)
文章作者: woyao
文章链接: https://chenwoyao.github.io/2021/05/24/前端笔记/打包系列/打包系列webpack配置篇/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 woyao的博客