Svelte 编译器错误全解:错误码目录、输出格式与底层生成机制
在 Svelte 项目中,编译期错误(compiler errors)是 Svelte 编译器在解析与校验 .svelte 文件时抛出的一类“带错误码、带源码位置、可被工具链程序化消费”的异常。本文以官方参考文档 Compiler errors 为主体,梳理其完整的错误码目录结构、每类错误码的典型触发场景,并深入 消息生成脚本、错误诊断类 与 编译器错误函数文件 的源码,说明一个错误码是如何从 Markdown 文档一路变成 CompileError 异常与文档页面的,帮助你在构建工具中正确捕获、展示和排查编译错误。
文档页面的实现:一行 @include 背后
30-compiler-errors.md 这个参考文档本身极简,正文只有一行:
@include .generated/compile-errors.md
也就是说,页面上列出的全部错误码内容并非手写在文档里,而是由脚本生成到 documentation/docs/98-reference/.generated/compile-errors.md(约 1264 行)后被包含进来。该生成文件开头明确标注 “This file is generated by scripts/process-messages/index.js. Do not edit!”。理解这一点很重要:错误码的唯一事实来源(single source of truth)是 packages/svelte/messages/compile-errors/ 目录下的 Markdown 源文件,文档页面和编译器抛错的 JS 函数都是由它派生的。
编译错误的输出格式:码、消息与代码帧
当编译器遇到错误时,会抛出 CompileError。从 compile_diagnostic.js 的实现看,每个诊断对象包含如下字段:
code:错误码(如block_unclosed),用于机器判断错误类型;message:人类可读的错误消息,末尾附带https://svelte.dev/e/<code>形式的说明页链接(见 compile-errors.js 模板 中e(node, 'CODE', ...)的拼接方式);filename、start、end、position:出错位置的文件名与行列信息;frame:代码帧(code frame)。
其中代码帧由 get_code_frame 生成:它取出错行的前 2 行与后 3 行,在出错位置下方用 ^ 指示具体列,并把行首 Tab 统一替换为两个空格以对齐指示符。因此终端中的典型输出形如(格式依据 get_code_frame 与 toString 的实现):
block_unclosed: Block was left open
src/App.svelte:5:0
3: <div>
4: <h1>hi</h1>
5: {#if show}
^
CompileDiagnostic 同时提供 toJSON(),返回 { code, message, filename, start, end, position, frame } 的纯数据对象。这对构建工具很有用:在 Vite 插件或自定义打包流程中,你可以 catch 到 CompileError 后调用 toJSON() 拿到结构化数据,再转成 IDE 悬浮提示或 CI 报告,而不是解析字符串。
另外注意抛出路径的细节:src/compiler/errors.js 中的内部类 InternalCompileError 继承自 Error,并把 CompileDiagnostic 的属性复制到自身上——源码注释说明这是为了让各种 bundler 插件能正确识别普通 Error,同时保持与 warning 相同的对象形状。它的 name 被设为 'CompileError',且故意清空 stack 以避免噪音。
错误码目录的结构:四个文件,各自管辖一类问题
生成脚本遍历 messages 目录下的分类目录,编译错误对应 compile-errors,其源文件按错误来源拆分为四个 Markdown 文件:
| 源文件 | 管辖范围 | 错误码数量(约) |
|---|---|---|
| options.md | 编译器选项 | 3 |
| script.md | <script> 块与 runes |
53 |
| template.md | 模板语法、元素、绑定、svelte: 元元素 | 约 100 |
| style.md | <style> 块与 :global 选择器 |
14 |
源文件的书写规范(决定生成的文档与函数长什么样)是:
## <code>标题声明一个错误码,每个文件内按字母序排列,出现重复错误码会直接抛Duplicate message code错误;- 紧随其后的
>引用块是错误消息本身;多个引用块表示消息重载(overload),后一条必须比前一条引入新的%参数%占位符,否则脚本报Message overloads must have new parameters; %name%形式的占位符会被转换为对应错误函数的参数,并在生成代码的 JSDoc 中体现为@param;- 引用块之后的普通文本(含代码示例)作为“details”附录,出现在生成文档中,解释错误的来龙去脉与修法。
这一套格式规范定义在 process-messages/index.js 的解析逻辑中,脚本还支持 -w 监听模式,在 messages 目录变化时自动重新生成。
options 类:编译器选项错误
options.md 中最具实践价值的是 options_unrecognised:
Unrecognised compiler option %keypath%
配合 validate-options.js 中的校验逻辑可以确认:Svelte 对未知选项采取“宽容但不沉默”的策略——已移除的旧选项(如 loopGuardTimeout、enableSourcemap、hydratable 等)会通过 warn_removed 走 warning 通道(options_removed),而完全无法识别的选项键则抛出 options_unrecognised,keypath 支持嵌套路径(如 foo.bar)。取值不合法则触发 options_invalid_value(消息为 Invalid compiler option: %details%)。这意味着在升级 Svelte 大版本后,构建配置里残留的旧选项要么收到弃用警告、要么直接编译失败,是很好的版本迁移“哨兵”。
script 类:runes 模式与 legacy 模式的边界
script.md 是错误码最密集的文件,核心主题是在 runes 模式与 legacy 模式之间,哪些写法被禁止、必须如何改写。
$props() 的形态约束
props_invalid_placement:$props()只能作为组件顶层的变量声明初始化器使用;props_invalid_identifier:$props()只能配合对象解构模式使用(即let { a, b } = $props(),不能给一个标识符赋值);props_invalid_pattern:解构中不能出现嵌套属性或计算键;props_duplicate:$props()不允许调用多次;props_illegal_name:以$$开头的 prop 名是保留的;bindable_invalid_location:$bindable()只能在$props()声明内部使用;props_id_invalid_placement:$props.id()只能出现在组件顶层的变量声明初始化器中。
legacy 写法进入 runes 模式
Svelte 5 起组件默认可用 runes,编译器会阻止两套语法混用:
legacy_export_invalid:runes 模式下不能使用export let,改用$props();legacy_reactive_statement_invalid:runes 模式下不允许$:语句,改用$derived/$effect;legacy_props_invalid/legacy_rest_props_invalid:runes 模式下不能使用$$props与$$restProps;legacy_await_invalid:非 runes 模式(或实验选项未开启)时,derived 与模板表达式内不能使用await。反之,在 runes 中误用非 runes 语法会触发rune_invalid_usage(Cannot use %rune% rune in non-runes mode)。
rune 误用与改名
rune_invalid_name:%name% is not a valid rune——当你写了$statex这类形似 rune 的标识符时触发;rune_missing_parentheses:rune 是关键字而非值,不能赋值或传参,只能调用。details 部分给出了正反示例:let count = $state报错,应写为let count = $state(0);rune_renamed/rune_removed:针对已改名或已移除的 rune 的升级提示,%name% is now %replacement%这类消息直接告诉你新名字;rune_invalid_arguments/rune_invalid_arguments_length/rune_invalid_spread:约束每个 rune 的参数形态;rune_invalid_computed_property:不允许访问 rune 的计算属性。
$state 声明位置与模块导出
state_invalid_placement:%rune%(...)只能用作变量声明初始化器、类字段声明,或构造函数顶层对类字段的首次赋值。这解释了为什么count = $state(0)(裸赋值)不合法,而this.count = $state(0)在构造函数中合法;state_field_duplicate:同一个类字段用$state/$derived声明只能发生一次(类体内或构造函数内各算一次机会);details 部分给出了class Counter { count = $state(0); }等示例;state_invalid_export:模块中被重新赋值的 state 不能直接export,应导出返回其值的函数,或只修改该 state 的属性;derived_invalid_export:同理,derived state 不能从模块导出,应导出一个返回其当前值的函数。
对 each 块参数的赋值
each_item_invalid_assignment 是 runes 模式下最典型的行为变化:不允许对 each 块参数重新赋值或绑定,应改用数组与索引变量。错误消息直接内嵌了修法,而 details 部分给出了完整的 legacy 与 runes 对照示例:
<!-- legacy 允许、runes 下报错的写法 -->
{#each array as entry}
<button on:click={() => entry = 4}>change</button>
<input bind:value={entry}>
{/each}
<!-- runes 下的正确写法 -->
<script>
let array = $state([1, 2, 3]);
</script>
{#each array as entry, i}
<button onclick={() => array[i] = 4}>change</button>
<input bind:value={array[i]}>
{/each}
store 订阅与作用域
store_invalid_subscription_module:$前缀访问 store 值只能在.svelte文件中使用,因为 Svelte 只在组件挂载/卸载时自动管理订阅;details 建议迁移到 runes;store_invalid_subscription:不能在<script module>内引用 store 值;store_invalid_scoped_subscription:不能订阅未在组件顶层声明的 store。
其他高频项
import_svelte_internal_forbidden:禁止导入svelte/internal/*,内部 API 随时可能变化,遇到 Svelte 本身的限制应去上游提 issue;typescript_invalid_feature:TypeScript 特性(如类型标注)不被原生支持,<script>内需要预处理器转换(如vitePreprocess({ script: true })),<script>外则完全不支持;declaration_duplicate/duplicate_class_field:重复声明检测;export_undefined:导出了未定义的名字;reactive_declaration_cycle/const_tag_cycle:响应式声明之间的循环依赖检测,%cycle%会列出依赖环;experimental_async:在 derived、模板表达式或组件顶层使用await需要开启experimental.async编译选项;snippet_invalid_export:导出的 snippet 只能引用<script module>中声明的东西或其他可导出 snippet。details 部分给出了完整反例——greetingsnippet 引用了实例<script>中的message,因此不能作为模块级导出。
template 类:解析、属性、绑定与 svelte: 元元素
template.md 覆盖了模板层面的全部硬性错误,可按主题分组理解。
解析与块结构
block_unclosed(Block was left open)、element_unclosed(<%name%>was left open)、unexpected_eof(Unexpected end of input):未闭合块/元素与文件截断;block_invalid_continuation_placement:{:else}等续接块位置不对,消息里直接提示“是不是忘了闭合前面的元素或块”;block_invalid_elseif:拼写纠正提示,'elseif' should be 'else if';expected_block_type/expected_tag:{#...}必须是if/each/await/key/snippet之一,{@...}必须是html/render/attach/const/debug之一;element_invalid_closing_tag与element_invalid_closing_tag_autoclosed:处理 HTML 自动闭合带来的结构推断冲突(如<li>未闭合又被下一个<li>顶掉);void_element_invalid_content:void 元素不能有子节点或闭合标签;node_invalid_placement:浏览器会“修复”违反 HTML 约束的结构从而破坏 Svelte 对组件结构的假设。details 部分举了三个经典例子:<p>hello <div>world</div></p>中<p>被自动闭合、<option>内的<div>被移除、<table>内自动插入<tbody>。
属性与绑定
attribute_duplicate:同一元素上属性必须唯一;attribute_invalid_sequence_expression:runes 模式下属性值不能是逗号序列表达式(如class={size, color})。details 给出两条修法:用数组class={[size, color]},或确需逗号运算符时加括号(size, color)(注意其求值结果是color,size被忽略);attribute_invalid_event_handler:事件属性(如onclick)必须是 JS 表达式而不能是字符串;attribute_contenteditable_missing/attribute_contenteditable_dynamic:textContent/innerHTML/innerText双向绑定要求静态的contenteditable属性;bind_invalid_expression:只能绑定到标识符、成员表达式或{get, set}对;bind_invalid_target说明某些bind:只能用于特定元素(如bind:height之于<iframe>等);bind_group_invalid_expression要求bind:group目标是标识符或成员表达式;bind_invalid_name:无效绑定名,且有带%explanation%的重载消息提供更具体的解释。
事件
mixed_event_handler_syntaxes:同一组件上混用旧语法(on:click)与新语法(onclick)不被允许,统一使用on%name%语法;event_handler_invalid_modifier/event_handler_invalid_modifier_combination:修饰符白名单与互斥组合校验;event_handler_invalid_component_modifier:除once外的修饰符只能用于 DOM 元素。
svelte: 元元素与 svelte:options
svelte_component_missing_this/svelte_element_missing_this:<svelte:component>必须有this属性,<svelte:element>必须有带值的this属性;svelte_meta_invalid_tag:列出合法的<svelte:*>标签名清单;svelte_meta_invalid_placement:<svelte:window>、<svelte:body>、<svelte:head>、<svelte:document>等不能出现在元素或块内部;svelte_options_invalid_customelement系列:校验<svelte:options>中customElement的形态——必须是合法自定义元素名的字符串字面量或{ tag, shadow, props }结构体,且props必须是静态可分析的{ [key: string]: { attribute?, reflect?, type? } }对象字面量,shadow只能是"open"/"none"或ShadowRootInit。
snippet、render 标签与 slot 的共存规则
render_tag_invalid_expression:{@render ...}只能包含调用表达式;render_tag_invalid_call_expression禁止用apply/bind/call调用 snippet 函数;render_tag_invalid_spread_argument禁止 spread 实参;slot_snippet_conflict:同一组件内不允许<slot>语法与{@render ...}标签共存,要求彻底迁移到{@render};snippet_conflict:显式 children snippet 与隐式 children 内容不能同时存在;const_tag_invalid_reference:{@const}声明在 snippet 作用域中的可见性问题。details 部分给出了关键示例——<svelte:boundary>(或组件)顶层代码属于隐式childrensnippet,其中的{@const foo = ...}在同级定义的failedsnippet 里不可见,并解释了这背后的 snippet 等价展开。
其余实用项
each_key_without_as:没有as子句的{#each}不能带 key;animation_missing_key/animation_invalid_placement:animate:指令要求元素是带 key 的{#each}的唯一子元素,消息里甚至贴心地问“你是不是忘了给 each 加 key”;transition_conflict/transition_duplicate:同一元素上不同/相同类型的 transition 指令冲突;script_duplicate/style_duplicate:组件至多一个顶层<script>(和/或一个<script module>)与一个<style>;js_parse_error:内嵌 JS 解析失败时透传底层解析器消息。
style 类::global 选择器规则
style.md 的全部错误都围绕 scoped CSS 中 :global 的合法用法:
css_global_invalid_placement::global(...)只能出现在选择器序列的开头或结尾,不能在中间;css_global_invalid_selector::global(...)必须恰好包含一个选择器;css_global_invalid_selector_list:作为复合选择器的一部分时,内部不能含类型选择器或通配选择器;css_type_selector_invalid_placement::global(...)后面不能紧跟类型选择器;css_global_block_invalid_modifier/css_global_block_invalid_modifier_start::global块不能“修饰”已存在的选择器,除非它本身是其他选择器的后代;css_global_block_invalid_combinator::global选择器不能紧跟某个组合符;css_global_block_invalid_list::global块不能与普通选择器混排在同一个选择器列表里。details 给出了具体反例与拆法——:global, x { y { color: red; } }必须拆成:global { y { ... } }与x y { ... }两个独立规则,因为块内“全部非 scoped”与外层“scoped”两种语义无法合并转换;css_global_block_invalid_declaration:顶层:global {...}块内只能有规则、不能有声明;css_nesting_selector_invalid_placement:嵌套选择器只能用于规则内部,或独立:global(...)的第一个选择器位置;css_selector_invalid/css_expected_identifier/css_empty_declaration:CSS 解析层的基础错误。
底层机制:从 Markdown 到 CompileError 的完整链路
理解 process-messages/index.js 的生成流程后,可以完整还原一条错误码的“一生”:
- 编写:维护者在
packages/svelte/messages/compile-errors/*.md中按## 错误码+> 消息+ 可选 details 的格式新增条目; - 解析与排序:脚本用正则
## ([\w]+)\n\n...提取每个错误码,检测重复,按字母序回写源文件; - 生成文档:把所有错误码渲染为
### 错误码+ 消息代码块(+ details)的 Markdown,写入documentation/docs/98-reference/.generated/compile-errors.md,即官方文档页面包含的内容; - 生成代码:脚本读取 templates/compile-errors.js 中的模板函数
CODE(node, PARAMETER),为每个错误码克隆出一份具名导出函数——如export function options_invalid_value(node, details)——函数体内部调用e(node, 'options_invalid_value',Invalid compiler option: ${details}\nhttps://svelte.dev/e/options_invalid_value)。%var%占位符在此阶段被转换为模板字符串插值与函数参数,重载消息则被编译为按“是否传入更多参数”分支的三元表达式; - 抛出:编译器各阶段(parse/analyze/transform)在检测出违规时调用对应函数,
e()构造InternalCompileError(name为'CompileError'),并基于传入的 AST 节点起点/终点(或数字位置)挂上行列信息与代码帧。
这套机制保证了三处永远一致:编译器实际抛出的消息、官方文档中的说明、错误函数的类型签名(JSDoc),三者都由同一份 Markdown 派生。
测试保障:compiler-errors 测试套件
仓库中的 tests/compiler-errors/ 目录为错误检测本身提供回归保障:samples/ 下每个子目录是一个用例,包含一个故意触发错误的 .svelte 文件与一个 .js 期望文件(如 each-key-without-as、runes-wrong-state-args、const-tag-cyclical 等上百个样例),由 test.ts 统一驱动编译并比对抛出的错误码与消息。如果你在上游看到某个错误码的样例目录被新增或修改,基本可以推断对应的错误检测逻辑同步发生了变化。
实践建议:如何在构建工具中消费编译错误
- 捕获与展示:在 Vite 插件、esbuild loader 或自定义构建脚本中
try/catchSvelte 编译调用;判断err.name === 'CompileError'后调用toJSON(),用code做程序化分支(例如对each_item_invalid_assignment给出“这是 runes 行为变化”的定制提示),用frame/start/end渲染 IDE 级别的错误定位; - 错误码即文档锚点:每个错误消息自带
https://svelte.dev/e/<code>链接,而仓库内的 错误码文档 与 生成文件 提供等价的离线查阅入口,排障时按错误码检索即可; - 区分四类诊断:Svelte 的诊断体系分为编译器错误(本文)、编译器警告(不中断编译)、运行时错误 与运行时警告,它们共用同一套消息生成管线,但分属
compile-errors、compile-warnings、client-errors、shared-errors等不同类别目录,排障时先确认错误出现的阶段(编译期还是运行期)能避免查错手册; - 升级检查:
options_removed/options_unrecognised与rune_renamed这类错误码是版本迁移的天然检查点,升级大版本后跑一遍构建,任何残留的旧选项或旧语法都会以确定性错误暴露出来。
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 StartedRust0624
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