博客系统代码质量修复: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。
最终方案很直接:禁用 responsive 和 maintainAspectRatio,手动接管画布尺寸管理。
// 核心改动
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.echarts、window.mermaid、window.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.astro 和 TagsWordCloud.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-check 和 npm run lint 集成进 npm run validate,可以防止同样的问题在未来悄悄累积。