通过webpack题,来构建自己的webpack5的知识体系
这里有一些webpack的经典问题:
-
问题:webpack中用过哪些loader?都是干嘛的?
-
问题:webpack中用过哪些plugin?都是干嘛的?
-
问题:如何自定义一个loader?
-
问题:loader和plugin有啥区别呢?
-
问题:webpack的开发模式和生产模式有什么区别呢?
-
问题:用过webpack的HMR吗?怎么使用的?
-
问题:webpack的tree-shaking知道吗?它是干嘛的呢?
-
……
接下来透过这些题来一步步地认识webpack, let’s go !
一、webpack基础篇
1、我们先从一些简单的配置出发
1.1 首先安装依赖
先在本地安装webpack和webpack-cli。
$ npm install webpack webpack-cli -D # 安装到本地依赖
安装成功
1.2 工作模式
1.2.1 在src
下新建index,js
,添加一些代码。
目录结构为:
1.2.2 通过npx webpack开始打包
执行命令后,出现一段提示
意思就是提示我们没有配置mode(模式),提醒我们配置上mode
mode(模式):告诉webpack以哪种工作模式进行处理打包,默认值为“production”(生产模式)
选项 | 描述 |
---|---|
development | 开发模式,打包速度快,没有进行代码优化等操作 |
production | 生产模式,打包速度相对较慢,会开启压缩代码、tree-shaking等功能 |
none | 不使用任何优化选项 |
那我们需要怎么配置呢?
第一种方法:
只需在webpack.config.js中添加mode属性,属性值根据需要按照上面的表格来填就好。
先在根目录下创建webpack.config.js
然后在里面添加配置对象,并加上mode属性
注:Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范
第二种方法:
从cli参数中传递mode
$ webpack --mode=development
1.3 配置文件
虽然webpack默认会有许多配置,但在实际项目中需要我们进行具体的配置来满足特定的需求。
1.3.1 在webpack.config.js中新增一些配置信息
// Node.js的核心模块,专门用来处理文件路径
const path = require("path")
module.exports = {
mode:"development",//模式
//入口
entry:"./src/index.js",//指示webpack从那个文件开始打包
//出口
output:{
//输出文件的名称
filename:"bundle.js",
//输出文件的地址
path:path.resolve(__dirname,"./dist")
}
}
1.4 添加Loader
1.4.1 我们先添加*./src/style.css*文件
1.4.2 修改entry配置
//入口
entry:"./src/style.css"
1.4.3 运行打包指令npx webpack
出现报错
没事儿,出现这个问题属于正常现象,让我们继续解决。
这是因为webpack本身只能处理js或者json文件,其他类型的文件都处理不了,所以这里需要Loader来帮助webpack来处理它识别不了的文件。
1.4.4 安装css-loader来处理css文件
$ npm install css-loader -D
1.4.5 在配置对象中使用这个css-loader
// Node.js的核心模块,专门用来处理文件路径
const path = require("path")
module.exports = {
mode:"development",//模式
//入口
entry:"./src/style.css",//指示webpack从那个文件开始打包
//出口
output:{
//输出文件的名称
filename:"bundle.css",
//输出文件的地址
path:path.resolve(__dirname,"./dist")
},
//加载器
module:{
//转换规则
rules:[
{
//匹配所有以.css结尾的文件
test:/.css$/,
use:"css-loader"//使用loader
}
]
}
}
1.4.6 重新运行打包命令 npx webpack
yeah!成功打包
我们看文件目录,原来的bundle.js变成的bundle.css
在这里我们只是体验一下loader的使用,还是要把入口文件改回原来的*./src/index.js*
所以Loader的作用就是:将webpack不认识的内容转化成它认识的内容
1.5 Plugin(插件)
1.5.1 我们先进入一个场景。在根目录下添加*./public/index.html*文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Webpack</title>
</head>
<body>
</body>
</html>
在这里,我想将打包后的文件(比如js、css)自动引入到我的index.html中,我们就需要用到一个plugin(插件)来帮助我们,就是html-webpack-plugin。
1.5.2 本地安装html-webpack-plugin
npm install html-webpack-plugin -D
1.5.3 配置插件
// Node.js的核心模块,专门用来处理文件路径
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
...
plugins:[
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:
//1. 内容和源文件一致
//2. 自动引入打包生成的js等资源
template: path.resolve(__dirname,"public/index.html"),
})
]
...
}
运行一下打包,打开 dist 目录下生成的 index.html 文件
可以看到它自动的引入了打包好的 bundle.js
1.6 自动清空打包目录
每次打包的时候,打包目录都会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录进行清空。
这里我们可以借助clean-webpack-plugin来实现。
1.6.1 安装插件
$ npm install clean-webpack-plugin -D
1.6.2 配置插件
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const {CleanWebpckPlugin} = require("clean-webpack-plugin")
module.exports = {
...
//插件
plugins:[
new HtmlWebpackPlugin({
template: path.resolve(__dirname,"public/index.html")
}),
new CleanWebpckPlugin()//引入插件
]
...
}
1.7 环境配置
本地开发和部署线上时,是会有不同的需求。
本地环境下:
- 需要更快的打包构建速度
- 需要source map定位错误信息
- 需要hot reload 或 live reload功能
生产环境下:
- 需要包的体积尽可能小,要有代码压缩以及tree-shaking等功能
- 需要进行代码分割
- 需要压缩图片体积
面对不同需求,我们要做好环境的区分
1.7.1 本地安装cross-env
npm install cross-env -D
1.7.2 配置启动命令
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack serve --mode development",
"test": "cross-env NODE_ENV=test webpack --mode production",
"build": "cross-env NODE_ENV=prod webpack --mode production"
},
1.7.3 在 webpack.config.js中获取环境变量
// Node.js的核心模块,专门用来处理文件路径
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const {CleanWebpckPlugin} = require("clran-webpack-plugin")
// 打印环境变量
console.log('process.env.NODE_ENV=', process.env.NODE_ENV)
const config = {
mode:"development",//模式
//入口
entry:"./src/index.js",//指示webpack从那个文件开始打包
//出口
output:{
//输出文件的名称
filename:"bundle.js",
//输出文件的地址
path:path.resolve(__dirname,"./dist")
},
//加载器
module:{
//转换规则
rules:[
{
test:/.css$/,
use:"css-loader"
}
]
},
//插件
plugins:[
new HtmlWebpackPlugin({
template: path.resolve(__dirname,"public/index.html")
}),
new CleanWebpckPlugin()
]
}
module.exports = (env, argv) => {
// 打印 mode(模式) 值
console.log('argv.mode=',argv.mode)
// 这里可以通过不同的模式修改 config 配置
return config;
}
1.7.4 测试
执行 npm run dev
结果:
执行npm run build
结果:
这样我们就可以通过不同的环境来动态修改 Webpack 的配置
1.8 使用devServer
1.8.1 本地安装webpack-dev-server
npm install webpack-dev-server -D
注:我这里安装的是 "webpack-dev-server": "^4.11.1"
1.8.2 配置服务
const config = {
...
devServer:{
static:{
directory: path.resolve(__dirname, 'public'),//静态目录文件
},
compress: true, //是否启动压缩 gzip
port: 8080, // 端口号
open:true,//是否自动打开浏览器
}
...
}
为什么要配置 static?
因为 webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 static之后,就直接到对应的静态目录下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。
注:在 v3
版本的 webpack-dev-server
中,我们是通过配置 devServer.contentBase
选项来告诉服务器从哪里提供静态资源。但到了 v4
版本的 webpack-dev-server
,devServer
中已经没有了 contentBase
选项,这一选项现在已经被放进了 static
选项中
1.8.3 启动本地服务
$ npm run dev
为了看到效果,我在 html 中添加了一段文字,并在 public 文件夹下面放入了一张图片 .png
接着访问 http://localhost:8080/logo.jpg
:
成功访问,木有问题!
1.9 引入css
在 Loader 里面讲到了使用 css-loader 来处理 css,但是只靠 css-loader 是没有办法将样式加载到页面上。这个时候,我们需要再安装一个 style-loader 来完成这个功能。style-loader 就是将处理好的 css 通过 style 标签的形式添加到页面上。
1.9.1 安装style-loader
npm install style-loader -D
1.9.2 配置loader
const config = {
// ...
module: {
rules: [
{
test: /.css$/, //匹配所有的 css 文件
use: ['style-loader','css-loader']
}
]
},
// ...
}
注意:Loader 的执行顺序是固定从后往前,即按 css-loader --> style-loader
的顺序执行。
1.9.3 在入口文件index.js
中引入样式文件
1.9.4 重启本地服务,访问http://localhost:8080:
yeah 样式生效了!
继续修改css文件,保存之后,样式就自动修改完成了。
style-loader的作用:通过动态添加style标签的方式,将样式引入页面。
1.10 CSS的兼容性处理
css3中的属性在旧版本浏览器上会有兼容性问题,所以我们通过postcss-loader
做一些兼容性处理,比如自动添加不同的浏览器前缀等等。
上面我们用到的 transform: translateX(-50%);
,需要加上不同的浏览器前缀,这个我们可以使用 postcss-loader 来帮助我们完成。
1.10.1 本地安装postcss-loader
npm i postcss-loader postcss postcss-preset-env -D
1.10.2 配置
const config = {
// ...
module: {
rules: [
{
test: /.css$/, //匹配所有的 css 文件
use: [
"style-loader",
"css-loader",
{
loader:"postcss-loader",
options:{
postcssOptions:{
plugins:[
// 能解决大多数样式兼容性问题
"postcss-preset-env",
]
}
}
}
]
}
]
},
// ...
}
创建 postcss-preset-env 配置文件 .browserslistrc
# 换行相当于 and
last 2 versions # 回退两个浏览器版本
> 0.5% # 全球超过0.5%人使用的浏览器,可以通过 caniuse.com 查看不同浏览器不同版本占有率
IE 10 # 兼容IE 10
1.10.3 运行结果
transform
的前缀自动加上了,nice!
1.11 引入Less和Sass
less 和 sass 同样是 Webpack 无法识别的,需要使用对应的 Loader 来处理一下
类型 | 需要的工具 |
---|---|
Less | less-loader(将Less文件编译成css文件) |
Sass | sass-loader(将Sass文件编译成css文件)、sass(sass-loader 依赖 sass 进行编译) |
1.11.1 下载包
less:
npm i less-loader -D
配置:
module:{
rule:[
...
{
test:/.less$/,
use:[
"style-loader",
"css-style",
"less-loader"
]
}
...
]
}
sass:
npm i sass-loader sass -D
配置:
...
{
test: /.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
}
...
添加src/sass/index.sass
文件
引入Sass文件
修改配置
...
{
test:/.(s[ac]ss)$/,
use:[
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
"sass-loader"
]
}
...
注意:要把postcss-loader
加在sass-loader
前面
运行结果:
成功 nice!
1.12 分离样式文件
之前,我们依赖style-loader
将样式通过style标签的形式添加到页面上。这样对于网站来说,会出现闪屏现象,用户体验不好。所以实际上我们需要通过css文件的形式引入到页面中,那么就需要用到mini-css-extract-plugin
。
1.12.1 安装mini-css-extract-plugin
$ npm install mini-css-extract-plugin -D
1.12.2 修改 webpack.config.js
配置
将之前的每一个style-loader
替换为MiniCssExtractPlugin.loader
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//加载器
module:{
//转换规则
rules:[
{
//匹配所有以.css结尾的文件
test:/.css$/,
use:[
MiniCssExtractPlugin.loader,
"css-loader",
{
loader:"postcss-loader",
options:{
postcssOptions:{
plugins:[
// 能解决大多数样式兼容性问题
"postcss-preset-env",
]
}
}
}
]
},
{
test:/.less$/,
use:[
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader"
]
},
{
test:/.(s[ac]ss)$/,
use:[
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
}
还要在plugins
中配置
//插件
plugins:[
new HtmlWebpackPlugin({
template: path.resolve(__dirname,"public/index.html")
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename:"[name].[hash:8].css"
})
],
1.12.3 查看打包结果
查看dist/index.html
:
1.13 处理图片和字体文件
实际上,Webpack 无法识别图片文件,需要在打包的时候处理一下。
1.13.1 添加字体图标资源
注意:iconfont.css
中文件路径要修改好
src/index.js
下:
public/index.html
下:
使用字体文件
1.13.2 配置
...
{
test:/.(ttf|woff2?)$/,
type:"asset/resource",
generator:{
//将字体文件输出到dist下的media文件夹下
//将文件命名为[hash:8][ext][query]
// [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename:"media/[hash:8][ext][query]"
}
}
...
注:
type:asset
和type:asset/resource
的区别是什么?
type:asset
:相当于file-loader
,将文件转化成webpack能识别的资源,其他不做任何处理。type:asset/resource
:相当于url-loader
,将文件转化成webpack能识别的资源,同时若文件大小小于某个值的时候,会处理成data URL
补充:
asset/resource
将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.asset/inline
将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.asset/source
将资源导出为源码(source code). 类似的 raw-loader 功能.asset
会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource
1.13.3 运行结果
字体出来了,就是稍微有点小。
1.14 处理js资源
有人对此会有疑问,webpack不是能自己处理js文件吗,怎么还需要配置?
这是因为webpack对于js的处理是有限的,只能编译js中ES 模块化语法,不能编译其他语法,导致js不能在一些低版本浏览器中运行,所以我们需要做一些兼容性处理。
还有在开发中,团队对代码格式是有严格要求的,我们不能用眼睛去检测代码格式,需要使用专业的工具来检测。
- 代码格式处理:Eslint
- js兼容性处理: Babel
1.14.1 Eslint
用来检测 js
和 jsx
语法的工具,可以配置各项功能。
使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查。
配置文件由很多种写法:
-
1、
.eslintrc.*
新建一个文件,位于项目根目录
-
.eslintrc
-
.eslintrc.js
-
.eslintrc.json
区别在于配置格式不一样
-
-
2、
package.json
中eslintConfig
:不需要创建文件,在原有文件基础上写
ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可。
具体配置:
我们以 .eslintrc.js
配置文件为例:
module.exports = {
// 解析选项
parserOptions: {},
// 具体检查规则
rules: {},
// 继承其他规则
extends: [],
// ...
// 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};
其中
1、parserOptions
:
parserOptions: {
ecmaVersion: 6, // ES 语法版本
sourceType: "module", // ES 模块化
ecmaFeatures: { // ES 其他特性
jsx: true // 如果是 React 项目,就需要开启 jsx 语法
}
}
2、rules
:
rules: {
// 禁止使用分号
semi: "error",
// 强制数组方法的回调函数中有 return 语句,否则警告
'array-callback-return': 'warn',
'default-case': [
// 要求 switch 语句中有 default 分支,否则警告
'warn',
// 允许在最后注释 no default, 就不会有警告了
{ commentPattern: '^no default$' }
]
}
更多规则详见:规则文档
3、extends
继承
开发中一点点写 rules 规则太费劲了,我们可以继承现有的规则。
现有以下较为有名的规则:
- Eslint 官方的规则open in new window:
eslint:recommended
- Vue Cli 官方的规则open in new window:
plugin:vue/essential
- React Cli 官方的规则open in new window:
react-app
// 例如在React项目中,我们可以这样写配置
module.exports = {
extends: ["react-app"],
rules: {
// 我们的规则会覆盖掉react-app的规则
// 所以想要修改规则直接改就是了
eqeqeq: ["warn", "smart"],
},
};
接下里在我们的webpack中使用Eslint
1、下载包:
npm i eslint-webpack-plugin eslint -D
2、定义 Eslint 配置文件 .eslintrc.js
module.exports = {
extends:["eslint:recommended"],
//解析选项
parserOptions:{
ecmaVersion:6,
sourceType:"module"
},
//具体检查规则
rules:{
"no-var":2,//不能使用var定义变量
},
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
}
}
3、配置webpack.config.js
:
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
plugins:[
...
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "src"),
})
...
]
3、测试,修改src/index.js
中的代码
4、运行结果
显然eslint帮助我们检查出了错误,报出error。
当然我们按照语法规则还是要把var
换成const
1.14.2 Babel
用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
1、下载包
npm i babel-loader @babel/core @babel/preset-env -D
2、编写babel配置文件。新建babel.config.js
:
module.exports = {
// 预设
presets: ["@babel/preset-env"],
};
注:
preset
就是一组 Babel 插件, 扩展 Babel 功能
@babel/preset-env
: 一个智能预设,允许您使用最新的 JavaScript。@babel/preset-react
:一个用来编译 React jsx 语法的预设@babel/preset-typescript
:一个用来编译 TypeScript 语法的预设
3、在webpack.config.js
中配置
...
{
test:/.(js)$/,
exclude:"/node_modules",//排除掉node_modules中的文件,不编译这里面的文件
loader:"babel-loader"
}
...
4、修改src/index.js
的内容,加点ES6的语法
5、运行结果 dist/bundle.js
发现箭头函数等 ES6 语法已经转换了。
1.15 压缩CSS文件
在生产模式(production)下,我们还可以对css文件进行压缩。
1、下载包
npm i css-minimizer-webpack-plugin -D
2、在webpack.config.js
中配置
3、运行打包
npm run build
2、sourceMap配置
SourceMap 是一种映射关系,当项目运行后,如果出现错误,我们可以利用 SourceMap 反向定位到源码位置。当执行打包后,dist 目录下会生成以 .map
结尾的 SourceMap 文件
接下来看如何进行配置:
1、devtool
配置
其实主要依靠一个devtool
属性,它的值有下面几种:
关键字 | 描述 |
---|---|
inline | 代码内通过 dataUrl 形式引入 SourceMap |
hidden | 生成 SourceMap 文件,但不使用 |
eval | eval(...) 形式执行代码,通过 dataUrl 形式引入 SourceMap |
nosources | 不生成 SourceMap |
cheap | 只需要定位到行信息,不需要列信息 |
module | 展示源代码中的错误位置 |
这么多种,我们该如何选择呢?
2、配置项的使用
-
如果是开发环境下,需要速度快,
推荐:
cheap-module-source-map
,原因:
- 在开发中,一般一行的代码不会很长,只需要定位到行即可,所以我们用
cheap
- 我们希望能找到源码的错误,不是打包后的,所以我们加上
module
eval
有缓存,rebuild会比较快,首次加载可能会稍微慢一点。
- 在开发中,一般一行的代码不会很长,只需要定位到行即可,所以我们用
-
生产环境下:
推荐:
source-map
或(none)
原因:source-map包括行和列的映射,none不会被看到源码
二、webpack优化篇
1、提升打包构建的速度
1.1、HMR(HotModuleReplacement)
- 为什么?
在开发中,我们有时候只修改或者替换了其中一个模块的代码,但是webpack会全部重新打包,速度会变慢很多。所以我们需要做到修改某个模块的代码,只会重新打包编译这个模块的代码,其他的模块不变。
- 是什么?
中文名:热模块替换。在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
-
怎么用?
基本配置:
module.exports = { // 其他省略 devServer: { host: "localhost", // 启动服务器域名 port: "8080", // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了) }, };
但是这样的配置对js文件没有效果,我们还需要对js进行配置
新增
src/sum.js
,在src/index.js
中引入sum.js开启js的HMR
但是这样写会很麻烦,所以一般借助其他的loader来解决。
比如:vue-loader、react-hot-loader
1.2、 oneOf
-
为什么?
打包时每个文件都会经过所有 loader 处理,虽然因为
test
正则原因实际没有处理上,但是都要过一遍,比较慢。 -
是什么?
让文件只能匹配上一个 loader, 剩下的就不继续匹配了
-
怎么用?
用oneOf包住每一项loader
1.3、 exclude/include
-
为什么?
开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。
比如我们在对 js 文件处理时,要排除
node_modules
下面的文件。 -
是什么?
- include:只处理xxx文件
- exclude:除了xxx文件,其他都要处理
-
怎么用?
webpack.config.js
中:
还有plugins里面也要配置:
1.4、 cache
-
为什么?
每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。
我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。
-
是什么?
对之前eslint检查和babel编译的结果进行缓存
-
怎么用?
-
eslint:
-
- babel
1.5、 Thread
-
为什么?
当项目变得庞大起来的时候,打包速度会越来越慢,开启多进程打包可以提升打包速度
-
是什么?
开启电脑的多个进程进行打包,速度更快。但是要在特别耗时的操作中使用,因为要启动一个进程就需要花费600ms左右的时间。
-
怎么用?
-
1、先获取电脑的cpu核数:
在
webpack.config.js
中:// nodejs核心模块,直接使用 const os = require("os"); // cpu核数 const threads = os.cpus().length;
-
2、下载包
npm i thread-loader -D
-
3、配置
-
eslint多进程处理
-
-
- babel多进程处理
2、减少代码体积
2.1、Tree Shaking
-
为什么?
开发时我们引用了第三方工具函数库或组件库。如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上了其中极小部分的功能。但是这样将整个库都打包进来,体积就太大了。
-
是什么?
一个术语。主要功能:用于移除 JavaScript 中的没有使用上的代码。
-
怎么用?
webpack已经内置开启这个功能了,不需要配置。
3、优化代码运行的性能
3.1、代码分割
-
为什么?
打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
-
是什么?
做两件事
-
分割文件
将打包生成的文件进行分割,生成多个 js 文件
-
按需加载
需要哪个文件就加载哪个文件
-
-
怎么做?
-
1、多入口
-
我们新建一个文件夹:
-
-
app.js
:
console.log("hello app");
main.js
:
console.log("hello main");
webpack.config.js
:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
//原来的单入口式写法
//entry:"./src/main.js"
//现在改用多入口式写法
entry:{
main:"./src/main.js",
app:"./src/app.js"
},
output:{
path:path.resolve(__dirname,"./dist"),
filename:"js/[name].js",
clean:true
},
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,"./public/index.html")
})
],
mode:"production"
}
注:output中的filename的[name]就是chunk的name,使用chunk的name作为输出的文件名。
有几个小问题:
1、什么是chunk?
打包的资源就是chunk,输出的文件就是bundle。
2、chunk的name是啥呢?
比如entry中的xxx:“./scr/xxx.js”,那么xxx就是name,前面的xxx和文件名无关
3、为啥要这么命名?
如果还是写成之前的main.js,那么打包后会生成两个js文件都叫做main.js,会发生覆盖。
看打包结果dist
:
此时在 dist 目录我们能看到输出了两个 js 文件。
小结:配置了几个入口,至少输出几个 js 文件。
- 提取重复代码
如果我们的多入口文件中都引用了同一个模块的代码,我们不希望这个模块都被打包到这两个文件中,这样导致代码重复,体积会变大。我们想提取多入口文件中的重复代码,只打包生成一个js文件,其他文件引用它就好。
-
修改文件
新增
src/math.js
:export const sum = (...args) => { return args.reduce((p, c) => p + c, 0); };
app.js
:import { sum } from "./math"; console.log("hello app"); console.log(sum(1, 2, 3, 4));
main.js
:import { sum } from "./math"; console.log("hello main"); console.log(sum(1, 2, 3, 4, 5));
-
配置
webpack.config.js
:module.exports = { ... optimization:{ splitChunks:{ chunks:"all",// 对所有模块都进行分割 cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 //test: /[\/]node_modules[\/]/, // 需要打包到一起的模块 //priority: -10, // 权重(越大越高) //reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, default: { // 其他没有写的配置会使用上面的默认值 // 我们定义的文件体积太小了,所以要改打包的最小文件体积 minSize: 0, minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, }, }, ... }
-
运行打包:
此时我们会发现生成 3 个 js 文件,其中有一个就是提取的公共模块。
- 按需加载,动态导入
想要实现按需加载,动态导入模块。还需要额外配置:
-
1、修改文件
main.js
:console.log("hello main"); document.getElementById("btn").onclick = function () { // 动态导入 --> 实现按需加载 // 即使只被引用了一次,也会代码分割 import("./math.js").then(({ sum }) => { alert(sum(1, 2, 3, 4, 5)); }); };
app.js
:console.log("hello app");
public/index.html
:添加一个按钮,通过onclick动态导入sum方法
- 2、运行打包
小结:通过动态导入的模块,该模块也会被分隔,同时也能进行按需加载。
我们一般会使用单入口 + 代码分隔 + 动态导入来进行配置。我们继续更新之前文件夹的配置文件。
webpack.config.js
:
然后,我们还要给动态导入的文件取名字
在src/index.js
中添加代码:
在public/index.html
中添加代码:
接下来我们还要修改eslint
配置:
先下载包:
npm i eslint-plugin-import -D
.eslintrc.js
:
module.exports = {
extends:["eslint:recommended"],
//解析选项
parserOptions:{
ecmaVersion:6,
sourceType:"module"
},
//具体检查规则
rules:{
"no-var":2,//不能使用var定义变量
},
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
},
plugins:["import"],// 解决动态导入import语法报错问题
}
然后进行统一命名配置:
webpack.config.js
:
还要去掉generator
中的filename
配置:
然后在提取css成单独文件的MiniCssExtractPlugin
中配置:
最后,我们将mode改为peoduction
,进行打包:
观察打包输出 js 文件名称
可以看到math.js也被单独打包出来了。
3.2、 preload/prefetch
-
为什么?
前面我们用到了代码分割,也能使用import动态导入的语法来进行代码按需加载。但是当某一个模块体积比较大,在用户点击时进行加载,可能会有卡顿现象。所以,我们可以让浏览器在空闲时间去加载后续所用到的资源,那么我们就要用到
preload
或prefetch
。
-
是什么?
- preload:让浏览器立即加载资源
-
prefetch:让浏览器在空闲时开始加载后续资源
共同点:
- 都会加载资源,但不执行
- 都会留有缓存
区别:
preload加载优先级更高,prefetch优先级较低
preload只能加载当前页面所需资源,prefetch既可以加载当前页面资源,也可以加载后续页面资源
注:它们的兼容性都比较差
-
怎么用?
-
下载包:
npm i @vue/preload-webpack-plugin -D
-
配置
webpack.config.js
:-
... const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); ... plugins:[ //preload new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }), ]
-
-
运行打包
我们查看一下
dist/index.html
:
-
这里会给我们的math.chunk.js
加上一个preload
到这里,webpack的基础使用就讲完啦,后续还会有webpack的进阶文章…… 咕咕咕(doge)
文章中引用了不少大佬的文章内容,十分感谢老师的技术文章,
作者:IT老班长 链接:juejin.cn/post/702324…