tsconfig 中 esModuleInterop 是什么?
tsconfig中esModuleInterop是什么?
英文含义:【Interop】互操作
babel、TS 等编译器更加偏爱 cjs。一方面是tsconfig.json 文件中的esModuleInterop用于改善CommonJS模块和ES Module之间的互操作性。另一方面 cjs 出现得比 esm 更早,所以已有大量的 npm 库是基于 cjs 的(数量远高于 esm),cjs 有着非常成熟、流行、使用率高的 runtime:Node.js。而我们目前写的很多代码都是esm,所以为了兼容性,我们通常会开启 esModuleInterop。
esModuleInterop 这个属性默认为 false,开启 esModuleInterop 后,TypeScript编译器会生成额外的代码来处理这种情况,TS 对于 import 的转译规则会发生一些变化(export 的规则不会变)。特别是允许ES模块更轻松地导入CommonJS模块的默认导出。
场景:在项目中普通的ts文件中 使用 import path from 'path' 会报错,但是vite.config.ts不会报错
path.d.ts(178, 5): This module is declared with 'export =', and can only be used with a default import when using the 'allowSyntheticDefaultImports' flag.
"allowSyntheticDefaultImports": true 兼容无默认导出的模块
原因:Node.js 内置模块(如 path)的 TypeScript 类型定义使用 CommonJS 导出方式。vite.config.ts 不会报错是因为 Vite 在解析配置文件时默认启用 esModuleInterop 选项(即使项目 tsconfig.json 未显式配置),而普通 TS 文件遵循项目级 TypeScript 配置。
解决:
// 修改插件文件中的导入方式(二选一)
// 方案一:使用兼容性更好的命名空间导入
import * as path from 'node:path'
// 方案二:在项目 tsconfig.json 中添加配置
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}建议:在项目根目录 tsconfig.json 中统一启用以下配置,保持整个项目导入规范一致
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}TS 开启 esModuleInterop 后的编译规则
ts 对于默认导入和 namespace(*)导入,TS 使用了两个辅助函数来帮忙。babel 默认的转译规则和 TS 开启 esModuleInterop 的情况差不多,也是通过两个 helper 函数来处理的。
__importDefault: 如果目标模块是 esm,就直接返回目标模块;否则将目标模块挂在一个对象的 defalut 上,返回该对象。
__importStar: 如果目标模块是 esm,就直接返回目标模块。否则
将目标模块上所有的除了 default 以外的属性挪到 result 上
将目标模块自己挂到 result.default 上
// 代码经过简化
var __importDefault = function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
var __importStar = function (mod) {
if (mod && mod.__esModule) {
return mod;
}
var result = {};
for (var k in mod) {
if (k !== "default" && mod.hasOwnProperty(k)) {
result[k] = mod[k]
}
}
result["default"] = mod;
return result;
};开发使用的 require 实际上是 TS 的一个语法糖,最终会被编译成 __importDefault 或者 __importStar。
// before
import React from 'react';
// after 代码经过简化
var react = __importDefault(require('react'));
console.log(react['default']);
// before
import {Component} from 'react';
console.log(Component);
// after 代码经过简化
var react = require('react');
console.log(react.Component);
// before
import * as React from 'react';
console.log(React);
// after 代码经过简化
var react = _importStar(require('react'));
console.log(react);总结
写前端代码一般是写 esm,所以常见的场景是 esm 导入 cjs 的库。但是由于 esm 和 cjs 存在概念上的差异,最大的差异点在于 esm 有 default 的概念而 cjs 没有,所以在 default 上会出问题。
TS babel webpack 都有自己的一套处理机制来处理这个兼容问题,核心思想基本都是通过 default 属性的增添和读取