commonjs-require-definition 源码解析:Brunch 的轻量级 CommonJS require() 运行时

发布时间:2026/10/6 1:58:27

commonjs-require-definition 源码解析:Brunch 的轻量级 CommonJS require() 运行时 构建工具前端【免费下载链接】brunch Web applications made easy. Since 2011.项目地址https://gitcode.com/gh_mirrors/br/brunch点击查看免费下载导读commonjs-require-definition是 Brunch 构建链中负责“在浏览器端执行 CommonJS 模块加载”的微型运行时。它仅有 116 行源码却完整实现了require()、require.register()、require.list()三件套并预留了热模块替换HMR的接入点。阅读本文后你将掌握该运行时的 API 用法、路径解析与模块缓存机制、别名与扩展名处理策略以及它如何在 Brunch 的构建产物中作为模块定义前缀被注入、如何与 HMR 运行时协同工作。定位一个被注入到 JS 构建产物顶部的运行时在 Brunch 的模块体系中commonjs-require-definition的角色非常清晰它不参与打包逻辑只提供打包完成后浏览器端需要的require()定义。package.json 中版本为0.6.3关键字为commonjs / module / require / define许可证为 MITindex.js 的全部逻辑只有一行用readFileSync将同目录下的 require.js 原文以字符串形式导出Brunch 根 package.json 声明了对commonjs-require-definition^0.6.3的依赖并在 lib/utils/modules.js 中以require(commonjs-require-definition)直接取得这份源码字符串。因此它不是一个“库”而是一段被拼进最终 JS 文件的运行时前缀代码。这也是为什么在 Brunch 构建出的app.js产物里能看到这份 require 定义的完整内联版本例如 test/fixtures/public/javascripts/app.js 中就保留了同构的require.define实现。快速上手原文档的 API 与用法根据 README.md这份 require 定义遵循 CommonJS Modules/1.1 规范公开了三个 APIAPI说明require(name)加载已注册的模块并返回其exportsrequire.register(name, fn)注册新模块fn回调接收(exports, require, module)require.list()列出所有已注册模块的 id在页面顶部引入require.js后即可使用script srcrequire.js/script script require.register(module, (exports, require, module) { // Expose count externally. exports.count 42; }); console.log(require(module).count); /script执行流程是先用require.register把模块定义存入注册表再通过require按名字取回其exports。console.log会输出42。源码剖析模块注册表、缓存与加载主流程1. 幂等挂载与全局对象识别require.js 用一个 IIFE 包裹全部实现var globals typeof global undefined ? self : global; if (typeof globals.require function) return;global对应 Node.js 环境self对应浏览器环境从而保证该脚本既能在 Node 里加载、也能在浏览器window/self上挂载。第二行是关键的自卫逻辑如果全局已经存在require函数直接放弃定义避免与 Node 原生 require 或重复注入的副本冲突。随后初始化三个核心容器var modules {}; // 注册表模块 id - 定义函数 var cache {}; // 缓存模块 id - 模块对象 {id, exports, hot} var aliases {}; // 别名表短名/无扩展名 - 完整模块 id2. 路径规范化expand、dirname与相对路径 require模块 id 内部统一使用/分隔不依赖path模块因为运行时最终要跑在浏览器里。路径处理由三个小函数完成require.jsexpand(root, name)用正则/^\.\.?(\/|$)/判断name是否以.或..开头若是相对路径则拼上root前缀再按/切分遇到..弹出一层、跳过.和空段最后重新 join得到一个绝对化的模块 iddirname(path)取路径的父目录段localRequire(path)返回一个“局部 require”它基于当前模块所在目录解析相对依赖var localRequire function(path) { return function expanded(name) { var absolute expand(dirname(path), name); return globals.require(absolute, path); }; };也就是说模块内部写require(./dep)时会被解析为“以当前模块 id 的目录为基准”的绝对 id再走全局require。这一层正是 CommonJS 相对路径语义的实现。3. 模块初始化initModule首次加载某个已注册模块时进入initModulerequire.jsvar initModule function(name, definition) { var hot hmr hmr.createHot(name); var module {id: name, exports: {}, hot: hot}; cache[name] module; definition(module.exports, localRequire(name), module); return module.exports; };它先创建模块对象{id, exports, hot}并先写入缓存再执行定义函数——这正是 CommonJS 规范要求的循环依赖保护在定义执行期间如果依赖图中有模块回引当前模块能从缓存拿到尚未完成填充的exports。定义函数收到的三个参数与 README 中require.register(name, fn)的签名(exports, require, module)一一对应其中require就是上文localRequire产出的局部 require。4. 加载主流程require(name, loaderPath)全局requirerequire.js按“缓存 → 注册表 → 抛错”三步查找var require function(name, loaderPath) { if (loaderPath null) loaderPath /; var path expandAlias(name); if (has.call(cache, path)) return cache[path].exports; if (has.call(modules, path)) return initModule(path, modules[path]); throw new Error(Cannot find module name from loaderPath ); };注意错误信息会带上loaderPath加载者路径方便在浏览器控制台定位是谁加载了不存在的模块。has {}.hasOwnProperty是防御性写法避免modules原型链污染干扰in判断。5. 别名机制require.alias与addExtensions为了兼容“不带扩展名”和“目录 index”这两种常见的模块引用写法运行时在注册模块时会自动补充别名require.jsvar extRe /\.[^.\/]$/; // 匹配最后一个扩展名 var indexRe /\/index(\.[^\/])?$/; // 匹配 /index[.ext] var addExtensions function(bundle) { if (extRe.test(bundle)) { var alias bundle.replace(extRe, ); if (!has.call(aliases, alias) || aliases[alias].replace(extRe, ) alias /index) { aliases[alias] bundle; } } if (indexRe.test(bundle)) { var iAlias bundle.replace(indexRe, ); if (!has.call(aliases, iAlias)) { aliases[iAlias] bundle; } } };注册app/main.js时app/main会被记录为别名注册app/index.js时app也会指向它。require.alias(from, to)则支持运行时手动登记别名且expandAlias会递归展开别名链require.js防止别名指向另一个别名时解析中断。6.require.register的两种形态require.register与require.define是同一个函数的两个名字require.js支持两种调用批量对象形态传入{id: fn}这样的映射对象逐个递归注册单条形态require.register(name, fn)写入注册表、删除该模块的缓存强制下次重新初始化并调用addExtensions补充扩展名别名。值得一提的是delete cache[bundle]这一行正是它让模块可以被“重新注册”从而支撑了 HMR 场景下的模块替换。Brunch 的 CHANGELOG.md 也记录了“bump commonjs-require-definition 以允许重置模块用于配合更新的 auto-reload-brunch 做 JS 热重载”这一演进。7. 枚举与标记require.list()require.js遍历modules注册表返回所有模块 id 数组可用于调试或服务端判断哪些模块被打入包内require.brunch truerequire.js是一个环境标记用于识别“这是 Brunch 注入的 require 运行时”。HMR 集成点_hmr、require.hmr与_cache运行时在文件末尾主动探测热更新宿主require.jsvar hmr globals._hmr new globals._hmr(_resolve, require, modules, cache); require._cache cache; require.hmr hmr hmr.wrap;若全局存在_hmr构造器则把解析器_resolve、全局 require、模块注册表、缓存四个句柄传给它由 HMR 运行时接管依赖图跟踪与模块热替换_resolve(name, dep)是“别名展开 相对路径展开”的复合解析函数被专门抽出供 HMR 计算模块间依赖关系require.hmr(graph, fn)是热更新入口Brunch 的 lib/utils/hmr.js 在开发模式下会生成形如require.hmr({...依赖图...}, function(require) { ...模块注册... });的代码段把依赖图 JSON 和注册回调一并交给运行时。对照 packages/addons/hmr-brunch/runtime.js 可以看到_hmr构造器正是按( _resolve, require, modules, cache )的签名接收参数并通过this.createHot(name)为每个模块生成带accept / decline / dispose / addDisposeHandler / removeDisposeHandler / data的 hot 对象回填到initModule创建的module.hot上。也就是说HMR 的“钩子”全部由这份 require 定义主动暴露热更新运行时只是寄生在它之上的客户端。在 Brunch 构建管线中的位置definition 与 wrappercommonjs-require-definition的注入由 Brunch 的模块配置驱动。在 lib/utils/modules.js 中exports.normalizeDefinition definition { switch (definition) { case commonjs: return () commonRequireDefinition; case false: return () ; } return definition; };当用户在brunch-config中设置modules.definition: commonjs时normalizeDefinition返回的正是 index.js 读出的 require.js 源码字符串若设为false则产出空字符串不注入运行时。同一文件中的getWrapperFnlib/utils/modules.js则生成了配套的模块包装前缀require.register(moduleName, function(exports, require, module) {即每个编译后的模块都被包进require.register(...)调用。这两个配置最终在 lib/utils/config.js 的normalizeConfig中被合并进config._normalized.modules.wrapper / definition进入构建的 join 流程。整体链条可概括为brunch-config 中 modules.definition: commonjs → normalizeDefinition 取回 require.js 源码字符串 → 作为构建产物顶部的运行时前缀 → 各模块以 require.register(id, function(exports, require, module){...}) 注册 → 浏览器端 require(id) 返回 exportsrequire.hmr(graph, fn) 支撑开发期热更新test/fixtures/public/javascripts/app.js 正是这种产物的真实写照运行时定义 this.require.define({ app: function(exports, require, module) {...} })的注册块二者组合即构成浏览器可直接执行的完整 bundle。适用前提与限制这份运行时面向CommonJS 风格的同步模块加载模块 id 统一使用/路径不处理 AMD、ES Modules 的运行时语法ES 模块在构建期已被编译器转换浏览器端加载必须保证require.js先于任何模块代码执行且整个 bundle 被设计为单文件注入——正如 lib/utils/hmr.js 中针对多文件输出场景给出的警告所示HMR 正常工作也依赖单文件编译产物若全局环境已存在require函数例如在 Node.js 中直接 require 该文件运行时会静默让位不会覆盖宿主实现。结语commonjs-require-definition用不到 120 行代码把 CommonJS Modules/1.1 的require语义搬进了浏览器并为 Brunch 暴露了三条关键接口require.register构建期注入模块、require.alias/扩展名别名加载容错以及require.hmr热更新接入。理解它的内部结构等于同时理解了 Brunch 产物顶部的运行时前缀、模块包装器的由来以及 HMR 运行时如何寄生在这份定义之上完成模块替换。赞分享构建工具前端【免费下载链接】brunch Web applications made easy. Since 2011.项目地址https://gitcode.com/gh_mirrors/br/brunch点击查看免费下载相关推荐webpack CommonJS 打包实战解析从 require 依赖链到 bundle 产物结构与运行原理webpack CommonJS 打包实战解析从 require 依赖链到 bundle 产物结构与运行原理 导读 CommonJS 是 Node.js 生态前端构建开发工具webpack 官方 CommonJS 示例深度解析一条 require 依赖链如何变成可运行的 bundlewebpack 官方 CommonJS 示例深度解析一条 require 依赖链如何变成可运行的 bundle CommonJS 是 webpack 能够开箱前端构建开发工具the-super-tiny-compiler中的CommonJS模块支持require/exports实现the super tiny compiler中的CommonJS模块支持require/exports实现 在前端开发中模块系统是组织代码的重要方式。Co编译器上一篇AndroidVideoCache内存缓存实现ByteArrayCache使用场景下一篇Lime前端交互设计提升用户编辑体验的关键细节创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/6 3:03:29

VSCode终端中文打印出???,一文讲透编码原理与根治方案

写Python脚本打印中文,终端蹦出来三个问号???。第一反应是怀疑print写错了,检查了一遍发现代码没有问题,上网一搜才知道“vscode终端窗口汉字打印为???”这组关键词居然是个长期热门问题。更让人头大的是,网上答案七零八落&…

2026/10/6 3:03:29

数字孪生如何助力新能源与装备制造?达索系统落地路径全解析

达索系统在2026年把企业数字化转型与智造论坛的主题定在“数字孪生智能助力新能源及装备行业”,其实传递了一个很明确的信号:上一轮数字化转型里,大家把ERP、MES、PLM这些管理软件上一遍,解决了“流程找人”的问题;而新…

2026/10/6 3:03:29

数据结构学习全攻略:四层框架、主线知识树与考研408冲刺

学习数据结构的人很多,但真正把这门课学明白、用起来的人,说实话不多。我见过太多同学,期末复习的时候把书从头翻到尾,考研刷题的时候把王道“数据结构”翻烂了,可一到需要自己设计存储结构、估算复杂度的时候&#xf…

2026/10/6 3:03:29

C# Windows客户端博客系统:文件续传与ClickOnce部署实战

简介:一套完整的原创私人博客云盘系统Windows客户端源码包,面向具备C#基础、希望自建博客或私有云盘的开发者。客户端内置博客发布、删除、点赞与收藏功能,后台支持资料修改、第三方账号绑定、安全验证策略及账号注销;文件模块涵盖…

2026/10/6 2:58:29

JSP图书管理系统源码实战:环境搭建、事务处理与避坑指南

简介:这份资源是一套基于JSPMySQLServlet技术栈实现的图书馆图书管理系统完整源码,面向正在学习Java Web开发、需要课程设计或毕业设计参考的学生与初级开发者。系统按角色划分权限:管理员可完成登录、用户管理、图书管理与罚款缴纳&#xff…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/5 17:38:27

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 0:03:23

MR25H40CDF+STM32F031C6工业级高可靠数据存储方案

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的 PLC 控制柜里、在风电变流器的散热片背面、在矿井监测终端的金属外壳下,你经常能看到一块指甲盖大小的黑色芯片——它既不是 Flash,也不是…

2026/10/6 0:03:23

MRAM+STM32工业断电数据保全实战指南

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的PLC柜里、在野外无人值守的环境监测终端里、在高速运转的包装机控制板上,你经常能看到一块指甲盖大小的黑色芯片,旁边贴着“MR25H40CDF”丝…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑