Puppeteer @puppeteer/browsers 的 getVersionComparator:按浏览器获取可排序的版本比较器
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
本篇技术指南围绕 Puppeteer 仓库中@puppeteer/browsers包对外暴露的公共函数 getVersionComparator 展开:先厘清它的签名、参数与返回值契约,再逐层深入其分发实现与 Chrome、Chromium、Firefox、ChromeDriver、chrome-headless-shell 各自不同的版本比较算法,并结合缓存模块与 CLI 的实际调用场景给出可直接运行的排序示例。读完本文,你将理解为什么同一套工具需要对不同浏览器使用不同的版本排序规则,以及如何在本地正确实现浏览器 buildId 列表的排序与"最新版本"选取。
函数概览:签名与返回类型
getVersionComparator是一个公开(@public)的顶层函数,声明于packages/browsers的入口导出中(见 main.ts)。它的完整 TypeScript 签名如下(原文见 browsers.getversioncomparator.md):
export declare function getVersionComparator( browser: Browser, ): (a: string, b: string) => number;| 项目 | 说明 |
|---|---|
参数browser | 类型为Browser(详见 browsers.browser.md),表示目标浏览器 |
| 返回值 | 一个(a: string, b: string) => number类型的比较器函数 |
| 作用 | 返回针对给定浏览器的版本比较器,可用于对浏览器版本进行排序 |
参数browser对应源码中的Browser枚举,定义在 types.ts,当前支持五个取值:
Browser.CHROME = 'chrome'Browser.CHROMEHEADLESSSHELL = 'chrome-headless-shell'Browser.CHROMIUM = 'chromium'Browser.FIREFOX = 'firefox'Browser.CHROMEDRIVER = 'chromedriver'
也就是说,这个 API 同时覆盖了@puppeteer/browsers支持安装与管理的全部浏览器/驱动形态。
返回值契约:可直接交给 sort 使用的比较器
返回的比较器是一个二元函数(a: string, b: string) => number,遵循 JavaScript 中Array.prototype.sort的比较器约定:
- 若返回值小于 0,表示
a排在b前面(a < b); - 若返回值等于 0,表示两个版本相等;
- 若返回值大于 0,表示
a排在b后面(a > b)。
因此它可以直接作为sort的回调传入,实现对同一种浏览器 buildId 的升序排列。由于不同浏览器厂商使用完全不同的版本号方案(Chrome 用120.0.6099.109式的点分数字,Chromium 用纯整数快照号,Firefox 的构建标识中还可能包含字母通道后缀),比较逻辑必须"因浏览器而异",这正是本函数存在的意义——调用方无需自己解析版本号格式,只需把Browser枚举传入,即可拿到语义正确的比较器。
分发机制:一张按浏览器索引的比较器映射表
从源码结构看,getVersionComparator本身只是一个"查表取函数"的薄封装。在 browser-data.ts 中维护了一张按Browser枚举索引的版本比较器映射表:
export const versionComparators = { [Browser.CHROMEDRIVER]: chromedriver.compareVersions, [Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.compareVersions, [Browser.CHROME]: chrome.compareVersions, [Browser.CHROMIUM]: chromium.compareVersions, [Browser.FIREFOX]: firefox.compareVersions, };而函数本体则直接从这个映射中取值返回(browser-data.ts):
export function getVersionComparator( browser: Browser, ): (a: string, b: string) => number { return versionComparators[browser]; }值得强调的是:CHROMEDRIVER与CHROMEHEADLESSSHELL并没有自己独立的比较器实现,而是分别从各自模块中再导出(re-export)了 Chrome 的实现——见 chromedriver.ts 与 chrome-headless-shell.ts。这背后的事实是:这三个目标的 buildId 都遵循 Chrome 版本号体系,复用同一套比较算法是合理的。
各浏览器比较算法的实现差异
Chrome / ChromeDriver / chrome-headless-shell:点分语义版本号
Chrome 的比较器实现于 chrome.ts,逻辑可以拆为三步:
- 去除首尾空白:对
a、b分别调用trim(); - 格式校验:用正则
/^\d+(?:\.\d+){0,3}$/校验,即必须是 1~4 段、由点号分隔的纯数字版本号,例如120.0.6099.109。若输入不合法,会直接抛出形如Version ${a} is not a valid Chrome version的Error; - 逐段数值比较:按
.切分后映射为数字,从第 0 段到第 3 段依次比较,某段缺失时按0处理(代码中const aPart = aParts[i] ?? 0;),一旦分出大小立即返回1或-1,全部相等则返回0。
换句话说,1.2.3与1.2.3.0会被判定为相等;1.2.10大于1.2.9,这是逐段数值比较而非字符串比较的必然结果。ChromeDriver 与 chrome-headless-shell 复用同一算法,因此它们也要求输入为合法 Chrome 版本号。
Chromium:纯整数快照号直接做差
Chromium 的安装包来自持续构建的快照,buildId 即LAST_CHANGE给出的版本号,本质是纯整数。因此它的比较器最为简单(chromium.ts):
export function compareVersions(a: string, b: string): number { return Number(a) - Number(b); }即把字符串转成数字后直接做差,结果天然满足排序比较器约定。
Firefox:十六进制位换算的近似比较
Firefox 的比较器在 firefox.ts 中实现,源码注释坦言这是"not a very reliable check"(不太可靠的检查):
export function compareVersions(a: string, b: string): number { // TODO: this is a not very reliable check. return parseInt(a.replace('.', ''), 16) - parseInt(b.replace('.', ''), 16); }其思路是把版本号字符串中的第一个.去掉,再按十六进制解析成数值做差。之所以用十六进制,与 Firefox 构建标识中可能包含a1、b这类字母形式通道后缀有关——直接按十进制parseInt会因字母而中断解析。需要说明的是:由于String.prototype.replace仅移除首个.,该算法对多段点分版本的处理并不完整,这正是它在源码中被标注为 TODO、可靠性有限的原因。因此在处理 Firefox 版本时,请把它视为一种工程近似而非严格语义比对。
仓库内的真实调用场景:缓存层解析 latest 别名
除了对外供用户排序 buildId,getVersionComparator本身就在@puppeteer/browsers内部被使用。最典型的一处在 Cache.ts 的resolveAlias方法中——当传入的别名是'latest'时,缓存需要从元数据里存储的全部已安装版本中挑出"最新"的那一个:
resolveAlias(browser: Browser, alias: string): string | undefined { const metadata = this.readMetadata(browser); if (alias === 'latest') { return Object.values(metadata.aliases || {}) .sort(getVersionComparator(browser)) .at(-1); } return metadata.aliases[alias]; }这里先用getVersionComparator(browser)对所有候选版本做升序排序,再通过.at(-1)取最后一个元素作为最新版本。注意它的两个隐含约定:
- 返回的比较器升序排列后,"最新的"在数组末尾,所以取
.at(-1); - 该逻辑限定在同一种浏览器的版本之间比较,不会跨浏览器混排。
实战:在本地项目中对已安装浏览器版本排序
下面给出一个完整可运行的示例。它展示了如何借助getVersionComparator对模拟的 Chrome buildId 数组排序,以及如何配合缓存接口解析latest别名。
import {getVersionComparator, Browser} from '@puppeteer/browsers'; // 1. 按浏览器获取比较器,并直接作为 sort 回调 const compareChrome = getVersionComparator(Browser.CHROME); const builds = [ '121.0.6167.85', '119.0.6045.159', '120.0.6099.109', '119.0.6045.105', ]; const sorted = builds.sort(compareChrome); console.log(sorted); // 升序结果: // ['119.0.6045.105', '119.0.6045.159', '120.0.6099.109', '121.0.6167.85'] // 取升序结果最后一个元素即"最新"版本 const latest = sorted.at(-1); console.log(latest); // '121.0.6167.85' // 2. Chromium 的比较器适用于纯数字快照 buildId const compareChromium = getVersionComparator(Browser.CHROMIUM); console.log(['112233', '120001', '118500'].sort(compareChromium)); // ['112233', '118500', '120001']若传入不满足对应格式的版本号,行为会随浏览器而异:例如把'120.0'传入 Chrome 比较器没有问题(缺失段按 0 补齐),但把'stable'、'beta'这类通道名传入会抛出Error;而 Chromium 比较器对纯数字之外的内容,其行为则取决于Number()的转换结果,实战时应保证输入为真实的 buildId。
对于实际项目,更常见的需求是结合getInstalledBrowsers(见 getinstalledbrowsers 文档)拿到本机已安装的浏览器记录,再按buildId排序挑选可用版本:
import {getInstalledBrowsers, Browser, getVersionComparator} from '@puppeteer/browsers'; const installed = await getInstalledBrowsers({cacheDir: '/path/to/.cache/puppeteer'}); const chromeList = installed.filter(entry => entry.browser === Browser.CHROME); chromeList.sort((x, y) => getVersionComparator(Browser.CHROME)(x.buildId, y.buildId)); console.log(chromeList.at(-1)?.buildId); // 最新已安装的 Chrome 版本使用注意事项
- 同一浏览器内比较:比较器语义与具体浏览器的 buildId 格式强绑定,切勿跨浏览器混用(例如拿 Chrome 比较器去排 Firefox 版本)。
- 输入需符合格式:Chrome 系的比较器会先校验格式,非法版本会抛错;而 Chromium、Firefox 的实现不做严格校验,依赖调用方传入合理 buildId。
- 升序结果取尾部为最新:无论是自己
sort还是参照Cache.resolveAlias,返回的比较器都是升序语义,"最新"一律是排序后数组的最后一个元素。 - Firefox 比较器是近似实现:源码中该算法带 TODO 注释、明确标注可靠性有限,若你的业务对 Firefox 版本顺序有强精确性要求,需要自行评估。
结语与进一步阅读
getVersionComparator虽只有两行核心逻辑,背后却浓缩了@puppeteer/browsers对多浏览器版本体系的抽象设计:通过一张映射表把"版本号格式差异"隔离在browser-data各模块内部,对外统一暴露"输入Browser、输出sort回调"的简洁接口,并被缓存层的latest别名解析直接复用。若想继续深入,可以沿着这条调用链阅读:
- 入口导出与整体导出清单:main.ts
- 分发映射与函数本体:browser-data.ts
- Chrome 系比较器完整实现:chrome.ts
- Chromium 与 Firefox 的差异实现:chromium.ts、firefox.ts
- 缓存层消费该比较器的场景:Cache.ts
- 包级 README 与 API 索引:README.md、browsers-api 文档索引
结合本仓库对应的 Cache 测试 与 browsers 测试目录,你还可以观察latest别名解析在真实安装/卸载流程中的预期行为,形成"文档 — 实现 — 测试"三层的完整认知闭环。
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考