本文章记录在 b 站学习 webpack5 的学习笔记,视频传送门。
课程收获
Webpack 在 前端工程化领域 的作用及原理;参与项目的 打包配置;
工程化层面优化 开发环境、项目性能;落地 面向前端业务的技术方案。
一、 概述
(概述内容参考文章:当面试官问Webpack的时候他想知道什么)
webpack 是一种前端资源构建工具,当它处理应用程序时,会在内部从一个或多个入口点构建一个依赖图,然后将项目中所需的每一个模块组合成一个或多个 bundles,用于内容的展示。
其作用有以下几点:
模块打包:可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。
编译兼容:弱化了浏览器代码兼容的问题,通过 Loader 机制,帮助我们对代码做 polyfill,还可以编译转换诸如 .less、.vue、.jsx 这类在浏览器中无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提升开发效率。
能力扩展:通过 Plugin 机制,可以在实现模块化打包和编译兼容的基础上,进一步实现诸如按需加载、代码压缩等一系列功能,帮助我们进一步提高自动化程度、工程效率以及打包输出的质量。
二、核心概念
1. 入口(entry)
指示 webpack 应该使用哪个模块来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后, webpack 会找出有哪些模块和库是入口起点依赖的。
2. 输出(output)
告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。
3. loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换成有效的 模块,供应用程序使用,以及被添加到依赖图中。
4. 插件(plugin)
执行范围更广的任务,包括:打包优化,资源管理,注入环境变量。
三、起步
1. 基本安装
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev // 此命令用于在命令行中运行 webpack
2. 配置文件
在文件根目录下新建 webpack.config.js 配置文件。
// webpack.config.js
module.exports = {
entry: './assets/js/main.js',
output: {
filename: 'app.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: []
},
plugins: [],
mode: 'development'
}
3. 打包命令
npx webpack
npm run build // npm scripts: "build": "webpack"
关于 npx 的命令
// 指定使用哪个 webpack 配置文件
npx webpack --config (-c) ./config/webpack.config.dev.js
// 指定使用哪个 webpack 文件启动服务
npx webpack-dev-server --open
npx webpack serve -c ./config/webpack.config.dev.js
// 指定当前环境变量
npx webpack --env production
四、管理资源
1. 加载样式
1.1 加载 CSS 样式
为了在 JavaScript 模块中引入一个 CSS 文件,需要安装 style-loader 和 css-loader,并且在 module 配置中添加这些 loader。
npm install style-loader css-loader -D
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 书写有顺序要求,先保证 css 打包没有问题,再使用 style-loader 把 css 放置在页面上
}
]
}
}
模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
- test: 识别出哪些文件会被转换。
- use: 定义出在进行转换时,应该使用哪个 loader。
1.2 加载 less 资源
npm install less-loader -D
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(css|less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
1.3 抽离 CSS 代码为独立文件
安装 mini-css-extract-plugin 插件
npm install mini-css-extract-plugin -D
webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
]
}
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
]
}
- filename: 指定文件名和路径。
1.4 压缩 CSS 代码
安装 css-minimizer-webpack-plugin 插件
npm install css-minimizer-webpack-plugin -D
webpack.config.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
...
optimization: {
// 压缩 CSS 代码
minimizer: [
new CssMinimizerPlugin()
]
},
// 模式
mode: 'production'
- 压缩 CSS 代码,只在生产模式下有效。
2. 加载 images 图像
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/,
type: 'asset/resource', // 发送一个单独的文件并导出url,之前通过 file-loader 实现
// 生成资源的路径和文件名,与 output 中的 assetModuleFilename 作用一致,两者同时存在时,generator 优先级高
generator: {
filename: 'images/[contenthash][ext]'
}
},
]
}
}
3. 加载 fonts 字体
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
}
}
4. 加载数据
可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的。要导入其他三种类型的数据,可以使用 csv-loader 和 xml-loader。
npm install csv-loader xml-loader -D
webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.(csv|tsv)$/, // 加载不同类型的数据
type: 'csv-loader'
},
{
test: /\.xml$/, // 加载不同类型的数据
type: 'xml-loader'
}
]
}
}
五、管理输出
1. 设置 HtmlWebpackPlugin
安装 html-webpack-plugin 插件
npm install --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
// 自动生成 html 入口文件和引用 js 文件的功能
new HtmlWebpackPlugin({
title: 'Output Management',
template: './index.html',
filename: 'app.html'
})
],
}
2. 清理 /dist 文件夹
在每次构建前清理 /dist 文件夹,这样只会生成用到的文件,使用 output.clean 配置项实现
module.exports = {
...
output: {
clean: true
}
}
六、开发环境
本章节旨在学习如何设置一个开发环境,使我们的开发体验变得更轻松。
1. 使用 source map
module.exports = {
mode: 'development',
devtool: 'inline-source-map' // 用来设置打包后代码的具体文件位置,精准定位代码的行数
};
2. 选择一个开发工具
每次编译代码时,手动运行 npm run build 比较麻烦,webpack 提供几种可选方式,帮助我们在代码发生变化时自动编译代码。(常用的是 webpack-dev-server)
2.1 webpack's Watch Mode
npm run webpack --watch // 使用 npm run webpack --watch 启动 webpack watch mode
缺点:需要刷新浏览器才能看到修改后的实际效果。
2.2 webpack-dev-server
webpack-dev-server 提供了一个基本的 web server,并且具有 live reloading(实时重新加载)功能
安装 webpack-dev-server 插件
npm install --save-dev webpack-dev-server
module.exports = {
...
devServer: {
static: './dist', // 告知 dev-server 从什么位置开始查找文件
port: 3001, // 指定监听请求的端口号
open: true, // 告知 dev-server 在服务器启动后打开浏览器
proxy: { // 有单独的 API 后端开发服务器并希望在同一域上发送 API 请求
'/api': 'http://localhost: 3001', // 将对 /api/users 的请求代理到 http: //localhost: 3001/api/users
pathRewrite: { // 不希望传递 /api 时,重写路径
'^/api': ''
}
},
hot: true, // 启用热模块替换功能
liveReload: true // 若生效,devServer.hot 必须禁用 或者 devServer.watchFiles 配置项必须启用
}
}
七、代码分离
通过此特性,能够将代码分离到不同的 bundle 中,然后可以按需加载或者并行加载这些文件。可以用于获取更小的bundle,以及控制资源加载优先级。
代码分离的三种方式
优点:将多个文件共享的代码分离出去,减少入口文件的大小,从而提高首屏加载的速度。
- 配置入口起点:使用 entry 配置手动地分离代码(缺点:如果多个入口存在共享文件,会重复打包。)
- 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
1. 防止重复
1.1 入口依赖
配置 dependOn option 选项,在多个 chunk 之间共享模块。
module.exports = {
...
entry: {
app: {
import: './src/app.js',
dependOn: 'shared'
},
web: {
import: './src/another-module.js',
dependOn: 'shared'
},
shared: 'lodash' // 将公共的代码抽离出来,生成单独的 bundle 文件
},
}
1.2 SplitChunksPlugin
- SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
module.exports = {
...
optimization: {
splitChunks: {
chunks: 'all'
}
},
}
- 分离 CSS 代码
安装 mini-css-extract-plugin 插件
npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 抽离 CSS
module.exports = {
...
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css' // 指定文件名和路径
})
],
}
2. 动态导入
2.1 案例
function getComponent() {
// 动态代码拆分时,使用 ECMAScript 提案的 import() 语法来实现
return import('lodash')
.then(({default: _}) => {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ')
return element
})
}
getComponent().then((element) => {
document.body.appendChild(element);
console.log('is async-module execute')
})
2.2 应用
- 懒加载:按需加载
是一种很好的优化网页或者应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块完成操作后,立即引用或者即将引用另外一些新的代码块。这样有利于加快应用的初始加载速度,减轻总体体积,因为有些代码块可能永远不会被加载
- 预获取/预加载模块
在声明 import 之前,使用内置的指令,可以让 webpack 输出 ‘resource hint’(资源提示),来告知浏览器。
prefetch(预获取):将来某些导航下可能需要的资源。
意义在于当首页内容都加载完毕之后,在网络空闲的时候,再去加载打包好的文件。这样既不会影响首屏加载速度,又省去了将来加载的延迟.
preload(预加载):当前导航下可能需要资源,实现页面模块的并行加载。
// prefetch
button.addEventListener('click', () => {
// 通过魔法注释修改打包后的文件名
import( /* webpackChunkName: 'math', webpackPrefetch: true */ './math').then(({
add
}) => {
console.log(add(4, 5));
})
})
// preload
import(/* webpackPreload: true */ 'ChartingLibrary');
八、缓存
webpack 打包模块化后的应用程序,会生成一个可部署的 ./dist 目录,打包后的内容放置于此。只要把该目录的内容部署到 server 上,clint 就能够访问此 server 的网站及其资源。由于获取资源比较耗费时间,于是采用浏览器缓存,通过命中缓存,降低网络流量,使网站加载速度更快。
1. 输出文件的文件名
如果部署新版本不更改资源的文件名,浏览器可能会认为它没有被更新,于是使用其缓存版本。为了杜绝这种问题发生,使用 substitution(可替换模板字符串) 的方式,保证文件名的唯一性。
module.exports = {
...
output: [
filename: '[name].[contenthash].js'
],
}
2. 缓存第三方的库
module.exports = {
...
optimization: {
runtimeChunk: 'single', // 将 runtime 代码拆分为一个单独的 chunk,设置为 single 来为所有 chuunk 创建一个 runtime bundle
splitChunks: {
// 将第三方的库单独提取到 vendor chunk 文件中,利用 client 的长效缓存机制,命中缓存来消除请求
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
},
}
九、资源模块
资源模块(Asset Module)是一种模块类型,允许在不额外配置 loader 的情况下使用图像、字体等资源文件,是 webpack 的内置模块。
十、生产环境
1. 配置
development(开发环境)和 production(生产环境)的构建目标存在着巨大差异。开发环境需要的是:强大的 source map 和一个有着 live reloading 或 hot module replacement 能力的 localhost server;而生产环境的关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。
遵循着不重复原则(Dont't Repeat Yourself - DRY),保留一个“common”配置,使用 webpack-merge 工具,将生产环境和开发环境的配置分别与通用配置合并在一起。
安装 webpack-merge 插件
npm install --save-dev webpack-merge
- project
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- webpack.common.js
|- webpack.dev.js
|- webpack.prod.js
|- /dist
|- /src
|- index.js
|- math.js
|- /node_modules
- webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Production',
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
};
- webpack.dev.js
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: './dist'
}
};
- webpack.prod.js
module.exports = {
mode: 'production'
};
- webpack.config.js
const { merge } = require('webpack-merge'); // 合并配置文件
const commonConfig = require('./webpack.config.common');
const developmentConfig = require('./webpack.config.dev');
const productionConfig = require('./webpack.config.prod');
module.exports = (env) => {
switch(env) {
case env.production:
return merge(commonConfig, productionConfig);
case env.development:
return merge(commonConfig, developmentConfig);
default:
return new Error('No matching configuration was found!')
}
}
模块解析(Resolve)
所引用的模块可以是来自应用程序的代码,也可以是第三方库。resolver 帮助 webpack 从每个 require / import 语句中,找到需要引入到 bundle 中的模块代码。当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。(webpack 基于 webpack_resolver 进行 treeshaking)
外部扩展(Externals)
为了减小 bundle 体积,把一些不变的第三方库用 CDN 的形式引入进来。
十一、构建性能
1. 通用环境
- 更新到最新版本;
Webpack、Node.js、npm 或 yarn 更新至最新版本,有助于提高性能。较新的版本能够建立更高效的模块树以及提高解析速度。
- 将 loader 应用于最少数量的必要模块;
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /.js$/,
include: path.resolve(__dirname, 'src'),
loader: 'babel-loader',
},
],
},
};
- 引导
每个额外的 loader/plugin 都有其启动的时间,尽量少地使用工具;
- 解析
- dll
- 小即是快(smaller = faster)
减少编译结果的整体大小,以提高构建性能。尽量保持 chunk 体积小。
- 使用数量更少/体积更小的 library;
- 在多页面应用程序中使用 SplitChunksPlugin,并开启 async 模式;
- 移除未引用的代码;
- 只编译当前正在开发的代码。
- worker 池
- 持久化缓存
- 自定义 plugin/loader
- Progress plugin
2. 开发环境
- 增量编译
- 在内存中编译
- stats.toJson 加速
- Devtool
- 避免在生产环境下才会用到的工具
- 最小化 entry chunk
- 避免额外的优化步骤
- 输出结果不携带路径信息
3. 生产环境
- Source Maps