Leecason

vuePress-theme-reco Leecason    2018 - 2020
Leecason Leecason

Choose mode

  • dark
  • auto
  • light
主页
分类
  • CSS
  • FrontEnd
  • GraphQL
  • JavaScript
  • TypeScript
  • Vue
  • Webpack
  • 其它
  • 数据结构与算法
  • 浏览器相关
标签
时间线
GitHub
author-avatar

Leecason

80

Article

61

Tag

主页
分类
  • CSS
  • FrontEnd
  • GraphQL
  • JavaScript
  • TypeScript
  • Vue
  • Webpack
  • 其它
  • 数据结构与算法
  • 浏览器相关
标签
时间线
GitHub

《深入浅出 webpack》 读书笔记 三

vuePress-theme-reco Leecason    2018 - 2020

《深入浅出 webpack》 读书笔记 三

Leecason 2019-04-12 webpack性能优化
  1. webpack 优化
  2. 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 负责刷新浏览器

原理,有三种方法:

  1. 借助浏览器扩展去通过浏览器 API 刷新
  2. 往开发的网页中注入代理客户端,通过代理客户端去刷新
  3. 把要开发的网页装进 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'),
  },
})
1
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