十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Babel 插件 `@babel/plugin-transform-modules-systemjs`:ES2015 模块到 SystemJS 的完整转换指南

Babel 插件 `@babel/plugin-transform-modules-systemjs`:ES2015 模块到 SystemJS 的完整转换指南 Babel 插件babel/plugin-transform-modules-systemjsES2015 模块到 SystemJS 的完整转换指南【免费下载链接】babel Babel is a compiler for writing next generation JavaScript.项目地址: https://gitcode.com/gh_mirrors/ba/babelbabel/plugin-transform-modules-systemjs是 Babel 生态中专用于将 ES2015ESM模块语法编译为 SystemJS 注册格式的官方插件属于 Babel 模块系统转换插件家族CommonJS / AMD / UMD / SystemJS中的一员。它适用于基于 SystemJS 加载器的浏览器端模块化方案、无打包器bundler-less的运行时模块架构以及需要按需动态加载模块的场景。读完本文你将掌握该插件的安装配置、全部可调参数allowTopLevelThis、systemGlobal、moduleId等、转换产物的结构原理以及动态import()、import.meta、顶层await等高级特性的落地方式。插件定位把 ESM 交给 SystemJS 运行时SystemJS 是一个面向浏览器的可插拔模块加载器支持System.register这一底层注册格式。Babel 的这个插件所做的工作就是把源代码中的import/export语句改写为对全局System.register的调用使模块可以在不支持原生 ESM 的浏览器环境中以 SystemJS 方式加载和执行。插件本身只负责语法层面的模块系统转换不包含 SystemJS 运行时。生产环境还需要引入systemjs库并配置System.import/script typesystemjs-module等加载入口。这一点在该插件的官方描述中亦有体现——This plugin transforms ES2015 modules to SystemJS见 README.md即它的职责边界仅限于模块系统的改写。在 Babel 的模块转换体系中它与babel/plugin-transform-modules-commonjs、babel/plugin-transform-modules-amd、babel/plugin-transform-modules-umd并列源码位于 packages/babel-plugin-transform-modules-systemjs由 src/index.ts 单文件实现包版本号与 Babel 8 主线保持一致见 package.json。安装使用 npmnpm install --save-dev babel/plugin-transform-modules-systemjs或使用 yarnyarn add babel/plugin-transform-modules-systemjs --dev安装后需要保证项目内存在babel/core该插件将其声明为peerDependencies当前要求^8.0.0详见 package.json。基础配置与用法在 Babel 配置babel.config.json或.babelrc.json中启用{ plugins: [babel/plugin-transform-modules-systemjs] }源码内部通过declare()注册插件插件名为transform-modules-systemjs见 src/index.ts。在pre()阶段插件会向文件元数据写入babel/plugin-transform-modules-*: systemjs标记供 Babel 内部其它插件判断当前模块体系类型。实际使用中更常见的做法是与其他 Babel 插件协同例如通过babel/preset-env的modules: systemjs选项间接启用。仓库的 fixture 测试也展示了最直接的组合方式见 test/fixtures/systemjs/options.json{ plugins: [transform-modules-systemjs] }使用 Babel CLI 编译babel src --out-dir dist --plugins babel/plugin-transform-modules-systemjs或配合babel/cli与配置文件使用输出文件即为System.register包装后的模块。转换输出结构剖析以官方 fixture 为例输入 test/fixtures/systemjs/overview/input.mjsimport foo; import foo-bar; import ./directory/foo-bar; import foo from foo; import * as foo2 from foo; import { bar } from foo; import { foo as bar2 } from foo; export { foo }; export var test2 5; export default foo;经过插件转换输出 test/fixtures/systemjs/overview/output.mjsSystem.register([foo, foo-bar, ./directory/foo-bar], function (_export, _context) { use strict; var foo, foo2, bar, bar2, test2; return { setters: [function (_foo) { foo _foo.default; foo2 _foo; bar _foo.bar; bar2 _foo.foo; }, function (_fooBar) {}, function (_directoryFooBar) {}], execute: function () { _export(foo, foo); _export(test2, test2 5); _export(default, foo); } }; });这段产物完整对应了System.register的运行时契约可以从源码中的模板直观看到整体骨架见 src/index.tsSystem.register(MODULE_NAME, SOURCES, function (_export, _context) { use strict; BEFORE_BODY; return { setters: SETTERS, execute: EXECUTE, }; });各部分组成与含义如下组成来源说明MODULE_NAMEgetModuleName(this.file.opts, options)模块名默认 undefined匿名模块可由moduleId等选项控制SOURCES收集到的所有import/export ... from的模块路径字符串数组依赖列表对应System.register的 deps 参数setters每个依赖模块对应一个 setter 函数依赖模块导出更新时被调用将导入值同步到本地变量execute原模块体经过重写模块执行体包含顶层await时会被生成为async function导入import如何变成 setters插件在Program.exit阶段遍历顶层语句将所有ImportDeclaration收集进按来源路径归组的模块元数据pushModule见 src/index.ts随后为每个来源生成一个 setter 函数见 src/index.ts默认导入import foo from foosetter 内生成foo _foo.default;命名空间导入import * as foo2 from foosetter 内生成foo2 _foo;命名导入import { bar } from foosetter 内生成bar _foo.bar;纯副作用导入import foo-barsetter 为空函数function (_fooBar) {}注意原import语句会从模块体中移除被替换为var变量声明所有顶层导入绑定统一提升到execute之前的beforeBody区见 src/index.ts 与 src/index.ts这正是 SystemJS 分阶段执行先 setters 赋值、后 execute 执行所要求的。导出export如何变成 _export 调用插件将各类导出统一改写为_export(导出名, 值)调用export var test2 5;改写为_export(test2, test2 5);export default foo;改写为_export(default, foo);导出函数声明、类声明会通过beforeBody/ 赋值语句做提升处理函数声明整体上提类声明改写为先var声明、再赋值的形态见 src/index.ts。export { foo }这类重新导出本地绑定的语句若绑定不存在全局变量则直接生成_export调用若绑定是提升的函数声明则记录导出名见 src/index.ts。关键点导出绑定的重赋值跟踪ESM 的导出是活绑定live binding即导出变量的后续修改必须同步到导入方。为此插件在Program.exit末尾注册了一个针对AssignmentExpression | UpdateExpression的重写访问器见 src/index.ts凡是对已导出变量进行赋值或自增/自减的位置都会追加_export(...)调用。例如test2 6会被改写为_export(test2, test2 6)。该逻辑还处理了两个边界情况解构赋值({a} obj)且a被导出改写为序列表达式(原赋值, _export(a, a))后缀更新表达式x且x被导出改写为(_export(x, x 1), x)保证更新表达式的返回值语义不变。字符串导出名与__proto__防护对于import { any unicode as foo } from m这类字符串形式的导入/导出名插件使用babel/helper-validator-identifier的isIdentifierName判断字符串能否直接作为标识符不能的才记入stringSpecifiers输出时以字符串字面量形式生成成员访问见 src/index.ts。此外构造export *的导出对象时会显式初始化__proto__: null避免原型污染风险见 src/index.ts。仓库为此维护了一整套 interop-module-string-names 测试夹具。模块名moduleId控制插件通过babel/helper-module-transforms的getModuleName读取模块名见 src/index.ts。仓库的 get-module-name-option 展示了显式指定模块名的配置{ plugins: [ [ transform-modules-systemjs, { moduleIds: true, moduleId: my custom module name } ] ] }开启moduleIds: true并设置moduleId后产物将变为System.register(my custom module name, [...], function ...)。若只开启moduleIds而不设置moduleId则会依据filenameRelative/sourceFileName等文件信息自动推导模块名。选项详解插件选项接口定义于 src/index.ts同时继承babel/helper-module-transforms的PluginOptions其中包含moduleId、moduleIds、getModuleId等通用选项export interface Options extends PluginOptions { allowTopLevelThis?: boolean; systemGlobal?: string; }allowTopLevelThis默认false{ plugins: [[babel/plugin-transform-modules-systemjs, { allowTopLevelThis: true }]] }在 ESM 中模块顶层this是undefined。开启转换后若不处理顶层this会变成System.register回调里的this即undefined的严格模式上下文语义恰好吻合但为了防止历史代码中依赖顶层this指向全局对象的行为插件默认会在Program.enter阶段调用rewriteThis(path)把顶层this改写为void 0见 src/index.ts。设置为true时跳过该改写保留顶层this原样。对应的测试夹具位于 test/fixtures/allow-top-level-this分为false/与true/两组可直接对照输入输出验证行为差异。systemGlobal默认System控制产物中调用register的全局对象名。默认生成System.register(...)若你的运行时将 SystemJS 挂载到自定义全局例如window.SystemJS或window.JSPM可以这样配置{ plugins: [[babel/plugin-transform-modules-systemjs, { systemGlobal: SystemJS }]] }源码中通过systemGlobal System的默认值解构选项见 src/index.ts最终以systemGlobal.register的形式拼进输出模板见 src/index.ts。高级特性与边界行为动态 import() 的转换对import(mod)动态导入插件会将其改写为_context.import(mod)形式见 src/index.ts把加载职责交给 SystemJS 运行时的context.import。需要特别注意的是插件要求必须同时启用动态导入转换插件否则会在编译期直接抛错。源码中MISSING_PLUGIN_ERROR明确给出提示见 src/index.tsERROR: Dynamic import() transformation must be enabled using the babel/plugin-transform-dynamic-import plugin. Babel 8 no longer transforms import() without using that plugin.也就是说包含import()的代码需要配置为{ plugins: [ babel/plugin-transform-dynamic-import, babel/plugin-transform-modules-systemjs ] }import.meta 与 __moduleNameimport.meta被替换为_context.meta见 src/index.ts交由 SystemJS 运行时提供meta信息如url对应测试夹具见 test/fixtures/import-meta。出于对旧版 SystemJS 的兼容插件还支持__moduleName标识符当代码中引用__moduleName且当前作用域内没有同名绑定即确认为全局引用而非用户变量时替换为_context.id见 src/index.ts从而获得当前模块名。顶层 awaitTop-Level Await插件会在Program.exit阶段检测模块体内是否存在顶层await表达式遍历时跳过函数内部见 src/index.ts。若存在生成的execute函数会被标记为async function见 src/index.ts从而支持 SystemJS 对异步模块执行的约定。相关测试位于 test/fixtures/tla覆盖了tla纯顶层 await、tla-block块级作用域内 await与not-tla仅有函数内 await应保持同步执行三种情形。顶层 this 重写与提升语义除了rewriteThis插件还对顶层语句做了系统性的提升处理以保证执行顺序正确函数声明被移入beforeBody在System.register回调体内、return之前执行顶层let/const声明统一改写为var因为 SystemJS 的 execute 是延迟执行的函数体变量必须提升为var才能在 setter 阶段被引用见 src/index.ts未初始化的导出绑定如export let x;通过hoistVariables收集并补充生成_export(x, void 0)的初始导出调用见 src/index.ts。fixture 目录 test/fixtures/systemjs 中还有hoisting-bindings、hoist-function-class、hoist-function-exports、module-level-variable、module-level-variable-destructuring、export-uninitialized等大量用例覆盖各类声明与导出的组合。循环依赖与 export * 的转发export * from m会被转换为对目标模块命名空间的转发插件生成一个以__proto__: null初始化的导出对象用for (var KEY in TARGET) { if (KEY ! default KEY ! __esModule) EXPORT_OBJ[KEY] TARGET[KEY]; }遍历复制导出项模板见 src/index.ts并调用_export(导出对象)一次性注册见 src/index.ts。该逻辑同时被放置在对应依赖模块的setter中执行从而在依赖更新时同步刷新转发结果——这是 SystemJS 下正确处理循环依赖与 re-export 的关键设计。systemjsfixture 中的export-from-*、export-named-alongside-with-export-star、export-from-proto-name等用例专门验证了这些场景。在项目中集成 SystemJS 运行时的最小示例编译产物依赖 SystemJS 运行时才能执行。一个最小化的接入流程如下通过 npm 安装systemjs运行时与插件相互独立插件不内置运行时npm install systemjs在入口 HTML 中引入 SystemJS 并加载编译后的模块script srcnode_modules/systemjs/dist/system.js/script script System.import(./dist/main.js); /scriptBabel 编译时对应用源码启用babel/plugin-transform-modules-systemjs得到System.register格式的产物。总结babel/plugin-transform-modules-systemjs是 Babel 官方模块转换插件家族中面向 SystemJS 体系的一员其核心价值在于把 ESM 的静态导入导出语义完整映射为System.register的setters/execute两阶段结构并在此过程中正确处理了活绑定重赋值、声明提升、import.meta、动态import()、顶层await与export *转发等复杂语义。理解它的转换模板与选项allowTopLevelThis、systemGlobal、moduleId即可在无打包器的 SystemJS 架构中稳定落地现代 ES 模块代码。若需深入了解每一类语法组合的转换细节可直接研读 src/index.ts 及 test/fixtures/systemjs 下的输入输出对照夹具。【免费下载链接】babel Babel is a compiler for writing next generation JavaScript.项目地址: https://gitcode.com/gh_mirrors/ba/babel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表