webpack作用

模块打包

将不同模块文件整合在一起,并确保他们之间引用正确,执行有序。利用打包我们在开发的时候就可以根据业务自由划分模块,保证项目结构清晰。

为什么打包

将零碎的文件整合成一个整体,减少单页面请求次数,提高网站效率。
编译兼容,利用loader机制,可以对代码进行polyfill填充,还可以转译.vue .jsx .less等浏览器无法识别的文件
压缩混淆代码,提高安全性

能力扩展

利用plugin机制,可以在实现模块化打包和兼容编译的基础上,进一步实现按需加载,代码压缩等功能,进一步提高自动化程度

webpack构建流程

1、初始化参数
合并shell命令和配置文件参数,创建compiler对象
2、开始编译
加载所有配置的插件,执行compiler对象中的run方法
3、编译模块
找到入口文件,使用相应的loader对其进行编译,遇到依赖模块时递归这一步,直到入口依赖模块都完成此编译步骤
4、完成模块编译
loader会将模块编译为AST树,遍历AST树,得到模块之间依赖关系
5、输出编译资源
根据依赖关系,会输出含有多个模块的chunk,之后将chunk转义成单独文件添加进输出列表,这一步是修改输出内容的最后机会
6、输出完成
根据配置的输出路径和文件名将输出列表中的文件写入文件系统

简单说:
通过配置项实例化compiler对象,从入口文件开始对各个模块用相应的loader编译,找到依赖的模块递归编译,之后将编译后文件根据依赖关系转换成chunk,再将chunk转换成文件列表输出
image.png

babel原理

AST(抽象语法树)

将代码转换成AST目的是为了让我们的计算机更好的理解我们的代码

babel工作三个大步骤

1、解析
将代码转换成AST ,通过Babylon实现的。在解析过程中分为两个阶段:词法分析和语法分析。
词法分析:将字符串形式的代码转换成令牌(tokens)流,令牌类似AST中的节点。
语法分析:将令牌流转换成AST,并将令牌中的信息转换成AST的表述结构。
2、转换
babel接收到AST,并使用babel-traverse对其进行深度优先遍历,对节点进行更新、删除、添加操作。babel的插件在此部分工作
3、生成
在这个阶段 通过转换的AST 经过babel-generator的转换,生成js代码。过程就是深度优先遍历整个AST,然后构建表示 转换后代码的字符串。

HMR

  • 使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入 HMR 客户端代码
  • 浏览器加载页面后,与 WDS 建立 WebSocket 连接
  • Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件
  • 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围
  • 浏览器加载发生变更的增量模块
  • Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑

    webpack分包

    Entry分包

    重点:seal 阶段遍历 entry 对象,为每一个 entry 单独生成 chunk,之后再根据模块依赖图将 entry 触达到的所有模块打包进 chunk 中。

  • webpack会根据entry入口配置创建相应的chunk对象,此时chunk只包含入口模块

  • webpack会读取ModuleDependencyGraph模块依赖图,将入口文件以同步方式引入的模块添加到对应的入口chunk中

基于动态加载生成的 chu__nk 在 webpack 官方文档中,通常称之为 Initial chunk

异步模块分包

重点:分析 ModuleDependencyGraph 时,每次遇到异步模块都会为之创建单独的 Chunk 对象,单独打包异步模块。
Webpack 4 之后,只需要用异步语句 require.ensure(“./xx.js”) 或 import(“./xx.js”) 方式引入模块时,webpack会为该模块单独生成一个 chunk 对象,并将其子模块都加入这个 chunk 中。
可以实现模块的动态加载,这种能力本质也是基于 Chunk 实现的。
由 entry 生成的 Chunk 之间相互孤立,没有必然的前后依赖关系,但异步生成的 Chunk 则不同,两者间可能存在单向依赖关系,在 webpack 中称引用者为 parent、被引用者为 child,分别存放在 ChunkGroup.parents 、ChunkGroup._children 属性中。
_PS: 基于异步模块的 chunk 在 webpack 官方文档中,通常称之为 Async chunk

Runtime 分包

重点: Webpack 5 之后还能根据 entry.runtime 配置单独打包运行时代码。
除业务代码外,Webpack 编译产物中还需要包含一些用于支持 webpack 模块化、异步加载等特性的支撑性代码,这类代码在 webpack 中被统称为 runtime。
虽然每段运行时代码可能都很小,但随着特性的增加,最终结果会越来越大,特别对于多 entry 应用,在每个入口都重复打包一份相似的运行时代码显得有点浪费,为此 webpack 5 专门提供了 entry.runtime 配置项用于声明如何打包运行时代码。

  1. module.exports = {
  2. entry: {
  3. index: { import: "./src/index", runtime: "solid-runtime" },
  4. }
  5. };

Webpack 执行完 entry、异步模块分包后,开始遍历 entry 配置判断是否带有 runtime 属性,如果有则创建以 runtime 值为名的 Chunk,因此,上例配置将生成两个chunk:chunk[index.js] 、chunk[solid-runtime],并据此最终产出两个文件:

  • 入口 index 对应的 index.js 文件
  • 运行时配置对应的 solid-runtime.js 文件

在多 entry 场景中,只要为每个 entry 都设定相同的 runtime 值,webpack 运行时代码最终就会集中写入到同一个 chunk。

splitChunks分包规则

webpack的异步加载原理及分包策略
原因:
如果多个 chunk 同时包含同一个 module,那么这个 module 会被不受限制地重复打包进这些 chunk
optimization.splitChunks
它内置的代码分割策略是这样的:

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩之前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

默认配置

  1. splitChunks: {
  2. // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
  3. chunks: "async",
  4. // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
  5. minSize: 30000,
  6. // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
  7. minChunks: 1,
  8. // 表示按需加载文件时,并行请求的最大数目。默认为5。
  9. maxAsyncRequests: 5,
  10. // 表示加载入口文件时,并行请求的最大数目。默认为3。
  11. maxInitialRequests: 3,
  12. // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
  13. automaticNameDelimiter: '~',
  14. // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
  15. name: true,
  16. // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
  17. cacheGroups: {
  18. vendors: {
  19. test: /[\\/]node_modules[\\/]/,
  20. priority: -10
  21. },
  22. //
  23. default: {
  24. minChunks: 2,
  25. priority: -20,
  26. reuseExistingChunk: true
  27. }
  28. }
  29. }

TreeShaking

ESM 下模块之间的依赖关系是高度确定的,与运行状态无关,编译工具只需要对 ESM 模块做静态分析,就可以从代码字面量中推断出哪些模块值未曾被其它模块使用,这是实现 Tree Shaking 技术的必要条件。

前提配置

  • 使用ES6 Moudle
  • 配置 optimization.usedExportstrue,启动标记功能
  • 启动代码优化功能,可以通过如下方式实现:
    • 配置 mode = production
    • 配置 optimization.minimize = true
    • 提供 optimization.minimizer 数组 ```css // webpack.config.js module.exports = { entry: “./src/index”, mode: “production”, devtool: false, optimization: { usedExports: true, }, };
  1. <a name="sZmEl"></a>
  2. #### 实现原理
  3. - Make 阶段,收集模块导出变量并记录到模块依赖关系图 ModuleGraph 变量中
  4. - Seal 阶段,遍历 ModuleGraph 标记模块导出变量有没有被使用
  5. - 生成产物时,若变量没有被其它模块使用则删除对应的导出语句
  6. <a name="RmF1d"></a>
  7. ### browserslistrc工作流程
  8. 1、webpack实现兼容大体步骤<br />![实现兼容(css新特性、js新语法).png](https://cdn.nlark.com/yuque/0/2022/png/368597/1644844606943-422391ae-1975-48a4-ad74-8e5e1c7111d4.png#clientId=u59e90400-d30d-4&from=drop&height=385&id=uc1e30b7b&margin=%5Bobject%20Object%5D&name=%E5%AE%9E%E7%8E%B0%E5%85%BC%E5%AE%B9%EF%BC%88css%E6%96%B0%E7%89%B9%E6%80%A7%E3%80%81js%E6%96%B0%E8%AF%AD%E6%B3%95%EF%BC%89.png&originHeight=2456&originWidth=4860&originalType=binary&ratio=1&size=551870&status=done&style=none&taskId=uba72ed49-d2d4-4347-8c14-b17d177d1d4&width=761)<br />2、browserslistrc 解释<br />给出一些条件,然后匹配对应的浏览器平台,例如:>1%,last 2 version,not dead 表示:市场占有率大于百分之1,最新的两个版本,近24个月有更新(not dead)
  9. 3、browserslistrc如何工作的?<br />在装webpack时默认下了一个browserslist的包,在nodemodules中找到这个包,入口文件index.js中引入了caniuse-lite的工具<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/368597/1644845729782-8566afa6-ce12-4cbd-8c9b-c36d734d0f2b.png#clientId=u59e90400-d30d-4&from=paste&height=79&id=uc74434b7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=158&originWidth=1192&originalType=binary&ratio=1&size=95051&status=done&style=none&taskId=ucf9c9e4b-695a-47a7-b802-7681987b46d&width=596)<br />该工具会默认会带上browserslistrc中列出的条件去caniuse.com 请求数据,返回相应的浏览器平台。
  10. 4、如何使用?<br />方法1 、项目根目录创建.browserslistrc文件 列出查询条件 条件之间换行<br />方法2、在package.json 中新增"browserslist":[">1%","last 2 version"....条件 用逗号分割]
  11. <a name="jWj0F"></a>
  12. ### postcss 工具
  13. <a name="cgX16"></a>
  14. #### 基础工作流程![browserslist指定的浏览器平台对css样式格式要求不一样(前缀)(autoprefixer.github.io).png](https://cdn.nlark.com/yuque/0/2022/png/368597/1644931685850-75e3d902-19fd-49fb-a0cd-e15d6dbb2c1d.png#clientId=u233587b6-0621-4&from=ui&id=u9c6a2424&margin=%5Bobject%20Object%5D&name=browserslist%E6%8C%87%E5%AE%9A%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E5%B9%B3%E5%8F%B0%E5%AF%B9css%E6%A0%B7%E5%BC%8F%E6%A0%BC%E5%BC%8F%E8%A6%81%E6%B1%82%E4%B8%8D%E4%B8%80%E6%A0%B7%EF%BC%88%E5%89%8D%E7%BC%80%EF%BC%89%28autoprefixer.github.io%29.png&originHeight=870&originWidth=1494&originalType=binary&ratio=1&size=124513&status=done&style=none&taskId=u3a0690e0-8cf1-4cc0-a284-61914dff75a)
  15. 在webpack.config.js中应该如此配置:
  16. ```javascript
  17. test:/\.css$/,//
  18. use:[
  19. 'style-loader' ,
  20. 'css-loader',
  21. {
  22. loader:'postcss-loader', //loader名称
  23. options:{ //配置项
  24. postcssOptions:{ //postcss 配置
  25. plugins:[ //插件合集
  26. require('autoprefixer') //autoprefixer 插件
  27. ]
  28. }
  29. }
  30. },
  31. ]

打包前样式:

  1. .myTitle {
  2. color: green;
  3. display: grid;
  4. transition: all 0.5s;
  5. user-select: none;
  6. }

打包后样式:
image.png
相关前缀都加上了

进阶工作流程

进阶版browserslist指定的浏览器平台对css样式格式要求不一样(前缀)(autoprefixer.github.io).png
问题1: autoprefixer 插件仅仅能添加css前缀,如果有其他更多的兼容规则,岂不是要引入很多插件?
答:有 postcss-preset-env 预设,插件集合,其中就包含了autoprefixer插件

问题2:作为样式处理插件,在webpack.config.js 中,要在匹配到.css/.less/.sass中重复配置,是在冗余,有没有一种好的解决方案?

答:有 在根目录新建postcss.config.js(名字不能改)
image.png

  1. module.exports={
  2. plugins:[
  3. require('postcss-preset-env')
  4. ]
  5. }

在webpack.config.js中 可继续简写,此时一旦遇到postcss-loader 就自动去找postcss-config.js这个文件了。

  1. rules:[
  2. {
  3. // test:/\.css$/,//
  4. // use:[
  5. // {
  6. // loader:'css-loader'
  7. // }
  8. // ]
  9. // 简写1
  10. // test:/\.css$/,//
  11. // loader:'css-loader'
  12. // 简写2
  13. test:/\.css$/,//
  14. use:[
  15. 'style-loader' ,
  16. 'css-loader',
  17. 'postcss-loader'
  18. // {
  19. // loader:'postcss-loader', //loader名称
  20. // options:{ //配置项
  21. // postcssOptions:{ //postcss 配置
  22. // plugins:[ //插件合集
  23. // require('autoprefixer')
  24. // ]
  25. // }
  26. // }
  27. // },
  28. ]
  29. },
  30. {
  31. test:/\.less$/,//
  32. use:['style-loader' ,'css-loader','postcss-loader','less-loader']
  33. }
  34. ]

importLoaders 工作流程

image.png
正常webpack是单向流程的
但是可能文件中引入了其他文件,而loader并未处理被引入的文件
这时就要在处理流程的某个loder中去使用配置 importLoaders:xx (xx代表向前推进xx步)
这时整个流程就会去重新处理那个被引入的文件

  1. {
  2. // test:/\.css$/,//
  3. // use:[
  4. // {
  5. // loader:'css-loader'
  6. // }
  7. // ]
  8. // 简写1
  9. // test:/\.css$/,//
  10. // loader:'css-loader'
  11. // 简写2
  12. test:/\.css$/,//
  13. use:[
  14. 'style-loader' ,
  15. {
  16. loader:'css-loader',
  17. options:{
  18. importLoaders:1
  19. }
  20. },
  21. 'postcss-loader'
  22. ]
  23. },

cssloader和styleloader

css-loader:如果在js中引用了css,需要用css-loader来识别。会处理 import / require() @import / url 引入的内容。
style-loader:作用是把样式插入到 DOM中,方法是在head中插入一个style标签,并把样式写入到这个标签的 innerHTML里

url-loader 和file-loader

url-loader:将资源转化成base64格式,在js中引入。 优点是减少请求次数,缺点主文件可能会很大,在做首屏加载不适用。
file-loader:将资源拷贝进指定目录。分开请求,请求次数会多。
url-loader中可以使用file-loader 通过limit 文件大小 字段设定 大于limit则用file-loader

  1. {
  2. test:/\.(png|jpe?g|gif)$/,
  3. use:[
  4. {
  5. loader:'url-loader',
  6. options:{
  7. name:'img/[name].[hash:6].[ext]' ,//占位符 控制打包输出
  8. limit:200*1024//大小限制
  9. }
  10. }
  11. ]
  12. }


asset 模块

asset处理图片

asset/resource :将资源拷贝进指定目录,相当于file-loader

  1. {
  2. test:/\.(png|jpe?g|gif)$/,
  3. type:'asset/resource',
  4. generator:{
  5. filename:'img/[name].[hash:6].[ext]' //将文件放进指定目录并命名
  6. }
  7. }

asset/inline:将资源转换成base64 格式,在js中引用,相当于url-loader

  1. {
  2. test:/\.(png|jpe?g|gif)$/,
  3. type:'asset/inline',
  4. }

更多的是两者结合使用

  1. test:/\.(png|jpe?g|gif)$/,
  2. type:'asset',//不需要配置
  3. generator:{
  4. filename:'img/[name].[hash:6].[ext]' //将文件放进指定目录并命名
  5. },
  6. parser:{
  7. dataUrlCondition:{
  8. maxSize:200*1024 //大小限定
  9. }
  10. }

跟url-loader 的limit 控制打包方式类似,asset通过 parser.dataUrlCondition.maxSize 来进行限定,大小没超过限定的用asset/inline来进行资源处理,超过则通过asset/resource来进行处理

asset处理字体

  1. {
  2. test:/\.(ttf|woff2?)$/ ,//匹配ttf,woff woff2 字体文件
  3. type:'asset/resource',
  4. generator:{
  5. filename:'font/[name].[hash:3].[ext]' //将文件放进指定目录并命名
  6. }
  7. }

插件(plugin)

loader: 对特定类型的文件进行转换
plugin:功能更加齐全,可以在webpack打包任何阶段做特定的事儿(比如对loader处理过的css进行压缩、打包前对上一次打包文件的清空等。)

clean-webpack-plugin

作用:打包前 对上一次打包文件 的清空
npm i clean-webpack-plugin 下载插件

在webpack配置文件中引入
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

在配置文件中的entry 、output 同级新建plugins数组,数组中可以放多个插件配置。
每个插件核心本身就是一个类,所以用new操作,具体传什么参数要看插件本身

image.png

html-webpack-plugin

作用:打包后根据模版自动生成html并且自动引入依赖文件。
npm i html-webpack-plugin 下载插件
在webpack中引入
constHtmlWebpackPlugin = require('html-webpack-plugin')

在plugins数组中引入

  1. plugins:[
  2. new CleanWebpackPlugin(),
  3. new HtmlWebpackPlugin({
  4. title:'yhd测试',//html title
  5. template:'./public/index.html'
  6. })
  7. ]

参数:
title:页面title
template:模版html路径,用作插件自动生成html的模版。非必选,但是使用vue react 项目必选,因为vue和react项目的html统一在#app中,需要使用模版Html配置。

关联插件 :define-plugin
作用:定义常量。
比如在模版模版Html<link rel="icon" href="<%= BASE_URL %>favicon.ico"> 中的BASE_URL
为常量,其作用相当于一个可配置的路径,能找到图标。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  6. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  7. <!-- --------------- -->
  8. <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  9. <!-- -------------- -->
  10. <title>
  11. <%= webpackConfig.name %>
  12. </title>
  13. </head>
  14. <body>
  15. <noscript>
  16. <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it
  17. to continue.</strong>
  18. </noscript>
  19. <div id="app"></div>
  20. <!-- built files will be auto injected -->
  21. </body>
  22. <!-- <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
  23. <script src="https://cdn.bootcss.com/jspdf/1.5.3/jspdf.min.js"></script>
  24. <script src="https://cdn.bootcss.com/html2canvas/0.5.0-alpha2/html2canvas.min.js"></script> -->
  25. </html>

define-plugin 是webpack内置插件,不需要下载,可直接引入

  1. plugins:[
  2. new CleanWebpackPlugin(),
  3. new HtmlWebpackPlugin({
  4. title:'yhd测试',//html title
  5. template:'./public/index.html'
  6. }),
  7. new DefinePlugin({
  8. BASE_URL:'"./"'
  9. })
  10. ]

为什么 BASE_URL:'"./"' 要加引号
如果不加
因为在webpack工作过程中 会const BASE_URL = ./ 这样就有语法错误
加了以后才会是const BASE_URL = ‘./‘

copy-webpack-plugin

作用:某些资源不用经过webpack打包就可以直接使用,这些资源我们可以用这个插件直接复制过来
安装 cnpm i copy-webpack-plugin -D
配置:(需要在webpack配置中引入)

  1. new CopyWebpackPlugin({
  2. patterns: [
  3. {
  4. from: 'public', //代表从public文件夹拷贝
  5. // to:'dist',//默认不用写 ,会自动找上面的output里的path
  6. globOptions: {
  7. ignore: ['**/index.html'], //** 表示从上面的public里面找 index.html
  8. },
  9. },
  10. ],
  11. }),

babel

为什么需要babel?

一些js语法浏览器可能无法识别,例如JSX、TS、ES6+等等,如果想要这些语法能让浏览器直接使用,就需要babel工具进行转换。

使用流程

npm i @babel/core -D安装
想要babel直接在命令行使用 npm i @babel/cli -D安装babel cli
命令: npx babel src --out-dir asset 表示babel将处理src目录下的js文件,并输出到asset文件夹下。
此时并不会对代码做任何语法转换(作用类似于post-css),是一个独立的工具,需要配合其他工具包使用。
配合@babel-preset 预设使用
npm i @babel-preset
安装成功后
npx babel src --out-dir asset --presets=@babel/preset-env
表示babel将处理src目录下的js文件,并输出到asset文件夹下。使用的预设是@babel/preset-env。

babel-loader

cnpm i babel-loader安装
在webpack配置文件中配置

  1. {
  2. test:/\.js$/,
  3. use:[
  4. {
  5. loader:'babel-loader',
  6. options:{
  7. presets:['@babel/preset-env']
  8. }
  9. }
  10. ]
  11. }

babel-loader 也是根据.browserslistrc来筛选所需要兼容的浏览器
也可在babelloader配置中指定要兼容的浏览器

  1. {
  2. test:/\.js$/,
  3. use:[
  4. {
  5. loader:'babel-loader',
  6. options:{
  7. presets:[
  8. [
  9. '@babel/preset-env',
  10. {targets:'chrome 91'} //指定要兼容的浏览器
  11. ]
  12. ]
  13. }
  14. }
  15. ]
  16. }

如果targets 和browserslistrc都有写,babel-loader以target为准

babel.config.js配置文件
跟postcss.config.js一样,babel也有统一配置文件

  1. module.exports={
  2. presets:[
  3. [
  4. '@babel/preset-env',
  5. // {targets:'chrome 91'}
  6. ]
  7. ]
  8. }

在webpack中就可以简写babel-loader 了

polyfill配置

why&what?

preset-env 并不能转化所有的js语法,例如Promise Symbol 等 可以使用polyfill进行填充。
在webpack5之前,会自动填充,不需要进行配置,但是在webpack5 之后为了性能优化需要我们手动进行配置。

安装

不需要直接安装@babel/polyfill babel 官方文档中建议使用:
core-js/stable:专门做语法功能
regenerator-runtime/runtime: 专门转Symbol Promise 等
npm i core-js regenerator-runtime

配置

在babel.config.js 中进行配置,
语法如下:

  1. module.exports={
  2. presets:[
  3. [
  4. '@babel/preset-env',
  5. {
  6. useBuiltIns:'usage',
  7. corejs:3
  8. }
  9. ]
  10. ]
  11. }

useBuiltIns的值:
false:默认值,不对当前打包文件做polyfill填充处理。此时打包文件大小很小
useBuiltIns:依据用户源代码中的新语法进行polyfill填充,打包过程中遇到填充的就立即填充。
entry:根据浏览器填充,当前适配的浏览器所需要兼容的语法全部填充(不要用)

corejs:值默认是2 ,要设置成3

在填充中可能会对node_modules 进行二次填充,所以要在webpack.config.js 中排除node_modules

  1. {
  2. test:/\.js$/,
  3. exclude:/node_modules/,
  4. use:['babel-loader']
  5. }