OpenPLC 编辑器是一款跨平台的桌面 IDE,基于Electron运行时构建,结合了 React 驱动的渲染进程与 Node.js 主进程,提供专业的 PLC 编程能力。该技术栈中的每一项技术选择都有其明确目的:从 IEC 61131-3 语言编辑器到实时工业协议通信,整个技术栈针对工业自动化工具的独特需求进行了深度优化。
运行时与语言基础
在底层,编辑器运行于Electron v35之上,为 UI 提供了 Chromium 渲染引擎,并为系统级操作提供了 Node.js 运行时。整个代码库使用TypeScript 5.4编写,并启用了严格模式,编译器输出和 lib 环境均目标设定为 ES2022。这种组合使项目能够访问现代 JavaScript 特性(如顶层 await、私有类字段、structuredClone),同时在主进程、渲染进程和共享中间件层之间保持类型安全。
Node.js 引擎版本要求为≥20.x <24,npm 版本要求为≥10.x,以确保项目运行在当前的 LTS 版本线上。TypeScript 配置了esModuleInterop和allowSyntheticDefaultImports,旨在与 Electron 和 Webpack 所依赖的庞大 CommonJS 包生态系统实现平滑互操作。
进程架构
Electron 应用本质上采用多进程架构,OpenPLC 编辑器通过清晰的职责分离践行了这一点。主进程(src/main/main.ts) 是 Node.js 进程,负责拥有BrowserWindow、管理原生菜单、处理 IPC,以及协调编译器和硬件模块等后端服务。渲染进程(src/main.tsx) 是由 Chromium 托管的 React 应用,负责渲染整个 UI。预加载脚本连接了这两个世界,通过 Electron 的上下文桥接,向渲染进程暴露了经过严格作用域限定的window.bridgeAPI。
┌──────────────────────────────────────────────────────────────┐ │ OpenPLC Editor 进程模型 │ ├──────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 主进程 (Node.js) │ │ │ │ ───────────────────── │ │ │ │ • BrowserWindow 生命周期 │ │ │ │ • IPC 处理器 (MainProcessBridge) │ │ │ │ • 后端: 编译器, 硬件, 服务 │ │ │ │ • 自动更新器 (electron-updater) │ │ │ │ • 持久化存储 (electron-store) │ │ │ └─────────────┬──────────────────────────┬────────────┘ │ │ │ IPC (ipcMain ↔ ipcRender)│ │ │ ┌─────────────▼──────────────────────────▼────────────┐ │ │ │ 预加载脚本 (沙盒化) │ │ │ │ ───────────────────── │ │ │ │ • contextBridge.exposeInMainWorld('bridge', ...) │ │ │ │ • 作用域 API 接口 — 无完整 Node 访问权限 │ │ │ └─────────────┬──────────────────────────────────────┘ │ │ │ window.bridge.* │ │ ┌─────────────▼──────────────────────────────────────┐ │ │ │ 渲染进程 (Chromium + React 18) │ │ │ │ ───────────────────── │ │ │ │ • React 组件树 (App → Screens) │ │ │ │ • Zustand 存储 (16 个切片) │ │ │ │ • Monaco / XYFlow 编辑器 │ │ │ │ • Radix UI + Tailwind CSS │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘预加载脚本运行在沙盒上下文中,在 BrowserWindow 的webPreferences中启用了sandbox: true。这意味着渲染进程对 Node.js API 拥有零直接访问权——所有通信都必须流经显式暴露的window.bridge方法。当添加新的平台能力时,你必须定义一个端口接口,实现一个适配器,并通过预加载桥接进行连接。
前端框架与 UI
渲染进程是一个React 18应用,使用了支持并发渲染的现代createRootAPI。组件架构遵循原子设计(Atomic Design) 原则,在src/frontend/components/目录下组织为四个层级:
| 层级 | 目录 | 用途 | 示例 |
|---|---|---|---|
| 原子 | _atoms/ | 最小的可复用基元 | 按钮、输入框、复选框、选项卡 |
| 分子 | _molecules/ | 由原子组合而成,具有领域感知 | 面包屑、库树、变量表 |
| 有机体 | _organisms/ | 完整的功能区块 | 调试器、资源管理器、控制台、变量编辑器 |
| 模板 | _templates/ | 页面级布局组合 | AppLayout、WorkspaceLayout、EditorLayout |
组件库与样式
UI 构建在Radix UI基元之上——这些是无样式、可访问的无头组件,项目使用Tailwind CSS 3和class-variance-authority (CVA)对其进行样式定制。这种组合在保留无障碍访问性的同时,赋予了完全的设计控制权。项目使用的 Radix 基元包括 Accordion、Dialog、Dropdown Menu、Popover、Select、Tabs、Toast、Tooltip 等。Lucide React和Radix Icons提供了图标集。
Tailwind 配置了自定义设计令牌系统:brand调色板使用 CSS 自定义属性(从--primary-light到--primary-dark)来实现运行时主题切换,自定义字体系列(用于展示的Poppins,用于说明文本的Inter)保持了工业 IDE 的视觉美学。暗模式采用基于类名的策略(darkMode: 'class')。
状态管理
应用状态由Zustand 5结合切片组合模式进行管理。根状态并非采用单一庞大的存储,而是由 16 个独立的切片组装而成,每个切片定义了各自的状态形状和操作:
| 切片 | 领域 | 核心职责 |
|---|---|---|
| Project | 项目生命周期 | 创建、打开、保存、POU 管理 |
| Editor | 编辑状态 | 活动编辑器、选择、光标 |
| Tabs | 选项卡导航 | 打开/关闭/聚焦选项卡管理 |
| Device | 硬件配置 | 主板选择、引脚映射、Modbus |
| Ladder | LD 编辑器 | 梯形图节点与连接 |
| FBD | FBD 编辑器 | 功能块图流状态 |
| Library | 函数库 | 系统/用户功能块和函数 |
| Console | 日志 | 日志输出与过滤 |
| Search | 搜索 | 全项目搜索与替换 |
| History | 撤销/重做 | 编辑历史栈 |
| Workspace | 工作区布局 | 面板尺寸与可见性 |
| File | 文件操作 | 文件系统交互 |
| Modal | 对话框管理 | 模态框开启/关闭状态 |
| VersionControl | 版本跟踪 | 项目版本状态 |
| WebRTC | 实时通信 | 对等连接状态 |
| AI | AI 功能 | AI 辅助编程配置 |
Zustand 引入Immer(通过enableMapSet())以可变语法实现不可变状态更新,并使用subscribeWithSelector中间件实现细粒度响应性。auto-zustand-selectors-hook包通过自动生成的选择器钩子封装了存储,防止组件仅需要部分状态时引发不必要的重新渲染。
每个切片遵循工厂模式:createXxxSlice(...a),其中...a是 Zustand 的set/get/store元组。这使得切片具备良好的可组合性与可测试性。根存储通过将所有切片工厂展开到单个create()调用中创建——没有嵌套的 reducer,也没有 action 分发的样板代码。
编辑器集成
OpenPLC 编辑器支持全部五种 IEC 61131-3 编程语言,每种语言类型采用专门的编辑技术:
| 语言 | 编辑器技术 | 包 | 描述 |
|---|---|---|---|
| 结构化文本 (ST) | Monaco Editor | monaco-editor+@monaco-editor/react | 支持语法高亮、自动补全的完整代码编辑器 |
| 指令表 (IL) | Monaco Editor | monaco-editor | 支持 PLC 语言的文本编辑器 |
| 梯形图 (LD) | 自定义画布编辑器 | react-draggable+ 自定义渲染 | 触点/线圈可视化编程 |
| 功能块图 (FBD) | React Flow | @xyflow/reactv12 | 基于节点的图编辑器 |
| 顺序功能图 (SFC) | React Flow | @xyflow/reactv12 | 步骤/转换图编辑器 |
Monaco Editor(v0.54) 与驱动 VS Code 的编辑器引擎相同,通过@monaco-editor/react集成,并使用monaco-editor-webpack-plugin进行优化打包(配置中包含对 Python 语言的支持,以用于 PLC 编译器集成)。React Flow(@xyflow/reactv12) 为 FBD 和 SFC 编辑器提供了交互式的节点图画布,并支持针对 PLC 特定视觉元素的自定义节点类型和边渲染。
端口与适配器架构
该代码库中最具特色的架构决策之一是端口与适配器(六边形架构)模式。前端 UI 和存储仅依赖于端口接口——即在docs/ports/中定义的抽象契约——而从不依赖于特定平台的实现。具体的适配器在组合根处通过PlatformProvider注入:
┌─────────────────────────────────────────────────────┐ │ 共享 UI 层 │ │ (React 组件, hooks, Zustand 存储) │ │ 仅依赖于 ↓ │ ├─────────────────────────────────────────────────────┤ │ 端口接口 (10 个端口) │ │ Compiler · Runtime · Debugger · Simulator │ │ Project · Device · System · Window │ │ Accelerator · Theme │ ├────────────────────┬────────────────────────────────┤ │ 编辑器适配器 │ Web 适配器 (未来) │ │ (Electron IPC) │ (HTTP/WebRTC) │ └────────────────────┴────────────────────────────────┘
这十个端口接口定义了 UI 与平台之间的完整边界:CompilerPort用于 PLC 编译管线,RuntimePort用于远程 PLC 运行时通信,DebuggerPort用于调试协议操作,SimulatorPort用于内置 AVR 模拟器,ProjectPort用于项目生命周期管理,DevicePort用于硬件发现,SystemPort用于平台服务,WindowPort用于原生窗口管理,AcceleratorPort用于键盘快捷键,ThemePort用于主题检测。src/middleware/adapters/editor/中的每个适配器通过委托给预加载脚本暴露的window.bridge.*方法来实现其端口。
工业与领域特定库
除了标准的 Web 技术栈,编辑器还集成了多个为其工业自动化能力提供动力的领域特定库:
| 库 | 版本 | 用途 |
|---|---|---|
| avr8js | 0.20.0 | 基于 JavaScript 的 AVR 微控制器模拟器——在浏览器中运行编译后的 PLC 代码 |
| fast-xml-parser | ^5.6.0 | 解析 PLC 项目 XML 文件 (IEC 61131-3 格式) |
| xmlbuilder2 | ^3.1.1 | 生成 PLC 项目 XML 以用于保存/导出操作 |
| socket.io-client | ^4.8.1 | 与 OpenPLC 运行时进行实时通信 |
| serialport | ^12.0.0 | 用于主板编程的串口访问 (开发依赖) |
| vscode-languageserver | ^9.0.1 | 用于 ST/IL 智能提示的语言服务器协议 |
| monaco-pyright-lsp | ^0.1.7 | 用于编译器集成的 Python 类型检查 LSP |
avr8js库尤为引人注目:它是一个用纯 JavaScript 编写的完整 AVR ATmega 模拟器,使编辑器无需外部硬件即可模拟编译后的 PLC 程序。fast-xml-parser/xmlbuilder2对负责处理 PLC 项目在 IEC 61131-3 工具链中使用的标准 XML 格式的双向序列化。
数据处理与校验
应用中的数据流由一系列精心挑选的库进行管理:
@tanstack/react-query(v5) 负责处理来自运行时和编译器的异步数据获取的服务器状态管理,支持缓存、后台重新获取和乐观更新。
react-hook-form(v7) 结合zod(v3) 与@hookform/resolvers提供了类型安全的表单校验——Zod 中的 schema 定义可推断为 TypeScript 类型,并在运行时进行验证。
axios(v1) 作为 HTTP 客户端,负责与 OpenPLC 运行时进行 REST API 通信。
i18next(v24) 结合react-i18next(v15) 提供了国际化基础设施,当前配置以英语作为默认和回退语言。
immer(v10) 在 Zustand 的不可变存储中启用可变风格的状态更新,并激活了
enableMapSet()以原生支持存储状态中的Map和Set。
构建与打包流水线
构建系统是自定义的Webpack 5配置,包含六个专门的配置文件,通过webpack-merge进行组合:
| 配置文件 | 目标 | 用途 |
|---|---|---|
webpack.config.base.ts | 共享 | 通用加载器 (ts-loader)、解析配置、外部依赖 |
webpack.config.main.dev.ts | electron-main | 支持 HMR 的开发环境主进程 |
webpack.config.main.prod.ts | electron-main | 生产环境主进程 + 预加载打包、Terser 压缩 |
webpack.config.preload.dev.ts | preload | 开发环境预加载脚本编译 |
webpack.config.renderer.dev.ts | web/electron-renderer | 包含 React Refresh、CSS 模块、Monaco 插件的开发环境渲染进程 |
webpack.config.renderer.prod.ts | web/electron-renderer | 生产环境渲染进程优化 |
webpack.config.renderer.dev.dll.ts | web | 用于加速开发环境重构建的 DLL 打包 |
渲染进程的开发配置目标设定为['web', 'electron-renderer'],并包含用于热模块替换的React Refresh、配合 Tailwind 的Sass/PostCSS处理、用于 SVG 转 React 组件的SVGR,以及用于优化代码编辑器打包的Monaco Editor Webpack Plugin。生产环境构建使用TerserPlugin进行并行压缩,并使用BundleAnalyzerPlugin进行包体积检查。
桌面打包由electron-builder(v26) 处理,具备针对 macOS(支持 arm64/x64 通用构建、强化运行时和公证的 DMG)、Windows(NSIS 安装程序,x64)和 Linux(AppImage,x64)的特定平台配置。外部二进制文件(编译器工具、平台资源)在安装时通过scripts/download-binaries.ts下载,并打包至特定平台的extraResources目录中。
质量与开发者体验
项目通过分层的工具链强制保障代码质量:
ESLint 9搭配
typescript-eslint(recommended-type-checked 配置)、react-hooks插件、react-refresh插件,以及用于确定性导入排序的simple-import-sortPrettier 3搭配
prettier-plugin-tailwindcss实现自动类名排序Husky+lint-staged—— 预提交钩子对暂存的
.{ts,tsx}文件执行 ESLint 修复和 Prettier 写入通过
npm run validate:arch进行架构验证——src/__architecture__/validate.ts中的自定义规则强制执行依赖边界cspell.json用于全代码库的拼写检查
测试策略
测试技术栈在两个层级上运作:
单元测试与集成测试—— 在 jsdom 环境中结合ts-jest使用Jest 30,使用@testing-library/react进行组件测试,使用@testing-library/jest-dom进行 DOM 断言。覆盖率阈值强制要求关键路径(存储切片、工具函数、后端共享代码和中间件适配器)实现 100% 的函数/行/语句覆盖。测试配置使用@root/*路径别名映射和 identity-obj-proxy 来模拟 CSS 模块。
端到端测试——Playwright针对完全构建并打包的应用运行,验证跨越渲染进程、主进程和预加载桥接的多进程工作流。
技术栈概览
| 层级 | 技术 | 版本 |
|---|---|---|
| 桌面运行时 | Electron | ^35.0.0 |
| 语言 | TypeScript | ^5.4.5 |
| UI 框架 | React | ^18.3.1 |
| 状态管理 | Zustand | ^5.0.5 |
| 不可变更新 | Immer | ^10.1.1 |
| 组件基元 | Radix UI | 多个包 |
| 样式 | Tailwind CSS | ^3.4.19 |
| 变体系统 | class-variance-authority | ^0.7.0 |
| 代码编辑器 | Monaco Editor | ^0.54.0 |
| 图编辑器 | @xyflow/react | ^12.0.1 |
| 服务端状态 | @tanstack/react-query | ^5.90.21 |
| 表单校验 | react-hook-form + zod | ^7.66.0 / ^3.25.67 |
| XML 处理 | fast-xml-parser + xmlbuilder2 | ^5.6.0 / ^3.1.1 |
| 仿真 | avr8js | 0.20.0 |
| 实时通信 | socket.io-client | ^4.8.1 |
| 国际化 | i18next + react-i18next | ^24.2.2 / ^15.4.0 |
| 构建系统 | Webpack 5 | ^5.97.1 |
| 打包 | electron-builder | ^26.0.12 |
| 单元测试 | Jest + ts-jest + Testing Library | ^30.0.5 / ^29.4.0 |
| E2E 测试 | Playwright | ^1.52.0 |
| 代码检查 | ESLint + typescript-eslint | ^9.20.1 / ^8.24.0 |
| 格式化 | Prettier | 3.4.2 |