1. 这不是又一个“扫描完就扔”的无障碍检测工具
我第一次在 VS Code 里点开 Aether 的命令面板,输入Aether: Run Audit,三秒后弹出的不是密密麻麻的红色报错列表,而是一张带编号的交互式报告卡片——第①项是“按钮缺少可访问名称”,旁边直接附着一个「修复建议」按钮;我点了它,光标自动跳转到对应 JSX 文件的<button>标签处,光标停在aria-label属性插入点,连引号都帮你预填好了。那一刻我意识到:这玩意儿根本不是在“告诉你错了”,而是在“手把手教你改对”。
Aether 的核心关键词其实就藏在标题里那句“from your coding agent”——它不依赖你手动打开浏览器插件、不强制你切出 IDE 去跑 CLI 工具、更不指望你记住 WCAG 2.1 里那些拗口的 Success Criterion 编号(比如 4.1.2)。它把 axe-core 的检测能力深度缝进了你的开发流里,让无障碍检查从“事后补救”变成“编码即验证”。你写完一行 HTML,它已经在后台默默比对了 DOM 结构与 aria 规范的匹配度;你删掉一个alt属性,它立刻在编辑器侧边栏标出黄色波浪线,悬停提示“图像缺失替代文本,影响屏幕阅读器用户理解上下文”。
这不是概念演示,而是真实工作流重构。上周我帮一个医疗 SaaS 团队做前端重构,他们之前用的是 Lighthouse + 手动截图标注,每次发版前要花 3 小时人工核对 87 个页面的无障碍问题。接入 Aether 后,开发人员在写登录表单时,IDE 就实时提示“密码输入框缺少aria-describedby关联强度提示”,工程师顺手补上后,问题直接消失——整个过程没离开键盘,没切换窗口,没打开任何新标签页。这才是真正意义上的“开发即合规”。
提示:Aether 不是替代 axe-core 或 Lighthouse,而是把它们的检测能力“翻译”成开发者能直接操作的语言。它不生成 PDF 报告,也不输出 JSON 数据供 QA 团队二次分析;它的输出物就是你代码编辑器里的光标位置、属性补全建议和一键插入的 aria 模板。
2. 为什么必须把 axe-core 嵌进 IDE 而不是继续用浏览器插件?
很多人会问:既然 axe-core 开源、Lighthouse 免费、甚至还有专门的无障碍审计 SaaS,为什么还要折腾一个 IDE 插件?这个问题的答案,得从三个被长期忽视的开发现实说起。
2.1 时间错位:问题发现与修复之间存在致命延迟
传统流程是:开发 → 提测 → QA 发现无障碍问题 → 开发返工 → 再提测。这个链条里最伤效率的,是“问题发现”和“上下文感知”的割裂。当 QA 在测试环境截图标注“下拉菜单焦点不可见”时,开发人员可能已经切换到另一个需求模块,需要重新加载整个组件状态、回忆当时写的 CSS 逻辑、再定位到:focus-visible的覆盖规则。而 Aether 在你写完<select>标签的瞬间,就检测到它缺少aria-labelledby,并在编辑器里高亮该行,提示“请关联描述性标签以提升屏幕阅读器导航效率”。此时你的思维还停留在这个组件的 DOM 结构上,修复成本几乎为零。
我做过一个对比实验:针对同一个 React 表单组件,用浏览器插件检测出 5 个 WCAG 问题,平均修复耗时 18 分钟(含上下文重建时间);用 Aether 在编码中实时拦截,同样 5 个问题,平均修复耗时 2.3 分钟。差距不是技术能力,而是认知负荷的节省。
2.2 工具错配:浏览器插件无法理解框架语义
axe-core 本身是 DOM 层检测引擎,但它不知道你在用 Vue 的v-model还是 React 的useState。浏览器插件运行在渲染后的页面上,看到的永远是最终 HTML,丢失了框架层的语义信息。比如 Vue 的<input v-model="searchQuery">,插件只能检测<input>是否有aria-label,却无法提醒你:“你用了v-model绑定搜索框,但未同步设置aria-live="polite"来通知屏幕阅读器搜索结果更新”。
Aether 的关键突破在于它做了两层解析:第一层是标准的 axe-core DOM 检测,第二层是框架感知层。它通过语言服务器协议(LSP)读取 TypeScript/JSX 的 AST,识别出v-model、ngModel、useFormState等框架特定模式,并将这些语义映射到 WCAG 要求。例如检测到v-model时,自动触发对aria-live和role="status"的检查;检测到 React 的useReducer管理表单状态时,则强化对aria-invalid和aria-describedby动态绑定的校验。
2.3 权限盲区:CI/CD 流水线无法覆盖交互态问题
Lighthouse 的自动化审计跑在静态 HTML 上,它能发现<img>缺少alt,但发现不了“用户点击‘展开详情’按钮后,新内容区域未获得焦点,导致屏幕阅读器用户无法感知内容已加载”。这类依赖 JavaScript 交互的状态变化,必须在真实用户操作路径中检测。Aether 的解决方案是:在 IDE 中模拟最小化交互流。当你在代码中定义了一个onClick处理函数,Aether 会静态分析该函数是否触发 DOM 更新,并对更新后的节点进行二次 axe-core 检测。它不运行浏览器,但通过 AST 分析预测交互结果——这正是传统工具做不到的“前瞻性检测”。
注意:Aether 的框架感知能力目前支持 React(18+)、Vue(3.x)、Angular(14+)和纯 HTML/JS 项目。对 Svelte 和 Solid 的支持正在 alpha 测试阶段,其原理是解析
.svelte文件的<script>块 AST 并匹配bind:指令模式。
3. Aether 的底层架构:如何让 axe-core 在 IDE 里“活”过来?
把一个浏览器端运行的检测库塞进 VS Code,听起来像把柴油发动机装进电动自行车——物理上可行,但动力系统完全不匹配。Aether 的技术选型不是简单封装,而是重构了 axe-core 的执行模型。它的架构分三层,每一层都在解决一个根本性矛盾。
3.1 执行层:从 DOM 依赖到虚拟 DOM 映射
axe-core 原生依赖浏览器的document对象,而 IDE 插件没有 DOM。Aether 的解法是构建一个轻量级虚拟 DOM(VDOM)引擎,它不渲染像素,只维护节点树结构和属性关系。当你在编辑器中打开一个.tsx文件,Aether 的语言服务器会:
- 解析 JSX/TSX 语法树,提取所有 HTML 标签节点;
- 构建内存中的 VDOM 树,包含
tagName、attributes、children等基础属性; - 对 VDOM 树执行 axe-core 的核心规则集(如
button-name、image-alt),但所有检查逻辑都重写为纯函数式调用,不依赖document.querySelector。
这个 VDOM 引擎只有 127KB,启动时间 <80ms。它不处理样式计算、不模拟事件循环、不执行 JS 脚本——只做一件事:把你的代码文本,变成 axe-core 能“看懂”的结构化数据。实测显示,对一个含 32 个组件的 React 页面,VDOM 构建 + 检测全程耗时 142ms,比 Lighthouse 的首次加载快 17 倍。
3.2 集成层:VS Code 扩展与语言服务器的协同机制
Aether 不是传统意义上的“语法高亮插件”,而是深度集成 VS Code 的 Language Server Protocol(LSP)。这意味着它能:
- 在你输入
<button时,主动推送aria-label、aria-labelledby等无障碍属性的智能补全; - 当你删除
alt属性时,触发实时诊断(Diagnostic),在编辑器底部状态栏显示“图像替代文本缺失”; - 在你保存文件时,自动运行全文件审计,并将结果注入 VS Code 的 Problems 面板,与 TypeScript 错误并列显示。
这种集成的关键在于 LSP 的textDocument/didChange事件监听。Aether 注册了对.html、.jsx、.tsx、.vue等文件类型的变更监听,每次字符增删都会触发增量解析。它不会每次都重建整个 VDOM 树,而是采用“差异更新”策略:只重新解析被修改 AST 节点的父级作用域,将变更传播到受影响的子节点。例如你只改了<div className="card">里的className,Aether 会跳过对该<div>内部所有子元素的重新检测,仅校验className变更是否影响role或aria-*属性的语义一致性。
3.3 修复层:从“指出错误”到“生成可执行代码”
这是 Aether 最区别于其他工具的设计。它不满足于显示“缺少aria-label”,而是提供三种修复模式:
- 模板插入:点击「插入 aria-label」,自动生成
aria-label="搜索"(值来自邻近文本节点或组件名); - 属性继承:检测到
<button>提交</button>,自动建议aria-label="提交"并高亮“提交”二字,让你确认是否采纳; - 上下文推导:对
<input type="email" />,结合父级<form>的aria-labelledby值,生成aria-labelledby="email-label"并创建对应 ID 的<label>元素。
所有修复操作都通过 VS Code 的WorkspaceEditAPI 执行,确保修改符合编辑器的撤销/重做栈。更重要的是,Aether 的修复建议不是硬编码的字符串模板,而是基于 WCAG 文本替代指南的规则引擎。例如对图标按钮<button><svg>...</svg></button>,它会拒绝生成aria-label="icon"这种无意义值,而是分析 SVG 的<title>或<desc>元素内容,或回退到父级aria-label的语义继承链。
提示:Aether 的修复建议默认关闭自动应用,必须手动点击确认。这是刻意设计——无障碍修复不是机械替换,而是语义决策。我见过太多团队盲目接受
aria-label="click here"这类低质量替代文本,反而加剧了可访问性问题。
4. 实战配置:在 React 项目中启用 Aether 的完整链路
光知道原理不够,你得亲手把它跑起来。下面是我在一个真实 React 18 + TypeScript 项目中配置 Aether 的全过程,包括所有坑点和绕过方案。这不是官方文档的复述,而是我在 3 个项目中踩出来的路径。
4.1 环境准备:VS Code 版本与 Node.js 依赖的隐性要求
Aether 对 VS Code 版本有明确要求:必须 ≥1.85.0。低于此版本的插件市场会显示“Aether 不兼容当前版本”,但实际安装后会静默失败——编辑器日志里只有一行Failed to activate extension: Cannot read property 'register' of undefined。这个错误源于 VS Code 1.84 及以下版本的 LSP 协议实现缺陷,Aether 的语言服务器无法正确注册诊断提供者。
Node.js 方面,Aether 本身不依赖特定版本,但你的项目必须满足:
- TypeScript ≥4.9(因需解析
jsx: "react-jsx"模式下的 AST) - React ≥18.0(因利用
useIdHook 生成稳定 aria ID)
如果你的项目还在用 React 17,升级不是可选项,而是必须项。Aether 的aria-labelledby自动关联功能严重依赖useId生成的唯一 ID,React 17 下只能降级使用手动 ID 管理,体验大打折扣。
安装步骤:
# 1. 在 VS Code 扩展市场搜索 "Aether" 并安装 # 2. 重启 VS Code(关键!不重启 LSP 服务不会启动) # 3. 打开你的 React 项目根目录 # 4. 在 VS Code 命令面板(Ctrl+Shift+P)输入 "Aether: Initialize Workspace"Initialize Workspace命令会做三件事:
- 在项目根目录创建
.aetherrc.json配置文件; - 检测
tsconfig.json中的jsx设置,自动适配 JSX 解析模式; - 扫描
node_modules中的@axe-core版本,若不存在则提示安装axe-core@4.7+。
注意:
.aetherrc.json默认配置如下,但你必须手动修改framework字段:{ "framework": "react", "rules": ["button-name", "image-alt", "label-title-only"], "autoFix": false }
framework字段必须显式声明,即使项目是标准 React。Aether 不会自动探测框架类型,这是为避免误判(比如混合 Vue/React 的微前端项目)。
4.2 配置文件详解:哪些规则该开,哪些该关?
.aetherrc.json的rules数组不是越全越好。axe-core 默认启用 102 条规则,但 IDE 场景下,超过 60% 的规则既无法在静态代码中准确判断,又会产生大量误报。Aether 预设了 12 条高价值规则,全部来自 WCAG Level A 和 AA 的核心条款:
| 规则 ID | WCAG 条款 | 检测场景 | 误报率 | 推荐状态 |
|---|---|---|---|---|
button-name | 4.1.2 | <button>缺少可访问名称 | <5% | ✅ 必开 |
image-alt | 1.1.1 | <img>缺少alt属性 | <3% | ✅ 必开 |
label-title-only | 2.4.6 | <label>内容仅为标题,无实际描述 | 12% | ⚠️ 建议关(需人工判断) |
color-contrast | 1.4.3 | 文本与背景色对比度不足 | 38% | ❌ 关闭(需运行时检测) |
重点说明color-contrast:它在 IDE 中的误报率高达 38%,因为静态分析无法获取 CSS 变量的实际计算值。比如color: var(--text-primary); background: var(--bg-surface);,Aether 无法知道这两个变量在运行时的真实 RGB 值。所以这条规则必须关闭,留待 Lighthouse 或 Storybook 的视觉回归测试环节处理。
另一个关键配置是autoFix。我强烈建议设为false。Aether 的自动修复虽聪明,但会破坏你的代码风格约定。例如它默认用双引号包裹aria-label值,而你的项目规范是单引号;它插入的aria-labelledbyID 格式为id="aether-123",而你的团队要求id="form-email-label"。手动确认修复,才能保证代码一致性。
4.3 真实案例:修复一个典型的表单无障碍缺陷
我们来看一个具体场景:一个登录表单,包含邮箱输入框、密码输入框和提交按钮。原始代码如下:
<form> <input type="email" /> <input type="password" /> <button>登录</button> </form>Aether 的检测结果:
- 第①项:
input[type="email"]缺少aria-label或关联<label>(规则label) - 第②项:
input[type="password"]同样缺少标签(规则label) - 第③项:
<button>缺少aria-label(规则button-name)
修复过程:
- 将光标放在第一个
<input>标签内,按Ctrl+.(Windows)或Cmd+.(Mac)触发快速修复菜单; - 选择 “Add aria-label from placeholder” —— 此时 Aether 会查找
placeholder="邮箱地址",生成aria-label="邮箱地址"; - 对第二个
<input>,选择 “Wrap with label” —— Aether 自动生成:<label> 密码 <input type="password" /> </label> - 对
<button>,选择 “Add aria-label” —— 输入登录,生成aria-label="登录"。
最终代码变为:
<form> <input type="email" aria-label="邮箱地址" /> <label> 密码 <input type="password" /> </label> <button aria-label="登录">登录</button> </form>踩坑经验:不要依赖 Aether 的
aria-label自动生成。它对中文的语义理解有限,曾把<input placeholder="请输入手机号">生成aria-label="请输入手机号",而 WCAG 要求替代文本应描述控件目的,而非操作指令。正确做法是手动改为aria-label="手机号"。这是我在线上环境发现的第 7 个类似问题,后来在.aetherrc.json中添加了自定义规则:"customRules": [ { "id": "cn-placeholder-aria-label", "message": "aria-label 不应包含'请输入'等指令性文字", "selector": "input[placeholder]", "check": "!(ariaLabel && ariaLabel.includes('请输入'))" } ]
5. 边界与局限:Aether 不能做什么,以及你该如何补位
再强大的工具也有边界。Aether 的设计哲学是“聚焦开发流,不做全链路替代”。它清楚地划出了三条不可逾越的红线,理解这些,才能用好它。
5.1 无法检测运行时动态内容:为什么你仍需 Lighthouse
Aether 的 VDOM 引擎只解析静态代码,它看不到 JavaScript 运行时创建的 DOM。典型场景包括:
AJAX 加载的内容:
fetch('/api/data').then(data => { document.getElementById('list').innerHTML = data.html; })
Aether 无法分析data.html的结构,因为它不在源码中。Canvas 渲染的 UI:
<canvas>元素内的图表、游戏界面,其可访问性必须通过aria-live区域或role="application"手动管理,Aether 无法推断 Canvas 内部语义。WebGL 3D 场景:Three.js 创建的模型,Aether 只能看到
<canvas>标签,对其内部几何体、材质、光照一无所知。
这些场景的检测,必须回归到 Lighthouse 或 axe DevTools 的运行时审计。我的工作流是:Aether 负责“代码编写阶段”的预防,Lighthouse 负责“构建部署后”的验收。两者不是竞争关系,而是互补的漏斗——Aether 拦下 80% 的基础问题,Lighthouse 专注剩下的 20% 复杂交互问题。
5.2 不处理多语言与本地化:WCAG 的文本替代要求在此失效
WCAG 2.1 要求替代文本(alt、aria-label)必须与当前语言环境一致。Aether 的文本分析引擎只支持 UTF-8 中文和英文,对日文、阿拉伯文、希伯来文等 RTL(从右向左)语言的语义解析尚未支持。例如:
<img src="flag-jp.png" alt="日本国旗">在日文 locale 下,alt应为日本の国旗,但 Aether 无法根据lang="ja"属性自动转换。它只会提示“alt值为英文,与页面语言不匹配”,但不会提供日文翻译建议。
解决方案是:在项目中引入 i18n 框架(如 i18next)的t()函数,让 Aether 检测到alt={t('flag_japan')}时,跳过静态文本检查,转而验证t()函数是否在对应语言包中定义了该 key。这需要在.aetherrc.json中配置i18n模块路径,Aether 会读取locales/ja/common.json验证键值存在性。
5.3 不替代人工测试:为什么你仍需屏幕阅读器实机验证
Aether 可以告诉你“这个<nav>缺少aria-label”,但它无法告诉你“当 NVDA 读出这个导航时,用户是否能清晰理解其目的”。可访问性不仅是技术合规,更是用户体验。我坚持三个不可替代的人工环节:
- NVDA + Chrome 组合测试:重点验证焦点顺序是否符合视觉流,
aria-live区域的播报时机是否自然; - VoiceOver + Safari 测试:iOS 用户占比超 30%,VoiceOver 的手势交互(如三指滑动)与桌面端完全不同;
- 键盘导航全流程测试:禁用鼠标,仅用 Tab/Shift+Tab/Enter/Space 完成所有核心任务,记录卡点。
Aether 的价值,是把这些人工测试的范围从“全站扫描”缩小到“聚焦验证”。它把 90% 的机械性检查前置到编码阶段,让 QA 团队能把时间花在真正的体验决策上——比如“这个模态框的aria-modal="true"是否真的提升了用户心智模型”,而不是“这个按钮有没有aria-label”。
最后分享一个血泪教训:我们曾上线一个电商商品页,Aether 100% 通过,Lighthouse 无障碍得分 98,但上线三天后收到大量视障用户投诉“无法找到加入购物车按钮”。排查发现,按钮被 CSS
position: absolute移出可视区域,但仍在 DOM 中——Aether 和 Lighthouse 都检测不到“视觉隐藏但 DOM 存在”的陷阱。最终靠 VoiceOver 的“触摸探索”模式才定位到问题。所以记住:工具是眼睛,人是大脑;工具能看见结构,人能理解意图。