观看视频:尚硅谷最新版Webpack5实战教程(从入门到精通)
Webpack 简介
webpack 是什么
webpack 是一种 前端资源构建工具 ,一个静态模块打包器(module bundler)
在 webpack 看来,前端的所有资源文件(js/json/css/img/less…)都会作为模块处理。它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
webpack 五个核心概念
Entry
入口,指示 webpack 以哪个文件作为入口起点开始打包,分析构建内部依赖图
Output
输出,指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名
Loader
让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
Plugins
可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等
Mode
选项 描述 特点 development 会将 DefinePlugin 中的 process.env.NODE_ENV 的值设为 development 能让代码本地调试运行的环境 production 会将 DefinePlugin 中的 process.env.NODE_ENV 的值设为 production 能将代码优化上线运行的环境
Webpack 初体验
初始化配置
初始化 package.json:npm init
下载安装 webpack:(webpack4 以上的版本需要全局/本地都安装 webpack-cli)
全局安装:
npm i webpack@4 webpack-cli@3 -g
本地安装:
npm i webpack@4 webpack-cli@3 -D
编译打包应用
运行指令:
- 开发环境:
webpack ./src/index.js -o ./build/built.js --mode=development
,webpack 会以./src/index.js
为入口文件开始打包,打包后输出到./build/built.js
整体打包环境,是开发环境 - 生产环境:
webpack ./src/index.js -o ./build/built.js --mode=production
,webpack 会以./src/index.js
为入口文件开始打包,打包后输出到./build/built.js
整体打包环境,是生产环境
结论:
- webpack 能处理 js、json,不能处理 css、img等其他资源
- 生产环境和开发环境将 ES6 模块化编译成浏览器能识别的模块化
- 生产环境比开发环境多一个压缩 JS 代码
Webpack 环境的基本配置
webpack.config.js
是 webpack 的配置文件
- 作用:指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
- 所有构建工具都是基于 nodejs 平台运行的,模块化采用 commonjs
path 模块的 resolve __dirname
nodejs 的变量,代表当前文件的目录的绝对路径
loader,rules 下的 use 数组中 loader 执行顺序:从右到左、从下到上依次执行
1 | npm i less-loader@6 style-loader css-loader@ -D |
url-loader
只能处理样式中的图片资源,这个 loader 对图片资源进行 ES6Module 引入 ,为了跟 html-loader
一致,需关闭 ES6Module(esModule :false
)
可以做图片 base64 处理
优点:减少请求数量(减轻服务器压力)
缺点:图片体积会更大(文件请求速度更慢)
html-loader
处理html中的图片资源,这个 loader 对图片资源进行 commonJS 引入
devServer:npx webpack-dev-server
开发服务器devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)。特点:只会在内存中编译打包,不会有任何输出
1 | const { resolve } = require('path') |
Webpack 生产环境的基本配置
https://github.com/topics/javascript
- 安装
1 | npm i mini-css-extract-plugin -D |
压缩一般使用 plugin 完成
兼容一般使用 loader 完成
1 | const HtmlWebpackPlugin = require('html-webpack-plugin') |
css 兼容性处理
package.json
中的 browserslist
配置
1 | "browserslist": { |
MiniCssExtractPlugin
提取js中的css成单独文件
OptimizeCssAssetsWebpackPlugin
对 css 代码进行压缩
1 | { |
eslint 语法检查
package.json
中的 eslintConfig
配置
1 | "eslintConfig": { |
使用 eslint-loader
自动修复
1 | { |
js 兼容性处理
首先需要安装 babel-loader
、 @babel/core
基本 js 兼容处理 ->
@babel/preset-env
问题:只能转换基本语法,比如:promise 不能转换
全部 js 兼容性处理 ->
@babel/polyfill
在 js 中引入
import '@babel/polyfill'
问题:只要解决部分兼容性问题,但是所有兼容性代码全部引入,体积太大
按需加载 js 兼容性处理
使用
core-js
或使用 link 引入
https://polyfill.io/v3/url-builder/
1 | { |
html 压缩
生产环境会自动压缩 js 代码(默认加载 UglifyJsPlugin
)
1 | plugins: [ |
Webpack 优化配置
开发环境性能优化
HMR:(优化打包构建速度)
HMR(hot module replacement)热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包这一个模块(而不是所有模块)。极大提高构建速度
样式文件,可以使用 HMR 功能,因为
style-loader
内部实现了(比如引入style-loader
)js 文件,默认不能使用 HMR 功能 -> 需要修改 js 代码,添加支持 HMR 功能代码
注意:HMR 功能对 js 处理,只能处理非入口 js 文件的其他文件
html 文件,默认不能使用 HMR 功能,同时导致问题:html 文件不能热更新(不用做 HMR 功能)
解决方法:修改 entry 入口,将 html 文件引入
1 | devServer: { |
source-map:(优化代码调试)
一种提供 源代码到构建后代码的映射 的技术(如果构建后代码出错了,通过映射可以追溯源代码错误)
1 | // [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map |
内联和外部的区别:
- 外部生成了文件,内联没有
- 内联构建速度更快
开发环境:速度快,调试更友好
速度快(eval>inline>cheap>…)
eval-cheap-source-map
eval-source-map
调试更友好
source-map
cheap-module-source-map
cheap-source-map
推荐:eval-source-map(脚手架默认使用这个;完整度高,内联速度快)、eval-cheap-module-source-map(错误提示忽略列但是包含其他信息,内联速度快)
生产环境:源代码要不要隐藏?调试要不要更友好
内联会让代码体积大,所以生产环境不用内联
nosources-source-map 全部隐藏
hidden-source-map 只隐藏源代码
source-map
推荐:source-map(最完整)、cheap-module-source-map(错误提示一整行忽略列)
生产环境性能优化
oneOf:惰性匹配(优化打包构建速度)
匹配到 loader 后就不会再向后进行匹配,提升性能
1 | rules: [ |
多线程打包:(优化打包构建速度)
1 | npm i thread-loader -D |
某个任务消耗时间较长会卡顿,多线程可以同一时间干多件事,效率更高。
- 优点:提升打包速度
- 缺点:每个进程的开启和交流都会有开销
1 | { |
externals:排除打包(优化打包构建速度)
externals:让某些库不打包(通过 cdn 引入)
1 | externals: { |
dll:单独打包(优化打包构建速度)
- 如果使用 cdn 引入,建议使用 externals
- 如果不使用 cdn 引入,推荐使用 dll
1 | npm i add-asset-html-webpack-plugin -D |
dll:让某些库单独打包,后直接引入到 build 中
- 可以在 code split 分割出 node_modules 后再用 dll 更细的分割,优化代码运行的性能
- 使用 dll 技术,对某些库(第三方库:jquery、react、vue…)进行单独打包
webpack.dll.js 配置:(将 jquery 单独打包)
1 | const { resolve } = require('path') |
webpack.config.js 配置:(告诉 webpack 不需要再打包 jquery,并将之前打包好的 jquery 跟其他打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下)
1 | const webpack = require('webpack') |
缓存:
- babel 缓存:第二次打包构建速度更快(优化打包构建速度)
1 | { |
- 文件资源缓存:上线代码运行缓存更好使用(优化代码运行性能)
- hash:每次webpack构建时会生成一个唯一的hash值。不管文件是否有变化它都会变化
- chunkhash:如果打包来源于同一个chunk,那么hash值就一样。如果在 js 中引入 css,js 和 css 就会绑定在一起
- contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样
1 | /* |
tree shaking:树摇(优化代码运行性能)
去除程序中没有使用的代码,从而使体积更小,请求速度快
前提:
- 必须使用 ES6 模块化
- 开启 production 环境
在 package.json
中配置
1 | // 可能会把css @babel/polyfill 文件都干掉 |
code split:代码分割(优化代码运行性能)
代码分割:将打包输出的一个大的 bundle.js 文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快
多入口拆分
1
2
3
4
5
6
7
8
9entry: {
// 多入口:有一个入口,最终输出就有一个bundle
main: './src/js/index.js',
test: './src/js/test.js',
},
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
},optimization:
- 可以将 node_modules 中代码单独打包一个 chunk 最终输出
- 自动分析多入口 chunk 中,有没有公共的文件。如果有会打包成单独一个 chunk(比如两个模块中都引入了 jquery 会被打包成单独的文件)
1
2
3
4
5optimization: {
splitChunks: {
chunks: 'all',
},
},import 动态导入语法
通过 js 代码,让某个文件被单独打包成一个 chunk
import 动态导入语法:能将某个文件单独打包
1
2
3
4
5
6
7import(/* webpackChunkName: 'test' */ './utils')
.then(result => {
console.log('文件加载成功', result)
})
.catch(() => {
console.log('文件加载失败')
})
lazy loading:懒加载(优化代码运行性能)
- 懒加载:当文件需要使用时才加载
- 正常加载:并行加载(同一时间加载多个文件)
- 预加载 prefetch:会在使用之前,提前加载 js 文件。等其他资源加载完毕,浏览器空闲了,再加载资源
1 | document.getElementById('btn').onclick = function () { |
PWA:离线可访问技术(优化代码运行性能)
渐进式网络开发应用程序,使用 serviceworker 和 worker 技术
- 优点:离线也能访问
- 缺点:兼容性差
1 | npm i workbox-webpack-plugin -D |
webpack.config.js 中配置:
1 | const workboxWebpackPlugin = require('workbox-webpack-plugin') |
index.js 中还需要写一段代码来激活它的使用
eslint 不认识 window、navigator 全局变量
解决:需要修改 package.json 中 eslintConfig 配置
1
2
3
4
5"eslintConfig": {
"env": {
"browser": true // 支持浏览器端全局变量
}
},sw 代码必须运行在服务器上
1
2
3npm i serve -g
# 启动服务器,将build目录下所有资源作为静态资源暴露出去
serve -s build
1 | // 注册serviceWorker 处理兼容性问题 |
Webpack 配置详情
entry
string ->
entry: './src/index.js'
单入口打包形成一个 chunk。输出一个 bundle 文件,此时 chunk 的名称默认是 main
array ->
entry: ['./src/index.js', './src/add.js']
多入口所有入口文件最终只会形成一个 chunk。输出出去只有一个 bundle 文件
只有在 HMR 功能中让 html 热更新生效
object ->
entry: { index: './src/index.js', add: './src/add.js' }
多入口有几个入口文件就形成几个 chunk,输出几个 bundle 文件,此时 chunk 的名称是 key
1 | entry: { |
output
library 一般是作为暴露库去使用,通常是结合 dll,将某个库单独打包
1 | output: { |
module
1 | module: { |
resolve
1 | // 解析模块的规则 |
dev server
正常浏览器和服务器之间有跨域,但是服务器之间没有跨域。代码通过代理服务器运行,所以浏览器和代理服务器之间没有跨域,浏览器把请求发送到代理服务器上,代理服务器再把接收到的响应给浏览器
1 | devServer: { |
optimization
1 | npm i terser-webpack-plugin -D |
contenthash 缓存会导致一个问题:修改 a 文件导致 b 文件 contenthash 变化
因为在 index.js,打包后 index.js 记录了 a.js 的 hash 值,而 a.js 改变,其重新打包后的 hash 改变,导致 index.js 文件内容中记录的 a.js 的 hash 也变化,从而重新打包后 index.js 的 hash 值也会变,这样就会缓存失效
- splitChunks:能帮我们提取一些公共代码,呈单独 chunk 打包
- runtimeChunk:解决 splitChunks 出现的一些问题(某个文件 js 修改,会导致其他 js 文件失效)
- minimizer:生产环境压缩 js 可以做的更好
1 | const { resolve } = require('path') |
Webpack 5
此版本重点关注以下内容:
- 通过持久缓存提高构建性能
- 使用更好的算法和默认值来改善长期缓存
- 通过更好的树摇和代码生成来改善捆绑包大小
- 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改
- 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5
下载:
1 | npm i webpack@next webpack-cli -D |
自动删除 Node.js Polyfills
早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用
尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的
webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块
- 尽可能尝试使用与前端兼容的模块
- 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标
Chunk 和模块 ID,添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能
1 | chunkIds: "deterministic", moduleIds: "deterministic" |
Chunk ID
你可以不用使用 import(/* webpackChunkName: "name" */ "module")
在开发环境来为 chunk 命名,生产环境还是有必要的
webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2) 命名了
Tree Shaking
webpack 现在能够处理对嵌套模块的 tree shaking
1
2
3
4
5
6
7
8
9
10
11
12/* 在生产环境中,inner 模块暴露的 b 会被删除 */
// inner.js
export const a = 1;
export const b = 2;
// module.js
import * as inner from './inner';
export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a);webpack 现在能够处理多个模块之间的关系
当设置了
"sideEffects: false"
时,一旦发现test
方法没有使用,不但删除 test,还会删除"./someting"
1
2
3
4
5
6
7
8
9import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}webpack 现在能处理 Commonjs 的 tree shaking
Output
webpack 4 默认只能输出 ES5 代码
webpack 5 开始新增一个属性 output.ecmaVersion,可以生成 ES5 和 ES6 代码,如:output.ecmaVersion: 2015
SplitChunk
1 | // webpack4 |
Caching
缓存将存储到 node_modules/.cache/webpack
1 | // 配置缓存 |
监视输出文件
之前 webpack 总是在第一次构建时输出全部文件,但是监视重新构建时只更新修改的文件
此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件
默认值
entry: "./src/index.js"
output.path: path.resolve(__dirname, "dist")
output.filename: "[name].js"
推荐版本
1 | "devDependencies": { |
Webpack 问题汇总
TypeError: this.getOptions is not a function
- 原因:less-loader 安装版本过高(我安装的是 9 版本的)
- 解决方案:
npm i less-loader@6 -D
即可
1 | ERROR in ./src/index.less (../node_modules/css-loader/dist/cjs.js!../node_modules/less-loader/dist/cjs.js!./src/index.less) |
ERROR in Error: Child compilation failed:
- 原因:html-loader 安装版本过高(我安装的是 2 版本的)
- 解决方案:
npm i html-loader@0.5 -D
即可
1 | ERROR in Error: Child compilation failed: |
TypeError: Cannot read property ‘tap’ of undefined
- 原因:html-webpack-plugin 安装版本过高(我安装的是 5 版本的)
- 解决方案:
npm i html-loader@4 -D
即可
1 | TypeError: Cannot read property 'tap' of undefined at HtmlWebpackPlugin.apply (F:\git_demo\Webpack-study\node_modules\html-webpack-plugin\index.js:40:31) |
img src=”[object Module]”
因为 url-loader
默认使用 ES6 模块化解析,而 html-loader
引入图片是CommonJS,解析时会出问题:[object Module]
解决:关闭 url-loader
的 ES6 模块化,使用 CommonJS 解析 esModule: false,
Module build failed (from ../node_modules/mini-css-extract-plugin/dist/loader.js):
- 原因:postcss-loader 安装版本过高(我安装的是 6 版本的)
- 解决方案:
npm i postcss-loader@3 -D
即可
1 | Module build failed (from ../node_modules/mini-css-extract-plugin/dist/loader.js): |