news 2026/9/7 4:28:49

Svelte 编译器 `<script>` 编译错误全解:52 个 script 错误码、触发条件与源码级排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Svelte 编译器 `<script>` 编译错误全解:52 个 script 错误码、触发条件与源码级排查指南

Svelte 编译器<script>编译错误全解:52 个 script 错误码、触发条件与源码级排查指南

【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte

本文基于 Svelte 仓库中packages/svelte/messages/compile-errors/script.md这份错误消息定义文档,完整梳理 Svelte 编译器在<script>(含<script module>)层面会抛出的全部编译错误:每个错误码的含义、触发场景、修复方式,以及它们在编译器2-analyze阶段的真实触发点。读完本文,你可以准确读懂每一条CompileError的报错信息,定位对应的源码校验逻辑,并通过测试样例目录复现和验证。

错误消息是如何生成与抛出的

理解这份错误清单前,先看它的生产管线,这决定了你在报错信息中看到的固定格式:

  • 错误文案以 Markdown 形式维护在 messages/compile-errors/script.md 中,每个错误以## 错误码小节定义消息模板,%name%形式的占位符在抛出时被替换为实际值(如%rune%%name%%cycle%)。
  • 仓库通过 scripts/process-messages/templates/compile-errors.js 模板把每个小节转换成导出函数。该文件头部注明「This file is generated by scripts/process-messages/index.js. Do not edit!」——真正的可执行版本被生成到 src/compiler/errors.js。
  • 每个生成函数形如state_invalid_placement(node, rune),内部调用e(node, 'state_invalid_placement', 消息),构造一个继承自ErrorInternalCompileErrorname'CompileError'),并携带[start, end]源码位置,最后以模板字符串形式抛出,且每条消息尾部都追加https://svelte.dev/e/<错误码>形式的帮助链接。从源码结构看,这意味着报错信息可以直接被 bundler 插件捕获(构造函数注释里明确说明了「extend from Error so that various bundler plugins properly handle it」)。

因此,实际开发中遇到CompileError: $props() can only be used at the top level...时,错误码即对应本文档中某个小节标题,帮助链接指向官方错误文档页。

下面按主题分组完整覆盖文档中的全部 52 个错误码。

分组速查表

1. Props 与$props()/$bindable()校验

错误码消息触发场景
props_invalid_placement$props()can only be used at the top level of components as a variable declaration initializer$props()没写在组件实例脚本顶层的变量声明初始化位置,例如写进<script module>或函数内
props_duplicateCannot use%rune%()more than once同一组件中多次调用$props()(或对$props.id()重复调用)
props_invalid_identifier$props()can only be used with an object destructuring patternlet props = $props()而不是对象解构let { a } = $props()
props_invalid_pattern$props()assignment must not contain nested properties or computed keys解构模式里出现嵌套属性或计算键
props_id_invalid_placement$props.id()can only be used at the top level of components as a variable declaration initializer$props.id()未在顶层以简单标识符声明
props_illegal_nameDeclaring or accessing a prop starting with$$is illegal (they are reserved for Svelte internals)声明或访问以$$开头的 prop
bindable_invalid_location$bindable()can only be used inside a$props()declaration$bindable()出现在$props()声明之外

源码印证:phases/2-analyze/visitors/CallExpression.js 中对$props的三条校验与消息逐条对应——先用context.state.has_props_rune判重(props_duplicate),再校验父节点必须是VariableDeclarator、所在脚本类型是instance、且处于实例脚本作用域根(props_invalid_placement),最后要求$props()不接受任何实参(复用rune_invalid_arguments)。对$bindable(),同文件 L42-L52 沿 AST 路径向上回溯三层,确认它必须位于$props()解构模式内的AssignmentPattern中,否则抛bindable_invalid_location

2. Runes 调用形态校验($state/$derived/$effect等)

错误码消息说明
state_invalid_placement%rune%(...)can only be used as a variable declaration initializer, a class field declaration, or the first assignment to a class field at the top level of the constructor.$state$state.raw$derived$derived.by只能出现在三种位置
effect_invalid_placement$effect()can only be used as an expression statement$effect/$effect.pre不能作为表达式值传递,必须独立成句
rune_invalid_arguments%rune%cannot be called with arguments对不接受参数的 rune(如$host$effect.tracking)传了实参
rune_invalid_arguments_length%rune%must be called with %args%实参数量不对。从源码看,$derived/$derived.by要求「exactly one argument」,其余 rune 要求「zero or one arguments」(CallExpression.js L128-L132)
rune_invalid_computed_propertyCannot access a computed property of a rune形如$state['raw']的括号动态访问
rune_invalid_name%name%is not a valid rune引用了不存在的 rune,如$states$state.xyz
rune_invalid_spread%rune%cannot be called with a spread argument对 rune 使用展开调用
rune_invalid_usageCannot use%rune%rune in non-runes mode在非 runes 模式组件中使用 rune
rune_missing_parenthesesCannot use rune without parentheses裸引用 rune 而不带括号
rune_removedThe%name%rune has been removed已移除的 rune。Identifier.js L65-L67 中硬编码针对$state.is报此错
rune_renamed%name%is now%replacement%已更名的 rune。源码中对$effect.active$effect.tracking$state.frozen$state.raw专门给出替换建议(Identifier.js L57-L63)

rune_missing_parentheses在原文档中有专门展开:Runes 是关键字而非值,不能被赋值或作为参数传递,只能被调用,因此不带括号引用即报错:

let count = $state;

无论是 rune 本身($state)还是通过属性到达的 rune($derived.by),都要加上括号及所需参数:

let count = $state(0);

触发点印证:visitors/Identifier.js L40-L76 中,当标识符名符合 rune 形态、且作用域中没有同名变量遮蔽时,编译器沿父链向上合并MemberExpression拼出完整 rune 名逐一校验;若最终父节点不是CallExpression,就抛rune_missing_parentheses

一个特殊案例来自 visitors/EachBlock.js L21-L24:{#each items as $state}这种把 rune 名当作 each 块参数的写法,会直接复用state_invalid_placement报错,源码中还留有一条// TODO weird that this is necessary注释。

3. State 字段(class 中的 rune 声明)

错误码消息说明
state_field_duplicate%name%has already been declared on this class同一个字段被两次声明为 state 字段
state_field_invalid_assignmentCannot assign to a state field before its declaration在字段声明之前给它赋值(如构造函数内先赋值后声明)
state_invalid_exportCannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties模块中被重新赋值的$state不能直接导出
derived_invalid_exportCannot export derived state from a module. To expose the current derived value, export a function returning its value模块中的 derived 状态只能以函数形式对外暴露

state_field_duplicate原文档给出了完整示例。对使用了$state$derived的 class 字段赋值被视为state 字段声明,可以写在类体内:

class Counter { count = $state(0); }

也可以写在构造函数里:

class Counter { constructor() { this.count = $state(0); } }

同一字段只能声明一次,重复即报state_field_duplicate。触发逻辑见 visitors/PropertyDefinition.js L10-L18:按字段名在state_fields中查找已有记录,若当前节点不是原始声明节点且带值,还会比较start位置——先赋值后声明则抛state_field_invalid_assignment

4. 命名与声明规则

错误码消息说明
declaration_duplicate%name%has already been declared同一作用域重复声明同名变量
declaration_duplicate_module_importCannot declare a variable with the same name as an import from<script module>实例脚本变量与模块级 import 重名
duplicate_class_field%name%has already been declared类字段重名
constant_assignmentCannot assign to %thing%给常量(如const、import 等)赋值
constant_bindingCannot bind to %thing%bind:目标指向常量
dollar_binding_invalidThe $ name is reserved, and cannot be used for variables and imports变量或 import 恰好叫$
dollar_prefix_invalidThe $ prefix is reserved, and cannot be used for variables and imports变量或 import 以$开头(runes 模式下$前缀被保留)
global_reference_invalid%name%is an illegal variable name. To reference a global variable called%name%, useglobalThis.%name%想要引用形如$foo的全局变量
export_undefined%name%is not defined导出了未定义的名称
invalid_arguments_usageThe arguments keyword cannot be used within the template or at the top level of a component在模板或组件顶层使用arguments
module_illegal_default_exportA component cannot have a default export.svelte组件中出现export default
reactive_declaration_cycleCyclical dependency detected: %cycle%检测出循环依赖,%cycle% 会列出具体链条

global_reference_invalid有一条现成的测试样例 tests/compiler-errors/samples/store-global-disallowed,断言报错消息为`$foo` is an illegal variable name. To reference a global variable called `$foo`, use `globalThis.$foo`,可直接用于复现。

5.each块与 snippet 参数(不可变迭代目标)

each_item_invalid_assignmentCannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. \array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)`

原文档对这条错误做了完整的历史对比。Legacy 模式下允许直接给 each 块参数重新赋值或双向绑定:

<script> let array = [1, 2, 3]; </script> {#each array as entry} <!-- reassignment --> <button on:click={() => entry = 4}>change</button> <!-- binding --> <input bind:value={entry}> {/each}

这种做法后来被证明有缺陷、行为不可预测(尤其是在array.map(...)等派生值上遍历时),因此在 runes 模式中被禁止。等价写法是用下标代替:

<script> let array = $state([1, 2, 3]); </script> {#each array as entry, i} <!-- reassignment --> <button onclick={() => array[i] = 4}>change</button> <!-- binding --> <input bind:value={array[i]}> {/each}

对应测试 tests/compiler-errors/samples/runes-invalid-each-binding 校验的正是这条消息原文。

snippet_parameter_assignmentCannot reassign or bind to snippet parameter。snippet 参数与 each 块参数同理,是不可重新赋值、不可绑定的。

6. legacy 模式与 runes 模式的互斥规则

错误码消息说明
legacy_export_invalidCannot useexport letin runes mode — use$props()insteadrunes 组件中使用了export let
legacy_props_invalidCannot use$$propsin runes moderunes 组件中引用$$props
legacy_rest_props_invalidCannot use$$restPropsin runes moderunes 组件中引用$$restProps
legacy_reactive_statement_invalid$:is not allowed in runes mode, use$derivedor$effectinsteadrunes 组件中使用了$:响应式语句
legacy_await_invalidCannot useawaitin deriveds and template expressions, or at the top level of a component, unless in runes mode非 runes 模式下在 derived、模板表达式或组件顶层使用await
experimental_asyncCannot useawaitin deriveds and template expressions, or at the top level of a component, unless theexperimental.asynccompiler option istruerunes 模式下上述await用法需要显式开启experimental.async编译选项
runes_mode_invalid_import%name% cannot be used in runes mode在 runes 模式导入了与之冲突的旧式 API(如 legacy 生命周期函数)

从源码看,legacy 标识符在 Identifier.js L81-L89 中还有专门处理:非 runes 模式引用$$props/$$restProps时会设置analysis.uses_props/uses_rest_props标记,供后续 transform 阶段生成 props 对象使用。

7. Store 订阅($前缀自动订阅)

错误码消息
store_invalid_scoped_subscriptionCannot subscribe to stores that are not declared at the top level of the component
store_invalid_subscriptionCannot reference store value inside<script module>
store_invalid_subscription_moduleCannot reference store value outside a.sveltefile

store_invalid_subscription_module原文档有补充解释:用$前缀引用 store 值只在.svelte文件内有效,因为只有在那里 Svelte 才能自动在组件挂载时创建订阅、卸载时取消订阅;文档同时建议考虑迁移到 runes。三条错误的共同背景:自动订阅依赖组件生命周期管理,因此 store 必须声明在组件顶层、且不能在<script module>(无组件实例)或.svelte文件之外使用$store语法。

8. 其他专项

错误码消息说明
host_invalid_placement$host()can only be used inside custom element component instances$host()只能在customElement组件实例中使用,且不能带参数(CallExpression.js L59-L66 同时校验参数为空和custom_element标记)
inspect_trace_generator$inspect.trace(...)cannot be used inside a generator function生成器函数中无法使用 trace
inspect_trace_invalid_placement$inspect.trace(...)must be the first statement of a function bodytrace 必须是函数体第一条语句
snippet_invalid_exportAn exported snippet can only reference things declared in a<script module>, or other exportable snippets导出 snippet 的作用域限制
import_svelte_internal_forbiddenImports ofsvelte/internal/*are forbidden...禁止导入内部运行时代码,因其属于私有实现且随时可能变更

snippet_invalid_export的原文档示例值得保留。你不能这样做:

<script module> export { greeting }; </script> <script> let message = 'hello'; </script> {#snippet greeting(name)} <p>{message} {name}!</p> {/snippet}

原因是greeting引用了第二个<script>(实例作用域)里定义的message,而导出 snippet 只能引用<script module>中声明的东西或其他可导出 snippet。

import_svelte_internal_forbidden的消息原文还建议:如果是为了绕过 Svelte 的某个限制而导入svelte/internal/*,请到官方 Svelte 项目仓库提交 issue 说明使用场景。

9. TypeScript

typescript_invalid_feature:TypeScript language features like %feature% are not natively supported, and their use is generally discouraged. Outside of<script>tags, these features are not supported. For use within<script>tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are usingvitePreprocess, make sure to specifically enable preprocessing script tags (vitePreprocess({ script: true }))。

要点:<script>外的 TS 特性直接不支持;<script>内需要通过 preprocessor 先转成 JS 再交给编译器,使用vitePreprocess时务必显式开启script: true

这些错误在哪里被触发:2-analyze 阶段的 visitor 机制

从源码结构看,以上错误全部在编译的第二阶段(分析阶段)由 AST visitor 抛出,核心文件与职责如下:

  • phases/2-analyze/visitors/CallExpression.js:runes 调用形态校验中心——$bindable/$host/$props/$props.id/$state系列/$effect系列的位置、参数、重复性检查都在这里完成(如 L115-L145 对$state/$derived的位置三条件与实参数量校验)。
  • phases/2-analyze/visitors/Identifier.js:所有以$开头的标识符统一入口,负责rune_invalid_namerune_missing_parenthesesrune_renamedrune_removedrune_invalid_computed_property的判定。
  • phases/2-analyze/visitors/EachBlock.js:each 块上下文校验,包括把 rune 名误当 each 参数的特判。
  • phases/2-analyze/visitors/PropertyDefinition.js:类字段的 state 声明顺序校验(state_field_invalid_assignment)。
  • phases/2-analyze/visitors/VariableDeclarator.js:$props()解构模式逐属性校验(props_invalid_pattern针对嵌套属性/计算键)。
  • 统一出口为 src/compiler/errors.js 中按消息文件分组生成的导出函数,全部经InternalCompileError携带位置信息抛出。

如何验证与复现:compiler-errors 测试套件

仓库内置了针对这些错误的完整回归测试,每个样例目录包含一个能触发错误的.svelte/.js输入文件和一份_config.js断言(错误码 + 消息原文):

  • 样例总入口:tests/compiler-errors/test.ts,样例位于 tests/compiler-errors/samples/ 下,目录名与错误场景一一对应,如runes-wrong-state-placementrunes-wrong-props-placement-instancerunes-invalid-each-bindingstore-global-disallowedsnippet-invalid-exportrunes-before-after-update等。
  • 代表性断言示例:runes-wrong-state-placement/_config.js 断言错误码state_invalid_placement及其完整消息;runes-invalid-each-binding/_config.js 断言each_item_invalid_assignment的索引替代写法提示。

排查建议:遇到某个 script 编译错误时,先记下错误码,在本文分组表中定位语义;再按上文的 visitor 文件路径找到对应校验分支;最后到tests/compiler-errors/samples/下找同名样例,对照输入文件即可看清该错误的最小复现形式与期望消息。

小结

  • 这份 script.md 是 Svelte 编译器<script>编译错误的唯一事实来源,52 个错误码覆盖了 runes 使用位置、参数形态、legacy/runes 模式互斥、命名保留字、store 自动订阅作用域、snippet 导出边界、state 字段声明与 TypeScript 预处理等全部<script>层面约束。
  • 每条错误都经 process-messages 模板 生成带svelte.dev/e/<错误码>链接的CompileError抛出,报错信息可直接作为检索与定位的索引。
  • 所有校验集中在2-analyze阶段的 AST visitors 中实现,配合tests/compiler-errors/samples/下的样例断言,形成了「消息定义 → 生成函数 → 校验触发 → 测试固化」的完整闭环。

【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:28:39

零氪断网禁豆无损:旧版功夫世界第30关通关机制解析

很多老玩家应该还记得&#xff0c;早期版本《植物大战僵尸2》最难熬的关卡之一&#xff0c;就是功夫世界第30关的最终战。当年卡在这关的人非常多&#xff0c;有人以为是植物等级不够&#xff0c;有人以为要充钱买强力植物&#xff0c;还有人反复试了几十次都过不去&#xff0c…

作者头像 李华
网站建设 2026/9/7 4:27:15

基于MyEMS与CNN-LSTM的设备故障预警实战:92%准确率如何炼成

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:25:51

Ace Data Cloud实战:基于RAG的知识库问答系统构建全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:25:42

Corba Explorer实战:从命名服务连接到IOR排错的完整指南

简介&#xff1a;面向CORBA服务端开发与测试人员的实用工具箱&#xff0c;以CORBA Explorer为核心&#xff0c;用于浏览对象引用、调用接口并验证分布式服务端信息&#xff0c;在服务端调试、接口契约确认和异常排查等环节都能派上用场。压缩包共538个文件&#xff0c;包含99个…

作者头像 李华
网站建设 2026/9/7 4:25:18

IDA Pro反编译.so文件实战:从定位关键函数到还原核心算法

简介&#xff1a;面向逆向工程与安全分析学习者的反编译 .so 文件配套附件&#xff0c;依托 IDA Pro 工具链&#xff0c;聚焦移动端原生库的静态分析与交叉引用梳理&#xff0c;适合具备 C/C 基础的读者进阶。压缩包共 2000 个文件&#xff0c;以 Python 脚本、文本说明、头文件…

作者头像 李华