ARTICLE DETAIL

资讯详情

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

Svelte 5 深入解析 .svelte.js 与 .svelte.ts 文件:在纯 JS 模块中编写响应式逻辑

Svelte 5 深入解析 .svelte.js 与 .svelte.ts 文件:在纯 JS 模块中编写响应式逻辑 Svelte 5 深入解析 .svelte.js 与 .svelte.ts 文件在纯 JS 模块中编写响应式逻辑【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte在 Svelte 5 中除了常见的.svelte组件文件外.svelte.js和.svelte.ts文件让开发者可以直接在普通 JavaScript/TypeScript 模块里使用 runes如$state、$derived编写可复用的响应式逻辑并在整个应用中共享响应式状态。本文以 Svelte 仓库中的官方文档为骨架结合svelte/compiler的源码实现讲清这类文件的定位、编译器处理入口compileModule、可用的编译选项以及跨模块共享状态这一核心限制背后的原理与两种合规写法帮助你在实际项目中正确组织共享的响应式逻辑模块。什么是 .svelte.js 与 .svelte.ts 文件根据 官方文档说明Besides.sveltefiles, Svelte also operates on.svelte.jsand.svelte.tsfiles.These behave like any other.jsor.tsmodule, except that you can use runes.也就是说这两类文件满足两个条件行为与普通的.js/.ts模块完全一致它们通过 ESMexport导出命名导出或默认导出可以被任何模块import不受组件模型约束额外支持 runes这是它们与普通 JS 模块的唯一区别。你可以在其中使用$state、$derived等响应式原语。典型用途有两类创建可复用的响应式逻辑例如把购物车会话管理主题切换等逻辑从组件中抽离写成带状态的独立模块多个组件共同引用跨模块共享响应式状态多个组件 import 同一个.svelte.js模块时拿到的是同一份响应式状态但有约束见下文。注意适用前提这是 Svelte 5 引入的概念。在 Svelte 4 及更早版本中不存在.svelte.js/.svelte.ts文件类型因此使用它意味着项目运行在 Svelte 5 的 runes 模式下。编译器视角compileModule 如何处理这类文件在 Svelte 仓库源码中处理.svelte.js/.svelte.ts的入口是svelte/compiler导出的compileModule函数而不是用于.svelte文件的compile。见 compiler/index.js/** * compileModule takes your JavaScript source code containing runes, and turns it into a JavaScript module. * * param {string} source The component source code * param {ModuleCompileOptions} options * returns {CompileResult} */ export function compileModule(source, options) { source remove_bom(source); state.reset({ warning: options.warningFilter, filename: options.filename }); const validated validate_module_options(options, ); const analysis analyze_module(source, validated); return transform_module(analysis, source, validated); }从源码结构看其调用链是state.reset(...)重置每次编译独立的全局状态源文件位置、警告过滤器等见 compiler/state.jsvalidate_module_options校验并填充模块编译选项analyze_module进入2-analyze阶段分析 AST 中的 runes 用法transform_module进入3-transform阶段把 runes 转换为底层信号signals调用其中 source map 的回退文件名也被标记为input.svelte.js见 phases/3-transform/index.js。常见的构建工具如 Vite、Rollup 的 Svelte 插件会对.svelte.js/.svelte.ts扩展名走同一条管线先把 TS 语法转译为 JS再调用compileModule完成 runes 转换。对.svelte.ts而言可以推断工具链负责剥离类型而 runes 的语义转换仍由 Svelte 编译器完成。模块编译选项比组件少一大截与.svelte组件不同模块编译选项只保留了与编译一个 JS 模块相关的公共选项。在 validate-options.js 中可以看到export const validate_module_options /** type {ValidatorModuleCompileOptions, ValidatedModuleCompileOptions} */ ( object({ ...common_options, ...Object.fromEntries(Object.keys(component_options).map((key) [key, () {}])) }) );其中common_options同文件第 11–49 行定义了模块真正可用的选项选项默认值说明filename(unknown)源文件名用于诊断信息rootDirprocess.cwd()根目录filename会以它为基准做相对化devfalse是否生成开发模式代码含额外校验generateclient生成目标可取client/server/falsewarningFilter() true警告过滤器experimental.asyncfalse实验性异步特性开关而所有组件级选项css、customElement、namespace、runes等component_options都被替换为空的() {}占位实现——这与直觉一致.svelte.js文件没有style、没有模板、不是组件因此 CSS 相关、命名空间等选项对它没有意义。典型用法可复用的响应式逻辑模块下面给出两种符合规则的模块写法规则来源见下一节。写法一导出不可重赋的状态对象由于深层$state会生成响应式代理state proxy对象本身引用不变、内部属性可自由修改因此可以安全导出/// file: store/counter.svelte.js export const counter $state({ count: 0 }); export function increment() { counter.count 1; } export function reset() { counter.count 0; }组件端直接消费即可script import { counter, increment, reset } from ../store/counter.svelte.js; /script button onclick{increment}clicks: {counter.count}/button button onclick{reset}reset/button因为counter是一个深层响应式状态代理组件模板中读取counter.count时会自动建立依赖任何对counter.count的修改包括通过increment都会触发精确更新。$derived同样可以在模块中使用/// file: store/cart.svelte.js export const items $state([]); export const total $derived(items.reduce((sum, i) sum i.price, 0)); export function add(item) { items.push(item); }写法二模块内部状态 显式 API当状态本身需要被重新赋值例如切换会话、清空整个列表时就不要直接导出它改为导出访问函数/// file: store/session.svelte.js let session $state(null); let loading $state(false); export function login(user) { loading true; // ... 模拟网络请求 ... session user; loading false; } export function logout() { session null; } export function isLoggedIn() { return session ! null; }模块内部的所有赋值与读取都在同一个文件里编译器能看到全部引用因此这里的重赋值完全合法组件通过isLoggedIn()等函数读取当前值读取发生在组件的响应式上下文中依然可以建立依赖。核心限制为什么不能导出会被重新赋值的 state原文档特别警告你不能导出会被重新赋值的 state并指向了 $state 文档的 Passing state across modules 一节。该节给出的完整解释如下这里完整继承其内容。下面这种写法是不允许的/// file: state.svelte.js export let count $state(0); export function increment() { count 1; }原因在于Svelte 编译器会把这个文件中每一个对count的引用都进行转换。上面的代码大致等价于这样的编译器输出引自 $state 文档/// file: state.svelte.js (compiler output) // filename: index.ts interface SignalT { value: T; } interface Svelte { stateT(value?: T): SignalT; getT(source: SignalT): T; setT(source: SignalT, value: T): void; } declare const $: Svelte; // ---cut--- export let count $.state(0); export function increment() { $.set(count, $.get(count) 1); }可以看到$state(0)被转换为一个底层信号signal对象赋值被转换为$.set(...)。而count本身被导出后它的值已经是这个信号对象而不是普通数字。由于编译器一次只处理一个文件当另一个文件import { count } from ./state.svelte.js时Svelte 并不知道需要把对count的每次引用包裹进$.get/$.set。后果是// filename: state.svelte.js export let count 0; // filename: index.js // ---cut--- import { count } from ./state.svelte.js; console.log(typeof count); // object, not number导出的count在消费端是一个对象信号而非期望的数字——重赋值语义在模块边界处断裂了。因此跨模块共享状态只有两条合规路径正是上文两种写法导出引用不变的对象内部修改属性export const counter $state({...}) 修改counter.x——因为引用没有变化编译器不会把它包进$.state重赋值逻辑不直接导出状态导出读写函数getCount()/increment()——所有对状态的重赋值都留在模块内部。实践要点小结把.svelte.js/.svelte.ts当作能写 runes 的普通 JS 模块优先用它承载可复用的响应式逻辑与跨组件共享状态导出状态前检查该变量是否会被重新赋值会被重赋值 → 用内部状态 函数 API只做属性级修改 → 可以导出深层$state对象模块编译只支持filename、rootDir、dev、generate、warningFilter、experimental等公共选项不要对模块传 CSS、命名空间等组件级选项会被忽略或报未识别选项错误该文件类型仅存在于 Svelte 5runes 模式从 Svelte 4 迁移时属于新增能力可参考 v5 迁移指南 了解版本边界想查看编译器为某个.svelte.js模块生成的代码可以在官方 playground 中切换 JS Output 标签该说法来自 $state 文档 中的说明。延伸阅读仓库内路径关联文档原文.svelte.js and .svelte.ts filescompileModule 入口实现模块/组件编译选项校验编译器全局状态管理跨模块传递状态的完整解释【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表