webpack基础
本章将会介绍
- loader & plugins
- 出入口
- 静态文件数据
- 热加载
1. 安装
yarn add webpack webpack-cli --dev
一行解决,但有几个要注意的问题
- 每个项目对
webpack的版本要求不同,因此不可能使用全局webpack进行打包编译 - 然而当我们默认执行
webpack xxx打包文件时,实际上跑的webpack是全局的,若全局没有安装则会出现找不到的情况 - 可以使用
npx webpack或yarn webpack指令,会从内到外一层一层node_modules中去寻找对应的webpack包 - 通常我们会发现在老项目的
shell脚本中,会使用./node_modules/.bin/webpack xxx的指令,实际上就是为了指定使用我们自己项目中安装的webpack
2. 配置文件介绍
webpack.config.js 说是 webpack 的灵魂也不为过。因为对于我们来说可能见到和能操作最多的就是这个文件了
ps: 此处略去命令行的形式,因为实际上用到的并不多,webpack官方也更推荐这种配置文件的形式
3. 打包模式
mode也是耳熟能详的配置项,区分为开发环境
development和生产环境production
webpack.config.js
module.exports = {
mode: 'development' | 'production'
...
}
为了区分打包模式,常常会分两个 webpack.config.[mode].js 去实现,会在下面介绍。
4. 打包文件出入口
对于entry和output也非常好理解,一个是定义从哪里开始打包,另一个是定义产出
要注意的是,output的路径无法使用相对路径,只能使用绝对路径,因此就需要引入path模块
webpack.config.js
const path = require('path');
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
// 静态文件输出路径 和 asset type有关
assetModuleFilename: 'assets/[hash][ext][query]',
// 每次打包产出默认不会消失(除非文件名相同覆盖),添加后将清空上次打包信息
clean: true
}
...
}
此时调用 npx webpack 就会默认自动找到当前目录下的 webpack.config.js 文件进行读取打包了~
可是,我们发现每次修改代码后,都需要手动执行:1、执行webpack打包指令 2、页面刷新
不能说相当麻烦,只能说狗都不干!因此我们将使用
webpack-dev-server去实现修改代码后的 自动重新加载
5. 热加载
官网描述很清楚:
webpack-dev-server为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。其实也就是我们常说的热加载
安装
yarn add webpack-dev-server --dev
webpack.config.js
module.exports = {
...
output: {
filename: 'haha.js',
path: path.resolve(__dirname, './dist')
},
// 也可以不配置,会默认选用output打包出来的路径
devServer: {
static: './dist', // 配置读取打包后的文件
},
...
};
运行
npx webpack server --open
当出现下图情况代表成功了,赶紧试试看吧~

此时我们的基本框架搭好了,就该对我们各式各样的代码进行打包了。我们知道除了js外,各种各样的其他文件类型
- 静态文件图片(png,svg)
- 文件(csv, execl,json)
- 还有前端三剑客中的css,和各种框架vue、react,那么对于当前基础的
webpack来说,实际上只能打包js文件而已,那么对于其他的来说我们该怎么办呢?
解决方案
- 对于第一点,我们可以使用资源模块的加载
- 对于第二点,此时就要拿出大杀器 loaders了
我们分别来进行介绍~
6. 静态资源模块
在老版本中,我们会看到各种 loader比如
url-loaderraw-loaderfile-loader去处理静态资源对于最新版本已经将其内置 asset module type
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
| asset module type | 作用 | 替换之前loader |
|---|---|---|
asset/resource | 发送一个单独的文件并导出 URL | file-loader |
asset/inline | 导出一个资源的 data URI | url-loader |
asset/source | 导出资源的源代码 | raw-loader |
asset | 在导出一个 data URI 和发送一个单独的文件之间自动选择 | url-loader |
官方相关文档 写的很清楚,看文档前我们需要带着几个问题去看,
6.1 资源模块使用
实际上很简单匹配对应的rules中,使用上 {type: ModuleType} 对应字段即可。对于原先都是写 {use: xx-loader} 的形式
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'|'asset/inline'|'asset/source'|'asset'
}
]
}
};
知道了配置在哪,但何时选用哪种配置呢?让我们一起看看使用结果是啥样的就知道了
6.2 使用结果
asset/resource
官网介绍: 所有文件都将被发送到输出目录,并且其路径将被注入到 bundle 中。
实际上指的是,将所有匹配上的资源,copy一份并按命名规则改名到我们的output产出中。并在打包出来的bundle.js中,引用资源的地方会直接引用刚刚复制的文件。看下例子:

ps: 注意需要在生产模式下打包才可以看到,因为都知道热加载模式为了更快改动是将打包的文件放在内存中的
asset/inline
官网介绍: 所有文件都将作为 data URI 注入到 bundle 中。
这也很好理解,就将文件不在作为源文件的形式,换成dataURL的形式,那么什么是dataURL呢?→ 看文档
由此可知,所有的文件都可以被解析为 data: 前缀开头的字符形式。我们常见的 base64 格式就是其中之一。

asset/source
官网介绍: 所有文件将原样注入到 bundle 中。
其实这个描述不准确,是将文件内容以文本形式注入。对于读取文本数据/md文件这个是最适合的。然而对于图像,则将会无法正常读取。因为就相当于直接将图像用文本文档打开,这是完全不可读的,对于html也是一样。

asset
官网介绍: 自动地在
resource和inline之间进行选择:小于 8kb 的文件,将会视为inline模块类型,否则会被视为resource模块类型。
换而言之,就是会根据文件大小进行判断选用不同的形式引入。因为我们看到当文件转换为dataURL将会变得很大而且不可读!!这是一个很好的中和情况。
默认是8KB作为中间值,但可以使用 parser.dataUrlCondition.maxSize 去自定义限定这个中间值,这里同样给出一个例子:
6.3 自定义存储文件路径
我们往往需要控制将静态文件都集合在打包出来的某个文件夹下,不会混乱。
显然对于静态文件存储只有在我们设置type为 asset 或 asset/resource 时才会生效,因为这两者才会将文件进行copy,直接打包到我们的产出文件夹中。
1、配置 output.assetModuleFilename 即可
- [hash] - 按文件内容生成hash值,若文件不变则hash不变
- [ext] - 扩展名
- [query] - loader后的参数 例如
babel-loader?a=1其中a=1会被匹配

2、在对应 rules.generator.filename / rules.generator.publicPath里面配置对应
对特定规则创建不同的文件夹,比如我希望图片放在
static/img/*下,txt文件放在static/txt/*下的情况,就需要使用单独配置了。
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
...
output: {
filename: "haha.js",
path: path.resolve(__dirname, "./dist"),
assetModuleFilename: 'assets/[hash][ext][query]',
clean: true,
},
module: {
rules: [
{
test: /\.png/,
type: "asset",
generator: {
// filename: 'static/[hash][ext]', // 文件名和output.assetModuleFilename一样
publicPath: 'assets/', // 静态文件目录
},
}
]
}
...
};
优先级比较 rules.generator.filename > rules.generator.publicPath > output.assetModuleFilename
6.4 使用场景总结
我们都知道 webpack是默认能识别打包
js和json文件的,因此这两个不需要我们的静态资源类型指定就直接可以使用。
-
对于
csv格式的静态数据,我们只能使用读取内容的形式即asset/source的形式去获取内容文本。 -
对于
svg、png等图片形式、字体比如ttf类型文件,其实可以通过asset自动判断使用inline或resource去将文件导入。(实际上ttf只能用resource但大小肯定大于限定值因此统一到此)
此外其实还有些其他格式,而且对于 csv 格式我们仅仅只能读取文本内容,再对文本进行拆分等,其实往往是希望能转换成我们可操作的 json 形式,对于 css 族类的样式文件也无法直接操作,很显然之前的方法都不在适合了,有什么办法呢?此时就要引出我们的 loader 了!!
7. loaders和plugins介绍
各式各样的
plugins和loaders可以让webpack按我们设想的方式打包出东西

7.1 loaders
Webpack 支持使用 loader 对文件进行预处理。你可以构建包括 JavaScript 在内的任何静态资源。并且可以使用 Node.js 轻松编写自己的 loader。推荐loaders
我们都知道对于 webpack 来说,json 和 js 类型的文件都是内置可读的。但对于其他文件形式,我们除了上述的 [静态资源模块](#2.4 静态资源模块) 加载方式外,还有很多形式是无法去直接读取的,css 、less 等。此时就要使用 loaders 了
对于loader来说有两种配置方式,应该都很常见
- config(推荐)
这是最常见的一种配置方式,在 webpack.config.js 文件中指定 loader。去设定针对那些资源使用哪些
loader
配置静态资源模块时是配置 module.rules.type ,此时要配置 module.rules.use 去设定针对那些资源使用哪些 loader
webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' },
],
},
};
- 内联形式
在每个
import语句中显式指定 loader。
例: import Styles from 'style-loader!css-loader?modules!./styles.css';相信大家在一些老项目都见到过这种形式,在import的时候指定当前所需的loader和loader的匹配规则。
老样子先来看看官网描述:通过为内联 import 语句添加前缀,可以覆盖 配置 中的所有 loader, preLoader 和 postLoader:
-
使用
!前缀,将禁用所有已配置的 normal loader(普通 loader)import Styles from '!style-loader!css-loader?modules!./styles.css'; -
使用
!!前缀,将禁用所有已配置的 loader(preLoader, loader, postLoader)import Styles from '!!style-loader!css-loader?modules!./styles.css'; -
使用
-!前缀,将禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoadersimport Styles from '-!style-loader!css-loader?modules!./styles.css';
选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}。
相信大家对
preLoader和postLoader是什么感到疑惑。我们都知道loader都是从右向左进行加载的,在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的pitch方法。其实看下下面的例子就知道和 redux 的洋葱圈很像
对于以下 use 配置:
module.exports = {
//...
module: {
rules: [
{
//...
use: ['a-loader', 'b-loader', 'c-loader'],
},
],
},
};
将会发生这些步骤:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
先别深究,之后在写手写 loader 时会再详细说。
7.2 Plugins
插件 是 webpack 的主要功能。插件目的在于解决 loader 无法实现的其他事。Webpack 提供很多开箱即用的 插件。
插件注册方法大同小异:
- 安装后
require导入插件 - 在
plugins中注册 插件实例 (new 一个)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
下面我们来对一下常见的 loaders 和 plugins 进行介绍~
8. 常用loaders
8.1 css族
就静态资源方式我们无法直接操作
css族 -css、less、sass,那么我如何使用loader配置呢?
下面将以less为例:
- 首先我们知道
loader生效都是从右向左的 - less文件先编译为css再转换成style标签引入
- 因此从左向右需要
style-loadercss-loaderless-loader三个loader进行转化
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(css|less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
...
};
可能有人会问: 为什么还要 style-loader 按道理 css-loader 不就已经实现了转成 css 浏览器能认识的语言,为什么还要再进行转换?我们来看下面两张对比图
- 实际上css-loader对于文件来说确实已经转化好了我们的样式写入js中(可以打包后在产出的js中查看)
- 但并没有去引用,因此并不会生效,在此处我们使用了
style-loader去通过style的形式去引入
此外,虽然 style 形式引入确实可以用了,但我们往往不会去关心这个文件,因此希望对其进行压缩再将 css 通过link方式抽离出去。应该怎么办呢?这就需要请出我们的耳熟能详的压缩css插件 MiniCssExtractPlugin 和 css-minimizer-webpack-plugin
安装
yarn add css-minimizer-webpack-plugin mini-css-extract-plugin --dev
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`)
// 这是一个内置插件,可以极大压缩js文件
`...`,
new CssMinimizerPlugin(), // 压缩css文件
],
},
plugins: [new MiniCssExtractPlugin()], // 注册分离css插件
};
我们看到原先 style-loader 的地方被替换为了 MiniCssExtractPlugin.loader 用作分离loader
Ps:这将仅在生产环境开启 CSS 优化。如果还想在开发环境下启用 CSS 优化,请将
optimization.minimize设置为true:
webpack.config.js
// [...]
module.exports = {
optimization: {
// [...]
minimize: true,
},
};
至此对于 css 族的文件读取与优化都在此介绍了,
8.2 数据文件族
我们知道
js和json文件都能直接被webpack读取,但其他数据文件比如csv、xml、tsv、yaml、toml、json5等格式的文件webpack就无法帮我们了,需要使用对应的loader。或者我们可以使用对应的parser进行解析,下面将会举两个例子说明。
- 直接使用
文件-loader
安装
yarn add csv-loader xml-loader --dev
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(tsv|csv)$/,
use: ['csv-loader']
},
{
test: /\.xml$/,
use: ['xml-loader']
}
]
},
...
};
在文件中使用import引入,最终对于 csv、tsv 将会被转换为 Array ,xml 文件将会被转换为 Object 格式。
- 使用 文件库提供的
parser
安装
yarn add toml --dev
webpack.config.js
const toml = require('toml');
module.exports = {
...
module: {
rules: [
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
}
]
},
...
};
总结
至此我们学会了所有文件导入方式,实际上优先级(方便程度)也能看出 asset > loader > parser。目前学习到的知识,对于所有文件都能进行解析打包成 js 了,但对于浏览器就一定认识我们的 js 吗? 我相信大部分新同学肯定没有感受过兼容ie的苦,干啥啥不行的感觉。因为另一大神器 babel 已经帮我们解决了这个问题~
8.3 babel
对于低版本浏览器/IE这种毒瘤,我们新版的js语法可能还未支持,react、vue常见框架的jsx浏览器就更认不得了
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlWebpackPlugin = new HtmlWebpackPlugin({
template: path.join(__dirname, '../example/src/index.html'),
filename: './index.html'
});
module.exports = {
...
module: {
rules: [{
test: /\.(jsx?|tsx?)$/,
use: ['babel-loader'],
exclude: /node_modules/
}]
}
...
};
babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
corejs: '3',
loose: true,
},
],
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
'@babel/preset-typescript',
]
};
多的babel相关就不多描述,可以自己查阅 babel文档 或者可以找我出一期相关介绍~
8.4 Typescript
typescript可以说已经是我们前端开发必不可少的东西了,对于ts文件的解析,那肯定也跑不掉需要对应的loader啦~
截止目前,有两兄弟一直在打架 ts-loader 和 上面的 babel-loader 。两边都想成为打倒另一个成为 ts 专属,但最终目前最好的方案就是两者共存。
我们先来分析一下两者优缺点:
| 优点 | 缺点 | |
|---|---|---|
ts-loader | 正常转化效率快 | 但无法将 js 转换为低版本兼容。换句话说无法识别 babel 配置进行相应转换 |
babel-loader + @babel/preset-typescript | 会进行相应 babel 转译 | 但打包出的包不会保留任何 ts 信息,只剩下 js 。对于我们的工具类函数/组件库来说这就没了意义,除非你专门去手写一个 @type/xxx 。速度慢于 ts-loader |
因此最终总结下来最高效的就是:
1、先用 ts-loader 转出 js文件
2、对转出的 js 文件,进行 babel 转译低版本
9. 常用plugins
所有规范的
plugins都被收录在 传送门 中,数量不多大致通读一遍有个印象后,开发需要/看见能及时反应查找。
下面将会介绍常见plugin
9.1 HtmlWebpackPlugin
这个可以说是最常见的插件了,基本所有的项目中都会遇到。
作用:
在我们每次编译脚本/代码时,会动态生成一个 html 页面,并将我们打包好的js文件注入到这个页面中
使用场景:
- 很显然对于目前前端框架SPA来说,都是为了展示页面
- 对于一些工具类函数等,想要测试在浏览器运行情况时,也会使用这个,无需手动测试了
使用方法:
安装: yarn add html-webpack-plugin --dev
很好理解,所有打包过程都是开发环境下就执行完的,所以无需作为生产依赖安装 (指的是dependencies不是指 mode)
webpack.config.js:
// 项目中常见配置
// 给定template 会按照我们的模板 index_template.html 进行生成注入,否则会自动生成最简洁的html文件
// 默认注入在 head 标签中,使用 defer 延迟加载,默认生成文件名 index.html
// 更多配置项可看 https://github.com/jantimon/html-webpack-plugin#options
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: './index_template.html'
})
]
...
};