- webpack 优化
- webpack 输出分析
# 缩小文件搜索范围
遇到导入语句时
- 寻找导入对应的文件
- 根据文件后缀,选择对应 loader 去解析
尽量减少上述两件事的发生,加快构建
# 优化 loader 配置
使用 test
include
exclude
三个配置来命中 loader 要应用规则的文件
# 优化 resolve.modules
默认是一层一层往上查找 node_modules
的第三方模块,可以直接指明在当前根目录下的 node_modules
# 优化 resolve.mainFields
用于配置第三方文件的入口,target
为 web
或 webworker
时,为 ["browser", "module", "main"]
,其它情况为 ["module", "main"]
,为减少搜索步骤,可以指明入口文件描述字段
# 优化 resolve.alias
通过别名来把原路径映射为新的导入路径,减少耗时的递归操作,会影响 tree shaking,需要完整文件的库可以使用此方法如 React,Vue 等,工具类库不使用此方法如 lodash 等
# 优化 resolve.extensions
导入语句没带后缀时 webpack 会尝试加上后缀去询问文件,默认为 ["js", "json"]
列表越长,越正确的在后面,尝试次数越多
- 后缀尝试列表要尽可能的小
- 频率高的放最前面
- 写导入语句时尽量加上后缀
# 优化 module.noParse
可以让 webpack 忽略部分没采用模块化的文件的递归解析处理,提高构建性能,例如 JQuery, ChartJS,庞大又没有采用模块化规范,此类文件做解析没有意义
# DllPlugin
动态链接库,包含给其他模块调用和使用的数据
- 把网页依赖的基础模块抽离出来,打包到动态链接库中,可以包含多个模块
- 当需要导入的模块在动态链接库中时,该模块不能在再次打包,去向动态链接库获取
- 页面依赖的所有动态链接库需要被加载
加快构建的原因是复用的模块只用编译构建一次,之后会直接使用动态链接库的代码,通常是常用的第三方模块,模块不升级,动态链接库就不需要升级
webpack.DllPlugin
和 webpack.DllReferencePlugin
DllPlugin
中的 name
参数必须和 output.library
中保持一致,name
会影响输出的 manifest.json
中的 name
字段的值,DllReferencePlugin
会去读取 manifest.json
中的 name
字段,把值的内容作为从全局变量中获取动态链接库中内容的全局变量名
# HappyPack
文件数量变多后,webpack 构建速度会变慢。运行在 Node.js 上的 webpack 是单线程的,需要处理的事情只能一件一件的做
HappyPack 把任务分解成多个子进程并发执行,子进程处理完后再交给主进程。
由于 JS 是单线程模型,要发挥多个 CPU 威力,只能通过多进程
- 将所有文件的处理都交给
HappyPack/loader
,?id='babel'
告诉HappyPack
使用哪个HappyPack实例
来处理 Plugin
配置中新增HappyPack
实例告诉HappyPack/loader
如何处理文件,id
与上面对应,选项中的loaders
属性和之前一致
可以开启多个子进程,默认为 3 个,多个 HappyPack 可以共享一个进程池,避免资源占用过多
原理:
- webpack 构建中最耗时的是 loader 对文件对转换操作,因为文件数多只能一个一个处理,HappyPack 核心原理是把这部分任务分解到多个进程去并行处理,从而减少总构建时间
- 核心调度器的代码在主进程中,也就是运行 webpack 进程中,核心调度器会把任务分配给空闲的子进程,处理完毕后发送给核心调度器,之间的数据交换通过进程间通信 API 实现
- 核心调度器收到子进程处理完毕结果后会通知 webpack 文件处理完毕
# ParallelUglifyPlugin
压缩代码需要把代码解析成用 Object 表示的 AST 语法树,再去应用各种规则分析和处理 AST,导致计算量大,耗时多
ParallelUglifyPlugin
会开启多个子进程,把对多个文件的压缩分配给多个子进程,变成了并行执行,更快的对多个文件进行压缩
# 文件监听
原理:
定时获取该文件的最后编辑时间,每次都存下最后编辑时间,发现当前获取时间与保存的最后编辑时间不一致时,则认为发生了修改,
watchOptions.poll
用于控制定时检查周期认为发生变化后,不会立即告诉监听者,而是先缓存起来,收集一段时间的变化后再一次性告诉监听者,
watchOptions.aggregateTime
用于配置等待时间由于保存文件的路径和最后编辑时间需占用内存,定时检查会占用 CPU 和文件 I/O,所以最好减少需要监听的文件的数量和降低检查频率
可以忽略到 node_modules 的文件,不去监听它们
watchOptions.poll
越小越好,降低检查频率watchOptions.aggregateTime
越大越好,降低重新构建频率
但会感觉到监听模式的反应和灵敏度降低了
# 自动刷新
webpack 负责监听模块,webpack-dev-server 负责刷新浏览器
原理,有三种方法:
- 借助浏览器扩展去通过浏览器 API 刷新
- 往开发的网页中注入代理客户端,通过代理客户端去刷新
- 把要开发的网页装进 iframe 中,通过刷新 iframe 来达到效果
DevServer 支持后两种,第 2 种是默认方法
devServer.inline
控制是否注入代理客户端,默认为 true
表示会注入,每个 chunk 都会注入代理客户端代码,因为不知道网页依赖了哪几个 chunk,就都注入了,会导致构建变慢,可以关闭 inline
,则需要通过 localhost: 8000/webpack-dev-server
来访问项目,网页被装进了 iframe 中,也可以手动注入 <script src="http://localhost:8080/webpack-dev-server.js"></script>
代理客户端脚本
# 模块热替换
不刷新网页的情况下做到实时预览,原理是当一个模块变化时,只重新编译变化的模块,再用重新输出的新模块替换老模块
原理:
与自动刷新相似,需要往网页注入一个代理客户端用于连接 DevServer 和网页
当模块发生更新时,更新事件会一层一层往上传递,直到有某层接收了当前变化的模块,如果事件一直往上抛到最外层都没有文件接收它,就会直接刷新网页
要优化模块热替换,与自动刷新一样,监听更少的文件,忽略掉 node_modules
下的文件,模块热替换的运行依赖在每个 chunk 中,都包含代理客户端代码,不能手动关闭 inline
手动注入代理客户端。
# 区分环境
开发环境和生产环境差异包括:
- 代码压缩
- 提示开发者的提示日志
- 开发时所连接的后端数据接口地址和线上环境不同
当代码中使用了 process
时 webpack 就自动打包进 process 代码以支持 Node.js 运行环境,这个注入的模块模仿 Node.js 中的 process
new DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
},
})
2
3
4
5
在定义环境变量时的值使用 JSON.stringify
包裹字符串的原因是环境变量的值需要一个双引号包裹的字符串
通过 Shell 去定义的环境变量 NODE_ENV=production
webpack 是不认识的,对 webpack 需要处理的代码中的环境区分语句没有作用
同时也可以区分第三方库中的环境区分代码,可以去除一些日志警告
NODE_ENV
与 production
为社区约定
# 压缩代码
浏览器从服务器访问网页时获取的 JS、CSS 都是文本,文件越大加载时间越长、为了提升网页加载速度和减少网络传输流量,可以对资源进行压缩,除了通过 Gzip 算法压缩外,还可以对文本本身进行压缩
# 压缩 JS
- 提升加载速度
- 混淆源码,使可读性变差,不利于他人分析代码
策略:
- 不生成 sourceMap,开启时耗时大大增加
- 输出可读性较差代码
- 移除注释
- 移除删除代码时的警告输出语句
- 剔除 console 语句,兼容 IE
- 内嵌定义了只使用了一次的变量
- 提取多次使用没有定义为变量的静态值
# 压缩 CSS
可以提升加载速度和混淆源码,可使用 cssnano
# CDN 加速
可以通过压缩来减小网络传输大小,最影响用户体验的是首屏加载速度,导致这个问题是因为网络传输耗时大,CDN 的作用就是加快网络传输
把资源遍布世界各地,在访问时按就近加载原则从离用户最近的服务器获取资源
做法:
- 针对 HTML 文件,不把 HTML 文件放到 CDN 上,放到自己服务器,同时关闭自己服务器的缓存。自己的服务器只提供 HTML 和数据接口
- 静态资源 JS、CSS、图片等文件,开启 CDN 和缓存,给每个文件带上唯一的 hash 值,文件名随着文件的变化而变化,只要文件变化 url 就会变化,即使有缓存,也会重新获取
浏览器针对同一时刻同一域名的并行请求有限制,如果资源过多会阻塞加载,可以把不同资源放到不同的 CDN 服务器上去,过多域名会增加域名解析时间,根据需要权衡得失
output.publicPath
中设置 JS 的地址css-loader.publicPath
中设置被 CSS 导入的资源的的地址。WebPlugin.stylePublicPath
中设置 CSS 文件的地址
# Tree Shaking
剔除用不上的死代码,依赖静态的 ES6 模块化语法
# 提取公共代码
大型网站有多个页面,每个页面都是单页应用,页面间会有很多同样的代码
- 相同的资源会被重复加载,浪费流量和服务器成本
- 每个页面加载资源大,导致首屏加载慢
好处:
- 减少网络传输流量,降低成本
- 虽然第一次加载时得不到优化,但访问其它页面时速度将大大提升
# 按需加载
- 将网站的功能划分成一个个小功能,按照功能的相关程度分类
- 将每一类合为一个 chunk,按需加载对应的 chunk
- 对于用户首次加载时看到的功能不做按需加载,放到执行入口的 chunk 中,降低用户能感知的网页加载时间
- 对于个别大量代码的功能点,可再对其进行按需加载
依赖 babel-plugin-syntax-dynamic-import
# Prepack
代码的压缩和分块都是在网络加载层面上的,除此之外可以提升运行时的效率
原理:
- 通过 Babel 把 JS 解析成 AST
- Prepack 实现了一个 JS 解析器,用于执行源码,借助这个解析器掌握源码具体如何执行,并返回执行后的输出
# Scope Hoisting
可以让打包出来的体积更小,运行的更快
分析出各个模块的依赖关系,尽可能把打散的模块合并到一个函数中去,由于需要分析依赖关系,所以需要采用 ES6 模块化语句
# 输出分析
需要对输出结果进行分析,以决定下一步优化方向
webpack --profile --json > stats.json
记录耗时以 JSON 格式输出到 stats.json
webpack-bundle-analyzer