CommonJS虽不支持动态加载,但可通过运行时路径解析、文件系统探测和缓存控制实现插件化架构;需约定插件目录结构、用require.resolve验证入口、手动管理require.cache以支持热加载,并定义init/destroy生命周期及安全沙箱约束。

CommonJS 本身不支持真正的“动态加载”——它的 require 是同步、运行时、阻塞式的,不能像 import() 那样返回 Promise 或按需异步拉取代码。但插件化架构的核心诉求(运行时发现、加载、注册、卸载模块)在 Node.js 环境中完全可实现,关键是**利用 CommonJS 的运行时路径解析能力 + 文件系统探测 + 缓存控制**,而非强行模拟 ESM 的异步语义。
插件目录结构与自动发现
插件化第一步是约定插件存放位置和识别规则。常见做法是统一放在 plugins/ 目录下,每个子目录为一个插件,含 index.js 或 package.json 声明入口:
- 插件目录结构示例:
plugins/<br> auth-plugin/<br> index.js<br> package.json<br> logger-plugin/<br> index.js
- 用
fs.readdirSync扫描目录,过滤掉非文件夹或无有效入口的项; - 对每个候选插件路径调用
require.resolve(pluginPath)验证是否可解析,避免require报错中断流程。
运行时 require + 模块缓存管理
CommonJS 的缓存机制(require.cache)是插件热加载的关键。默认情况下,require('./plugin') 会命中缓存,重复调用返回同一对象。要支持插件启用/禁用或热重载,需主动干预缓存:
- 加载插件时:先清除旧缓存(如有),再
require(pluginPath); - 卸载插件时:从
require.cache中删除对应模块路径及其所有子依赖(需递归清理,防止残留); - 示例清理逻辑:
function clearModuleCache(modulePath) {<br> const resolved = require.resolve(modulePath);<br> if (require.cache[resolved]) {<br> delete require.cache[resolved];<br> }<br>}
插件注册与生命周期契约
仅加载模块不够,还需定义插件如何与主系统交互。推荐约定插件导出一个对象,含标准方法:
立即学习“Java免费学习笔记(深入)”;
-
init(appContext):插件启动时调用,接收核心服务实例(如 logger、config、eventBus); -
destroy():插件卸载前调用,用于释放资源(关闭定时器、断开连接等); -
name和version字段用于标识; - 主系统维护一个插件注册表,按需调用各插件的
init,并在退出前遍历调用destroy。
安全与沙箱约束(可选但重要)
直接 require 第三方插件存在风险(如污染全局、执行恶意代码)。生产环境建议加一层隔离:
- 限制插件只能访问白名单 API(通过传入的
appContext控制); - 使用
vm.Script在独立上下文中运行插件代码(Node.js 提供),但会失去 CommonJS 原生模块解析能力,需自行实现require模拟; - 更轻量的做法:用
module.createRequire创建受限的 require 实例,禁用危险内置模块(如fs,child_process)。


















