Vite create-vite 的 Svelte + TypeScript 模板解析:svelte-ts 技术选型与配置实现
本篇技术文章围绕 Vite 仓库中 packages/create-vite 提供的 Svelte + TypeScript 模板(svelte-ts)展开。读完你将理解该模板的定位与适用场景、相对 SvelteKit 的取舍原因,并掌握其 TypeScript 工程化配置(project references、types、allowJs/checkJs)、Vite 构建链路、Svelte 5 入口挂载方式,以及 HMR 状态保持的已知限制与外部 store 规避方案。
模板定位:create-vite 中的 svelte-ts 变体
create-vite 是 Vite 官方的项目脚手架工具,在 Svelte 技术栈下提供三个变体。从 create-vite 入口源码 中可以看到其定义:
{
name: 'svelte',
display: 'Svelte',
color: red,
variants: [
{ name: 'svelte-ts', display: 'TypeScript', color: blue },
{ name: 'svelte', display: 'JavaScript', color: yellow },
{
name: 'custom-svelte-kit',
display: 'SvelteKit ↗',
customCommand: 'npm exec sv create TARGET_DIR',
},
],
},
即选择 Svelte + TypeScript 时会使用本模板(模板目录),而 SvelteKit 变体实际上是把命令委托给 sv create,并不走 create-vite 的本地模板复制流程。
本地模板目录的完整结构如下,内容刻意保持最小化:
template-svelte-ts/
├── .vscode/
│ └── extensions.json # VS Code 扩展推荐
├── public/
│ ├── favicon.svg
│ └── icons.svg
├── src/
│ ├── assets/ # hero.png / svelte.svg / vite.svg
│ ├── lib/
│ │ └── Counter.svelte # 演示 $state 的计数组件
│ ├── App.svelte
│ ├── app.css
│ └── main.ts # Svelte 5 应用入口
├── index.html
├── package.json
├── svelte.config.js
├── tsconfig.json # project references 根配置
├── tsconfig.app.json # 应用代码(src)
├── tsconfig.node.json # Node 侧(vite.config.ts)
├── vite.config.ts
└── _gitignore # 脚手架生成时重命名为 .gitignore
使用方式是在目标目录执行(模板的 README 即面向该使用场景编写):
npm create vite@latest my-app -- --template svelte-ts
cd my-app
npm install
npm run dev
推荐 IDE 配置:VS Code + Svelte 扩展
模板 README 给出的推荐 IDE 组合是 VS Code + Svelte 官方 VS Code 扩展(扩展 ID 为 svelte.svelte-vscode)。
更值得注意的机制是:README 提到,其他模板往往只在 README 里"间接推荐"扩展,而本模板额外提供了 .vscode/extensions.json 文件。从 _gitignore 可以看到 .vscode/* 目录默认被忽略、仅保留 extensions.json 进入版本控制:
# Editor directories and files
.vscode/*
!.vscode/extensions.json
而该文件的内容只有一条推荐:
{
"recommendations": ["svelte.svelte-vscode"]
}
其作用是:用户用 VS Code 打开由模板生成的项目时,VS Code 会主动弹出提示,建议安装 Svelte 扩展,从而保证 .svelte 文件的语法高亮、补全等 IntelliSense 能力开箱即用。
技术选型:为什么用这个模板而不是 SvelteKit
模板 README 的 "Technical considerations" 一节直接回答了两个高频问题,以下完整继承其结论并结合仓库现状说明。
为什么用这个模板而不是 SvelteKit?
- SvelteKit 自带一套路由方案,这对部分用户而言并非首选;
- SvelteKit 本质是一个"恰好底层使用 Vite 的框架",而不是一个纯粹的 Vite 应用。
本模板的设计目标是包含尽可能少的内容,让人能以最简形态启动 Vite + TypeScript + Svelte 项目,同时在开发体验上兼顾 HMR 与 IntelliSense。它的功能定位与 create-vite 的其他模板对齐,适合初学者入门 Vite + Svelte 项目。README 同时指出:该模板的结构刻意与 SvelteKit 保持相似,以便日后需要 SvelteKit 的扩展能力与可扩展性时能够平滑迁移。
TypeScript 配置体系:project references 与 types
模板的 TypeScript 采用 project references 拆分为应用侧与 Node 侧两个子项目。根 tsconfig.json 只负责引用,不直接编译任何文件:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
应用侧:tsconfig.app.json
tsconfig.app.json 继承 @tsconfig/svelte 的官方共享配置,并在此基础上针对 Svelte + Vite 场景做了关键定制:
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"module": "esnext",
"types": ["svelte", "vite/client"],
"allowArbitraryExtensions": true,
"noEmit": true,
"allowJs": true,
"checkJs": true,
"moduleDetection": "force"
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}
逐项解读:
types: ["svelte", "vite/client"]:显式引入svelte(Svelte 编译相关类型)与vite/client(import.meta.env、?.svg/?.png等资源导入声明)两类全局类型,使import logo from './assets/svelte.svg'这类资源导入在类型检查下合法。这与 App.svelte 中直接import svelteLogo from './assets/svelte.svg'的写法相配套。target: es2023/module: esnext/noEmit: true:产物交给 Vite/Rolldown 处理,TypeScript 只负责类型检查(noEmit),esnext模块形态匹配 ESM 打包器。allowArbitraryExtensions:允许导入带任意扩展名的模块,配合 Svelte 文件的模块解析需要。include覆盖src下的.ts、.js与.svelte文件——svelte-check会基于这份配置对.svelte文件做完整类型检查(见后文check脚本)。- 注释中特别提示:
allowJs设为false并不能阻止在.svelte文件里写 JavaScript,这直接引出了下一节的设计取舍。
关于 global.d.ts 与 compilerOptions.types 的取舍
README 专门回答了"为什么用 global.d.ts 而不是 jsconfig.json/tsconfig.json 里的 compilerOptions.types":设置 compilerOptions.types 会屏蔽掉所有未显式列出的类型声明包;而使用三斜杠引用(/// <reference types="..." />)的 global.d.ts 方式,既能保留 TypeScript"接受整个工作区类型信息"的默认行为,又能叠加 svelte 与 vite/client 的类型。
需要说明的是,从当前仓库的模板实现看,tsconfig.app.json 已经改为通过 "types": ["svelte", "vite/client"] 显式声明这两类类型(同时 include 限定了 src 范围),README 中的讨论可视为对这一设计问题的原始论证。两种方案的核心权衡一致:types 白名单更严格、可预测,三斜杠引用则保留工作区默认类型发现。
Node 侧:tsconfig.node.json
tsconfig.node.json 单独覆盖 vite.config.ts,采用 "Bundler mode" 的一整套现代 lint 型编译选项:
{
"compilerOptions": {
"target": "es2023",
"lib": ["ES2023"],
"types": ["node"],
"module": "nodenext",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
要点:types: ["node"] 让配置文件内可以使用 Node 全局;module: nodenext + verbatimModuleSyntax 保证 import 语法与 Node 模块解析语义一致;erasableSyntaxOnly 要求只使用可被纯文本擦除的 TS 语法(不允许需要编译期转换的 enum 等),这也是 Svelte/Vite 生态对轻量 TS 的常见约束。
为什么在 TS 模板中启用 allowJs
这是 README 里论证最充分的一个决策,其结论值得原样继承:
allowJs: false确实能阻止项目中出现.js文件,但无法阻止在.svelte文件内部使用 JavaScript 语法——所以对.svelte单文件组件来说,禁用 JS 文件并不彻底;- 更糟的是,
allowJs: false会连带强制checkJs: false,落入"两全不得"的局面:既不能保证整个代码库都是 TypeScript,又让现存 JavaScript 丧失了更好的类型检查; - 此外,混合代码库本身存在合理的使用场景(例如渐进式迁移中的项目)。
当前模板的落地方式是在 tsconfig.app.json 中同时打开 allowJs: true 与 checkJs: true,并附注释说明"默认对 .svelte 与 .js 文件中的 JS 做类型检查;若希望 JS 使用动态类型,可关闭 checkJs"。即:允许 JS 存在,但对它保持严格检查。
构建链路:vite.config.ts 与 package.json 脚本
Vite 配置只有三行核心内容——注册 Svelte 官方 Vite 插件:
// vite.config.ts
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
})
配套的 svelte.config.js 同样是空配置占位,仅标注了 @sveltejs/vite-plugin-svelte 的 SvelteConfig 类型,留待用户按需扩展(预处理器、编译选项等)。
package.json 定义了四个脚本与完整依赖清单(版本以当前仓库为准):
{
"name": "vite-svelte-ts-starter",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.3.0",
"@tsconfig/svelte": "^5.0.8",
"@types/node": "^24.13.3",
"svelte": "^5.57.0",
"svelte-check": "^4.7.6",
"typescript": "~6.0.2",
"vite": "^8.2.2"
}
}
各脚本职责:
| 脚本 | 命令 | 作用 |
|---|---|---|
dev |
vite |
启动开发服务器,含 .svelte 文件 HMR |
build |
vite build |
产物构建(ESM 打包 + 资源处理) |
preview |
vite preview |
本地预览 build 产物 |
check |
svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json |
双阶段类型检查:先由 svelte-check 按应用侧配置检查 src(含 .svelte),再由 tsc 检查 Node 侧配置 |
注意 check 脚本显式传入了 --tsconfig ./tsconfig.app.json,这正是 project references 拆分的直接收益:svelte-check 只看应用侧语义,tsc 只看 Node 侧,互不干扰。
应用代码:Svelte 5 的 mount 入口与 $state
入口 src/main.ts 使用 Svelte 5 的 mount API(替代旧版 new App({ target })):
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app')!,
})
export default app
Counter.svelte 则演示了 Svelte 5 的响应式 rune:
<script lang="ts">
let count: number = $state(0)
const increment = () => {
count += 1
}
</script>
<button type="button" class="counter" onclick={increment}>
Count is {count}
</button>
index.html 中以 <div id="app"></div> 作为挂载点,并用 <script type="module" src="/src/main.ts"> 引入入口;App.svelte 组合了 Counter 组件、logo 资源导入与静态部署图标(<use href="/icons.svg#...">),并提示"编辑 src/App.svelte 保存以测试 HMR"。
HMR 为什么不保留组件局部状态
README 的最后一个 FAQ 涉及一个高频困惑:HMR 更新后,为什么组件里的局部状态(如 Counter 的 count)被重置?
结论是:HMR 的状态保持存在一系列"坑"。由于其行为经常出人意料,svelte-hmr 与 @sveltejs/vite-plugin-svelte 都默认禁用了状态保持。README 给出的工程化对策是:把需要跨 HMR 更新存活的状态提升到外部 store——外部 store 模块不会被组件自身的 HMR 替换掉,因此状态得以保留。README 给出的最小示例:
// store.ts
// 一个极简的外部 store
import { writable } from 'svelte/store'
export default writable(0)
需要指出:该示例沿用了 svelte/store 的 writable 经典写法;在 Svelte 5 中同样可以直接用模块级 $state(模块只会被 HMR 失效一次,组件内局部状态则会被重置)达到类似目的,但 README 给出的 writable 外部 store 方案在 4/5 版本下都通用,是文档钦定的稳妥路径。
总结
svelte-ts 模板展示了 Vite 生态"最小可用脚手架"的完整设计思路:
- 结构最小:仅入口、根组件、演示组件与全局样式,无路由、无状态库;
- 工程严格:project references 拆分应用/Node 两侧 TS 配置,
svelte-check+tsc双检查脚本,allowJs/checkJs兼顾混合代码库; - DX 保障:
.vscode/extensions.json自动推荐 Svelte 扩展,vite/client类型让资源导入合法化; - 迁移友好:目录结构刻意对齐 SvelteKit,为日后升级预留通道。
结合 README 与上述源码文件(tsconfig.app.json、package.json、src/main.ts),即可完整复现该模板从脚手架生成、本地开发到类型检查、构建预览的全流程。
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