当你的node_modules目录膨胀到 500MB 时我在用 63KB 的单个 HTML 文件交付 24 个交互式可视化。这不是标题党。451 个自包含 HTML 文件总计 27.7MB包含 3584 个交互式 SVG 模块10752 个可视化视图——每个文件零外部依赖零 CDN 引用零 npm 包。双击打开即用离线可用永久可用。这篇文章不是介绍这些项目做了什么而是拆解一个工程问题当你选择零依赖时你获得了什么又必须付出什么代价答案藏在 CSS 变量的蝴蝶效应、gridBg必须画在g上的血泪教训以及子 Agent 系统性产出畸形语法的规律里。图1单个可视化实验室的运行时效果——暗色主题、8模块导航、SVG节点流程图、右侧信息面板全部在63KB的HTML文件内渲染零依赖架构的数学27.7MB vs 500MB先看事实。以下数据来自实际文件系统统计指标数值来源HTML 可视化文件数448 个Get-ChildItem *-viz-lab.html每文件模块数8 个MODULES数组定义每模块视图数3 个mViews数组总可视化模块3584448 × 8总可视化视图10752448 × 24文件总体积27.7 MBMeasure-Object -Property Length -Sum平均文件体积63.3 KB27.7MB / 448最小文件24.1 KB排序取最小最大文件146.9 KB排序取最大外部依赖0全文无import、require、script src独立 GitHub 仓库225 个已启用 GitHub Pages一个典型的可视化实验室文件约 63KB包含完整的暗色主题、8 个交互模块、24 个差异化视图、雷达图/节点图/流程图/进度条等多种 SVG 图形元素。作为对比一个空白的 React D3 项目node_modules轻松突破 200MB。零依赖的收益不止体积。可移植性文件复制到任何地方都能打开U盘、邮件附件、内网服务器。持久性十年后这个 HTML 照样能跑而 npm 生态的包可能早已废弃或 breaking change。审计性63KB 的源码一个下午就能通读全部逻辑。代价是什么你需要自己造所有轮子。双轨色彩系统一个 CSS 变量如何驱动 3584 个模块零依赖意味着没有 Tailwind没有 CSS-in-JS。451 个文件保持视觉一致性的核心是一套双轨色彩系统。第一轨CSS:root变量:root{ --bg:#0a0b0f;--bg2:#0f1117;--bg3:#161922;--bg4:#1c2030; --border:#1e2433;--border2:#2a3144; --text:#e2e8f0;--text2:#94a3b8;--text3:#64748b; --teal:#00d4aa;--cyan:#22d3ee;--amber:#fbbf24; --red:#ef4444;--purple:#a78bfa;--blue:#3b82f6; --pink:#f472b6;--green:#34d399; --mono:SF Mono,JetBrains Mono,Consolas,monospace; }18 个变量定义了整个设计系统。背景分 4 层bg→bg4文本分 3 级text→text38 种语义色覆盖所有图表场景。这不是随意选的——每一层都有明确的用途bg是画布底色bg2是面板bg3是卡片bg4是内嵌元素。第二轨JavaScript 镜像对象Cvar C{ bg:#0a0b0f,bg2:#0f1117,bg3:#161922,bg4:#1c2030, border:#1e2433,border2:#2a3144, text:#e2e8f0,text2:#94a3b8,text3:#64748b, teal:#00d4aa,cyan:#22d3ee,amber:#fbbf24, red:#ef4444,purple:#a78bfa,blue:#3b82f6, pink:#f472b6,green:#34d399, mono:SF Mono,JetBrains Mono,Consolas,monospace };C对象与:root一一对应。为什么需要两套因为 SVG 元素的stroke和fill属性无法引用 CSS 变量stroke:var(--teal)在 SVG attribute 中不生效。每个arrow()调用需要直接传入颜色字符串C.teal就是那个字符串。这套设计的连锁效应是修改一个颜色值451 个文件的所有模块同步变化。但前提是——你必须手动同步:root和C。这是零依赖的代价一致性靠纪律不靠框架。gridBg 的血泪教训为什么必须画在g上这是整个项目中代价最高的一堂课。gridBg函数为每个 SVG 画布绘制背景网格function gridBg(g,w,h){ for(var x0;xw;x45){ E(g,line,{x1:x,y1:0,x2:x,y2:h,stroke:#141926,stroke-width:1}); } for(var y0;yh;y45){ E(g,line,{x1:0,y1:y,x2:w,y:y,stroke:#141926,stroke-width:1}); } E(g,rect,{x:0,y:0,width:w,height:h,fill:none, stroke:#1e2433,stroke-width:1.5,rx:4}); }问题出在调用位置。drawModule是每次切换模块时的重绘入口function drawModule(mi){ mCurmi; var svg$(svg); clearNode(svg); // 清空 SVG 所有子元素 var gE(svg,g,{id:layer}); // 创建新的 g 层 gridBg(g,900,480); // 在 g 上画网格 —— 正确 buildNav(); buildTabs(mi); var fns[drawM1,drawM2,drawM3,drawM4,drawM5,drawM6,drawM7,drawM8]; fns[mi](g,mViews[mi]); }关键在第 4-5 行先clearNode(svg)清空画布再创建新的g元素最后在g上画网格。最初版本把gridBg直接画在svg根元素上。看起来没问题——但每次调用drawModuleclearNode(svg)会删除所有子元素而gridBg在svg上累积的线条在清除后不会完全消失。原因是 SVG 命名空间的元素在直接附加到根svg时某些浏览器会缓存渲染层。网格线会一层层叠加最终变成纯黑矩形遮盖所有内容。修复方法就是你现在看到的永远在新建的g元素上画网格。clearNode删除旧g新建g是干净的网格只画一次。这个 bug 影响了早期几十个文件。教训SVG 操作的副作用与 DOM 操作不同命名空间元素的清除和重建有微妙差异。子 Agent 的系统性错误.textContents)不是笔误在批量创建 451 个文件的过程中大量代码由 AI 子 Agent 生成。一个反复出现的语法错误暴露了模式// 正确 t.textContents; // 子 Agent 系统性产出错误 t.textContents);多了一个右括号。这不是随机错误——它在几十个文件中以完全相同的形式出现。原因是子 Agent 在生成txt函数时将return t;的语义与t.textContents;混淆在语句末尾残留了函数调用的闭合括号。这个错误的检测方法值得记录。用 Node.js 的vm.Script做语法校验var vm require(vm); var js html.match(/script([\s\S]*?)\/script/)[1]; try { new vm.Script(js); console.log(OK); } catch(e) { console.log(FAIL: e.message); }vm.Script提供精确的错误行号和列号远优于渐进式编译。批量验证脚本在几秒内扫描 448 个文件定位每个语法错误的确切位置。另一个系统性错误同样有规律circle元素缺少r属性。子 Agent 偶尔产出// 错误 —— 缺少属性键名 r circ(g,cx,cy,sz/24,{stroke:C.teal}); // 正确 —— r 显式声明 circ(g,cx,cy,sz/24,{r:sz/24,stroke:C.teal});修复后circ函数本身被加固强制要求r参数function circ(g,cx,cy,r,opts){ optsopts||{}; var a{cx:cx,cy:cy,r:r,fill:opts.fill||none, stroke:opts.stroke||C.teal,stroke-width:opts.sw||1.5}; if(opts.cls)a[class]opts.cls; E(g,circle,a); }教训当用 AI 批量生成代码时系统性错误会以固定模式重复出现。人类笔误是随机的AI 错误是结构性的。对付结构性错误最有效的方法不是逐个修复而是加固底层函数让错误在结构上无法发生。15 个核心函数替代整个前端框架零依赖的代价是自己造轮子。但轮子的数量比你想象的少——15 个核心函数覆盖了所有可视化需求函数用途替代品E(parent,tag,attrs)创建 SVG 元素document.createElementNS封装$(id)获取 DOM 元素document.getElementByIdclearNode(n)清空子元素while(n.firstChild)box(g,x,y,w,h)矩形容器CSSborder-boxtxt(g,x,y,s)文本标签HTMLspanline(g,x1,y1,x2,y2)直线SVGlinearrow(g,...)带箭头连线D3 的linkHorizontalcirc(g,cx,cy,r)圆形D3 的circle生成器poly(g,pts)多边形D3 的polygonradar(g,...)雷达图ECharts radar 组件gridBg(g,w,h)背景网格CSSbackground-imagenodeBox(g,...)带标题的节点框React 组件pbar(g,...)进度条CSSwidth动画rbox(g,...)指标卡片React Stat 组件chip(g,...)标签芯片CSSborder-radius以radar函数为例它用 20 行 vanilla JS 实现了 ECharts 雷达图组件的核心功能——多边形网格、数据多边形、标签定位function radar(g,cx,cy,r,labels,data,opts){ optsopts||{}; var nlabels.length,i,a,rr; // 画 4 层同心多边形网格 for(var ring1;ring4;ring){ var pts[]; for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; rrr*ring/4; pts.push((cxMath.cos(a)*rr).toFixed(1), (cyMath.sin(a)*rr).toFixed(1)); } E(g,polygon,{points:pts.join( ),fill:none, stroke:C.border,stroke-width:1}); } // 画轴线 for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; E(g,line,{x1:cx,y1:cy, x2:cxMath.cos(a)*r,y2:cyMath.sin(a)*r, stroke:C.border,stroke-width:1}); } // 画数据多边形 var dpts[]; for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; rrr*data[i]/100; dpts.push((cxMath.cos(a)*rr).toFixed(1), (cyMath.sin(a)*rr).toFixed(1)); } E(g,polygon,{points:dpts.join( ), fill:opts.fill||rgba(0,212,170,0.15), stroke:opts.stroke||C.teal,stroke-width:2}); // 画标签 for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; var lxcxMath.cos(a)*(r22), lycyMath.sin(a)*(r22)4; txt(g,lx,ly,labels[i],{fs:10,fill:C.text2,ta:middle}); } }ECharts 的雷达图组件源码超过 2000 行。不是 ECharts 臃肿——它处理了动画、tooltip、响应式、主题切换等大量边缘场景。但如果你只需要静态渲染一个数据多边形加标签20 行就够了。这就是零依赖架构的核心权衡你放弃的是通用性和边缘场景处理获得的是极简、可控和永恒。模块系统8 × 3 24 的约束设计每个文件包含 8 个模块每个模块 3 个视图共 24 个可视化。这不是随意选择——这是约束驱动设计。var MODULES[ {n:M1,t:概览,d:项目全景,v:[全景图,核心指标,定位分析]}, {n:M2,t:架构,d:技术架构,v:[架构图,模块分解,数据流]}, // ... M3-M8 ]; var mCur0,mViews[0,0,0,0,0,0,0,0],mLayers[];mViews是一个长度为 8 的数组记录每个模块当前显示的视图索引。切换模块时drawModule(mi)重绘整个 SVG切换视图时只改变mViews[mi]的值。约束在哪里drawModule的设计强制每个模块函数drawM1到drawM8必须接受(g, vi)两个参数其中vi是 0、1 或 2。这迫使作者为每个概念准备三个不同的可视化角度function drawM1(g,vi){ var side$(mpSide); clearNode(side); if(vi0){ // 视图1全景架构图 —— nodeBox arrow 流程 }else if(vi1){ // 视图2核心指标 —— rbox 数字卡片 radar 雷达图 }else{ // 视图3定位分析 —— 对比表格 pbar 进度条 } }8 模块的约束来自认知负荷理论7±2 是人类工作记忆的容量上限。8 个模块恰好在上限附近迫使作者提炼核心维度而不是堆砌信息。3 个视图的约束来自同一概念的三种表达方法论——架构图、数据指标、对比分析覆盖空间、数值和关系三个维度。部署架构一个合集 225 个独立仓库451 个文件有两种部署路径互为补充合集仓库TW-main所有 HTML 文件放在一个仓库一个index.html作为导航门户。优势是集中管理、统一搜索、一站式浏览。最新 commit2c2c642包含 481 个 HTML 文件含可视化实验室 工具页面。独立仓库225 个每个可视化实验室复制为独立仓库的index.html启用 GitHub Pages。优势是每个项目有独立 URL利于搜索引擎索引和社交分享。例如superpowers-agent-skills-framework-viz-lab仓库对应https://wangzifan396-wzf.github.io/superpowers-agent-skills-framework-viz-lab/。双轨部署的代价是同步开销——每次新增项目需要更新合集仓库的index.html新增卡片 更新统计数字然后运行 PowerShell 脚本批量创建独立仓库。但收益是搜索曝光最大化合集仓库获取聚合流量独立仓库获取长尾关键词流量。局限性零依赖架构不是银弹。以下是我明确承认的局限不适用于复杂状态管理。当应用状态超过mViews数组的复杂度需要路由、Store、副作用管理时零依赖方案的维护成本会指数级上升。451 个文件之所以可行是因为每个文件的状态模型完全相同。不适用于团队协作。15 个核心函数没有类型系统、没有 lint 规则、没有模块边界。一个人维护没问题5 个人同时改就会冲突。TypeScript ESLint 构建工具的存在是有理由的。不适用于需要动画引擎的场景。CSS 动画类apulse、aglow、adash、ablink、aspin覆盖了基础动画需求但无法实现物理引擎、粒子系统或复杂时间轴。这些场景 GSAP 或 D3 是正确的选择。文件体积在增长。早期文件约 24KB最新文件已达 146KB。随着模块内容复杂度增加单文件体积会继续增长。如果超过 200KB加载性能可能受影响届时需要考虑代码拆分——但这与零依赖单文件理念冲突。结论451 个零依赖 HTML 文件证明了一件事在特定场景下——可视化展示、教育内容、技术文档——零依赖架构不仅是可行的而且在可移植性、持久性和审计性上有不可替代的优势。代价是真实的你自己造轮子、自己维护一致性、自己处理边缘场景。但当你为一个 63KB 的文件添加一个模块双击打开看到 24 个交互式可视化瞬间渲染完成时你会理解为什么这个选择值得。所有代码开源合集仓库地址GitHub - wangzifan396-wzf/TW: AI 可视化实验室集 · 176 个交互式项目 · 1408 模块 · 零外部依赖 · 纯 HTML/CSS/JS SVG · 覆盖 AI/ML、CS 系统、计算理论、交叉学科全谱系 · GitHub
零依赖架构实战:451个HTML文件如何用63KB平均体积交付3584个交互模块
当你的node_modules目录膨胀到 500MB 时我在用 63KB 的单个 HTML 文件交付 24 个交互式可视化。这不是标题党。451 个自包含 HTML 文件总计 27.7MB包含 3584 个交互式 SVG 模块10752 个可视化视图——每个文件零外部依赖零 CDN 引用零 npm 包。双击打开即用离线可用永久可用。这篇文章不是介绍这些项目做了什么而是拆解一个工程问题当你选择零依赖时你获得了什么又必须付出什么代价答案藏在 CSS 变量的蝴蝶效应、gridBg必须画在g上的血泪教训以及子 Agent 系统性产出畸形语法的规律里。图1单个可视化实验室的运行时效果——暗色主题、8模块导航、SVG节点流程图、右侧信息面板全部在63KB的HTML文件内渲染零依赖架构的数学27.7MB vs 500MB先看事实。以下数据来自实际文件系统统计指标数值来源HTML 可视化文件数448 个Get-ChildItem *-viz-lab.html每文件模块数8 个MODULES数组定义每模块视图数3 个mViews数组总可视化模块3584448 × 8总可视化视图10752448 × 24文件总体积27.7 MBMeasure-Object -Property Length -Sum平均文件体积63.3 KB27.7MB / 448最小文件24.1 KB排序取最小最大文件146.9 KB排序取最大外部依赖0全文无import、require、script src独立 GitHub 仓库225 个已启用 GitHub Pages一个典型的可视化实验室文件约 63KB包含完整的暗色主题、8 个交互模块、24 个差异化视图、雷达图/节点图/流程图/进度条等多种 SVG 图形元素。作为对比一个空白的 React D3 项目node_modules轻松突破 200MB。零依赖的收益不止体积。可移植性文件复制到任何地方都能打开U盘、邮件附件、内网服务器。持久性十年后这个 HTML 照样能跑而 npm 生态的包可能早已废弃或 breaking change。审计性63KB 的源码一个下午就能通读全部逻辑。代价是什么你需要自己造所有轮子。双轨色彩系统一个 CSS 变量如何驱动 3584 个模块零依赖意味着没有 Tailwind没有 CSS-in-JS。451 个文件保持视觉一致性的核心是一套双轨色彩系统。第一轨CSS:root变量:root{ --bg:#0a0b0f;--bg2:#0f1117;--bg3:#161922;--bg4:#1c2030; --border:#1e2433;--border2:#2a3144; --text:#e2e8f0;--text2:#94a3b8;--text3:#64748b; --teal:#00d4aa;--cyan:#22d3ee;--amber:#fbbf24; --red:#ef4444;--purple:#a78bfa;--blue:#3b82f6; --pink:#f472b6;--green:#34d399; --mono:SF Mono,JetBrains Mono,Consolas,monospace; }18 个变量定义了整个设计系统。背景分 4 层bg→bg4文本分 3 级text→text38 种语义色覆盖所有图表场景。这不是随意选的——每一层都有明确的用途bg是画布底色bg2是面板bg3是卡片bg4是内嵌元素。第二轨JavaScript 镜像对象Cvar C{ bg:#0a0b0f,bg2:#0f1117,bg3:#161922,bg4:#1c2030, border:#1e2433,border2:#2a3144, text:#e2e8f0,text2:#94a3b8,text3:#64748b, teal:#00d4aa,cyan:#22d3ee,amber:#fbbf24, red:#ef4444,purple:#a78bfa,blue:#3b82f6, pink:#f472b6,green:#34d399, mono:SF Mono,JetBrains Mono,Consolas,monospace };C对象与:root一一对应。为什么需要两套因为 SVG 元素的stroke和fill属性无法引用 CSS 变量stroke:var(--teal)在 SVG attribute 中不生效。每个arrow()调用需要直接传入颜色字符串C.teal就是那个字符串。这套设计的连锁效应是修改一个颜色值451 个文件的所有模块同步变化。但前提是——你必须手动同步:root和C。这是零依赖的代价一致性靠纪律不靠框架。gridBg 的血泪教训为什么必须画在g上这是整个项目中代价最高的一堂课。gridBg函数为每个 SVG 画布绘制背景网格function gridBg(g,w,h){ for(var x0;xw;x45){ E(g,line,{x1:x,y1:0,x2:x,y2:h,stroke:#141926,stroke-width:1}); } for(var y0;yh;y45){ E(g,line,{x1:0,y1:y,x2:w,y:y,stroke:#141926,stroke-width:1}); } E(g,rect,{x:0,y:0,width:w,height:h,fill:none, stroke:#1e2433,stroke-width:1.5,rx:4}); }问题出在调用位置。drawModule是每次切换模块时的重绘入口function drawModule(mi){ mCurmi; var svg$(svg); clearNode(svg); // 清空 SVG 所有子元素 var gE(svg,g,{id:layer}); // 创建新的 g 层 gridBg(g,900,480); // 在 g 上画网格 —— 正确 buildNav(); buildTabs(mi); var fns[drawM1,drawM2,drawM3,drawM4,drawM5,drawM6,drawM7,drawM8]; fns[mi](g,mViews[mi]); }关键在第 4-5 行先clearNode(svg)清空画布再创建新的g元素最后在g上画网格。最初版本把gridBg直接画在svg根元素上。看起来没问题——但每次调用drawModuleclearNode(svg)会删除所有子元素而gridBg在svg上累积的线条在清除后不会完全消失。原因是 SVG 命名空间的元素在直接附加到根svg时某些浏览器会缓存渲染层。网格线会一层层叠加最终变成纯黑矩形遮盖所有内容。修复方法就是你现在看到的永远在新建的g元素上画网格。clearNode删除旧g新建g是干净的网格只画一次。这个 bug 影响了早期几十个文件。教训SVG 操作的副作用与 DOM 操作不同命名空间元素的清除和重建有微妙差异。子 Agent 的系统性错误.textContents)不是笔误在批量创建 451 个文件的过程中大量代码由 AI 子 Agent 生成。一个反复出现的语法错误暴露了模式// 正确 t.textContents; // 子 Agent 系统性产出错误 t.textContents);多了一个右括号。这不是随机错误——它在几十个文件中以完全相同的形式出现。原因是子 Agent 在生成txt函数时将return t;的语义与t.textContents;混淆在语句末尾残留了函数调用的闭合括号。这个错误的检测方法值得记录。用 Node.js 的vm.Script做语法校验var vm require(vm); var js html.match(/script([\s\S]*?)\/script/)[1]; try { new vm.Script(js); console.log(OK); } catch(e) { console.log(FAIL: e.message); }vm.Script提供精确的错误行号和列号远优于渐进式编译。批量验证脚本在几秒内扫描 448 个文件定位每个语法错误的确切位置。另一个系统性错误同样有规律circle元素缺少r属性。子 Agent 偶尔产出// 错误 —— 缺少属性键名 r circ(g,cx,cy,sz/24,{stroke:C.teal}); // 正确 —— r 显式声明 circ(g,cx,cy,sz/24,{r:sz/24,stroke:C.teal});修复后circ函数本身被加固强制要求r参数function circ(g,cx,cy,r,opts){ optsopts||{}; var a{cx:cx,cy:cy,r:r,fill:opts.fill||none, stroke:opts.stroke||C.teal,stroke-width:opts.sw||1.5}; if(opts.cls)a[class]opts.cls; E(g,circle,a); }教训当用 AI 批量生成代码时系统性错误会以固定模式重复出现。人类笔误是随机的AI 错误是结构性的。对付结构性错误最有效的方法不是逐个修复而是加固底层函数让错误在结构上无法发生。15 个核心函数替代整个前端框架零依赖的代价是自己造轮子。但轮子的数量比你想象的少——15 个核心函数覆盖了所有可视化需求函数用途替代品E(parent,tag,attrs)创建 SVG 元素document.createElementNS封装$(id)获取 DOM 元素document.getElementByIdclearNode(n)清空子元素while(n.firstChild)box(g,x,y,w,h)矩形容器CSSborder-boxtxt(g,x,y,s)文本标签HTMLspanline(g,x1,y1,x2,y2)直线SVGlinearrow(g,...)带箭头连线D3 的linkHorizontalcirc(g,cx,cy,r)圆形D3 的circle生成器poly(g,pts)多边形D3 的polygonradar(g,...)雷达图ECharts radar 组件gridBg(g,w,h)背景网格CSSbackground-imagenodeBox(g,...)带标题的节点框React 组件pbar(g,...)进度条CSSwidth动画rbox(g,...)指标卡片React Stat 组件chip(g,...)标签芯片CSSborder-radius以radar函数为例它用 20 行 vanilla JS 实现了 ECharts 雷达图组件的核心功能——多边形网格、数据多边形、标签定位function radar(g,cx,cy,r,labels,data,opts){ optsopts||{}; var nlabels.length,i,a,rr; // 画 4 层同心多边形网格 for(var ring1;ring4;ring){ var pts[]; for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; rrr*ring/4; pts.push((cxMath.cos(a)*rr).toFixed(1), (cyMath.sin(a)*rr).toFixed(1)); } E(g,polygon,{points:pts.join( ),fill:none, stroke:C.border,stroke-width:1}); } // 画轴线 for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; E(g,line,{x1:cx,y1:cy, x2:cxMath.cos(a)*r,y2:cyMath.sin(a)*r, stroke:C.border,stroke-width:1}); } // 画数据多边形 var dpts[]; for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; rrr*data[i]/100; dpts.push((cxMath.cos(a)*rr).toFixed(1), (cyMath.sin(a)*rr).toFixed(1)); } E(g,polygon,{points:dpts.join( ), fill:opts.fill||rgba(0,212,170,0.15), stroke:opts.stroke||C.teal,stroke-width:2}); // 画标签 for(i0;in;i){ a-Math.PI/2i*2*Math.PI/n; var lxcxMath.cos(a)*(r22), lycyMath.sin(a)*(r22)4; txt(g,lx,ly,labels[i],{fs:10,fill:C.text2,ta:middle}); } }ECharts 的雷达图组件源码超过 2000 行。不是 ECharts 臃肿——它处理了动画、tooltip、响应式、主题切换等大量边缘场景。但如果你只需要静态渲染一个数据多边形加标签20 行就够了。这就是零依赖架构的核心权衡你放弃的是通用性和边缘场景处理获得的是极简、可控和永恒。模块系统8 × 3 24 的约束设计每个文件包含 8 个模块每个模块 3 个视图共 24 个可视化。这不是随意选择——这是约束驱动设计。var MODULES[ {n:M1,t:概览,d:项目全景,v:[全景图,核心指标,定位分析]}, {n:M2,t:架构,d:技术架构,v:[架构图,模块分解,数据流]}, // ... M3-M8 ]; var mCur0,mViews[0,0,0,0,0,0,0,0],mLayers[];mViews是一个长度为 8 的数组记录每个模块当前显示的视图索引。切换模块时drawModule(mi)重绘整个 SVG切换视图时只改变mViews[mi]的值。约束在哪里drawModule的设计强制每个模块函数drawM1到drawM8必须接受(g, vi)两个参数其中vi是 0、1 或 2。这迫使作者为每个概念准备三个不同的可视化角度function drawM1(g,vi){ var side$(mpSide); clearNode(side); if(vi0){ // 视图1全景架构图 —— nodeBox arrow 流程 }else if(vi1){ // 视图2核心指标 —— rbox 数字卡片 radar 雷达图 }else{ // 视图3定位分析 —— 对比表格 pbar 进度条 } }8 模块的约束来自认知负荷理论7±2 是人类工作记忆的容量上限。8 个模块恰好在上限附近迫使作者提炼核心维度而不是堆砌信息。3 个视图的约束来自同一概念的三种表达方法论——架构图、数据指标、对比分析覆盖空间、数值和关系三个维度。部署架构一个合集 225 个独立仓库451 个文件有两种部署路径互为补充合集仓库TW-main所有 HTML 文件放在一个仓库一个index.html作为导航门户。优势是集中管理、统一搜索、一站式浏览。最新 commit2c2c642包含 481 个 HTML 文件含可视化实验室 工具页面。独立仓库225 个每个可视化实验室复制为独立仓库的index.html启用 GitHub Pages。优势是每个项目有独立 URL利于搜索引擎索引和社交分享。例如superpowers-agent-skills-framework-viz-lab仓库对应https://wangzifan396-wzf.github.io/superpowers-agent-skills-framework-viz-lab/。双轨部署的代价是同步开销——每次新增项目需要更新合集仓库的index.html新增卡片 更新统计数字然后运行 PowerShell 脚本批量创建独立仓库。但收益是搜索曝光最大化合集仓库获取聚合流量独立仓库获取长尾关键词流量。局限性零依赖架构不是银弹。以下是我明确承认的局限不适用于复杂状态管理。当应用状态超过mViews数组的复杂度需要路由、Store、副作用管理时零依赖方案的维护成本会指数级上升。451 个文件之所以可行是因为每个文件的状态模型完全相同。不适用于团队协作。15 个核心函数没有类型系统、没有 lint 规则、没有模块边界。一个人维护没问题5 个人同时改就会冲突。TypeScript ESLint 构建工具的存在是有理由的。不适用于需要动画引擎的场景。CSS 动画类apulse、aglow、adash、ablink、aspin覆盖了基础动画需求但无法实现物理引擎、粒子系统或复杂时间轴。这些场景 GSAP 或 D3 是正确的选择。文件体积在增长。早期文件约 24KB最新文件已达 146KB。随着模块内容复杂度增加单文件体积会继续增长。如果超过 200KB加载性能可能受影响届时需要考虑代码拆分——但这与零依赖单文件理念冲突。结论451 个零依赖 HTML 文件证明了一件事在特定场景下——可视化展示、教育内容、技术文档——零依赖架构不仅是可行的而且在可移植性、持久性和审计性上有不可替代的优势。代价是真实的你自己造轮子、自己维护一致性、自己处理边缘场景。但当你为一个 63KB 的文件添加一个模块双击打开看到 24 个交互式可视化瞬间渲染完成时你会理解为什么这个选择值得。所有代码开源合集仓库地址GitHub - wangzifan396-wzf/TW: AI 可视化实验室集 · 176 个交互式项目 · 1408 模块 · 零外部依赖 · 纯 HTML/CSS/JS SVG · 覆盖 AI/ML、CS 系统、计算理论、交叉学科全谱系 · GitHub