Svelte 浏览器兼容底线解析:browser-support-features 条件特性表的生成机制与三大高基线特性底层剖析
browser-support-features.md 是 Svelte 文档站“Browser support”页面的自动产物:它逐条列出了只有在使用某些特定 Svelte 特性时才会突破 Svelte 运行时兼容底线(Baseline 2020)的浏览器 API。本文以这张条件特性表为主体,先解释它如何被 Svelte 仓库的构建脚本全自动推导出来(打包 → TypeScript 类型感知扫描 → 逐特性 fixture 扫描 → Baseline 年份换算),再逐条深入三行条目背后的真实源码:$state.snapshot 与 structuredClone、bind:devicePixelContentBoxSize 与 ResizeObserver 的 device-pixel-content-box 选项、svelte/animate 中 flip 的 getComputedStyle(...).zoom 读取,帮助你在评估目标浏览器版本时做出有依据的判断。
表格本体:只有三行“条件特性”
该文件位于 documentation/docs/07-misc/.generated/browser-support-features.md,首行注释声明了它的来源:
<!-- generated in packages/svelte/scripts/generate-browser-support.ts. do not edit -->
当前仓库快照下的完整内容是一张三列表格(Feature / Chrome-Edge / Firefox / Safari):
| 特性 | Chrome/Edge | Firefox | Safari |
|---|---|---|---|
$state.snapshot |
98 | 94 | 15.4 |
bind:devicePixelContentBoxSize |
— | 93 | not supported |
flip(来自 svelte/animate) |
— | 126 | — |
解读这张表需要理解两个约定:
- “—” 表示该浏览器所需版本不高于 Svelte 运行时底线版本,即已被主兼容表覆盖,不需要额外注意;
- 具体版本号 表示使用该特性时,这一浏览器必须达到该版本;
- “not supported” 表示该浏览器至今没有对应 API,该特性在该浏览器上不可用。
也就是说,如果你不使用 $state.snapshot、bind:devicePixelContentBoxSize 和 flip 这三个特性,就完全不必考虑这张表——Svelte 其余能力的兼容底线由主页面 documentation/docs/07-misc/.generated/browser-support.md 承载。该主表当前为:Chrome/Edge 87、Firefox 83、Safari 14、Opera 73、Opera (Android) 62、Samsung Internet 14.0、Android WebView 87、Internet Explorer 不支持,注释说明其等价于 Baseline 2020 目标。主表与本表均由 documentation/docs/07-misc/05-browser-support.md 通过 @include .generated/browser-support.md 和 @include .generated/browser-support-features.md 两行指令拼装进最终页面。
生成机制:generate-browser-support.ts 的四步流水线
生成脚本是 packages/svelte/scripts/generate-browser-support.ts。文件头注释描述了完整流水线(main() 函数从 第 806 行 开始依次执行):
第 1 步:按用户实际拿到代码的方式打包运行时。 browser_subpaths() 遍历 packages/svelte/package.json 的 exports 字段,过滤掉纯 Node 子路径(./compiler、./server、./internal/server,见 NODE_ONLY_EXPORTS,第 153 行),得到所有会进浏览器的子包;随后用 rollup 以 exportConditions: ['production', 'import', 'browser', 'default'] 逐个打包(bundle(),第 292 行),并用 import * as __ns from '...' 的虚拟入口保住默认导出,确保扫描到的就是浏览器真正执行的代码。
第 2 步:类型感知扫描,找出运行时引用的全部 web 特性。 扫描器是 packages/svelte/scripts/browser-support.detector.js:它先把 web-features 数据集里每个特性的 compat_features 路径(如 api.ResizeObserver、api.HTMLElement.inert、javascript.builtins.Promise)解析成三类查找表——全局标识符、(类型, 成员) 对、JS 语法谓词(SYNTAX_PREDICATES 覆盖空值合并、可选链、async 迭代、私有字段等 19 种语法特性,第 12-61 行)。扫描时用 TypeScript 编译器 API 构建 ts.Program 并取 TypeChecker(build_program(),第 219 行),因此成员访问规则是类型感知的:expr.member 会解析 expr 的实际类型(含基类与 apparent type,get_type_names(),第 246 行)再查表,避免同名属性误报;全局标识符还会用 is_global_binding() 确认符号确实来自 lib 声明而非用户局部变量。
同时,脚本把编译器快照测试目录 packages/svelte/tests/snapshot/samples 下所有 _expected/client/*.js 产物作为 fixture 读入(load_compiler_output_fixtures(),第 336 行),用纯语法模式(detect_features_in_text())扫描编译器可能产出的全部模式(hydration 标记、<svelte:element>、async derived 等)。两者合并后取最高 Baseline 年份作为运行时底线(find_minimum_target(),第 422 行),并有 Math.max(year, 2015) 兜底。
第 3 步:抑制项(ignore set)——这正是“条件特性”概念的来源。 脚本定义了两组豁免:
SAFE_TO_IGNORE(第 131 行):devicepixelratio、trusted-types——前者是web-features数据集误标,后者是 Svelte 用?.运行时探测、缺失时优雅降级的 API;BEHAVIORAL_IGNORE(第 138 行):structured-clone、extra:css-zoom-read、extra:device-pixel-content-box——这三个 API 确实存在于运行时中,但只在特定代码路径才会触达,因此不算进整体底线,而是被单独拆到条件特性表里。
validate_ignore_features()(第 458 行)会反向校验每个 BEHAVIORAL_IGNORE 条目仍能被检测器命中,防止“API 已从运行时移除但豁免注释残留”的陈旧配置。
第 4 步:逐用户特性生成 fixture,筛出高于底线的行。 enumerate_features()(第 484 行)枚举出四类用户可见特性并各自生成一个最小 fixture:编译器接受的全部 bind:*(由 packages/svelte/src/compiler/phases/bindings.js 的 binding_properties 驱动,binding_fixture() 会按 valid_elements 自动拼出合法元素与属性)、全部公开子包的每个具名导出(通过动态 import 枚举)、编译器 RUNES 数组里的每个 rune($state、$derived.by、$props.id、$effect.root、$host 等,fixture 定义在 第 214 行)、以及 TESTED_DIRECTIVES 中的 transition:、animate:、use:、{@attach}、{@html}、自定义元素。每个 fixture 经 svelte_compile() 编译成客户端 JS 再打包、扫描,凡 Baseline 年份高于运行时底线者(find_all_conditional_features(),第 602 行)才输出一行。
对于 AST 扫描器看不见的盲区,脚本注册了两条补充规则(register_extra_rules,第 65 行),恰好对应后文第 2、3 行特性:
// 1. CSSStyleDeclaration 上的 .zoom 读取(svelte/animate 的 flip 回退路径)
// Firefox 直到 126 才暴露该属性,且 web-features 无对应条目
{
receiver_type: 'CSSStyleDeclaration',
member: 'zoom',
feature_id: 'extra:css-zoom-read',
baseline_year: 2024,
versions: { firefox: '126' }
},
// 2. ResizeObserver 的 box: 'device-pixel-content-box' 字符串字面量
// (Safari 接受构造选项但从不提供 entry.devicePixelContentBoxSize 属性)
{
string_literal: 'device-pixel-content-box',
feature_id: 'extra:device-pixel-content-box',
baseline_year: 2023,
versions: { chrome: '84', edge: '84', firefox: '93', safari: null, safari_ios: null }
}
最后,版本号换算优先使用 web-features 的逐特性精确版本(versions_from_features(),取各浏览器最严格的版本、null 传播为“not supported”),无精确数据时才回退到 baseline-browser-mapping 的年份映射。表格渲染在 render_conditional_table()(第 669 行):版本不高于运行时底线的格子直接渲染为灰色 “—”,null 渲染为 “not supported”。
逐行深挖:三行条目背后的源码
第 1 行:$state.snapshot 依赖 structuredClone
$state.snapshot() 用于把响应式状态转成不可变的深拷贝。从源码结构看,它最终落在 packages/svelte/src/internal/shared/clone.js 中两处对 structuredClone(value) 的直接调用(第 110 行 与 第 131 行)。structuredClone 恰好是 Baseline 2022 特性:Chrome/Edge 98、Firefox 94、Safari 15.4——与表格第一行完全吻合。由于 Svelte 运行时其他地方不强制依赖它,该特性被归入 BEHAVIORAL_IGNORE:只有调用 $state.snapshot 的代码路径会触达 structuredClone,因此底线只在使用该 rune 时才抬升到 2022。
第 2 行:bind:devicePixelContentBoxSize 在 Safari 上“not supported”
该绑定把元素的设备像素级内容盒尺寸绑定到状态。运行时实现在 packages/svelte/src/internal/client/dom/elements/bindings/size.js:ResizeObserverSingleton 类按观察选项分为三个共享实例(content-box / border-box / device-pixel-content-box,第 73-75 行),bind_resize_observer() 按绑定类型选实例并回传 entry[type]。编译器侧的声明在 packages/svelte/src/compiler/phases/bindings.js(devicePixelContentBoxSize 条目),客户端转换在 packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js。
表格中 Chrome/Edge 是 “—” 而非 84,正是因为 versions_from_features() 取的是最严格版本与运行时底线的比较:Chrome 84 低于底线的 Chrome 87,无需单独标注;Firefox 93 高于底线 83,故标出。而 Safari 一列是 “not supported”——脚本注释解释了原因:Safari 从 15.4 起会静默接受构造选项 box: 'device-pixel-content-box',却从不提供 ResizeObserverEntry.devicePixelContentBoxSize 属性(BCD 中 version_added: false),因此该绑定在任何 Safari 上读到的都是 undefined。这是“能编译、能运行、但永远拿不到值”的兼容性陷阱,值得在选型时特别注意。
第 3 行:flip 动画依赖 getComputedStyle(...).zoom
flip 实现位于 packages/svelte/src/animate/index.js。它的 get_zoom() 函数(第 63-78 行) 会沿父链向上累乘每一级的 getComputedStyle(current).zoom,把祖先元素的 CSS zoom 纳入位移与缩放计算——这是动画数学正确性的前提。Firefox 直到 v126(2024 年 5 月)才在 CSSStyleDeclaration 上暴露 .zoom,更早版本该读取返回空字符串,导致 sx/sy 缩放计算出错。由于 web-features 数据集没有为这个 CSS IDL 访问器建立条目,脚本用第一条补充规则(extra:css-zoom-read)按类型感知方式(接收者类型 CSSStyleDeclaration + 成员 zoom)捕获它,这正是第 2 步中“盲区补充规则”的典型应用。
工程视角:如何使用这两张表
- 默认按 Baseline 2020 规划:主表(Chrome/Edge 87、Firefox 83、Safari 14 等)已足够宽裕,覆盖绝大多数用户;
- 只在你实际用到的特性触发时才看条件表:例如一个用了
flip的列表动画应用,若需支持 126 以下的 Firefox,需要自行兜底或放弃该动画; - 表格是自动推导的,不是人工维护的:任何新运行时 API 的引入、新子包导出的添加,都会在下次运行 generate-browser-support.ts 时自动进入扫描范围——
browser_subpaths()从exports自动发现新子包、enumerate_features()从RUNES与binding_properties自动枚举新特性,且缺少文档链接的新特性会直接抛错(第 660 行)阻止发布错误表格; - 该表只覆盖 Svelte 自身:05-browser-support.md 明确说明不含 SvelteKit、其他 Svelte 生态库与你的业务代码,后者的兼容需求需另行评估。
这套“打包真实产物 → 类型感知扫描 → 逐特性 fixture 验证 → Baseline 换算”的流水线,本质上是把浏览器兼容承诺从文档声明变成了可复现、可校验的构建步骤:条件特性表中的每一个版本号,都能回溯到某段具体源码(structuredClone 调用、device-pixel-content-box 字符串、.zoom 读取)与某条特性数据(web-features 或补充规则)的双重证据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00