Henry Henry
  • JavaScript
  • TypeScript
  • Vue
  • ElementUI
  • React
  • HTML
  • CSS
  • 技术文档
  • GitHub 技巧
  • Nodejs
  • Chrome
  • VSCode
  • Other
  • Mac
  • Windows
  • Linux
  • Vim
  • VSCode
  • Chrome
  • iTerm
  • Mac
  • Obsidian
  • lazygit
  • Vim 技巧
  • 分类
  • 标签
  • 归档
  • 网站
  • 资源
  • Vue 资源
GitHub (opens new window)

Henry

小学生中的前端大佬
  • JavaScript
  • TypeScript
  • Vue
  • ElementUI
  • React
  • HTML
  • CSS
  • 技术文档
  • GitHub 技巧
  • Nodejs
  • Chrome
  • VSCode
  • Other
  • Mac
  • Windows
  • Linux
  • Vim
  • VSCode
  • Chrome
  • iTerm
  • Mac
  • Obsidian
  • lazygit
  • Vim 技巧
  • 分类
  • 标签
  • 归档
  • 网站
  • 资源
  • Vue 资源
GitHub (opens new window)
  • 技术文档

  • GitHub

  • Nodejs

    • 打造属于自己的项目脚手架工具
    • node 和 npm 常见问题
    • node 和 npm 简介
    • 手搓一个 TinyPng 压缩图片的脚手架
    • node 监听文件夹并处理
    • 相对路径转别名路径之 chokidar
    • 脚手架性能分析
      • 拷贝 benchmark.mjs
      • 安装依赖
      • 统一端口号和运行命令
      • 修改根文件和叶子文件
      • 启动
      • 问题
        • buildTime 异常
      • 原理分析
        • 项目启动时间
        • 根文件热更新时间
        • 叶子文件热更新时间
        • 构建耗时
        • 统计时间
      • vite 及其他脚手架性能分析
    • 自动化打包微前端多个项目
  • Chrome

  • VSCode

  • VSCode 更新文档

  • AIGC

  • Other

  • 技术
  • Nodejs
Henry
2024-02-23
目录
拷贝 benchmark.mjs
安装依赖
统一端口号和运行命令
修改根文件和叶子文件
启动
问题
buildTime 异常
原理分析
项目启动时间
根文件热更新时间
叶子文件热更新时间
构建耗时
统计时间
vite 及其他脚手架性能分析

脚手架性能分析

以 rsBuild 为例

rsBuild 官网有构建 1000 个 React 组件的性能分析

介绍 - Rsbuild (opens new window)

以上数据基于 Farm 团队搭建的 benchmark,更多信息请参考 performance-compare (opens new window)。

我们可以参照 benchmark 来生成自己项目的性能报告

# 拷贝 benchmark.mjs

首先将 benchmark.mjs (opens new window) 拷贝到项目根目录

# 安装依赖

benchmark.mjs 需要 puppeteer 和 tree-kill 这两个包,需要下载一下

注意 puppeteer 需要安装 ^19.7.4 的版本,最新的版本是 20+,运行有问题

pnpm i tree-kill puppeteer@19.7.4 -D
1

# 统一端口号和运行命令

在 benchmark.mjs 中搜索 buildTools,是一个数组

由于这个工具是测试很多脚手架的,而我们目前只需要测试 rsbuild,所以需要将里面非 rsbuild 的部分注释掉

然后看一下 BuildTool 的参数:

new BuildTool(
  'Rsbuild 0.2.0',
  8080,
  'start:rsbuild',
  /in (.+) (s|ms)/,
  'build:rsbuild',
  /in (.+) (s|ms)/,
  '@rsbuild/core/bin/rsbuild.js'
)
1
2
3
4
5
6
7
8
9

第一个参数是脚手架名称,第二个参数是运行端口号,第三个参数是运行命令,第四个参数是匹配运行命令启动后的正则,第五个参数是编译命令,第六个参数是匹配编译命令完成后的正则,第七个参数是脚手架的路径

然后修改 Rsbuild 第 2、3 和 5 参数,将端口号和运行命令,与 rsbuild.config.js 中的端口号保持一致,与 package.json 中的运行命令保持一致

new BuildTool(
  'Rsbuild 0.2.0',
  8081,
  'start',
  /in (.+) (s|ms)/,
  'build',
  /in (.+) (s|ms)/,
  '@rsbuild/core/bin/rsbuild.js'
),
1
2
3
4
5
6
7
8
9

以下是 rsbuild.config.js 的示例

server: {
  port: 8081,
},
1
2
3

以下是 package.json 的示例

"scripts": {
  "start": "rsbuild dev",
  "build": "rsbuild build",
},
1
2
3
4

注意 start 的时候不要使用 open 参数来打开浏览器

# 修改根文件和叶子文件

全局搜索 triangle.jsx,将 resolve 中的路径修改为项目启动后打开页面对应的根文件

然后搜索 triangle_1_1_2_1_2_2_1.jsx,将 resolve 中的路径修改为项目启动后打开页面中的叶子文件

为后续计算根文件和叶子文件热更新速度提供依据

# 启动

使用 node benchmark.mjs 来启动

等待片刻即可看到结果

(index) startup(serverStartTime + onLoadTime) rootHmr leafHmr buildTime
Rsbuild 0.2.0 '13319ms' '2591ms' '1685ms' '435ms'

# 问题

# buildTime 异常

在上方表格中可以看到 buildTime 为 435ms,半秒钟就编译完成了,这应该是不可能的,检查一下 build 代码,看看哪里出问题了

async build() {
  return new Promise(async (resolve) => {
    console.log(`Running build command: ${this.buildScript}`);
    let startTime = null;

    const child = spawn(`npm`, ['run', this.buildScript], {
      stdio: ['pipe'],
      shell: true,
    });

    child.stdout.on('data', (data) => {
      const startMatch = startConsoleRegex.exec(data.toString());
      if (startMatch) {
        startTime = startMatch[1];
      }

      const match = this.buildRegex.exec(data.toString());
      if (match) {
        if (!startTime) {
          throw new Error('Start time not found');
        }
        resolve(Date.now() - startTime);
      }
    });
    return new Promise((resolve, reject) => {
      child.on('exit', resolve);
      child.on('error', reject);
    });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

我们来解读一下上面的代码:

先是定义了一个 startTime 变量,用来存储编译开始的时间

然后定义了一个 child 变量,用来存储子进程的信息

然后我们用 child.stdout.on('data', (data) => {}) 监听了子进程的输出(就是命令行的输出)

然后定义了一个 startMatch 变量,用来存储子进程输出中的 startConsoleRegex 的匹配结果,startConsoleRegex = /Farm Start Time (\d+)/;

然后定义了一个 match 变量,用来存储子进程输出中的 this.buildRegex 的匹配结果,buildRegex = /in (.+) (s|ms)/

然后在匹配成功的时候我们就用 resolve(Date.now() - startTime) 来返回结果,这样就完成了 buildTime 的计算

可能就是正则匹配出错了,需要检查一下

我们输出一下 startMatch 和 match

🚀 ~ BuildTool ~ child.stdout.on ~ startMatch: [
  'Farm Start Time 1708585068291',
  '1708585068291',
  index: 0,
  input: 'Farm Start Time 1708585068291\n',
  groups: undefined
]
🚀 ~ BuildTool ~ child.stdout.on ~ match: [
  'in output.cssModules.localIdentName is currently not s',
  'output.cssModules.localIdentName is currently not',
  's',
  index: 26,
  input: "warn    Custom hashDigest in output.cssModules.localIdentName is currently not supported when using Rspack, the 'base64' will be ignored.\n",
  groups: undefined
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

startMatch 匹配的结果是正确的,match 匹配的结果应该是错误的

然后我们手动执行一下 build 命令,就可以看到子进程的输出结果了

> rsbuild build

Farm Start Time 1708583173367
  Rsbuild v0.2.18

postcss-px2rem: postcss.plugin was deprecated. Migration guide:
https://evilmartians.com/chronicles/postcss-8-plugin-migration
warn    Custom hashDigest in output.cssModules.localIdentName is currently not supported when using Rspack, the 'base64' will be ignored.
● Client ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) dones
ready   Client compiled in 8.9 s
info    Production file sizes:
1
2
3
4
5
6
7
8
9
10
11

对比上方的输出,buildRegex 应该匹配 in 8.9 s 才是对的

但由于上方 cssModules 的警告输出,导致正则匹配错误,还没有执行构建,就认为构建完成了

所以我们修改一下 buildRegex,让其匹配 in 8.9 s,就可以了

/Client compiled in (.+) (s|ms)/
1

这样再执行就没有问题了

(index) startup(serverStartTime + onLoadTime) rootHmr leafHmr buildTime
Rsbuild 0.2.0 '9103ms' '1972ms' '1017ms' '9366ms'

顺便把 startedRegex 也改一下吧

# 原理分析

该脚本共统计了 4 个指标,分别是 项目启动时间(脚手架启动和页面 loaded,startup)、根文件热更新时间(rootHmr)、叶子文件热更新时间(leafHmr)、构建耗时(buildTime)

# 项目启动时间

serverStartTime 为脚手架启动的时间,onLoadTime 为页面加载完成的时间

先看 serverStartTime 的计算逻辑:

  1. 先向脚手架入口文件注入一段输出当前时间的代码

    "console.log('Farm Start Time', Date.now());";

  2. 然后运行 start 命令

  3. 监听子进程的输出

  4. 在输出中匹配 Farm Start Time,获取其时间戳,即为脚手架开始启动时间

  5. 然后再匹配脚手架 Client compiled 时间,即为脚手架编译完成并打开页面时间

  6. 两者相减计算出脚手架编译启动时间

再看 onLoadTime 的计算逻辑:

这个就比较简单了:脚手架编译启动后,记录当前时间,然后使用 puppeteer 启动一个 browser,然后创建一个 page,监听 load 即可,loadTime - 记录的当前时间即为页面加载完成的时间

# 根文件热更新时间

  1. 先在根文件最后注入一段输出当前时间的代码,并记录当前时间

    console.log('root hmr', Date.now());

  2. page 监听 console 的输出

  3. 在输出中匹配 root hmr,获取其时间戳,即为根文件热更新完成时间

  4. 使用热更新完时间减去记录的当前时间即为根文件热更新时间

# 叶子文件热更新时间

与根文件热更新时间类似

# 构建耗时

与项目启动时间类似

# 统计时间

该脚本共计运行 3 次,取平均数作为最后的统计值

# vite 及其他脚手架性能分析

参考上方

编辑 (opens new window)
上次更新: 2/23/2024, 5:48:26 AM
相对路径转别名路径之 chokidar
自动化打包微前端多个项目

← 相对路径转别名路径之 chokidar 自动化打包微前端多个项目→

最近更新
01
搭配 Jenkins 实现自动化打包微前端多个项目
09-15
02
自动化打包微前端多个项目
09-15
03
el-upload 直传阿里 oss 并且显示自带进度条和视频回显封面图
06-05
更多文章>

Related Issues not found

Please contact @HenryTSZ to initialize the comment

Theme by Vdoing | Copyright © 2017-2025 HenryTSZ | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式