Svelte 组件审计器
对 Svelte 和 SvelteKit 组件进行深度审计。分析响应式声明、存储模式、可访问性、SSR 准备就绪性和渲染性能。生成带有具体修复的优先审计结果。
使用时:审查 Svelte/SvelteKit 项目、准备生产、审计可访问性或建立组件质量标准。
分析步骤
cat package.json 2>/dev/null | jq '{svelte: .devDependencies.svelte, sveltekit: .devDependencies["@sveltejs/kit"]}'
cat svelte.config.js 2>/dev/null || cat svelte.config.ts 2>/dev/null
find . -name "
.svelte" -not -path '/node_modules/
' -not -path '/.svelte-kit/
' | wc -l
find . -path "/routes/
" -name "+" -not -path '
/node_modules/' 2>/dev/null | sort | head -20
确定:Svelte 版本(4 vs 5 runes)、SvelteKit 存在、适配器类型、组件数量、路由结构。
# 响应式声明($: 标签语法 — Svelte 4)
grep -rn '^\s
\$:' --include=".svelte" . 2>/dev/null | head -25
# 响应式赋值不会触发(数组突变)
grep -rn '\$:.
\.push\|\$:.\.splice\|\$:.
\.sort' --include=".svelte" . 2>/dev/null | head -15
# 复杂响应式链(>5 个每个组件 = 异味)
for f in $(find . -name "
.svelte" -not -path '/node_modules/
' 2>/dev/null); do
count=$(grep -c '^\s\$:' "$f" 2>/dev/null)
if [ "$count" -gt 5 ]; then
echo "COMPLEX($count): $f";
fi
done | head -15
# Svelte 5 runes
grep -rn '\$state\|\$derived\|\$effect\|\$props' --include="
.svelte" --include=".svelte.ts" . 2>/dev/null | head -15
检查:
在响应式声明中修改数组:$: items.push(x) 不会触发 — 必须重新分配:items = [...items, x]
循环响应式依赖:导致运行时错误
过于复杂的响应式链:>5 $: 声明表明需要存储或实用程序提取
$effect 没有清理:具有订阅/定时器的效果必须返回清理函数
grep -rn "writable(\|readable(\|derived(" --include="
.ts" --include=".js" --include="
.svelte" . 2>/dev/null | grep -v 'venv/' | head -20
# 存储订阅没有取消订阅(非组件代码中的泄漏)
grep -rn "\.subscribe(" --include=".ts" --include="
.js" . 2>/dev/null | head -15
# 自动订阅组件($store)
grep -rn '\$[a-zA-Z]' --include=".svelte" . 2>/dev/null | grep -v '\$:\|//' | head -15
标记:
存储订阅在 .ts/.js 中没有取消订阅:自动取消订阅($store)仅在 .svelte 文件中有效
全局存储与 SSR:模块范围的可写存储会导致跨请求数据泄漏 — 使用上下文或页面数据
缺少派生存储:组件中的重复计算应派生
过载存储:单个存储管理无关问题 — 按域拆分
# 大型组件(>200 行)
for f in $(find . -name "
.svelte" -not -path '/node_modules/
' 2>/dev/null); do
lines=$(wc -l < "$f");
if [ "$lines" -gt 200 ]; then
echo "LARGE($lines): $f";
fi
done | sort -t'(' -k1.7 -rn | head -10
# 未键入的 each 块
grep -rn '{#each' --include=".svelte" . 2>/dev/null | grep -v '(' | head -15
# bind:this 过度使用
grep -rn "bind:this" --include="
.svelte" . 2>/dev/null | head -15
标记:
未键入的 each 块:{#each items as item} 没有键会导致列表的任何更改都重新渲染
组件 >300 行:分解为子组件
过度使用 bind:this:直接 DOM 操作绕过响应式# 图像没有 alt
grep -rn '/dev/null | grep -v 'alt=' | head -15
# 非交互元素上的点击处理程序
grep -rn 'on:click' --include=".svelte" . 2>/dev/null | grep '/dev/null | grep -v 'aria-label\|id=.label\|type="hidden"\|type="submit"' | head -15
# 焦点管理
grep -rn 'focus()\|tabindex\|aria-live\|aria-expanded' --include=".svelte" . 2>/dev/null | head -10
标记带有 WCAG 引用:
图像没有 alt:WCAG 1.1.1 — 所有
必须具有 alt(装饰性为空)
非交互元素上的点击:WCAG 4.1.2 — 使用 或添加 role="button" + tabindex="0" + 键盘处理程序
输入没有标签:WCAG 1.3.1 — 每个表单控件都需要一个可访问的名称
没有 aria-live 区域:WCAG 4.1.3 — 动态状态消息需要 aria-live="polite"# 在 onMount 之外使用的浏览器仅 API
grep -rn 'window\.\|document\.\|localStorage\|sessionStorage\|navigator\.' --include=".svelte" --include=".ts" . 2>/dev/null | grep -v 'node_modules\|\.svelte-kit\|onMount\|onDestroy\|browser' | head -15
# $app/environment 浏览器守卫
grep -rn "import.browser.\$app" --include=".svelte" --include=".ts" . 2>/dev/null | head -10
# 在组件中而不是 load 函数中获取数据
grep -rn "fetch(" --include="*.svelte" . 2>/dev/null | grep -v 'node_modules' | head -10
# 私有环境泄漏到客户端
grep