webpack 优化配置
现在很多主流的框架都已经先把 bundler 的相关 config 都写好了,但了解这些
相关配置也能帮助到自己在开发时可以思考一下要如何改良自己的代码,进而提升整体的打包性能。
- 数据性能分析
- 编译时间优化
- 编译体积优化
- 运行速度优化
数据性能分析
1. 速度分析
这个性能到底是快还是慢,快多少,慢多少,得有数据,那么就需要工具帮我我们统计数据来分析。
speed-measure-webpack-plugin
2. 文件体积监控
webpack-bundle-analyzer
- 是个webpack的插件,需要配合webpack和webpack-cli一起使用。这个插件的功能是生成代码分析报告,帮助提升代码质量和网站性能
-
可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项,文件是否重复,压缩后大小如何,针对这些,我们可以进行文件分割等操作。
编译时间优化
1. 缩小查找范围
extensions
- 定extensions之后可以不用在require或是import的时候加文件扩展名
-
查找的时候会依次尝试添加扩展名进行匹配
alias
- 配置别名可以加快webpack查找模块的速度
-
当引入bootstrap模块的时候,它会直接引入bootstrap,而不需要从node_modules文件夹中按模块的查找规则查找
modules
直接声明依赖名的模块webpack会使用类似Node.js一样进行路径搜索,搜索node_modules目录
果可以确定项目内所有的第三方依赖模块都是在项目根目录下的node_modules中的话可以直接指定
-
默认配置
-
直接指定
oneOf
- 没个文件对于rules中的所有规则都会遍历一遍,如果使用oneOf,只要能匹配一个即可退出
-
oneOf中不能两个配置处理同一种类型文件
external
- 如果我们想引用一个库,但是又不想让webpack打包,那就可以通过配置externals排除打包
-
在模板 html 文件中引入 cdn 地址,可通过区分开发和生产环境来判断是否使用 cdn 链接(process.env.NODE_ENV 属性),本地就通过 node_modules 中的资源编译就好,不会在项目里生成文件,而是存储于内存当中,这个时候通过内存获取数据会比从网络中获取 cdn 资源更快。
2.减少处理文件
noParse
- module.noParse 字段,可以用于配置哪些模块文件的内容不需要进行解析
- 需要解析依赖(即无依赖) 的第三方大型类库等,可以通过这个字段来配置,以提高整体的构建速度
-
用 noParse 进行忽略的模块文件中不能使用 import、require 等语法
备注:比如jquery,loadsh工具库
IgnorePlugin
- ignore-plugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
- requestRegExp 匹配(test)资源请求路径的正则表达式
- contextRegExp (可选)匹配(test)资源上下文(目录)的正则表达式
- moment会将所有本地化内容和核心功能一起打包,可使用 IgnorePlugin 在打包时忽略本地化内容
==moment中其它语言包不要了,不打包,忽略掉,需要中文直接import,从600多K 减少到 100多K==
thread-loader(多进程)
- 把thread-loader放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的worker 池(worker pool)中运行
- include 表示哪些目录中的 .js 文件需要进行 babel-loader
- exclude 表示哪些目录中的 .js 文件不要进行 babel-loader
- exclude 的优先级高于 include,尽量避免 exclude,更倾向于使用 include
==thread-loader开启线程池,开线程和线程通信都需要时间,大概0.7所有,所以项目特别大,任务特别多的时候才用thread-loader (happypack 在webpack5中已经废掉了,不再维护了)。==
利用缓存
利用缓存可以提升重复构建的速度
babel-loader
- Babel在转义js文件过程中消耗性能较高,将babel-loader执行的结果缓存起来,当重新打包构建时会尝试读取缓存,从而提高打包构建速度、降低消耗
-
默认存放位置是node_modules/.cache/babel-loader
备注:Babel-loader 自带缓存功能,直接通过配置开启。 其他loader没有缓存功能,比如css-loader等,可以使用cache-loader缓存。
cache-loader
- 在一些性能开销较大的loader之前添加cache-loader,可以以将结果缓存中磁盘中
-
默认保存在 node_modules/.cache/cache-loader 目录下
hard-source-webpack-plugin ==(替换了DllPlugin)==
- HardSourceWebpackPlugin为模块提供了中间缓存,缓存默认的存放路径是 node_modules/.cache/hard-source
- 配置hard-source-webpack-plugin后,首次构建时间并不会有太大的变化,但是从第二次开始,构建时间大约可以减少80%左右
-
webpack5中已经内置了模块缓存,不需要自己单独配置此插件
编译体积优化
1. 压缩JS、CSS、HTML和图片
- optimize-css-assets-webpack-plugin是一个优化和压缩CSS资源的插件
- terser-webpack-plugin是一个优化和压缩JS资源的插件
-
image-webpack-loader可以帮助我们对图片进行压缩和优化
2. 清除无用的CSS
- purgecss-webpack-plugin单独提取CSS并清除用不到的CSS
==mini-css-extract-plugin // 因为css和js的加载可以并行,所以我们可以通过此插件提取css成单独文件,干掉无用的css进行压缩==
==考虑通过js动态添加的css==
备注:
1. 不再使用style-loader,直接使用MiniCssExtractPlugin.loader。
2. const glob = require(’glob’) // 文件匹配模式,给他一个字符串,能够匹配文件出来。
3. const PATHS = { //一堆路径的集合
src: path.resolve(__dirname, ’src’)
};
4. **匹配任意字符,包括路径分隔符,*匹配任意字符,不包括路径分隔符。
3. Tree Shaking
- 一个模块可以有多个方法,只要其中某个方法使用到了,则整个文件都会被打到bundle里面去
- 原理是利用es6模块的特点,只能作为模块顶层语句出现,import的模块名只能是字符串常量
webpack默认支持,可在production mode下默认开启
package.json 配置:
1.sideEffects": false 所有的代码都没有副作用(都可以进行 tree shaking)
2.可能会把 css和@babel/polyfill文件干掉 可以设置 "sideEffects":["*.css"]
dev模式下通过配置babel看到tree shaking效果
备注:
- webpack5中的treeshaking是经过了加强优化的,功能比webpack4强大的多。
- 在 ES6 module 还没诞生以前我们也可以利用 commonJS 来进行 module 的导入,为什麽 ES6 module 可以做到 Tree Shaking 可是 commonJS 无法呢?其实是因为 ES6 module 有著非常多的特性,让 bundler 可以针对这些特性来进行静态的分析:
- module 必须要在顶层被 import。
- module 内部会自动被定义为 strict mode。
- module name 不能动态改变。
- module 内容为 immutable 无法在其他文件中被动态新增或删除内容。
因为这些强限制在,所以 ES6 module 就可以让 bundler 做到 Tree Shaking 的效果,而 commonJS则无法达到此点。
运行速度优化
1. 代码分割
1.1 入口点分割
Entry Points:入口文件设置的时候可以配置
这种方法的问题:
- 如果入口 chunks 之间包含重复的模块(lodash),那些重复模块都会被引入到各个 bundle 中
-
不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码
1.2 懒加载
用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载,在给单页应用做按需加载优化时,一般采用以下原则:
- 对网站功能进行划分,每一类一个chunk
- 对于首次打开页面需要的功能直接加载,尽快展示给用户,某些依赖大量代码的功能点可以按需加载
- 被分割出去的代码需要一个按需加载的时机
==import 天然的代码分割点,如果遇到import就会分割出去一个单独的代码块,可以单独加载==
备注:import语法:像vue,react,angular的懒加载组件,包括路由组件的懒加载原理都是一样的,通过import动态引入。
1.3 prefetch
- 使用预先拉取,表示该模块可能以后会用到。浏览器会在空闲时间下载该模块
- prefetch的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源
==通过魔法注释,给懒加载的代码块添加预获取配置==,此导入会让<link rel="prefetch" as="script" href="http://localhost:8080/hello.js">被添加至页面的头部。因此浏览器会在空闲时间预先拉取该文件。
备注:
- preload 预加载,该资源肯定用得到,优先级较高,需要提前获取,有可能有性能隐患(需慎用)。
- prefetch 预获取,该资源可能在以后用得到,它在浏览器空闲的时候加载,没有性能问题。
1.4 提取公共代码
splitChunks
提取公共代码,防止被重复打包,拆分过大的js文件,合并零散的js文件
module、chunk和bundle 这三个名词是什么意思 :
module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。
chunk: chunk是webpack根据功能拆分出来的,包含三种情况:
- 你的项目入口(entry)
- 通过import()动态引入的代码
- 通过splitChunks拆分出来的代码
chunk包含着module,可能是一对多也可能是一对一。
bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。
splitChunks 配置:
1. maxInitialRequests
是splitChunks里面比较难以理解的点之一,它表示允许入口并行加载的最大请求数,之所以有这个配置也是为了对拆分数量进行限制,不至于拆分出太多模块导致请求数量过多而得不偿失。
这里需要注意几点:
- 入口文件本身算一个请求
- 如果入口里面有动态加载得模块这个不算在内
- 通过runtimeChunk拆分出的runtime不算在内
- 只算js文件的请求,css不算在内
- 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
2. cacheGroups
是核心,配置提取模块的规则,test,priority,reuseExistingChunk,这三项是特有配置,其他跟外面选项一样,如果里面没配置,就用外面的。