TypeScript库兼容ESM与CommonJS:如何实现通用的__dirname?
兼容CommonJS和ESM的TypeScript脚本路径获取方案
我维护一个用TypeScript编写的库,希望同时支持CommonJS和ESM格式,因此会执行两次构建,这要求编写的TypeScript代码需兼容两种模块规范。其中一个问题是:CommonJS中存在__dirname变量,我用它来相对加载文件,但ESM中没有该变量。于是我尝试编写如下代码:
const _mydirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(url.fileURLToPath(import.meta.url)); // Example usage const file = fs.readFileSync(path.join(_mydirname, '../README.md'), 'utf-8')
不幸的是,用TypeScript构建时出现如下错误:
Error: src/application.ts(22,36): error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.
以下是几种可行的解决方案:
方案一:TypeScript条件编译区分环境
利用TypeScript的条件编译指令,根据不同的模块配置生成对应代码,避免语法检查冲突:
import * as path from 'path'; import * as url from 'url'; let _mydirname: string; // CommonJS环境分支 #if module === "CommonJS" _mydirname = __dirname; #else // ESM环境分支 _mydirname = path.dirname(url.fileURLToPath(import.meta.url)); #endif // 示例用法 const file = fs.readFileSync(path.join(_mydirname, '../README.md'), 'utf-8');
需要在tsconfig.json中启用条件编译支持:
{ "compilerOptions": { "allowConditionalCompilation": true } }
方案二:动态导入+全局变量判断
通过动态导入url模块,规避CommonJS环境下对import.meta的语法检查:
import * as path from 'path'; let _mydirname: string; if (typeof __dirname !== 'undefined') { _mydirname = __dirname; } else { // 动态导入url模块,仅在ESM环境执行 const { fileURLToPath } = await import('url'); _mydirname = path.dirname(fileURLToPath(import.meta.url)); } // 示例用法 const file = fs.readFileSync(path.join(_mydirname, '../README.md'), 'utf-8');
方案三:拆分模块分别实现
针对两种模块规范单独编写路径工具文件,通过构建流程切换导入:
path-utils.cjs(CommonJS版本)
const path = require('path'); module.exports = { getDirname: () => __dirname };
path-utils.mjs(ESM版本)
import path from 'path'; import { fileURLToPath } from 'url'; export const getDirname = () => path.dirname(fileURLToPath(import.meta.url));
在主代码中根据构建目标导入对应模块,或通过构建工具(如Rollup、Webpack)在不同构建流程中替换文件。
内容的提问来源于stack exchange,提问作者Evert
相关产品推荐
相关产品推荐

