概要
介绍 webpack 的基础用法。
为什么选择 webpack
代码转换
把浏览器不支持的东西转换成浏览器支持的东西。例如 ES6 有很多语法糖,值得我们使用。
ES6 -> ES5、SCSS -> CSS 等。
文件优化
浏览器处理资源时,需要耗费时间,所以需要压缩文件,这样,http 请求更快,浏览器处理更快。
压缩 JS、CSS、HTML、图片等。
代码检查
可以配置 ESLint 等工具,约束团队。前端单元测试。
代码规范、单元测试等。
模块合并与分割
将组件化、模块化的代码公共代码进行抽取,优化,合并。有些文件可以设置异步加载。
刷新与发布
webpack 提供了工程化、自动化的构建方案,方案即系统化、流程化、规范化的自动构建。
安装与使用
安装
npm i webpack@4.16.5 webpack-cli -D
不推荐使用全局安装。多个项目可能会使用不同的 webpack 版本与配置,全局安装很可能会导致诸多问题。
webpack-cli
webpack command line interface:webpack 命令行接口
- webpack3 中,webpack-cli 和 webpack 在一个包中
- webpack4 与 wenpack-cli 分来管理,也需要单独安装
- webpack 是核心功能库,webpack-cli 是提供 webpack 进行构建的命令行接口
使用
直接运行命令
npx webpack
编写 script 脚本
{"name": "webpack-demo","version": "1.0.0","description": "","private": true,"scripts": {"build": "webpack"},"keywords": [],"author": "heora","license": "ISC","devDependencies": {"webpack": "^5.17.0","webpack-cli": "^4.4.0"}}
npm run build
基础概念
entry
Entry 用来指定 webpack 的打包入口。
webpack中,所有都是模块,JS/CSS/HTML都是模块,如需打包,必须引入到入口文件里,
当然也可以借助 plugin 对模块进行单独处理,但常规情况下,都是通过入口文件。
理解依赖图的含义
webpack 是一个模块打包工具(module bundler)。
它会把一切的资源,不管是代码资源还是非代码资源(图片、字体依赖等),都会不断加入到依赖图中。

entry 用法
单入口
entry 是一个字符串。
module.exports = {entry: './src/index.js'}
一般比较适用于一个项目只有一个入口文件或者项目是单页面应用。
多入口
entry 是一个对象。
module.exports = {entry: {index: './src/index.js',detail: './src/detail.js'}}
适合多页面的场景,多页应用。
output
Output 用来告诉 webpack 如何将编译后的文件输出到磁盘。
单入口配置
module.exports = {entry: './src/index.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist')}}
多入口配置
可以通过占位符确保文件名称的唯一。
module.exports = {entry: {index: './src/index.js',detail: './src/detail.js'}output: {filename: '[name].js',path: path.resolve(__dirname, 'dist')}}
loader
webpack 开箱即用只支持 JS 和 JSON 两种文件类型,可以通过 Loader 去支持其他文件类型并且把它们转换成有效的模块,并且可以添加到依赖图中。
loader 本身是一个函数,接收源文件作为参数,返回转换的结果。
常见的 loader
| 名称 | 描述 |
|---|---|
| babel-loader | 转换 ES6、ES7 等 JS 新特性语法 |
| css-loader | 支持 .css 文件的加载和解析 |
| style-loader | 将 css-loader 处理后的内容挂载到页面的 head 部分 |
| less-loader | 将 less 文件转换成 css |
| ts-loader | 将 ts 转换为 JS |
| file-loader | 进行图片、字体的打包 |
| url-loader | 功能类似于 file-loader,可以指定限制,返回一个 DataURL |
| raw-loader | 将文件以字符串的形式导入 |
| thread-loader | 多进程打包 JS 和 CSS |
loader 的用法
module.exports = {module: {rules: [{test: /\.(jpg|png|gif)$/,use: {loader: 'url-loader',options: {name: '[name]_[hash].[ext]',outputPath: 'images/',limit: 2048}}},{test: /\.scss$/,use: ['style-loader','css-loader','sass-loader','postcss-loader']}]}}
test 指定匹配规则。use 执行使用的 loader 名称。
- 多个loader的执行顺序是从后向前
- 第一个loader将模块代码作为参数,然后处理参数并返回
- 其后的loader接收前一个loader的返回值作为参数,继续处理参数并返回
- 最后一个loader返回处理后的JS模块源码
Plugins
插件用于 bundle 文件的优化,资源管理和环境变量注入,作用于整个构建过程。
任何不能通过 loader 完成的事情,都可以用 plugins 完成,比如构建之前需要手动删除目录。
每一个 plugin 实际上是一个类(构造函数)。
常见的 Plugin
| 名称 | 描述 |
|---|---|
| CommonsChunkPlugin | 将 chunks 相同的模块代码提取成公共 js(通常用于多个页面打包的情况) |
| CleanWebpackPlugin | 清理构建目录 |
| ExtractTextWebpackPlugin | 将 CSS 从 bundle 文件里提取成一个独立的 CSS 文件 |
| MiniCssExtaractPlugin | 将 CSS 源码提取,创建为独立的文件引入相应的HTML中 |
| HtmlWebpackPlugin | 创建 html 文件去承载输出的 bundle |
| CopyWebpackPlugin | 将文件或者文件夹拷贝到构建的输出目录 |
| UglifyjsWebpackPlugin | 压缩 JS |
| ZipWebpackPlugin | 将打包出的资源生成一个 zip 包 |
Plugin 的用法
module.exports = {plugins: [new htmlWebpackPlugin({template: path.resolve(__dirname, 'src/index.html'),filename: 'index.html',chunks: ['index']}),new htmlWebpackPlugin({template: path.resolve(__dirname, 'src/list.html'),filename: 'list.html',chunks: ['list']}),new MiniCssExtractPlugin({// 独立打包 css 文件的相对路径与名称filename: 'css/[name].css',// 通过import导入的样式文件模块的文件名// 生成一个 JS 的 chunkchunkFilename: 'css/[id].[hash:16].css'}),// 优化与压缩 CSS 资源new OptimizeCssAssetsPlugin({// https://www.cssnano.cn/guides/optimisations/// https://www.cssnano.cn/guides/presets/// CSS 优化插件设置cssProcessor: require('cssnano'),// CSS 优化插件配置项cssProcessorOptions: {preset: ['default',{discardCommonents: {removeAll: true}}]}})]}
将定义好的插件实例化并放入 plugins 数组中。
- webpack在其生命周期中会向外界广播一些事件钩子
- 每一个plugin都可以监听其中的任何一个事件钩子
- 当被监听的事件响应,就执行对应的处理函数改变原本webpack正常输出的结果
- 插件的执行顺序是从上到下
mode
Mode 用来指定当前的构建环境是:production、development 还是 none。
设置 mode 可以使用 webpack 的内置函数,默认为 production。
Mode 的内置函数功能
| 选项 | 描述 |
|---|---|
| development | 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 |
| production | 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin. |
| none | 不开启任何优化选项 |
基础用法
解析 ES6 和 React JSX
解析 ES6
npm i @babel/core @babel/preset-env babel-loader -D
webpack 原生支持 js 解析,但是对于 ES6 语法,原生是不支持的。
可以使用 babel-loader,babel 的配置文件是:.babelrc。
const path = require('path');module.exports = {mode: "development",entry: './src/index.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist')},module: {rules: [{test: /.js$/,use: 'babel-loader'}]}}
增加 ES6 的 babel preset 配置。
{"presets": ["@babel/preset-env"]}
解析 React JSX
npm i react react-dom -S
npm i @babel/preset-react -D
package.json 中 dependencies 和 devDependencies 的区别是:devDependencies 用于本地环境开发时候,dependencies 用户发布环境,也就是开发阶段的依赖最后是不对被打入包内。 通常框架、组件和 utils 等业务逻辑相关的包依赖放在 dependencies s里面,对于构建、ESlint、单元测试等相关依赖放在 devDependencies 中。
在前面的基础之上增加 React 的 babel preset 配置,同理,vue 也可以这样配置。
{"presets": ["@babel/preset-env","@babel/preset-react"]}
babel-loader 解析 ES6 的语法也是需要知道哪些语法需要解析,需要通过 .babelrc 进行配置,这个 @babel/preset-env 就是告诉 babel-loader 要解析ES6的语法,其它的 react 语法可以通过其它的 babel preset 进行处理。
编写个测试代码
'use strict';import React from 'react';import ReactDOM from 'react-dom';class Search extends React.Component {render () {return <div>Hello React</div>;}}ReactDOM.render(<Search />,document.getElementById('app'));
解析 CSS、Less 和 Sass
解析 CSS
css-loader 用于加载 .css 文件,并且转换成 commonjs 对象。
style-loader 将样式通过 <style> 标签插入到 head 中。
npm i style-loader css-loader -D
index.js
'use strict';import React from 'react';import ReactDOM from 'react-dom';import './css/index.css';class Search extends React.Component {render () {return (<div className="text">Hello React</div>);}}ReactDOM.render(<Search />,document.getElementById('app'));
index.css
.text {font-size: 20px;color: red;}
webpack.config.js
const path = require('path');
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
}
解析 Less、Sass
less-loader 用于将 less 转换成 css。
npm i less less-loader -D
index.js
import './css/index.less';
webpack.config.js
module: {
rules: [
// ...
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
}
]
}
sass-loader 和 less-loader 配置基本一致。
npm i node-sass sass-loader -D
解析图片和字体
file-loader 用于处理文件。
npm i file-loader -D
解析图片
index.js
import './css/index.less';
import avator from './imgs/avator.png';
class Search extends React.Component {
render () {
return (
<div className="text">
Hello React <img width={ 200 } height={ 200 } src={ avator } />
</div>
);
}
}
webpack.config.js
module: {
rules: [
{
test: /\.(png|jpg|gif|jpeg)$/,
use: 'file-loader'
}
]
}
解析字体
file-loader 可以用来处理图片,也可以用来处理字体。
index.less
@font-face {
font-family: 'SourceHanSerifSC-Heavy';
src: url('../fonts/SourceHanSerifSC-Heavy.otf');
}
.text {
font-size: 20px;
color: red;
font-family: 'SourceHanSerifSC-Heavy';
}
webpack.config.js
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}
]
}
资源解析
url-loader 也可以处理图片和字体。还可以设置较小资源自动转换 base64。
url-loader 内部也是使用的 file-loader。对于图片资源,可以使用 url-loader 进行处理。
npm i url-loader -D
webpack.config.js
module: {
// {
// test: /.(png|jpg|gif|jpeg)$/,
// use: 'file-loader'
// },
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240
}
}
]
}
]
}
webpack 文件监听
文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。
开启方式
webpack 开启监听模式,有两种方式:
启动 webpack 命令时,带上 —watch 参数。
"scripts": { "build": "webpack", "watch": "webpack --watch" },webpack.config.js 中设置 watch: true。
缺陷
每次需要手动刷新浏览器。
原理分析
轮询判断文件的最后编辑时间是否变化。
某个文件发生变化,并不会立即告诉监听者,而是先缓存起来,等 aggregateTimeout。
webpack.config.js
// 默认 false,也就是不开启
watch: true,
// 只有开启监听模式,watchOptions 才生效
watchOptions: {
// 默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后会等 300ms 再去执行,默认 300ms
aggregateTimeout: 300,
// 判断文件是否变化是通过不停询问系统指定文件有没有变化实现的,默认每秒询问 1 次
poll: 1000
}
webpack 热更新
使用 webpack-dev-server。
npm i webpack-dev-server -D
特性
WDS 不刷新浏览器。
WDS 不输出文件,而是放在内存中。
配置 hot:true后,自动引入 HotModuleReplacementPlugin 插件。
webpack-dev-server(WDS)的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。 hot-module-replacement-plugin 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。 单独写两个包也是出于功能的解耦来考虑的。简单来说就是:hot-module-replacement-plugin 包给 webpack-dev-server 提供了热更新的能力。
配置
package.json
"scripts": {
"build": "webpack",
"watch": "webpack --watch",
"dev": "webpack serve --open"
},
webpack.config.js
webpack-dev-server 开发环境使用,生产环境不需要使用。
module.exports = {
// ...
mode: "development",
// ...
devServer: {
contentBase: './dist',
hot: true
}
}
其他方式
使用 webpack-dev-middleware。
WDM 将 webpack 输出的文件传输给服务器(通常使用 express,koa2)。
适用于灵活的定制场景。
原理分析
Webpack Compile 将 JS 编译成 Bundle。
Bundle server:提供文件在浏览器的访问。
HMR Server:建热更新的文件输出给 HMR Runtime。
HMR Runtime:会被注入到浏览器。

正常流程 1-2-A-B ,更新流程 1-2- 3-4。
文件指纹策略
文件指纹指打包后的文件名的后缀。
通常文件指纹用来做版本管理,只需要发布修改的页面。
常见指纹
文件指纹策略:chunkhash、contenthash、hash。
Hash
和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改。
Chunkhash
和 webpack 打包出的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值。
Contenthash
根据文件内容来定义 hash,文件内容不变,则 contenthash 不变。
JS 文件指纹设置
设置 output 的 filename,使用 [chunkhash]。
CSS 文件指纹设置
设置 MiniCssExtractPlugin 的 filename,使用 [contenthash]。
单纯使用 css-loader 和 style-loader 会把 css 插入到 head 头部,不存在文件的概念。
图片的文件指纹设置
设置 file-loader 的 name,使用 [hash]。
这里的 hash 也是指文件内容 hash,和上面提的 Hash 的含义不同。
| 占位符名称 | 含义 |
|---|---|
| [ext] | 资源后缀名 |
| [name] | 文件名称 |
| [path] | 文件的相对路径 |
| [folder] | 文件所在的文件夹 |
| [contenthash] | 文件的内容 hash,默认是 md5 生成 |
| [hash] | 文件内容的 hash,默认是 md5 生成 |
| [emoji] | 一个随机的指代文件内容的 emoj |
配置
webpack 的 chunkhash 不能和热更新一起使用(测试环境),需要在生产环境使用。
css 使用文件指纹,需要安装 plugin。
npm i mini-css-extract-plugin -D
package.json
"scripts": {
"watch": "webpack --watch",
"build": "webpack --config webpack.prod.js",
"dev": "webpack serve --config webpack.dev.js --open"
}
webpack.prod.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
publicPath: '/',
filename: '[name][chunkhash:8].js',
path: path.resolve(__dirname, 'dist')
},
mode: "production",
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
},
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]_[hash:8].[ext]',
limit: 10240
},
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
},
}
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
})
]
}
HTML、CSS、JavaScript 代码压缩
代码压缩分为三块。
HTML 压缩、CSS 压缩、JS 压缩。
JS 文件压缩
内置 uglifyjs-webpack-plugin。
生产环境默认开启此插件。
CSS 文件压缩
使用 optimize-css-assets-webpack-plugin,同时使用 cssnano。
npm i optimize-css-assets-webpack-plugin -D
npm i cssnano -D
HTML 文件压缩
修改 html-wbepack-plugin,设置压缩参数
npm i html-webpack-plugin -D
webpack5 使用 html-webpack-plugin 报错,建议降级 webpack4。
删除再安装发现可以,但是未复现解决策略,目前看是还是存在兼容问题的。
配置
webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js'
},
// ...
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano')
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'src/index.html'),
filename: 'index.html',
chunks: ['index'],
excludeChunks: ['node_modules'],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
]
}
