【笔记】Webpack学习笔记

前言

Webpack 是一个开源的前端打包工具。Webpack 提供了前端开发缺乏的模块化开发方式,将各种静态资源视为模块,并从它生成优化过的代码。(维基百科

下载依赖

1
npm install -D webpack webpack-cli

执行打包

  • 如果项目根目录存在webpack.config.js文件,则读取自定义配置,否则使用默认配置

--stats detailed:展示详细打包信息
--watch:观察者模式,实时侦测文件变化并自动重新打包
--env <key>--env <key>=<value>:指定环境变量
-c <file>--config <file>:指定配置文件,缺省值为./webpack.config.js

--entry <file>.js:指定入口文件,缺省值为./src/index.js
--ouput-filename <file_name>.js:指定打包后的文件名,缺省值为main.js
--output-path <dir_name>:指定打包后的文件所在目录,缺省值为./dist

1
webpack

配置文件

mode:打包环境

none:不指定打包环境
development:开发环境
production:生产环境

entry:入口配置
output:出口配置
devtool:SourceMap配置
module:解析文件的模块配置
resolve:解析规则配置
plugins:插件配置
optimization:代码优化配置
performance:性能配置

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require("path");

module.exports = {
mode: "none",
entry: {},
output: {},
devtool: false,
module: {},
resolve: {},
plugins: [],
optimization: {},
performance: {}
};

入口配置

  • 单入口
webpack.config.js
1
2
3
module.exports = {
entry: "./src/index.js"
};
  • 多入口

indexanother:定义多个入口

import:定义入口文件
dependOn:定义共享模块

<package_name>:定义共享模块名

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
entry: {
index: {
import: "./src/index.js",
dependOn: "shared",
},
another: {
import: "./src/another.js",
dependOn: "shared",
},
shared: ["<package_name>"]
},
output: {
filename: "[name].bundle.js"
}
};

出口配置

filename:定义文件名
chunkFilename:定义代码块文件名
path:指定打包后的文件所在目录,必须是绝对路径
clean:是否清理上一次打包的文件
publicPath:指定打包后的资源的URL前缀

webpack.config.js
1
2
3
4
5
6
7
8
9
module.exports = {
output: {
filename: "[name].bundle.js",
chunkFilename: "[name].chunk.js",
path: path.resolve(__dirname, "dist"),
clean: true,
publicPath: "http://127.0.0.1:80/",
}
};

SourceMap配置

false:不生成SourceMap
none:production环境下的缺省值,不生成SourceMap
eval:development环境下的缺省值,不生成SourceMap
source-map:production环境下的配置,生成SourceMap文件
cheap-source-map:development环境下的配置,生成SourceMap文件,不生成列映射,只生成行映射
cheap-module-source-map:development环境下的配置,生成SourceMap文件,不生成列映射,只生成行映射,使用Loader的情况下使用这种方式
eval-source-map:生成SourceMap,添加到eval()后面
inline-source-map:生成SourceMap,添加到源码文件末尾
hidden-source-map:生成SourceMap文件,但是源码文件末尾不会添加//# sourceMappingURL=注解
nosources-source-map:生成SourceMap文件,但是不会映射源码,只能查看到报错的源码位置,不能直接跳转查看源码

webpack.config.js
1
2
3
module.exports = {
devtool: false
};

解析器配置

手动指定导入的文件所使用的解析器

  • 通过<loader_name>来解析<file>
1
import "<loader_name>!<file>";

通过配置文件配置解析器

test:使用正则表达式匹配需要解析的文件名
use:指定解析器

"":只有一个解析器,并且无其他配置,则可以直接指定解析器名
["", ""]:有多个解析器,但无其他配置,则可以直接指定多个解析器名,多个解析器调用顺序为从后往前
[{}, {}]:有多个解析器,且有其他配置,则可以指定多个解析器配置,多个解析器调用顺序为从后往前

type:指定资源类型

asset/resource:直接打包文件,并得到文件的URL,通过内置的file-loader实现
asset/inline:得到文件的的Base64编码值,通过内置的url-loader实现
asset/source:得到文件的原始内容,通过内置的raw-loader实现
asset:缺省值,根据parser.dataUrlCondition.maxSize配置的文件大小(单位字节),小于等于则切换为asset/inline,大于则切换为asset/resource

generator:指定打包后的文件配置

filename:指定打包后的文件路径及文件名

[id]:文件id
[path]:原始文件路径
[name]:原始文件名,不包含扩展名
[ext]:原始文件后缀名
[query]:原始文件查询字符串
[hash]:原始文件哈希值,任意文件名或文件内容发生变化,哈希值都会变化

[hash:<num>]:截取指定长度的文件哈希值

[contenthash]:原始文件哈希值,任意文件内容发生变化,哈希值都会变化
[chunkhash]:原始文件哈希值,任意代码块文件内容发生变化,哈希值都会发生变化

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module: {
rules: [
{
test: /<file_name>/,
use: "<loader_name>",
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10*1024
}
},
generator: {
filename: "[name]"
},
}
]
}
};

解析规则配置

mainFiles:导入时自动匹配主文件名
extensions:导入时自动匹配扩展名
alias:定义路径别名

webpack.config.js
1
2
3
4
5
6
7
8
9
module.exports = {
resolve: {
mainFiles: ["index"],
extensions: [".wasm", ".mjs", ".js", ".json"],
alias: {
"@src": path.resolve(__dirname, "src")
}
}
};
  • 导入时仅指定目录,自动匹配主文件名
1
import "@src/dir"
  • 导入时仅指定文件名,自动匹配扩展名
1
import "@src/file"

插件配置

全局变量插件

定义全局变量
webpack.config.js
1
2
3
4
5
6
7
8
9
const { DefinePlugin } = require("webpack");

module.exports = {
plugins: [
new DefinePlugin({
"key": JSON.stringify("value")
})
]
};
使用全局变量
1
console.log(key);
使用内置全局变量
1
console.log(process.env.NODE_ENV);

作用域提升插件

webpack.config.js
1
2
3
4
5
module.exports = {
plugins: [
new Webpack.optimize.ModuleConcatenationPlugin()
]
};

压缩插件

webpack.config.js
1
2
3
4
5
6
7
8
9
10
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
plugins: [
new CompressionPlugin({
test: /\.(js|css)$/,
algorithm: "gzip"
})
]
};

代码优化配置

自动分包

  • 通过内置的插件split-chunks-plugin实现自动分包

chunks:指定分包范围

async:缺省值,异步的函数调用自动分包
all:所有函数调用自动分包

minSize:拆分后的每个代码块文件最小大小(单位字节)
maxSize:代码块文件达到指定大小时立即自动分包,对于一个整体不能再次拆分

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
entry: {
index: "./src/index.js",
another: "./src/another.js",
},
output: {
filename: "[name].bundle.js",
},
optimization: {
splitChunks: {
chunks: "all",
minSize: 10*1024,
maxSize: 10*1024
}
}
};
  • 自定义匹配
webpack.config.js
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
module.exports = {
entry: {
index: "./src/index.js",
another: "./src/another.js",
},
output: {
filename: "[name].bundle.js",
},
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
"chunk-name-1": {
test: /<file_name>/,
minSize: 10*1024,
maxSize: 10*1024
},
"chunk-name-2": {
test: /<file_name>/,
minSize: 10*1024,
maxSize: 10*1024
}
}
}
}
};

代码简化

简化JS代码
  • 通过Terser简化JS代码

extractComments:是否抽取注释,缺省值为true表示将注释抽取到单独的文件
parallel:是否使用多线程构建,缺省值为true表示使用多线程构建
terserOptions:Terser的配置

unused:是否保留没有使用过的函数,默认会优化

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: true,
parallel: true,
terserOptions: {
compress: {
unused: false
}
}
})
]
}
};
简化CSS代码

传送门

通过动态导入实现手动分包

  • 定义模块为懒加载,在访问先不加载这个资源,如果这个资源被调用,才会自动加载这个资源
1
import("模块名");
  • 调用导出的函数
1
2
3
import("模块名").then(function (模块名) {
模块名.导出的函数名();
});
  • 调用默认导出的函数
1
2
3
import("模块名").then(function (模块名) {
模块名.default();
});
  • 使用魔法注释定义更多参数

/* key: value */:魔法注释,多个参数之间用,隔开

webpackChunkName: "代码块文件名":定义代码块文件名
webpackPrefetch: true:定义模块为预获取,打包后的HTML中会引入<link rel="prefetch" as="script" href="">标签,在访问这个HTML时,先不加载这个资源,只有在网络空闲时才会自动加载这个资源
webpackPreload: true:定义模块为预加载,在访问先不加载这个资源,如果这个资源被调用,才会自动加载这个资源

1
2
3
import(/* webpackChunkName: "自定义文件名" */"模块名").then(function (模块名) {
模块名.导出的函数名();
});

环境变量

修改配置文件

  • 将配置文件中的对象改为函数,以支持环境变量
webpack.config.js
1
2
3
4
5
module.exports = function (env) {
return {
...
}
};

执行打包时指定环境变量

  • 执行打包时指定环境变量,通过环境变量添加了部分配置
1
webpack --env <key>=<value>

利用环境变量来确定打包环境

修改配置文件

  • 如果传递了环境变量production,则以生产环境打包,否则以开发环境打包
webpack.config.js
1
2
3
4
5
module.exports = function (env) {
return {
mode: env.production ? "production" : "development",
}
};

打包时指定环境变量

  • 传递环境变量production
1
webpack --env production

多环境配置

下载依赖

1
npm install -D webpack-merge

修改配置文件

  • 拆分多个配置文件(webpack.config.dev.jswebpack.config.prod.js)、抽离公共配置文件(webpack.config.common.js
1
2
3
4
5
6
+ config
- webpack.config.js
- webpack.config.common.js
- webpack.config.dev.js
- webpack.config.prod.js
- index.js
  • 如果不需要判定是否为生产环境,可以直接导出对象
  • 如果需要判定是否为生产环境,则需要将导出对象改为导出函数
webpack.config.common.js
1
module.exports = {};
webpack.config.dev.js
1
2
3
4
module.exports = function (env) {
const isProduction = env.isProduction;
return {};
};
webpack.config.prod.js
1
2
3
4
module.exports = function (env) {
const isProduction = env.isProduction;
return {};
};
webpack.config.js
1
2
3
4
5
6
7
8
9
const { merge } = require("webpack-merge");

const commonConfig = require("./webpack.config.common");
const devConfig = require("./webpack.config.dev");
const prodConfig = require("./webpack.config.prod");

module.exports = function (env) {
return merge(commonConfig, env.isProduction ? prodConfig(env) : devConfig(env));
};

执行打包时指定环境

1
webpack --config ./config/webpack.config.js --env production

DevServer配置

下载依赖

1
npm install -D webpack-dev-server

修改配置文件

static:指定静态资源目录

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
devServer: {
static: ["public"],
host: "0.0.0.0",
port: 8080.
proxy: {
"/api": {
target: "http://127.0.0.1:3000",
changeOrigin: true,
pathRewrite: {
"^/api": ""
}
}
}
}
};

启动服务器

1
webpack serve

实现TreeShaking

通过Terser对JS函数进行TreeShaking

optimization.usedExports:对从未使用的函数通过魔法函数unused harmony export null进行标记,Terser会自动忽略具有标记的函数的打包

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
optimization: {
usedExports: true
},
minimize: true,
minimizer: [
new TerserPlugin()
]
}

通过Purgecss对CSS类进行TreeShaking

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
optimization: {
usedExports: true
},
minimize: true,
minimizer: [
new TerserPlugin()
]
}

指定忽略TreeShaking的文件

  • 默认所有import ""导入但是没有使用的模块都会被TreeShaking

  • JS文件如果所有导出的模块都是纯函数,则无需配置或指定为false

sideEffects:指定需要Webpack忽略TreeShaking的文件,缺省值为false,表示没有需要忽略TreeShaking的文件

package.json
1
2
3
{
"sideEffects": false
}
  • JS文件如果导出的模块有非纯函数,具有副作用,则需要忽略这个JS文件的TreeShaking
package.json
1
2
3
4
5
{
"sideEffects": [
"./src/<file>.js"
]
}
  • 忽略所有CSS文件的TreeShaking
    • 因为所有CSS文件的导入一定没有使用,都是import "",所以需要忽略所有CSS文件的TreeShaking
package.json
1
2
3
4
5
{
"sideEffects": [
"*.css"
]
}

SourceMap文件

  • 如果启用了SourceMap,打包后会生成.js.map文件,.js文件的最后一行会自动配置指向的.js.map文件
bundle.js
1
2
3
4

...

//# sourceMappingURL=bundle.js.map
  • 浏览器需要开启SourceMap功能才会自动解析SourceMap文件,默认是开启状态
    • Settings->Preferences->Sources->勾选Enable JavaScript source maps->勾选Enable CSS source maps

完成