KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回踩坑地图
踩坑复盘高级6 分钟

define:vars 的顶层 const 让客户端换页后工具页集体死亡

Astro 内联脚本配合 define:vars 生成顶层声明,在 ClientRouter 下重复执行触发 SyntaxError,八个工具页交互一夜全灭的复盘。

#Astro#View Transitions#调试

现象

站点启用 View Transitions 客户端路由后,实验室工具页首次访问一切正常;但从首页点进任一工具,或工具 A 切到工具 B 之后,页面渲染完整、样式正常,唯独交互全部失灵——按钮无响应、输入无反馈,控制台里躺着一条刺眼的 SyntaxError: Identifier 'config' has already been declared

更迷惑的是复现条件:手动刷新(F5)立刻恢复;站内导航过去才复发。

排查

问题脚本都长这样:

<script is:inline define:vars={{ toolConfig }}>
  const config = toolConfig;
  // 后续几百行都依赖 config
</script>

define:vars 的展开方式是把变量以顶层 const 声明插进脚本头部。View Transitions 的客户端换页不重建 document,脚本体会在新页面里重新执行——第二次执行时,上一个页面留下的同名顶层绑定仍在同一全局作用域,重复 const 直接抛 SyntaxError,整个脚本块从第一行起死亡,后面的逻辑一行都没跑。

这解释了全部症状:首次直访 = 首次声明,正常;站内导航 = 重复声明,抛错后整段脚本被跳过。

根因与修复

根因是把脚本当模块用,却忽略了内联脚本共享同一个全局作用域。修复分两步:

  1. 数据与代码分离:配置改为 JSON script 标签注入,脚本体内不再有任何顶层 const 依赖外部插值:
<script
  type="application/json"
  id="lab-config"
  set:html={JSON.stringify(toolConfig)}
></script>
<script is:inline>
  (() => {
    const config = JSON.parse(
      document.querySelector('#lab-config')?.textContent || '{}',
    );
    // …
  })();
</script>
  1. IIFE 包裹一切:顶层作用域不再产生绑定,重复执行天然安全。

教训

  • 客户端路由会把“页面脚本只会执行一次”的假设全部作废。 引入 <ClientRouter> 时,应把所有内联脚本当作“可能执行 N 次”来审计。
  • define:vars 适合读一次就用的场景(内联样式变量、单一字符串),一旦脚本体在 SPA 导航中重复执行,它生成的顶层声明就是地雷。
  • 验证手段:浏览器里点击站内导航切换同类页面,观察控制台是否出现 already been declared;CI 中可用 Playwright 做跨导航冒烟测试——本项目后来把“lab 跨工具导航无控制台错误”固化为常规回归项。