ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Svelte 编译器 Script 警告完全指南:11 个 script 类 compile-warnings 的原理、触发条件与修复方案

Svelte 编译器 Script 警告完全指南:11 个 script 类 compile-warnings 的原理、触发条件与修复方案 Svelte 编译器 Script 警告完全指南11 个 script 类 compile-warnings 的原理、触发条件与修复方案【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本篇指南基于 Svelte 官方警告文档 script.md逐一讲解 Svelte 编译器在解析组件script阶段会产生的全部 11 个警告warning它们的准确文案、触发条件、源码中的判定逻辑以及修复方式。读完后你将能够准确读懂non_reactive_update、state_referenced_locally、store_rune_conflict等警告背后的检测机制并知道在什么场景下出现该警告、应如何改写代码消除它。警告消息的生成机制从 Markdown 到编译器在逐条讲解之前先理解这些警告是如何长出来的有助于你在遇到陌生警告时快速定位。Svelte 仓库将每类警告的文案定义在 packages/svelte/messages/ 目录下的 Markdown 文件中按类别拆分a11y.md、misc.md、options.md、script.md、style.md、template.md。packages/svelte/scripts/process-messages/index.js 会在构建时读取这些 Markdown生成 packages/svelte/src/compiler/warnings.js文件头部标注This file is generated ... Do not edit!。生成的文件中有两个关键结构警告代码注册表codes数组见 warnings.js#L41-L123列出全部警告代码包括本文覆盖的custom_element_props_identifier、non_reactive_update、state_referenced_locally、store_rune_conflict等。这个数组同时是svelte-ignore注释的校验依据——拼错忽略代码会触发unknown_code警告代码已废弃则会触发legacy_code警告。统一的上报函数w()见 warnings.js#L25-L39每个警告都有对应的导出函数如non_reactive_update(node, name)内部都调用w(node, code, message)。w()做了三件事结合ignore_map检查当前 AST 节点是否被svelte-ignore覆盖命中则直接吞掉构造InternalCompileWarning携带 code、message 和节点的[start, end]源码位置通过warning_filter过滤后推入全局warnings数组最终随编译结果返回给构建工具。每条警告消息尾部都附带https://svelte.dev/e/code形式的文档链接方便从终端报错直接跳转到文档。以下按 script.md 的原始顺序逐条展开。custom_element_props_identifier自定义元素的 props 无法推断警告文案Using a rest element or a non-destructured declaration with$props()means that Svelte cant infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying thecustomElement.propsoption.触发条件与源码印证当以customElement: true编译组件时Svelte 需要知道哪些 prop 要暴露为自定义元素的 HTML 属性。检查逻辑位于 VariableDeclarator.js#L72-L83当analysis.custom_element为真且customElementOptions.props null时若$props()的声明形式是裸标识符如let props $props()或对象解构中包含 RestElement如let { a, ...rest } $props()就会对该声明节点发出此警告。修复方式!-- 触发警告 -- script let { name, ...rest } $props(); /script!-- 修复方式一完整解构所有 props -- script let { name, title } $props(); /script// 修复方式二显式声明 customElement.props import { compile } from svelte/compiler; compile(source, { customElement: true, customElementOptions: { props: { name: String, title: String } } });export_let_unused未被使用的 export let 属性警告文案Component has unused export property %name%. If it is for external reference only, please consider usingexport const %name%含义与修复这是 legacySvelte 4 风格export let组件接口的警告某个通过export let声明的属性在组件内部从未被读取。若该属性只是供外部通过实例 API 访问应改用export const在 runes 模式下则对应通过$props()解构但从未使用的 prop这类未被消费的状态。!-- 触发警告name 声明了却在模板/逻辑中从未使用 -- script export let name; /script pHello!/p!-- 若只是给外部引用改为 export const -- script export const name world; /script注意在 Svelte 5 中export let本身已属于 legacy 语法长期方案是迁移到$props()参见 legacy-props 文档。legacy_component_creationSvelte 5 组件不再是 class警告文案Svelte 5 components are no longer classes. Instantiate them usingmountorhydrate(imported from svelte) instead.含义与修复这条警告直接指向 Svelte 5 最核心的运行时 API 变更组件不再是new Component({ target, props })这样的 class必须改用函数式 API// 旧写法触发 legacy_component_creation 警告 import App from ./App.svelte; const app new App({ target: document.body, props: { name: world } }); // 新写法 import { mount } from svelte; const app mount(App, { target: document.body, props: { name: world } }); // 服务端渲染产物的水合同样改用 hydrate import { hydrate } from svelte; hydrate(App, { target: document.body });更多细节包括bind:this返回值变化、$set/$on/$destroy的替代方案见 v5 迁移指南 Components are no longer classes 一节。non_reactive_update非$state变量被重新赋值警告文案%name%is updated, but is not declared with$state(...). Changing its value will not correctly trigger updates触发条件文档原文列出的三条均需在 runes 模式成立变量未经$state或$state.raw声明该变量被重新赋值该变量在响应式上下文通常是模板中被读取。此时改变它的值不会正确触发更新。文档给出的示例script let reactive $state(reactive); let stale stale; /script pThis value updates: {reactive}/p pThis value does not update: {stale}/p button onclick{() { stale updated; reactive updated; }}update/button修复方式用$state包裹声明即let stale $state(stale);。源码判定逻辑检测代码在 2-analyze/index.js#L737-L778。编译器遍历module.scope与instance.scope中所有kind normal reassigned的 binding再检查其引用路径只有当引用直接位于Fragment模板表达式下才告警。从源码结构看这里有两个精细的豁免规则若引用路径上出现FunctionDeclaration/FunctionExpression/ArrowFunctionExpression说明该变量只是被某个闭包引用而非直接被模板响应式读取则跳过若是bind:this绑定的变量且不在IfBlock/EachBlock/AwaitBlock/KeyBlock内则视为不会变化的 DOM 引用而豁免bind:this在会重建节点的块内仍需 state否则块重建后引用失效。!-- 以下写法不会触发 non_reactive_update 变量仅被函数闭包引用不在模板中直接读取 -- script let log []; function record() { log.push(Date.now()); // 闭包内引用不触发警告 } /scriptperf_avoid_inline_class避免new class警告文案Avoid new class — instead, declare the class at the top level scope触发条件与源码印证位于 NewExpression.js#L9-L12当new的目标是ClassExpression且当前function_depth 0即不在模块顶层时触发。性能动机在于类声明在顶层作用域会被提升到模块/组件作用域实例化时可直接引用而new class {...}会在每次执行到该表达式时创建一个新的类构造器无法被缓存复用。// 触发警告 function createCounter() { return new class { count 0; increment() { return this.count; } }; } // 推荐写法 class Counter { count 0; increment() { return this.count; } } function createCounter() { return new Counter(); }perf_avoid_nested_class避免在顶层以下声明 class警告文案Avoid declaring classes below the top level scope触发条件与源码印证位于 ClassDeclaration.js#L15-L22。源码中的注释说明了规则在模块脚本中只允许function_depth 0顶层在组件实例脚本中允许function_depth 1组件实例脚本整体被视为组件函数体深度 1 即组件作用域的顶层而new class表达式连组件作用域层面也不允许。原因与上一条相同——嵌套 class 声明会在组件每次实例化时重新创建类产生不必要的重复分配。script // OK组件作用域顶层等价于 function_depth 1 class Item {} function make() { class Nested {} // 触发 perf_avoid_nested_class return new Nested(); } /scriptreactive_declaration_invalid_placement$:声明位置错误警告文案Reactive declarations only exist at the top level of the instance script含义这是 legacy 响应式声明$:语句Svelte 4 语法的位置约束$: doubled count * 2只能出现在实例脚本不带module属性的script的顶层不能出现在script contextmodule、函数体内或模板中。runes 模式下等价的能力是$derived见 $derived 文档legacy$:语法的完整说明见 legacy reactive statements 文档。script let count $state(1); $: doubled count * 2; // OK实例脚本顶层 function f() { $: invalid count; // 触发 reactive_declaration_invalid_placement } /scriptreactive_declaration_module_script_dependency模块级变量参与响应式语句警告文案Reassignments of module-level declarations will not cause reactive statements to update含义与源码印证响应式语句legacy$:声明依赖其引用的变量重新赋值来触发更新但模块脚本script module中的声明被重新赋值不会触发组件内的响应式语句重新执行——模块作用域没有绑定到组件实例的响应式系统。检测代码在 Identifier.js#L154-L160当引用发生在响应式语句内context.state.reactive_statement为真、且该 binding 属于analysis.module.scope、且该变量确实被重新赋值binding.reassigned时触发。script module let theme light; export { theme }; /script script import { theme } from ...; // 模块级引用 $: label theme light ? dark mode : light mode; // 触发警告 // 模块级 theme 重新赋值不会让该响应式语句重新执行 /scriptstate_referenced_locally状态引用被局部捕获警告文案This reference only captures the initial value of%name%. Did you mean to reference it inside a %type% instead?触发条件文档原文三条声明了一个响应式变量$state/$derived/prop 等该变量之后会被重新赋值它在同一作用域中被值捕获式引用例如作为函数参数、被setContext传递。这会断开与原始 state 声明的链接。文档中的经典场景是把 state 通过 context 传给子组件!--- file: Parent.svelte --- script import { setContext } from svelte; let count $state(0); // warning: state_referenced_locally setContext(count, count); /script button onclick{() count} increment /button!--- file: Child.svelte --- script import { getContext } from svelte; const count getContext(count); /script !-- This will never update -- pThe count is {count}/p修复方式让引用变为惰性求值——把 state 包进函数传递需要值时再调用读取!--- file: Parent.svelte --- script import { setContext } from svelte; let count $state(0); setContext(count, () count); /script button onclick{() count} increment /button!--- file: Child.svelte --- script import { getContext } from svelte; const count getContext(count); /script !-- This will update -- pThe count is {count()}/p更多背景见 $state 文档 Passing state into functions 一节JavaScript 是 pass-by-value 语言直接传值就固定了初始值传递 getter 函数才能拿到当前值。源码判定逻辑完整条件在 Identifier.js#L104-L152。从源码可以读出几个精确细节仅 runes 模式生效且引用位置必须与声明处于同一function_depth跨函数边界的不算本地捕获对kind state的 binding额外要求被重新赋值或初始化表达式是单参数、且参数不可被 proxy 包装的$state调用——因为可被 proxy/freeze 的非基本类型如对象、数组天然支持深层访问此时值捕获并不致命警告帮助有限源码注释原文isnt that helpfulraw_state、derived、prop、rest_prop则无此豁免仅针对读操作排除赋值表达式的左值和自增自减表达式消息里的%type%closure或derived由沿父节点回溯得到若捕获点位于某次$derived(...)调用的参数中提示是否想引用在 derived 内部否则提示引用在闭包内部。store_rune_conflict$前缀局部绑定与 rune 冲突警告文案It looks like youre using the$%name%rune, but there is a local binding called%name%. Referencing a local variable with a$prefix will create a store subscription. Please rename%name%to avoid the ambiguity含义与源码印证在 runes 模式下$name既可能是对名为name的变量的 store 订阅legacy$store语法又可能是用户想使用的 rune。检测逻辑位于 2-analyze/index.js#L403-L413当一个$xxx表达式被识别为 store 订阅且存在名为xxx的局部声明、而该引用实际位于某个CallExpression即$xxx(...)的调用形式看起来像$state(...)之类的 rune中时说明开发者大概率是想写 rune却撞上了局部变量名——此时应重命名该局部变量以消除歧义。script // 局部存在名为 derived 的变量 let derived { value: 1 }; // 想调用 $derived rune但 $derived 被解析为 store 订阅 const result $derived(derived.value * 2); // 触发 store_rune_conflict /scriptscript // 修复重命名局部变量 let data { value: 1 }; const result $derived(data.value * 2); /script警告速查表警告代码一句话含义触发阶段修复方向custom_element_props_identifier自定义元素无法推断要暴露哪些 prop分析$props()声明完整解构或显式配置customElement.propsexport_let_unusedexport let属性从未使用分析实例导出删除或改为export constlegacy_component_creation用new Component()实例化组件用户代码Svelte 5改用mount/hydratenon_reactive_update非$state变量被重赋值且在模板读取响应式分析runes用$state声明perf_avoid_inline_classnew class出现在非顶层分析NewExpression把类提到顶层作用域perf_avoid_nested_classclass 声明位于函数深度过深的位置分析ClassDeclaration把类提到组件/模块顶层reactive_declaration_invalid_placement$:声明不在实例脚本顶层分析 legacy 响应式语句移到实例脚本顶层或迁移到$derivedreactive_declaration_module_script_dependency响应式语句依赖模块级可变声明分析标识符引用将模块变量作为只读输入或改为实例级声明state_referenced_locallystate 在同一作用域被值捕获分析标识符引用runes以函数/getter 形式惰性引用store_rune_conflict局部变量名与 rune 的$前缀写法冲突分析 store 订阅runes重命名局部变量延伸阅读警告类别总览compile-warnings 参考其余类别模板、样式、选项、可访问性分别对应 template.md、style.md、options.md、a11y.md消息文案的原始定义与生成流程packages/svelte/messages/compile-warnings/ 与 process-messages 脚本各警告的触发点实现集中在 packages/svelte/src/compiler/phases/2-analyze/ 分析阶段各 AST visitor 中调用w.xxx(...)。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表