博客系统代码质量修复:Chart.js 渲染与类型系统整理

博客系统代码质量修复:Chart.js 渲染与类型系统整理

这次主要做代码质量修复:把 TypeScript 类型错误从 26 个清零,ESLint 警告全部解决,顺手修了一个 Chrome 专属的 Chart.js 渲染 bug。最终 npm run validate 三项通过,构建产出 188 个页面。

Chart.js 的 Chrome 专属 bug

症状很反常:标签统计页(/tags)里的柱状图和累计占比曲线,在 Chrome 中持续缩小再放大,陷入无限循环。Firefox 正常,硬刷新和隐私模式都不管用。

排查下来,问题出在 Chart.js 的 responsive: true 模式。Chrome 的 GPU 合成层在处理 Canvas 元素的动态重绘时,会和 Chart.js 的内部 resize 逻辑互相触发,形成无限重绘循环。禁用动画、调 resizeDelay 都无效——只要 responsive 开着,Chrome 就一定会触发这个 bug。

最终方案很直接:禁用 responsivemaintainAspectRatio,手动接管画布尺寸管理。

// 核心改动
const width = container.clientWidth;
const height = Math.floor(width / 2);  // 固定 2:1 比例

canvas.width = width;
canvas.height = height;

chartInstance = new window.Chart(ctx, {
  options: {
    responsive: false,           // 完全禁用
    maintainAspectRatio: false,  // 禁用自动比例
    animation: false,
  }
});

响应式不再靠 Chart.js 的内置机制,而是通过监听 window.resize(加 300ms 防抖)手动重建图表。主题切换也是同样的策略——用 MutationObserver 监听 <html>class 变更,检测到主题切换就完全重新初始化,而非调用 update()

TypeScript 类型系统铺平

运行 tsc --noEmit 时扫出 26 个类型错误,集中在三类问题:

CDN 动态加载的库没有类型。 window.echartswindow.mermaidwindow.Chart 都是运行时注入的,TypeScript 不认识。解决方式是在 src/env.d.ts 中扩展全局 Window 接口:

interface Window {
  echarts?: {
    init: (container: HTMLElement, theme?: string | null, opts?: { renderer?: 'canvas' | 'svg' }) => EChartsInstance;
  };
  mermaid?: {
    initialize: (config: Record<string, unknown>) => void;
    render: (id: string, code: string) => Promise<{ svg: string }>;
  };
  Chart?: {
    new (ctx: CanvasRenderingContext2D, config: Record<string, unknown>): ChartInstance;
  };
}

同时为 ECharts 实例、Blog Galaxy 聚类数据、Mermaid 图表信息等内部数据结构补了接口定义。

DOM 操作缺少类型断言。 querySelectorAll 返回的是 Element 而非 HTMLElement,直接访问 style 属性会报错。统一加上类型断言:

// 修复前
cardImages.forEach((img) => {
  img.style.transform = 'scale(1)';  // TS 错误
});

// 修复后
cardImages.forEach((img) => {
  const htmlImg = img as HTMLElement;
  htmlImg.style.transform = 'scale(1)';
});

CDN 加载函数的返回值没有类型。 loadMermaid() 原来返回 unknown,调用方使用时 TypeScript 无法推断方法签名。改为明确的 Promise<typeof window.mermaid> 返回类型,再加上空值检查的 early return 模式,后续代码路径 TypeScript 就能自动收窄类型了。

ESLint 规范

主要处理了未使用变量和未使用参数——少数是有意保留的回调签名参数,用下划线前缀 _param 标记;其余直接删除。影响范围很小,集中在 Header.astroTagsWordCloud.astro

验证结果

$ npm run validate
 ESLint:     0 errors, 0 warnings
 Type Check: 0 errors, 0 warnings
 Build:      188 页构建成功

几点收获

Chrome 和 Chart.js 的 responsive 模式兼容性问题是一个已知坑,这次踩完的结论是:对于静态站点里的图表,手动管理尺寸比依赖框架的自动响应更可靠。主题切换时重建图表的策略也比 update() 干净——update() 不会重新读取 CSS 变量,重建才能拿到正确的主题色。

TypeScript 这边,CDN 动态加载的库是类型安全的盲区。补全局类型声明比在每处调用点加 as any 更稳,一次定义,全项目受益。env.d.ts 现在也承担了一部分项目文档职责:新增 CDN 依赖时,类型声明和加载脚本需要同步更新。把 npm run type-checknpm run lint 集成进 npm run validate,可以防止同样的问题在未来悄悄累积。