webpack 是什么?
webpack 是模块打包工具。它不能理解成 es6 的翻译器,因为它只认识 import 语法,其他的高级语法不认识。因为它是模块打包工具,所以各种模块的语法它都认识。
url-loader 和 file-loader
url-loader 可以完全替代 file-loader;
css-loader 的配置
1 | { |
- importLoaders 的含义
样式在解析时,通常是从下到上或从右到左的依次执行loader
,但是在写样式时,有时会出现@import ../list/index.less
的语法,这时在打包的时候import
的样式可能就不会走css-loader
后面的 loader,直接走css-loader
,所以通过设置importLoadders
,让通过import
引入的样式,在引入之前也都走css-loader
后的 2 个loader
- css 模块化
在引入 css 时通常有如下两种方式
1 | // 全局的方式 |
两种方式的区别
- 全局的方式引入,只要有元素使用的样式在
index.less
中有定义,样式就会起作用。如果多个css
文件中有相同名字的样式,则会出现样式污染的情况。 - 模块式引入,要想使用模块式样式,需要在
css-loader
中配置modules:true
,这样在使用时,- 只有引入的模块,
- 并且指定了样式的地方才会起作用。
1 | import styles from './index.less' |
打包字体文件
使用 file-loader 即可。
代码映射
devtool | 构建速度 | 重新构建速度 | 生产环境 | 品质(quality) |
---|---|---|---|---|
(none) | +++ | +++ | yes | 打包后的代码 |
eval | +++ | +++ | no | 生成后的代码 |
cheap-eval-source-map | + | ++ | no | 转换过的代码(仅显示行);eval 是最快的,另外这个不包含第三方模块,所以感觉开发中使用这个最合适。 |
cheap-module-eval-source-map | o | ++ | no | 原始源代码(仅显示行);但是视频推荐使用这个 。 |
eval-source-map | – | + | no | 原始源代码 |
cheap-source-map | + | o | no | 转换过的代码(仅显行) |
cheap-module-source-map | o | - | no | 原始源代码(仅显行) |
inline-cheap-source-map | + | o | no | 转换过的代码(仅显示行) |
inline-cheap-module-source-map | o | - | no | 原始源代码(仅显示行) |
source-map | – | – | yes | 原始源代码 |
inline-source-map | – | – | no | 原始源代码 |
hidden-source-map | – | – | yes | 原始源代码 |
nosources-source-map | – | – | yes | 无源代码内容 |
+++ 非常快速, ++ 快速, + 比较快, o 中等, - 比较慢, – 慢
cheap
的含义:只针对业务代码进行映射,不包括安装的第三方包。另外 cheap 是只映射到行,不会映射到列,即行中的第几个字符。module
的含义:就是包含安装的第三方模块。比如cheap-module-source-map
eval
的含义,eval
就是把源代码打包成字符串,放到eval
中。eval
的打包速度是最快的。视频中推荐开发模式使用
cheap-module-eval-source-map
,生产环境使用cheap-module-source-map
自我理解
带
eval
和inline
关键字的,会把源代码和打包后的文件打包在一起,不包含的会对源代码进行单独打包,生成一个单独的文件。
dev-server
dev-server
就是先通过webpack
打包,然后启动一个node服务
,把打包后的静态资源启动起来,但是dev-server
是把静态资源放到内存中了,并没有放到磁盘上。
所以可以在dev-server
的before
钩子中写自己的mock
。
就是启动了一个 node 服务
手写一个简单的 dev-server
1 | const express = require("express"); |
自己写需要配置的东西比较多,推荐使用dev-server
.
热更新 HMR
不配置 HMR,当有改动时,也会自动更新,但是这时是重新加载,如果页面上有数据或者状态值发生改变,是不会保留的。
而 HMR 是只更新变化的部分,并且保留页面的状态和数据。
1 | devServer:{ |
- hot 是开启 hmr 模式
- hotOnly 表示,启用热模块替换,在构建失败时不刷新页面作为回退。配置了这个有时候需要手动刷新,还是不要配置了。
记得配置
new webpack.HotModuleReplacementPlugin()
上面讲解了热更新及它的配置,但是这还没完,配置后,如果想让它起作用,需要在代码中编写相关的代码才行,如下:
1 | counter(); |
很多人奇怪,不对啊,在开发过程中没写过这样的代码啊,只要上面的配置完,HMR
就已经起作用了啊,这是为什么呢?
这是因为在开发过程中,我们使用的
loader
都已经实现了 HMR 的接口,也就是做了上面类似的操作。所以我们不需要手动再写。比如style-loader
、vue-loader
、react 的bable-preset
等,都已经实现了 HMR 接口。可参考官网“概念”模块里面的讲解。
Babel 解决 ES6 语法
由于现在很多浏览器还不支持 ES6 语法,所以需要使用 babel 把 es6 语法转换成所有浏览器都支持的 ES5 语法。babel-loader
并不会直接把ES6
语法转换成ES5
,它只是搭建babel
和webpack
的桥梁,它需要使用babel/preset-env
插件对 ES6 进行转换。babel/preset-env
只能转换 ES6 语法,比如箭头函数、let 等,它不能转换 ES6 中的函数,比如 Promise、Array.map()等,如果需要支持这些 ES6 的函数,需要使用babel/polyfill
1 | import "@babel/polyfill"; |
在使用时,我们需要引入;
由于 polyfill 文件很大,所以在打包时如果把没有用到的方法也打包进来,那打包后的文件会很大,如果只打包用到的方法,这样就比较符合我们的用意。通过设置useBuiltIns:'usage'
可以达到我们的目的。
1 | module:{ |
开发第三方库时 polyfill 的配置
由于上面的引入是全局的,所以如果开发第三方包时也那样做,就会污染全局变量,应该使用下面的方式
1 | module:{ |
另外 impor 的地方需要删除掉。同时需要安装npm install --save @babel/runtime-corejs2
不想在 options 中配置,可以创建 babel 的配置文件
.babelrc
来专门配置 babel
Tree Shaking
官网解释其实已经很明确了。
就是移除没用到的代码,打包的时候只把用到的代码进行打包。它依赖于 ES2015 模块系统中的静态结构特性,例如 import
和 export
。也就是说 Tree Shaking 只支持ES6
的module
,对于commonjs
的模块是不支持的,因为它是动态结构。
Tree Shaking 形象的描述了这一行为,摇动树,把没用的树叶摇掉,只留下有用的。
在开发环境下,这个功能默认是没开启的,所以在development
环境下,需要设置
1 | plugins:[], |
这样在开发模式下,就只会对使用到的代码进行打包,但是这样有个问题,比如@babel/polyfill
这类文件,需要引入,但是不需要专门导出其中的某个方法,这时打包时就会认为这个包没用到,打包时就会删除它,这样就会有问题。
我们可以通过设置package.json
中的sideEffects
来指定哪些包不需要tree shaking
的包。
1 | // 这样就不会对@babel/polyfill进行tree shaking了 |
但是由于@babel/polyfill
不需要手动引入,所以我们需要对所有的文件进行tree shaking
,这时把siideEffects
设置成false
即可。
1 | // 对所有的文件进行tree shaking |
但是如果 css 没有使用 module 的模式,则会认为 css 文件没有导出任何东西,也会被移除,所以需要对 css 进行配置
1 | // 不对所有的css进行tree shaking |
上面配置完,开发模式下的 tree shaking 就配置完了,但是通过查看打包后的文件发现,没用到的代码还是被打包了 😂,是不是没起作用,其实起作用了,在注释里面标记了文件有哪些方法,真正用到的是哪些方法,只是标记了一下,为什么不移除掉呢,因为如果移除掉,在调试的时候,代码的行数可能就对不上了,所以开发模式下配置这个就是脱裤放屁,妈了个巴子
所以 tree shaking 真正有意义的是在生产环境,但是生产环境不需要手动做 tree shaking 配置,webpack 自动都配置好了,所以这功能了解就行,不需要做什么。sideEffects 需要设置下。
webpack 和 code spliting
code spliting 就是把代码打包成多个包,这样在浏览器加载的时候就可以并行加载,提高性能和效率。
实现的方式有很多种,下面列举一些常用的。
- 把全局使用的包单独打包
比如lodash
,这个包封装了一些封装了一些常用的方法,完全可以挂在在 window 全局上,而不需要在每个使用的地方 import。
新建一个js
文件lodash.js
1 | import _ from "lodash"; |
然后打包的时候,就可以把这个文件单独打包,这样就不会在每个使用的地方都打包一次(也可以通过 webpack 提取模板的方式解决)
1 | entry:{ |
这样打包的时候,lodash
就被单独打包了。
使用 webpack 自带的代码分割
我们不需要专门的对 lodash 这种第三方模块进行处理,通过配置 webpack,它自动做代码分割。
1 | // 业务代码中 |
上面的配置是对同步代码的;
如果是异步加载,那么上面的配置就不需要了,webpack 会把异步加载的包自动单独进行打包。
默认打出的 chunk 包名是用包的 id 来命名的,也就是 0,1,2 等等,可以手动指定 chunk 的包名。
异步加载组件支持一种语法叫做“魔法注释”,可以通过这种方式设置生成 chunk 的名字。
比如下面是一段异步加载的代码(注意两端的空格)
1 | function getComponent(){ |
上面打包后lodash
的包就会被命名成vendors~lodash.js
; 是不是奇怪为什么会多一个vendors~
,如果不想让带着前缀,可以通过配置插件SplitChunksPlugin
来实现。
1 | module.exports = { |
这样再打包,包名就成lodash.js
了。
其实代码分割就是通过配置SplitChunksPlugin
插件来实现的。虽然上面是通过注释的方式,其实也是走的SplitChunksPlugin
插件。
SplitChunksPlugin 详细讲解
SplitChunksPlugin
有一个默认配置,参见官网
,手动设置成空对象或不配置,走的都是这个默认配置。
- chunks
chunks 参数值 | 含义 |
---|---|
all | 把动态和非动态模块同时进行优化打包;所有模块都扔到 vendors.bundle.js 里面。依赖cacheGroup 中的配置,如果cacheGroup 中的配置设置成 false,则不会分割打包。 |
initial | 把非动态模块打包进 vendor,动态模块优化打包 |
async | 把动态模块打包进 vendor,非动态模块保持原样(不优化) |
- minSize
需要进行代码分割的最小尺寸。只有大于这个值的包才会进行代码分割。单位是字节,(1024 字节=1kb) - maxSize
配置打包后 chunk 的包的大小,比如配置 50000,50kb,那么lodash
包的大小超过了这个,打包的时候lodash
会被拆分多个 50kb 的包。一般不配置这个选项。 - minChunks
设置模块被引用多少次才进行代码拆分。这里说的引用是指被打包后生成的 chunk 所引用的次数,不是开发代码中引用的次数(是这样吗?)
- maxAsyncRequests
设置页面同时加载的模块数量。比如设置 5,当打包前 5 个库的时候,会分别打包成 5 个包文件,超过 5 个的模块就不再进行代码分割。 - maxInitialRequests
设置整个网站首页或入口文件在进行加载时,可能也会引入其他代码库,这个就是设置入口文件引入的库最多拆分几个包。比如设置成 3,即使入口文件引入了 5 个文件,那也会只拆分成三个包。 - automaticNameDelimiter
设置生成文件时名称之间的连接符。 - name
设置cacheGroup
中的名字是否有效,这个参数一般不动。 - cacheGroups
配置拆分的规则。为什么叫“缓存组”,比如有两个第三方包,lodash 和 jquery,当遇到 lodash 时,看下是否满足拆规则,如果满足则先缓存下来,然后再分析 jquery,等把包都分析完了,把都满足规则的包一起打包到对应组指定的文件中。- reuseExistingChunk
指定打过包的文件不会再被打包。比如有 a,b 两个包,在 c 文件中引入了 a,b,但是 a 中也引入了 b,这样在打包的时候,由于在对 a 打包的时候已经打过 b 的包,所以在对 c 打包的时候,不会再对 b 进行打包。
- reuseExistingChunk
1 | module.exports = { |