Webpack: 7 款常用的性能分析工具

概述

Webpack 最大的优势在于它的功能非常强大、全面,加之繁荣活跃的组件生态,已经足够应对几乎所有 Web 构建需求,包括:SPA、MPA、SSR、桌面应用、Node 程序、WebAssemsbly、PWA、微前端等等,所以即使在近几年工程化领域异军突起、百花齐放的背景下,Webpack 也依然能保持老大哥的位置。

但软件世界没有银弹!Webpack 在大型项目中通常性能表现不佳,这一方面是因为 JavaScript 语言的单线程架构决定了 Webpack 的运算效率就不可能很高;另一方面则是因为在大型项目中,Webpack 通常需要借助许多组件(插件、Loader)完成大量的文件读写、代码编译操作。

幸运的是,站在开发者视角,我们有许多行之有效的性能优化方法,包括缓存、并发、优化文件处理步骤等,但在着手优化之前,有必要先简单了解一下 Webpack 打包的核心流程;了解哪些步骤比较耗时,可能会造成性能卡点;以及,如何借助一些可视化工具分析 Webpack 的编译性能。

核心流程

Webpack 最最核心的功能,一是使用适当 Loader 将任意类型文件转译为 JavaScript 代码,例如将 CSS 代码转译为 JS 字符串,将多媒体文件转译为 Base64 代码等;二是将这些经过 Loader 处理的文件资源合并、打包成向下兼容的产物文件。

为了实现这些功能,Webpack 底层的工作流程大致可以总结为这么几个阶段:

  1. 初始化阶段:
    • 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数;
    • 创建编译器对象:用上一步得到的参数创建 Compiler 对象;
    • 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等;
    • 开始编译:执行 compiler 对象的 run 方法,创建 Compilation 对象;
    • 确定入口:根据配置中的 entry 找出所有的入口文件,调用 compilation.addEntry 将入口文件转换为 dependence 对象。
  2. 构建阶段:
    • 编译模块(make):从 entry 文件开始,调用 loader 将模块转译为标准 JS 内容,调用 JS 解析器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 处理这些依赖模块,直到所有入口依赖的文件都经过了本步骤的处理;
    • 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的依赖关系图
  3. 封装阶段:
    • 合并(***seal***):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
    • 优化(optimization):对上述 Chunk 施加一系列优化操作,包括:tree-shaking、terser、scope-hoisting、压缩、Code Split 等;
    • 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在这个过程中有不少可能造成性能问题的地方:

  • 构建阶段:
    • 首先需要将文件的相对引用路径转换为绝对路径,这个过程可能涉及多次 IO 操作,执行效率取决于 文件层次深度
    • 找到具体文件后,需要读入文件内容并调用 loader-runner 遍历 Loader 数组完成内容转译,这个过程需要执行较密集的 CPU 操作,执行效率取决于 Loader 的数量与复杂度
    • 需要将模块内容解析为 AST 结构,并遍历 AST 找出模块的依赖资源,这个过程同样需要较密集的 CPU 操作,执行效率取决于 代码复杂度
    • 递归处理依赖资源,执行效率取决于 模块数量
  • 封装阶段:
    • 根据 splitChunks 配置、entry 配置、动态模块引用语句等,确定模块与 Chunk 的映射关系,其中 splitChunks 相关的分包算法非常复杂,涉及大量 CPU 计算;
    • 根据 optimization 配置执行一系列产物优化操作,特别是 Terser 插件需要执行大量 AST 相关的运算,执行效率取决于 产物代码量
  • 等等。

可以看出,Webpack 需要执行非常密集的 IO 与 CPU 操作,计算成本高,再加上 Webpack 以及大多数组件都使用 JavaScript 编写,无法充分利用多核 CPU 能力,所以在中大型项性能通常表现较差。

不过,这些性能问题是可以被优化的!

性能分析

有许多被反复实践、行之有效的构建性能优化手段,包括并行编译、缓存、缩小资源搜索范围等等,但在介绍这些具体的优化方法之前,有必要先聊聊:如何收集、分析 Webpack 打包过程的性能数据。

收集数据的方法很简单 —— Webpack 内置了 stats 接口,专门用于统计模块构建耗时、模块依赖关系等信息,推荐用法:

  1. 添加 profile = true 配置:

    // webpack.config.js
    module.exports = {
      // ...
      profile: true
    }
    
  2. 运行编译命令,并添加 --json 参数,参数值为最终生成的统计文件名,如:

    npx webpack --json=stats.json
    

上述命令执行完毕后,会在文件夹下生成 stats.json 文件,内容大致如下:

{
  "hash": "2c0b66247db00e494ab8",
  "version": "5.36.1",
  "time": 81,
  "builtAt": 1620401092814,
  "publicPath": "",
  "outputPath": "/Users/tecvan/learn-webpack/hello-world/dist",
  "assetsByChunkName": { "main": ["index.js"] },
  "assets": [
    // ...
  ],
  "chunks": [
    // ...
  ],
  "modules": [
    // ...
  ],
  "entrypoints": {
    // ...
  },
  "namedChunkGroups": {
    // ...
  },
  "errors": [
    // ...
  ],
  "errorsCount": 0,
  "warnings": [
    // ...
  ],
  "warningsCount": 0,
  "children": [
    // ...
  ]
}

stats 对象收集了 Webpack 运行过程中许多值得关注的信息,包括:

  • modules:本次打包处理的所有模块列表,内容包含模块的大小、所属 chunk、构建原因、依赖模块等,特别是 modules.profile 属性,包含了构建该模块时,解析路径、编译、打包、子模块打包等各个环节所花费的时间,非常有用;
  • chunks:构建过程生成的 chunks 列表,数组内容包含 chunk 名称、大小、包含了哪些模块等;
  • assets:编译后最终输出的产物列表、文件路径、文件大小等;
  • entrypoints:entry 列表,包括动态引入所生产的 entry 项也会包含在这里面;
  • children:子 Compiler 对象的性能数据,例如 extract-css-chunk-plugin 插件内部就会调用 compilation.createChildCompiler 函数创建出子 Compiler 来做 CSS 抽取的工作。

篇幅有限,这里不展开介绍每个节点的具体内容,有需要的同学可以查阅 Webpack 官网的 stats 介绍文档

可以从这些数据中分析出模块之间的依赖关系、体积占比、编译构建耗时等,Webpack 社区还提供了许多优秀的分析工具,能够将这些数据转换各种风格的可视化图表,帮助我们更高效地找出性能卡点,包括:

  • Webpack Analysis :Webpack 官方提供的,功能比较全面的 stats 可视化工具;
  • Statoscope:主要侧重于模块与模块、模块与 chunk、chunk 与 chunk 等,实体之间的关系分析;
  • Webpack Visualizer:一个简单的模块体积分析工具,真的很简单!
  • Webpack Bundle Analyzer:应该是使用率最高的性能分析工具之一,主要实现以 Tree Map 方式展示各个模块的体积占比;
  • Webpack Dashboard:能够在编译过程实时展示编译进度、模块分布、产物信息等;
  • Unused Webpack Plugin:能够根据 stats 数据反向查找项目中未被使用的文件。

Webpack Analysis

Webpack Analysis 是 webpack 官方提供的可视化分析工具,相比于其它工具,它提供的视图更全,功能更强大,能够通过创建依赖关系图对你的包进行更彻底的检查。

使用上只需要将上一节 webpack --json=stats.json 命令生成的 stats.json 文件导入页面,就可以看到一些关键统计信息:

请添加图片描述

点击页面中的 modules/chunks/assets 按钮,页面会渲染出对应实体的依赖关系图,例如:

请添加图片描述
modules/chunks/assets 外,右上方菜单栏 Hints 还可以查看构建过程各阶段、各模块的处理耗时,可以用于对比分析各个阶段的性能情况:
请添加图片描述

  • 提示:不过,实测发现 Hints 还不支持 webpack 5 版本的 stats 数据,等待官方更新吧。

Webpack Analysis 提供了非常齐全的分析视角,信息几乎没有失真,但上手难度稍高,信息噪音比较多,所以社区还提供了一个简化版 webpack-deps-tree,功能相似但用法更简单、信息更简洁,大家可以根据实际需要交叉使用。

Statoscope

Statoscope 也是一个非常强大的可视化分析工具,主要提供如下功能:

  • 完整的依赖关系视图,涵盖 modules/chunks/assets/entrypoints/packages 维度;
  • entrypoints/chunks/packages/module 体积分析;
  • 重复包检测;
  • 多份 stats 数据对比;
  • 等等。

有两种用法,一是将 stats 数据导入到 Statoscope 在线页面;二是使用 @statoscope/webpack-plugin 插件,用法:

  1. 安装依赖:

    yarn add -D @statoscope/webpack-plugin
    
  2. 注册插件:

    const StatoscopeWebpackPlugin = require('@statoscope/webpack-plugin').default;
    
    module.exports = {
      ...
      plugins: [new StatoscopeWebpackPlugin()],
    };
    

之后,运行 npx webpack 命令,编译结束后默认打开分析视图:

请添加图片描述

可以看到,Statoscope 提供了多种维度的统计信息,包括:Chunk 数量、模块总数、重复模块树、编译耗时、Initial Chunk 体积等;更重要的是,Statoscope 还展示了模块与模块、Chunk、Entry 等维度的依赖关系:

在这里插入图片描述

我们可以据此推断出模块体积、为何需要打包该模块、有哪些模块被重复引用等信息。

Webpack Bundle Analyzer

Webpack-bundle-analyzer 是一个非常有名的性能分析插件,只需要一些简单配置就可以在 Webpack 构建结束后生成 Tree Map 形态的模块分布统计图,用户可以通过对比 Tree Map 内容推断各模块的体积占比,是否包含重复模块、不必要的模块等,用法:

  1. 安装模块依赖:

    yarn add -D webpack-bundle-analyzer
    
  2. 添加插件:

    const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
      .BundleAnalyzerPlugin;
    
    module.exports = {
      ...
      plugins: [new BundleAnalyzerPlugin()],
    };
    

编译结束后,默认自动打开本地视图页面:

请添加图片描述

也可以直接用 Webpack-bundle-analyzer 命令直接打开 stats 文件:

npx webpack-bundle-analyzer ./stats.json

基于 Webpack Bundle Analyzer 提供的视图,我们可以分析出:

  • Bundle 包所包含的模块内容 —— 从而推断出产物中是否包含预期之外的模块;
  • 确定模块体积大小与占比 —— 从而确定是否存在优化空间;
  • 了解 Bundle 产物体积,以及经过压缩后的体积。

提示: webpack-bundle-size-analyzer、source-map-explorer 等工具也实现了类似功能,但分别适用于不同场景,建议你也了解一下相关用法,择优选用。

Webpack Visualizer

Webpack Visualizer 是一个在线分析工具,可用于检测、可视化 Webpack 产物的构成模块。有两种用法,一是将 stats.json 文件上传到在线 页面;二是使用 webpack-visualizer-plugin 生成统计页面,用法:

  1. 安装依赖:

    yarn add —D webpack-visualizer-plugin
    
  2. 添加插件:

    // webpack.config.js
    const VisualizerPlugin = require('webpack-visualizer-plugin');
    
    module.exports = {
      // ...
      plugins: [
        new Visualizer({
          filename: './stats.html'
        })
      ],
    }
    //...
    

两种方式最终都可以生成如下视图:

  • 提示:很遗憾,实测发现 webpack-visualizer-plugin 插件年久失修,只兼容 webpack 1.x ,所以现在几乎没有使用价值了。

此外,在线工具 Webpack Chart 也提供了类似的功能,功能重合度很高,这里就不展开讲了。

Webpack Dashboard

webpack-dashboard 是一个命令行可视化工具,能够在编译过程中实时展示编译进度、模块分布、产物信息等,用法:

  1. 安装依赖:

    yarn add -D webpack-dashboard
    
  2. 注册插件:

    const DashboardPlugin = require("webpack-dashboard/plugin");
    
    module.exports = {
      // ...
      plugins: [new DashboardPlugin()],
    };
    
  3. 注意了,需要用 webpack-dashboard 命令启动编译:

    # 打包
    npx webpack-dashboard -- webpack
    # Dev Server
    npx webpack-dashboard -- webpack-dev-server
    # 运行 Node 程序
    npx webpack-dashboard -- node index.js
    

之后,就可以在命令行看到一个漂亮的可视化界面:

请添加图片描述

Speed Measure Plugin

SpeedMeasureWebpackPlugin 插件能够统计出各个 Loader、插件的处理耗时,开发者可以根据这些数据分析出哪些类型的文件处理更耗时间,用法:

  1. 安装依赖:

    yarn add -D speed-measure-webpack-plugin
    
  2. 修改配置:

    const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
    
    const smp = new SpeedMeasurePlugin();
    
    const config = {
      entry: "./src/index.ts",
      // ...
    };
    
    // 注意,这里是用 `smp.wrap` 函数包裹住 Webpack 配置
    module.exports = smp.wrap(config);
    

之后运行打包命令如 npx webpack 即可,运行效果:

UnusedWebpackPlugin

最后分享 UnusedWebpackPlugin 插件,它能够根据 webpack 统计信息,反向查找出工程项目里哪些文件没有被用到,我日常在各种项目重构工作中都会用到,非常实用。用法也比较简单:

const UnusedWebpackPlugin = require("unused-webpack-plugin");

module.exports = {
  // ...
  plugins: [
    new UnusedWebpackPlugin({
      directories: [path.join(__dirname, "src")],
      root: path.join(__dirname, "../"),
    }),
  ],
};

示例中,directories 用于指定需要分析的文件目录;root 用于指定根路径,与输出有关。配置插件后,webpack 每次运行完毕都会输出 directories 目录中,有哪些文件没有被用到:

请添加图片描述

总结

首先需要理解 Webpack 编译的基本过程,以及过程中各个步骤的耗时,理解哪些节点可能会消耗更多时间等,在此基础上我们才能更精确、有的放矢地排查出项目中的性能问题。

理解基本原理后,可以借助一系列可视化工具分析构建、产物性能,可以沿着上面的介绍,逐一试用、学习这些分析工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/758030.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络io与select,poll,epoll

前言 网络 IO,会涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,另一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段: 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或…

产品经理-对产品经理的认识(1)

今天跟大家聊一下产品经理这个岗位的,产品经理是互联网岗位当中比较火的一个岗位,也是最接近CEO的岗位 产品经理岗位,技术门槛低,薪水和前景都很不错,又处于团队的核心位置 产品经理岗位没有完全相关的专业设置和清晰的学习路径,绝…

HarmonyOS Next开发学习手册——选项卡 (Tabs)

当页面信息较多时,为了让用户能够聚焦于当前显示的内容,需要对页面内容进行分类,提高页面空间利用率。 Tabs 组件可以在一个页面内快速实现视图内容的切换,一方面提升查找信息的效率,另一方面精简用户单次获取到的信息…

第11章 规划过程组(11.5创建WBS)

第11章 规划过程组(一)11.5创建WBS,在第三版教材第380~383页; 文字图片音频方式 视频22 第一个知识点:主要输入 1、项目管理计划 范围管理计划 定义了如何根据项目范围说明书创建WBS2、项目文件 项目范围说明…

实验三 时序逻辑电路实验

仿真 链接:https://pan.baidu.com/s/1z9KFQANyNF5PvUPPYFQ9Ow 提取码:e3md 一、实验目的 1、通过实验,理解触发的概念,理解JK、D等常见触发器的功能; 2、通过实验,加深集成计数器功能的理解,掌…

Docker部署Dillinger个人文本编辑器

Docker部署Dillinger个人文本编辑器 一、Dillinger介绍1.1 Dillinger简介1.2 Dillinger使用场景 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、拉取Dillinger镜像五、部署Dill…

创建一个vue3+vite+ts项目

目录 创建项目 ​编辑 下载jsx 插件 在根目录在新建.env vue.config.js tsconfig.json tsconfig.node.json 下载ui组件库和路由(组件库根据自己的项目需要选择) 在根目录下新建views/index.tsx 在根目录下新建router/index.ts 修改App.vue 创建…

机器学习笔记 人脸识别技术全面回顾和小结(2)

一、现实条件 随着人脸识别研究的深入,研究者开始关注现实条件下的人脸识别问题,主要包括以下几个方面的研究。首先,我们分析和研究了影响人脸识别的因素。第二,新特征表示的使用研究。第三,使用新数据源的研究。如表1…

TDD测试驱动开发

为什么需要TDD? 传统开发方式,带来大量的低质量代码,而代码质量带来的问题: 1.在缺陷的泥潭中挣扎 开发长时间投入在缺陷的修复中,修复完依赖测试做长时间的回归测试 2.维护困难,开发缓慢 比如重复代码&am…

数据库系统概论(第5版教材)

第一章 绪论 1、数据(Data)是描述事物的符号记录; 2、数据库系统的构成:数据库 、数据库管理系统(及其开发工具) 、应用程序和数据库管理员; 3、数据库是长期存储在计算机内、有组织、可共享的大量数据的集合&…

【List集合排序】

List集合排序Demo import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor;import java.util.*;/*** list order demo*/ public class ListOrderDemo {public static void main(String[] args) {List<String> lis…

Nginx和CDN运用

一.Web缓存代理 1.工作机制 代替客户机向网站请求数据&#xff0c;从而可以隐藏用户的真实IP地址。将获得的网页数据&#xff08;静态Web元素&#xff09;保存到缓存中并发送给客户机&#xff0c;以便下次请求相同的数据时快速响应。 2.代理服务器的概念 代理服务器是一个位…

操作系统期末复习真题四

一、前言&#x1f680;&#x1f680;&#x1f680; 小郑在刷题的过程中帮大家整理了一些常见的考试题目&#xff0c;以及易于遗忘的知识点&#xff0c;希望对大家有所帮助。 二、正文☀️☀️☀️ 1.OS的不确定性是指(ABC)。 A.程序的运行次序不确定 B.程序多次运行的时间不…

MySQL之如何定位慢查询

1、如何定位慢查询 1.1、使用开源工具 调试工具&#xff1a;Arthas 运维工具&#xff1a;Promethuss、Skywalking 1.2、MySQL自带慢日志 慢查询日志记录了所有执行时间超过指定参数&#xff08;long_query_time&#xff0c;单位&#xff1a;秒&#xff0c;默认10秒&#x…

ONLYOFFICE 桌面编辑器 8.1使用体验分享

目录 编辑器市场现状与用户选择 ONLYOFFICE桌面编辑器概览和功能 ONLYOFFICE桌面编辑器概览 功能丰富的PDF编辑器 演示文稿编辑器的创新 文档编辑的灵活性 电子表格的高级功能 语言和本地化 用户界面和体验 媒体播放 云服务和本地处理 跨平台支持 总结 在线亲身体…

【后端面试题】【中间件】【NoSQL】ElasticSearch 节点角色、写入数据过程、Translog和索引与分片

中间件的常考方向&#xff1a; 中间件如何做到高可用和高性能的&#xff1f; 你在实践中怎么做的高可用和高性能的&#xff1f; Elasticsearch节点角色 Elasticsearch的节点可以分为很多种角色&#xff0c;并且一个节点可以扮演多种角色&#xff0c;下面列举几种主要的&…

SpringBoot使用Spark的DataFrame API

什么是Spark&#xff1f; Apache Spark是一个开源的分布式计算系统&#xff0c;它提供了一个快速和通用的集群计算平台。Spark 能够处理大规模数据&#xff0c;支持多种编程语言&#xff0c;如Scala、Java和Python&#xff0c;并且具有多种高级功能&#xff0c;包括SQL查询、机…

论文浅尝 | 通过基于动态文档知识图谱增强的大语言模型故事理解

笔记整理&#xff1a;许方舟&#xff0c;天津大学硕士&#xff0c;研究方向为知识图谱 链接&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/21286 1. 动机 基于大型 Transformer 的语言模型在需要叙事理解的各种任务上取得了令人难以置信的成功&#xff0c;包括…

填报志愿选专业是兴趣重要还是前景重要?

进行专业评估&#xff0c;找到一个适合自己的专业是一件非常困难的事情。在进行专业选择时&#xff0c;身上理想化色彩非常严重的人&#xff0c;会全然不顾及他人的劝阻&#xff0c;义无反顾的以兴趣为主&#xff0c;选择自己热爱的专业。一些较多考虑他人建议&#xff0c;能听…

Golang | Leetcode Golang题解之第206题反转链表

题目&#xff1a; 题解&#xff1a; func reverseList(head *ListNode) *ListNode {if head nil || head.Next nil {return head}newHead : reverseList(head.Next)head.Next.Next headhead.Next nilreturn newHead }