- 开发工具
【免费下载链接】bowser
a browser detector
导读:本文以 Bowser(当前仓库为gh_mirrors/bo/bowser,一个轻量级浏览器/平台/引擎检测库)的 CHANGELOG.md 为主线,逐版本还原其 2.x 时代的演进脉络——从 2.0 的 API 全面重写、到satisfies/别名/常量导出等能力沉淀、再到面向 AI 爬虫与 User-Agent Client Hints 的现代化识别。读完后你不仅能看懂每个版本号背后的变更动机,还能把变更点对应到 src/ 下的真实源码实现与 test/ 中的测试用例,掌握「如何阅读一个检测库的 Changelog」以及「Bowser 当前版本能做什么」。
一、写在前面:Changelog 与源码的对应关系
CHANGELOG.md 覆盖了 Bowser 从1.4.0(2016 年 6 月)到2.11.0(2020 年 9 月)的全部公开版本,其中 2.x 是当前架构的定型期。值得注意的是,CHANGELOG 记录到 2.11.0 后便不再更新,但仓库源码仍在演进:例如 parser-browsers.js 中已出现GPTBot、ChatGPT-User、ClaudeBot、DuckDuckGo等新检测器,以及 parser.js 中完整的 Client Hints 支持。阅读时可以遵循一个原则:Changelog 给出变更意图,源码给出实现事实。
本文围绕以下核心脉络展开:
- 2.0 大版本重写:Bowser 从「一个函数」变成「Parser 类 + 静态 API」;
- 2.x 增量演进:别名、
is、some、常量导出、Typings 补全; - 识别能力扩张:浏览器、OS、平台、引擎的逐版本补丁;
- 工程化演进:构建产物、包体积、polyfill 策略;
- 未写入 Changelog 的现代化能力:Client Hints 与 AI 爬虫检测。
二、2.0.0:API 全面重写,从「检测函数」到「解析器类」
CHANGELOG 中 2.0 系列(alpha → beta → 正式版)是最具颠覆性的一段:
- 2.0.0-alpha.1(2018-07-09):新增
Bowser.getParser()、Bowser.parse,引入描述整个解析过程的Parser类,改变 bowser 的返回对象,并移除 bower 支持; - 2.0.0-alpha.2:将
src/bowser设为主文件,bundled 文件移到包根目录以便require('bowser/compiled'); - 2.0.0-alpha.3:新增
Parser.some,将compiled.js重命名为es5.js; - 2.0.0-beta.1:为
Parser.compareVersion()和Parser.satisfies()加入宽松版本比较; - 2.0.0 正式版(2019-01-19):
satisfies()支持非严格相等、Android 版本命名、新增 typings 文件、支持 Googlebot 识别。
2.1 源码印证:双入口 API 设计
这段重写在当前源码中清晰可见。src/bowser.js 提供了两个静态方法:
static getParser(UA, skipParsingOrHints = false, clientHints = null) { if (typeof UA !== 'string') { throw new Error('UserAgent should be a string'); } return new Parser(UA, skipParsingOrHints, clientHints); } static parse(UA, clientHints = null) { return (new Parser(UA, clientHints)).getResult(); }getParser()返回一个可复用的Parser实例,适合需要多次查询的场景;parse()是一次性调用,直接返回解析结果对象;- 文件末尾还通过
export const { parse, getParser } = Bowser;暴露了命名导出,配合 package.json 中的exports字段实现 tree-shaking。
Parser类的构造函数在 src/parser.js 中做了三件事:校验 UA 非空(空值直接抛错)、处理(UA, clientHints)与(UA, skipParsing, clientHints)两种重载、在skipParsing !== true时立即执行parse()。解析过程由parse()串联四个子解析器(src/parser.js):
parse() { this.parseBrowser(); this.parseOS(); this.parsePlatform(); this.parseEngine(); return this; }这对应 Changelog 2.0.0-alpha.1 所说的「Parser 类描述解析过程」——最终结果ParsedResult包含browser、os、platform、engine四个字段。单元测试 test/unit/parser.js 逐一验证了getBrowserName、getOSName、getEngineName等访问器。
2.2 宽松版本比较:compareVersion的语义
2.0.0-beta.1 加入的宽松比较,实现在 src/parser.js 的compareVersion()中,支持五类操作符:
| 操作符 | 语义 | 示例 | 说明 |
|---|---|---|---|
>/< | 严格大于/小于 | chrome: '>100' | 不匹配相等版本 |
>=/<= | 非严格比较 | opera: '>=43' | 匹配相等版本 |
= | 精确相等 | chrome: '=20.1.1432' | 只匹配特定 build |
~ | 宽松相等 | chrome: '~20.1' | 匹配20.1.*任意子版本 |
| 数组 | 或关系 | ['~40', '~41'] | 任一满足即通过 |
底层数值比较由 src/utils.js 的compareVersions()完成:先将两个版本补齐到相同精度(如"9"→"9.0"),再把每段数字左补零后逐段比较,从而避免"9" > "10"这类字符串比较陷阱。测试 test/unit/parser.js 覆盖了>42、~43.0、>=43.0.2442.1165及版本数组等全部用例。
三、2.x 增量演进:别名、is方法、常量与 Typings
2.x 中后期,Bowser 的 API 进入「补全期」:
- 2.2.0(2019-04-07):为浏览器名增加短别名,修复 Yandex Browser 版本检测;
- 2.2.1:为 Samsung Internet 增加别名;
- 2.9.0(2020-01-28):通过
.d.ts导出更多方法与常量; - 2.11.0(2020-09-12):
Parser#is支持别名、补充更多 typings、新增 MIUI Browser 支持。
3.1 别名机制:is()与satisfies()的便捷通道
别名机制的核心是 src/constants.js 的BROWSER_ALIASES_MAP(完整名称 → 短名)与BROWSER_MAP(短名 → 完整名称),共约 60 个映射,例如'Microsoft Edge' → 'edge'、'Internet Explorer' → 'ie'、'Samsung Internet for Android' → 'samsung_internet'、'NAVER Whale Browser' → 'naver'、'QQ Browser' → 'qq'、'WeChat' → 'wechat'。
Parser#is(anything, includingAlias)(src/parser.js)会同时检查浏览器、OS、平台三类名称;当includingAlias = true时,通过Utils.getBrowserTypeByAlias()把短名解析为完整名再比较。测试 test/unit/parser.js 验证了别名开启前后的差异:
// edgeParser 的 UA 是 Microsoft Edge(EdgA/41.1.35.1) t.is(edgeParser.is('edge', false), false); // 不开别名:'edge' 不等于 'Microsoft Edge' t.is(edgeParser.is('edge', true), true); // 开别名:短名被解析为完整名Parser#some(anythings)(src/parser.js)则是is()的批量版本——任一命中即返回true。satisfies()内部的浏览器匹配同样启用别名(src/parser.js),所以你可以写satisfies({ edge: '=41.1.35.1' }),测试 test/unit/parser.js 对此有专门断言。
3.2 常量导出与 Typings
2.9.0 之后,Bowser类以静态 getter 形式暴露四张常量表(src/bowser.js):BROWSER_MAP、ENGINE_MAP、OS_MAP、PLATFORMS_MAP。配合 index.d.ts 的类型声明,可以写出常量驱动的检测代码(test/unit/parser.js):
import Bowser from 'bowser'; const parser = Bowser.getParser(window.navigator.userAgent); parser.is(Bowser.BROWSER_MAP.opera); // 完整名 "Opera" parser.is(Bowser.PLATFORMS_MAP.desktop); // 平台类型 "desktop" parser.is(Bowser.OS_MAP.MacOS); // OS 名 "macOS"仓库同时提供 index.d.ts(CommonJS/UMD 类型)与 index.d.mts(ESM 类型),并通过 test/types/ 下的consumer-cjs.ts、consumer-esm.ts与check.mjs做类型冒烟验证(对应 package.json 的test:types脚本)。
四、识别能力扩张:浏览器、OS、平台与引擎的逐版本补丁
CHANGELOG 中大量的[FIX]/[ADD]条目都对应着 src/parser-browsers.js(约 1250 行,最大的规则表)、src/parser-os.js、src/parser-platforms.js、src/parser-engines.js 中具体规则的增删。下表梳理了主要条目与源码锚点:
| 版本 | 变更 | 源码位置 |
|---|---|---|
| 2.0.0 | Googlebot 识别 | parser-browsers.js,parser-platforms.js(标记为type: 'bot', vendor: 'Google') |
| 2.0.0 | Android 版本命名 | src/utils.js 的getAndroidVersionName()(如7.0 → 'Nougat',8.x → 'Oreo') |
| 2.1.0 | Parser.getEngineName()、ChromeOS 检测 | src/parser.js、src/parser-os.js 的 ChromeOS 规则 |
| 2.1.1 | PlayStation 4 检测 | parser-browsers.js、parser-os.js、parser-platforms.js |
| 2.2.1 | Samsung Internet 别名 | src/constants.js |
| 2.3.0 | Blink 内核的 MS Edge | src/parser-engines.js 的 Blink 规则 |
| 2.5.0 | 常量输出、Roku OS | src/parser-os.js(/Roku\/DVP/规则) |
| 2.7.0 | QQ 浏览器、GSA(Google Search App) | src/parser-browsers.js(/GSA/i规则) |
| 2.8.0 | Electron、iPad 上的 Firefox、Windows 上的 WeChat | src/parser-browsers.js(/electron/i规则)、src/parser-os.js(FxiOS规则) |
| 2.10.0 | iOS 13 上的 Firefox 修复 | src/parser-os.js 的 Firefox-on-iOS 规则 |
| 2.11.0 | MIUI Browser | src/parser-browsers.js(/MiuiBrowser/i规则,返回name: 'Miui') |
4.1 规则表的组织方式:test+describe
理解这些规则的关键是 parser-browsers.js 文件头部的注释:每个描述符(descriptor)包含test和describe两个部分,且描述符的顺序至关重要——Parser按顺序逐个测试,先命中的优先,因此通用性强的规则(如 GPTBot、各类 AI 爬虫)必须放在列表前部,避免把其他浏览器误判为 Chrome。
test既可以是正则数组(任一命中即通过),也可以是接收 Parser 实例的函数,例如 DuckDuckGo 的检测(src/parser-browsers.js)会先查 Client Hints 品牌,再回退到 UA 字符串:
test(parser) { if (parser.hasBrand('DuckDuckGo')) { // Client Hints 优先 return true; } return parser.test(/\sDdg\/[\d.]+$/i); // WebKit 平台回退到 UA }4.2 OS 版本命名的双维度输出
从 1.7.0 的 Windows/macOS 版本支持到 2.0.0 的 Android 命名,os结果对象如今最多有三个字段:name、version、versionName。例如 Windows 的version: "NT 6.3"对应versionName: "8.1"(映射见 src/utils.js 的getWindowsVersionName());macOS 的10.14 → 'Mojave'、13 → 'Ventura'见getMacOSVersionName()(src/utils.js)。README 中的完整输出示例(README.md)展示了 IE 11 在 Windows 8.1 上的四段式解析结果。
五、工程化演进:构建产物、polyfill 与包体积
Changelog 中反复出现的构建相关条目,最终形成了当前 package.json 的完整导出矩阵:
- ES5 主产物:
main: "es5.js",面向 CommonJS/<script>标签,不含任何 polyfill(2.5.1 优化 Babel 配置以缩减体积,README 称 ES5 版本约 4.8kB gzipped); - bundled 带 polyfill 产物:
require('bowser/bundled')得到内置core-jspolyfill 的 ES5 版本(2.8.0 为Array.find、Object.assign补充 polyfill,对应 src/utils.js 中手写的map/find/assign兜底实现); - ESM 产物:
bowser.mjs,配合exports的import/require条件分支,实现 tree-shaking(源码注释 src/parser.js 明确指出 es5.js 不打包 polyfill,因此内部查找一律走Utils.find而非Array.prototype.find); - 源码直用:
src/*.js全部通过exports子路径暴露,可import Parser from 'bowser/src/parser'。
2.5.4 起将docs排除出 npm 包(文档迁移至 GH Pages),2.6.0 在 package.json 中声明"module"字段,2.8.1 曾回滚一个破坏构建的改动——这些条目提醒我们:每个版本号背后都是一次真实的质量门禁。当前仓库的构建脚本(package.json)由tsdown驱动,配套eslint、ava、nyc覆盖率、publint、@arethetypeswrong/cli等一整套质量工具。
六、Changelog 之外的现代化:Client Hints 与 AI 爬虫
源码中还存在一批 Changelog 未记录的能力,它们是理解「当前 Bowser 能做什么」的关键补充。
6.1 User-Agent Client Hints
现代浏览器逐步收紧 UA 字符串后,README.md 明确推荐将navigator.userAgentData作为第二参数传入:
const browser = Bowser.getParser( window.navigator.userAgent, window.navigator.userAgentData // Client Hints ); browser.getHints(); // 获取完整 hints 对象 browser.hasBrand('Google Chrome'); // 品牌存在性检查(大小写不敏感) browser.getBrandVersion('Google Chrome'); // 取品牌版本,如 '131'ClientHints 的字段结构(brands、mobile、platform、platformVersion、architecture、model、wow64)定义在 src/parser.js 与 index.d.ts。测试 test/unit/parser.js 覆盖了 DuckDuckGo、Chrome、Edge 的 hints 检测,包括 GREASE 品牌变体(Not_A Brand、Not-A.Brand)的处理,以及「hints 缺失时回退到 UA 解析」的兜底逻辑。
6.2 AI 爬虫与新型浏览器
src/constants.js 的别名表中已出现GPTBot、ChatGPT-User、ClaudeBot、PerplexityBot、Perplexity-User、OAI-SearchBot、Meta-ExternalFetcher、Diffbot、AmazonBot、BingCrawler、InternetArchiveCrawler等条目;src/parser-platforms.js 将GPTBot/ChatGPT-User/OAI-SearchBot统一标记为type: 'bot', vendor: 'OpenAI'。这意味着当前版本的 Bowser 不仅能识别浏览器,还能区分搜索引擎爬虫与 AI 内容抓取机器人,对反爬统计、流量分析场景尤为实用。
七、实战:把 Changelog 变成可验证的检测代码
最后,用一段完整的示例串联全文。以 Opera 43 的 UA 为例(与 test/unit/parser.js 同一字符串):
import Bowser from 'bowser'; const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 ' + 'Safari/537.36 OPR/43.0.2442.1165'; const parser = Bowser.getParser(UA); parser.getBrowserName(); // 'Opera'(2.x 描述符顺序保证 Opera 优先于 Chrome) parser.getBrowserVersion(); // '43.0.2442.1165' parser.getOSName(); // 'macOS' parser.getEngineName(); // 'Blink' parser.satisfies({ opera: '>42' }); // true(beta.1 引入的宽松比较) parser.is(Bowser.BROWSER_MAP.opera); // true(2.9.0 常量导出)验证方式:仓库已内置完整测试体系,可运行pnpm test(ava + nyc)执行 test/unit/ 下的单元测试,pnpm test:package运行 test/package/ 的产物冒烟测试,pnpm test:types校验 index.d.ts 类型。若想扩充检测规则,test/acceptance/useragentstrings.yml 是官方维护的 UA 语料库,[src/constants.js](https://link.gitcode.com/i/5ff5285ab9ab6929dcf154a99149881c)的注释明确要求浏览器列表必须与该文件保持同步——这正是 Bowser 每个版本都能「改一个检测、补一串测试」的质量保障机制。
- 开发工具
【免费下载链接】bowser
a browser detector
相关推荐
Wox 2.x 版本演进全览:从跨平台重写到原生 UI 换代的技术脉络
Wox 2.x 版本演进全览:从跨平台重写到原生 UI 换代的技术脉络 Wox 是一个跨平台桌面启动器(launcher),其设计目标是"simply work
桌面应用AI 应用插件系统OverlayScrollbars 2.x 版本演进全解:从 TypeScript 重写到 2.16 的滚动条插件现代化之路
OverlayScrollbars 2.x 版本演进全解:从 TypeScript 重写到 2.16 的滚动条插件现代化之路 本文以 OverlayScroll
UI组件前端PPT Master:从一份 PDF 到可编辑 PPT 只要10分钟,零额外订阅费
PPT Master:从一份 PDF 到可编辑 PPT 只要10分钟,零额外订阅费 周五晚上九点,桌上摊着一沓 PDF,明早十点要给老板做季度汇报。你手头的 A
AI 技能人工智能AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考