架构总览
本节把 1972 行手写 IIFE 的
bundle.js「拆开看」,覆盖五层架构(helper / template / sub-component / main / export)、三对外组件的组件树、双通道数据流,以及与 metric_card 的架构代差对比。
IIFE 顶层结构
ne101_camera 的 bundle.js 第一行不是 import,而是一句对 window 的契约声明。查看源码:
// bundle.js L1-L5
var NE101CameraPanel = (function () {
var React = window.React;
var jsx = window.jsxRuntime.jsx;
var jsxs = window.jsxRuntime.jsxs;
Source: bundle.js L1-L5
这三行 var React = window.React + var jsx = window.jsxRuntime.jsx + var jsxs = window.jsxRuntime.jsxs 是 NeoMind 组件市场的「注入三连」,也是与 metric_card(6 metric_card 3.2)共享的底层约定。
它意味着 bundle 不打包 React,而是从宿主页面已经加载好的单例里「借」一份 React,从而保证全仪表板只有一个 React 实例,hooks 不会跨实例失效(useContext 返回 undefined、useRef 报错这类经典症状)。
为什么用 var Name = (function(){ ... })() 这种 IIFE 形式而不是 UMD / CommonJS / ESM? 根本原因是 Dashboard 宿主通过 <script> 标签注入 bundle。<script> 标签没有模块作用域,IIFE 是唯一能用「函数作用域 + 闭包」模拟私有命名空间的零依赖手段:函数体内的 function classColor / var white 等等不会泄漏到 window 上,只有最后那一句 return { ... } 的对象挂到 window.NE101CameraPanel。
UMD 虽然也能跑在 <script> 下,但它多了一层 define / module.exports 的探测分支,对「不跑打包器」的 NeoMind 范式是冗余的;CommonJS 的 require 在浏览器里根本不工作。
最后一行 bundle.js L1971 是命名导出 + default 双暴露:
// bundle.js L1971
return { default: NE101CameraPanel, NE101CameraPanel: NE101CameraPanel, ConfigPanel: ConfigPanel, AdvancedPanel: AdvancedPanel };
Source: bundle.js L1971
注意 manifest.json 的 export_name: "NE101CameraPanel" 选了命名导出:
// manifest.json L38-L39
"global_name": "NE101CameraPanel",
"export_name": "NE101CameraPanel"
Source: manifest.json L39(从 NE101CameraPanel.NE101CameraPanel 取主组件),而不是 default;但 default 同时被保留,是为了兼容那些仍写 bundle.default 的旧版 Dashboard 加载器(见 2.5 决策 #2)。
这种「双暴露」是 ne101_camera 区别于 metric_card 的一个细节——metric_card 也写了 default + MetricCard,但只导出一个组件,而 ne101_camera 要把 ConfigPanel / AdvancedPanel 一起带出去给配置对话框使用。
下图把 IIFE 的「window 注入 → IIFE 闭包 → 五层 → return 对象」这条主线画清楚。
图里的实线是「加载/注入」方向,虚线是「读取」方向。IIFE 闭包内部那五层不是物理分离的文件,而是按行号分段的「逻辑层」。
这也是为什么读 ne101_camera 源码比读 metric_card 吃力:同一个文件里 helper、sub-component、main 互相穿插,必须先在脑子里建立五层模型。
五层架构拆解
ne101_camera 的 1972 行 IIFE 按职责可以切成五层。这一节给每层一个 2-3 句的职责说明,并标出关键行号深链,方便后续章节交叉引用。