Skip to content

知识图谱 ForceAtlas2 + Sigma.js v3 完整修复记录

日期: 2026-04-05
耗时: 约 3 小时 (06:00 - 09:00 UTC)
状态: ✅ 已完成
部署: 39c5a6a production success
访问: https://nanobot-kb.pages.dev/knowledge/visualization/graph-view


📋 问题概述

知识图谱页面加载后无法渲染,连续出现多个 JavaScript 错误,导致图谱无法正常显示。


🔍 问题根因分析

问题 1: ForceAtlas2 导入缺失

现象:

Error: graphology-layout-forceatlas2: invalid number of iterations.

根因:

  • Vite 构建时 Tree-shaking 优化掉未显式导入的代码
  • ForceAtlas2 模块未被打包进最终产物

修复:

typescript
// 添加静态导入 (第 25 行)
import ForceAtlas2 from 'graphology-layout-forceatlas2/worker'

提交: 560f522


问题 2: 动态导入覆盖静态导入

现象:

  • 修复问题 1 后,线上仍报 invalid number of iterations 错误
  • 本地代码正确,但构建后行为异常

根因:

typescript
// 第 25 行 - 正确的 worker 版本导入
import ForceAtlas2 from 'graphology-layout-forceatlas2/worker'

// 第 120 行 - 错误的动态导入 (覆盖了第 25 行!)
const { default: ForceAtlas2 } = await import('graphology-layout-forceatlas2')
//                                                     ^^^^^^^^^^^^^^^^^^^^^^^^
//                                                     同步版本,不是 worker!

分析:

  • 第 120 行的动态导入在运行时执行,覆盖了第 25 行的静态导入
  • graphology-layout-forceatlas2 (同步版) 要求传入 iterations 参数
  • graphology-layout-forceatlas2/worker (worker 版) 不接受 iterations 参数

修复:

typescript
// 删除第 120 行的动态导入
// 改为注释说明
// ForceAtlas2 已在顶部静态导入 (worker 版本)
console.log('ForceAtlas2 已就绪 (worker 版本)')

提交: 8f17dea


问题 3: Sigma.js v3 节点类型错误

现象:

TypeError: Cannot read properties of null (reading 'blendFunc')
Sigma: could not find a valid renderer for node type 'default'

根因:

typescript
// 错误的配置
defaultNodeType: 'default'  // 'default' 类型不存在!

分析:

  • Sigma.js v3 的节点类型必须是内置类型或已注册的类型
  • 内置类型:'circle', 'dot', 'square', 'diamond', etc.
  • 'default' 不是有效的节点类型

修复:

typescript
// 改为内置的 'circle' 类型
defaultNodeType: 'circle'

提交: 39c5a6a


🛠️ 修复步骤

步骤 1: 添加 ForceAtlas2 静态导入

diff
  <script setup lang="ts">
  import { ref, onMounted, onUnmounted, nextTick } from 'vue'
+ import ForceAtlas2 from 'graphology-layout-forceatlas2/worker'
  
  const isClient = ref(false)
  // ...
  </script>

步骤 2: 删除错误的动态导入

diff
  console.log('正在导入 sigma...')
  const { default: Sigma } = await import('sigma')
  console.log('sigma 导入成功')
  
- console.log('正在导入 graphology-layout-forceatlas2...')
- const { default: ForceAtlas2 } = await import('graphology-layout-forceatlas2')
- console.log('ForceAtlas2 导入成功')
+ // ForceAtlas2 已在顶部静态导入 (worker 版本)
+ console.log('ForceAtlas2 已就绪 (worker 版本)')

步骤 3: 修正 Sigma.js 节点类型

diff
  sigmaInstance = new Sigma(graph, graphContainer.value, {
    renderEdgeLabels: false,
-   defaultNodeType: 'default',
+   defaultNodeType: 'circle',
    defaultEdgeType: 'line',
    // ...
  })

🧪 验证方法

1. 本地构建测试

bash
cd docs
npm run docs:build
# 确认无编译错误

2. 浏览器自动化测试 (Puppeteer)

javascript
const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setCacheEnabled(false);

const errors = [];
page.on('pageerror', error => errors.push(error.message));

await page.goto('https://nanobot-kb.pages.dev/knowledge/visualization/graph-view');
await new Promise(r => setTimeout(r, 5000));

// 检查关键错误
const hasIterationsError = errors.some(e => e.includes('iterations'));
const hasSigmaError = errors.some(e => e.includes('Sigma: could not find'));
const hasWebGLError = errors.some(e => e.includes('blendFunc'));

console.log(`iterations 错误:${hasIterationsError ? '❌' : '✅'}`);
console.log(`Sigma 错误:${hasSigmaError ? '❌' : '✅'}`);
console.log(`WebGL 错误 (Headless 预期): ${hasWebGLError ? '⚠️' : '✅'}`);

await browser.close();

3. 线上部署验证

bash
# 检查部署状态
curl -X GET "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/pages/projects/{PROJECT}/deployments" \
  -H "Authorization: Bearer {API_TOKEN}"

# 检查线上 JS 文件
curl -s "https://nanobot-kb.pages.dev/knowledge/visualization/graph-view" | \
  grep -oE "assets/chunks/theme\.[^\"']+\.js" | \
  xargs -I {} curl -s "https://nanobot-kb.pages.dev/{}" > /tmp/check.js

# 验证无 iterations 参数
python3 -c "
with open('/tmp/check.js') as f:
    content = f.read()
print('包含 iterations:', 'iterations' in content)
"

📊 最终状态

检查项状态
ForceAtlas2 worker 导入
无动态导入覆盖
iterations 参数✅ 已移除
Sigma 节点类型circle
线上部署39c5a6a success
图谱数据✅ 31 节点,24 边
浏览器测试✅ 所有关键错误通过

💡 经验教训

1. 导入语句冲突

教训: 静态导入和动态导入同时存在时,动态导入会覆盖静态导入。

最佳实践:

  • ✅ 优先使用静态导入
  • ✅ 避免混用静态和动态导入同一模块
  • ✅ 如必须动态导入,删除对应的静态导入

2. 模块版本差异

教训: graphology-layout-forceatlas2 有同步版和 worker 版,API 不同。

最佳实践:

  • ✅ 明确区分 modulemodule/worker
  • ✅ 查阅官方文档确认 API 差异
  • ✅ Worker 模式不传 iterations 参数

3. 第三方库版本升级

教训: Sigma.js v3 的节点类型与 v2 不同,'default' 不存在。

最佳实践:

  • ✅ 升级前查阅迁移指南
  • ✅ 检查 package.jsonexports 字段
  • ✅ 使用内置类型或显式注册自定义类型

4. 浏览器测试的重要性

教训: 安装了 Puppeteer 但没有充分利用,导致问题排查缓慢。

最佳实践:

  • ✅ 每次修复后运行完整的浏览器测试
  • ✅ 收集所有控制台错误并分类
  • ✅ 按优先级修复(代码错误 > 渲染错误)
  • ✅ 区分预期内错误(Headless WebGL)和真实错误

🔗 相关文件

  • 源代码: docs/.vitepress/theme/components/KnowledgeGraph.vue
  • 图谱数据: docs/public/graph_index.json
  • 构建脚本: scripts/build-graph-index.py
  • 测试脚本: scripts/test-graph-browser.js

📝 提交历史

39c5a6a fix: 修改默认节点类型为 'circle'(Sigma.js v3 内置支持)
8f17dea fix: 删除错误的动态导入,使用顶部静态导入的 worker 版本
560f522 fix: 添加缺失的 ForceAtlas2 导入语句
c905960 fix: 添加修复标记注释以强制重新构建
c48f406 chore: 强制重新部署以刷新 CDN 缓存
c09e3ec docs: 归档知识图谱修复任务 (2026-04-05)

最后更新: 2026-04-05 09:07 UTC
状态: ✅ 已完成

受控自动化架构 V2.0 | 仅限授权访问