用八千字、五十余张图带你走进webpack5的世界

62 阅读16分钟

通过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 # 安装到本地依赖

安装成功

微信截图_20230125143741.png

1.2 工作模式

1.2.1 在src下新建index,js,添加一些代码。

微信截图_20230125144237.png

目录结构为:

微信截图_20230125144338.png

1.2.2 通过npx webpack开始打包

执行命令后,出现一段提示

微信截图_20230125144609.png

意思就是提示我们没有配置mode(模式),提醒我们配置上mode

mode(模式):告诉webpack以哪种工作模式进行处理打包,默认值为“production”(生产模式)

选项描述
development开发模式,打包速度快,没有进行代码优化等操作
production生产模式,打包速度相对较慢,会开启压缩代码、tree-shaking等功能
none不使用任何优化选项

那我们需要怎么配置呢?

第一种方法:

只需在webpack.config.js中添加mode属性,属性值根据需要按照上面的表格来填就好。

先在根目录下创建webpack.config.js

微信截图_20230126120900.png

然后在里面添加配置对象,并加上mode属性

微信截图_20230126115925.png

注: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*文件

微信截图_20230126122033.png

1.4.2 修改entry配置
 //入口
 entry:"./src/style.css"
1.4.3 运行打包指令npx webpack

出现报错

微信截图_20230126122344.png

没事儿,出现这个问题属于正常现象,让我们继续解决。

这是因为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

微信截图_20230126123603.png

yeah!成功打包

我们看文件目录,原来的bundle.js变成的bundle.css

微信截图_20230126123836.png

在这里我们只是体验一下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 文件

微信截图_20230126130347.png

可以看到它自动的引入了打包好的 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

结果:

微信截图_20230126140641.png 执行npm run build

结果:

微信截图_20230126141245.png 这样我们就可以通过不同的环境来动态修改 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-serverdevServer 中已经没有了 contentBase 选项,这一选项现在已经被放进了 static 选项中

1.8.3 启动本地服务
$ npm run dev

为了看到效果,我在 html 中添加了一段文字,并在 public 文件夹下面放入了一张图片 .png

微信截图_20230126154201.png

微信截图_20230126154237.png

访问http://localhost:8080

微信截图_20230126154349.png

接着访问 http://localhost:8080/logo.jpg:

微信截图_20230126154534.png

成功访问,木有问题!

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中引入样式文件

微信截图_20230126155039.png

1.9.4 重启本地服务,访问http://localhost:8080

微信截图_20230126155419.png

yeah 样式生效了!

继续修改css文件,保存之后,样式就自动修改完成了。

微信截图_20230126155620.png

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 运行结果

微信截图_20230131164744.png

transform的前缀自动加上了,nice!

1.11 引入Less和Sass

less 和 sass 同样是 Webpack 无法识别的,需要使用对应的 Loader 来处理一下

类型需要的工具
Lessless-loader(将Less文件编译成css文件)
Sasssass-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文件

微信截图_20230131171734.png

引入Sass文件

微信截图_20230131170915.png

修改配置

 ...
 {
     test:/.(s[ac]ss)$/,
     use:[
         "style-loader", 
         "css-loader", 
         {
             loader: "postcss-loader",
             options: {
               postcssOptions: {
                 plugins: [
                   "postcss-preset-env", // 能解决大多数样式兼容性问题
                 ],
               },
             },
          },
         "sass-loader"
     ]
 }
 ...

注意:要把postcss-loader加在sass-loader前面

运行结果:

微信截图_20230131171833.png

成功 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 查看打包结果

微信截图_20230131173255.png

查看dist/index.html

微信截图_20230131173346.png

1.13 处理图片和字体文件

实际上,Webpack 无法识别图片文件,需要在打包的时候处理一下。

1.13.1 添加字体图标资源

微信截图_20230131174339.png

注意:iconfont.css中文件路径要修改好

src/index.js下:

微信截图_20230131174531.png

public/index.html下:

使用字体文件

微信截图_20230131174647.png

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:assettype: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 运行结果

微信截图_20230131195753.png

字体出来了,就是稍微有点小。

1.14 处理js资源

有人对此会有疑问,webpack不是能自己处理js文件吗,怎么还需要配置?

这是因为webpack对于js的处理是有限的,只能编译js中ES 模块化语法,不能编译其他语法,导致js不能在一些低版本浏览器中运行,所以我们需要做一些兼容性处理。

还有在开发中,团队对代码格式是有严格要求的,我们不能用眼睛去检测代码格式,需要使用专业的工具来检测。

  • 代码格式处理:Eslint
  • js兼容性处理: Babel
1.14.1 Eslint

用来检测 jsjsx 语法的工具,可以配置各项功能。

使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查。

配置文件由很多种写法:

  • 1、.eslintrc.*

    新建一个文件,位于项目根目录

    • .eslintrc

    • .eslintrc.js

    • .eslintrc.json

      区别在于配置格式不一样

  • 2、package.jsoneslintConfig:不需要创建文件,在原有文件基础上写

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 规则太费劲了,我们可以继承现有的规则。

现有以下较为有名的规则:

 // 例如在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中的代码

微信截图_20230131203500.png

4、运行结果

微信截图_20230131203547.png

显然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的语法

微信截图_20230131212207.png

5、运行结果 dist/bundle.js

微信截图_20230131212453.png

发现箭头函数等 ES6 语法已经转换了。

1.15 压缩CSS文件

在生产模式(production)下,我们还可以对css文件进行压缩。

1、下载包

 npm i css-minimizer-webpack-plugin -D

2、在webpack.config.js中配置

微信截图_20230201094751.png

3、运行打包

 npm run build

2、sourceMap配置

SourceMap 是一种映射关系,当项目运行后,如果出现错误,我们可以利用 SourceMap 反向定位到源码位置。当执行打包后,dist 目录下会生成以 .map 结尾的 SourceMap 文件

接下来看如何进行配置:

1、devtool配置

其实主要依靠一个devtool属性,它的值有下面几种:

关键字描述
inline代码内通过 dataUrl 形式引入 SourceMap
hidden生成 SourceMap 文件,但不使用
evaleval(...) 形式执行代码,通过 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

微信截图_20230131220727.png

但是这样写会很麻烦,所以一般借助其他的loader来解决。

比如:vue-loader、react-hot-loader

1.2、 oneOf
  • 为什么?

    打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍,比较慢。

  • 是什么?

    让文件只能匹配上一个 loader, 剩下的就不继续匹配了

  • 怎么用?

    用oneOf包住每一项loader

微信截图_20230131221406.png

1.3、 exclude/include
  • 为什么?

    开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

    比如我们在对 js 文件处理时,要排除 node_modules 下面的文件。

  • 是什么?

    • include:只处理xxx文件
    • exclude:除了xxx文件,其他都要处理
  • 怎么用?

    webpack.config.js中:

微信截图_20230131221729.png

还有plugins里面也要配置:

微信截图_20230131221949.png

1.4、 cache
  • 为什么?

    每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

    我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

  • 是什么?

    对之前eslint检查和babel编译的结果进行缓存

  • 怎么用?

    • eslint:

微信截图_20230131222419.png

-   babel

    

微信截图_20230131222612.png

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多进程处理

微信截图_20230131224118.png

  • babel多进程处理

微信截图_20230131223817.png

2、减少代码体积

2.1、Tree Shaking
  • 为什么?

    开发时我们引用了第三方工具函数库或组件库。如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上了其中极小部分的功能。但是这样将整个库都打包进来,体积就太大了

  • 是什么?

    一个术语。主要功能:用于移除 JavaScript 中的没有使用上的代码。

  • 怎么用?

    webpack已经内置开启这个功能了,不需要配置。

3、优化代码运行的性能

3.1、代码分割
  • 为什么?

    打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

    所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

  • 是什么?

    做两件事

    • 分割文件

      将打包生成的文件进行分割,生成多个 js 文件

    • 按需加载

      需要哪个文件就加载哪个文件

  • 怎么做?

    • 1、多入口

      • 我们新建一个文件夹:

微信截图_20230201095507.png

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:

微信截图_20230201100838.png

此时在 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,
                     },
                 },
             },
         },
         ...
     }
    
  • 运行打包:

微信截图_20230201102526.png

此时我们会发现生成 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方法

微信截图_20230201103553.png

  • 2、运行打包

微信截图_20230201103736.png

小结:通过动态导入的模块,该模块也会被分隔,同时也能进行按需加载。

我们一般会使用单入口 + 代码分隔 + 动态导入来进行配置。我们继续更新之前文件夹的配置文件。

webpack.config.js:

微信截图_20230201112851.png

然后,我们还要给动态导入的文件取名字

src/index.js中添加代码:

微信截图_20230201114008.png

public/index.html中添加代码:

微信截图_20230201115232.png

接下来我们还要修改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:

微信截图_20230201121236.png

还要去掉generator中的filename配置:

微信截图_20230201121330.png

然后在提取css成单独文件的MiniCssExtractPlugin中配置:

微信截图_20230201121550.png

最后,我们将mode改为peoduction,进行打包:

观察打包输出 js 文件名称

微信截图_20230201121740.png

可以看到math.js也被单独打包出来了。

3.2、 preload/prefetch
  • 为什么?

    前面我们用到了代码分割,也能使用import动态导入的语法来进行代码按需加载。但是当某一个模块体积比较大,在用户点击时进行加载,可能会有卡顿现象。所以,我们可以让浏览器在空闲时间去加载后续所用到的资源,那么我们就要用到preloadprefetch

  • 是什么?

    • 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:

微信截图_20230201124318.png

这里会给我们的math.chunk.js加上一个preload

到这里,webpack的基础使用就讲完啦,后续还会有webpack的进阶文章…… 咕咕咕(doge)

文章中引用了不少大佬的文章内容,十分感谢老师的技术文章,

作者:IT老班长 链接:juejin.cn/post/702324…