Angular18开发与生产环境CommonJS动态导入结果不一致问题
Angular 18 新构建系统下 CommonJS 模块动态导入的双 Default 问题
问题背景
Angular 18 切换到新构建系统后,本地开发依赖 Vite(底层基于 Rollup),生产构建使用 application 目标(底层基于 esbuild)。项目中使用 ngx-quill 搭配 CommonJS 规范的自定义模块 @enzedonline/quill-blot-formatter2,懒加载该模块时出现环境差异:
- 开发环境:动态导入后
qbf.default直接指向模块本身 - 生产环境:
qbf.default内部嵌套了一层额外的default(即双 default 问题)
原配置代码如下:
import { defer } from 'rxjs'; import { QuillConfig } from 'ngx-quill'; export const quillConfig: QuillConfig = { theme: 'snow', format: 'json', // ...其他配置项 customModules: [ { path: 'modules/blotFormatter', implementation: defer(() => import('@enzedonline/quill-blot-formatter2/dist/BlotFormatter').then(qbf => { console.log(qbf); return qbf.default; }) ) } ] };
差异产生的核心原因
这是 Rollup(Vite 底层)与 esbuild 对 CommonJS 模块转 ES 模块时,__esModule 标志的处理逻辑不一致导致的:
- Rollup 会自动识别 CommonJS 模块是否带有
__esModule: true标记,若存在则直接将模块的exports.default映射为 ES 模块的 default 导出,不会产生嵌套 - esbuild 对无
__esModule标记的纯 CommonJS 模块转换更"直白":如果原模块的导出结构是exports.default = xxx,esbuild 会保留这层结构,最终导致动态导入后出现default.default的嵌套情况
你的 @enzedonline/quill-blot-formatter2 属于纯 CommonJS 模块,未正确设置 __esModule 标记,因此触发了两个构建工具的处理差异。
更合理的解决方案
方案1:通用导出提取工具函数
不用复杂类型判断,通过通用函数统一兼容两种环境的导出结构:
import { defer } from 'rxjs'; import { QuillConfig } from 'ngx-quill'; // 通用工具:提取 CommonJS/ES 模块的正确默认导出 const getDefaultExport = <T>(module: T) => { return (module as any).default?.default ?? (module as any).default ?? module; }; export const quillConfig: QuillConfig = { theme: 'snow', format: 'json', // ...其他配置项 customModules: [ { path: 'modules/blotFormatter', implementation: defer(() => import('@enzedonline/quill-blot-formatter2/dist/BlotFormatter').then(getDefaultExport) ) } ] };
方案2:调整 esbuild 转换配置
在 angular.json 中给生产构建的 esbuild 添加配置,让它对 CommonJS 模块的转换逻辑贴近 Rollup:
{ "projects": { "你的项目名称": { "architect": { "build": { "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/你的项目", // ...其他配置 "esbuildOptions": { "commonjsOptions": { "transformMixedEsModules": true } } } } } } } }
transformMixedEsModules: true 会让 esbuild 对无 __esModule 标记的 CommonJS 模块进行智能转换,减少导出结构差异。
方案3:直接使用模块的 ES 版本
检查 @enzedonline/quill-blot-formatter2 的 package.json,若存在 module 或 exports 字段定义的 ES 模块入口,直接导入该版本:
// 替换原导入路径为 ES 模块入口(示例路径,以实际包为准) import('@enzedonline/quill-blot-formatter2/es/BlotFormatter').then(qbf => qbf.default)
从根源上避免 CommonJS 转 ES 模块的兼容问题。
内容的提问来源于stack exchange,提问作者uppy
相关产品推荐
相关产品推荐

