tsconfig.json的esModuleInterop使用场景是怎样的?

2023-11-10

  • 问题场景
  • npm包改造前,仅支持esm
  • npm包改造后,既支持esm,又支持cjs
  • 为什么改造后,还是会报错?
  • 如何理解ts编译配置esModuleInterop?
  • 总结

问题场景

遇到一个很有趣的场景,cjs中需要引入原先打包方式为esm方式的模块。

也就是想要通过require(),去引入一个export的模块。

my-npm-package包的暴露方式为:


import foo from "./foo";
import bar from './bar';
export { foo, bar };

支持的方式为

import {foo, bar} from 'my-npm-package';

cjs中想要使用esm方式的包

const { foo } = require("my-npm-package");

会报错:SyntaxError: Cannot use import statement outside a module

那么如何使得原先仅支持esm方式的包,改造为既支持esm又支持cjs呢?
打包方式commonjs。
这只支持了cjs,esm怎么支持呢?
支持esm是通过引入包的项目的babel进行转化进行支持的。

npm包改造前,仅支持esm

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "esnext",
  }
}

打包结果:

import foo from "./foo";
import bar from './bar';
export { foo, bar };
//# sourceMappingURL=index.js.map

npm包改造后,既支持esm,又支持cjs

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs"
  }
}

打包结果:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bar = exports.foo = void 0;
const foo_1 = require("./foo");
exports.foo = foo_1.default;
const bar_1 = require("./bar");
exports.bar = bar_1.default;
//# sourceMappingURL=index.js.map

cjs: exports.xxx
esm: Object.defineProperty(exports, “__esModule”, { value: true });

可以“csj引入原先方式为esm包”的原因是什么?
exports.xxx
原先esm方式的包,还可以正常使用的原因是什么?
Object.defineProperty(exports, "__esModule", { value: true });

那就是“__esModule”,webpack会根据__esModule,将模块识别为esm,最后通过babel转化为cjs模块方式引入。

回到我们的场景:改造esm模块为既支持cjs,又支持esm,能实现的原因是什么?

第一步:target从esm改为commonjs,从而支持cjs
第二步:这一步其实不用做,主项目的babel已经做了配置,对于所有esm和cjs的包,都可以通过esm方式引入。

为什么改造后,还是会报错?

先说结论:因为tsc cjs方式打包,默认会把import a from ‘a’, a.method()的包,转化为const a_1 = require(‘a’), a_1.default.method()。而有些npm包,没有exports.default。
如何解决:开启esModuleInterop。

TypeError: Cannot read properties of undefined (reading ‘stringify’)

这是因为,在我们的npm包中,有使用到query-string这个依赖。

import queryString from 'query-string';
const query_string_1 = require("query-string");
query_string_1.default.stringify(body) // 这里发生了报错

经过tsc打包后,会转换为为query_string_1.default。

但是query-string@7.1.1的index.js,并没有暴露default。

转换后

const query_string_1 = exports;
// query-string@7.1.1
exports.parseUrl
exports.stringifyUrl 
exports.pick
exports.exclude
exports.stringify
exports.extract
exports.parse

那么如何解决这个问题呢?开启tsconfig.json中的esModuleInterop为true。
从而将exports作为default返回。

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "esModuleInterop": true
  }
}

打包结果:

// index.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bar = exports.foo = void 0;
const foo_1 = __importDefault(require("./foo"));
exports.foo = foo_1.default;
const bar_1 = __importDefault(require("./bar"));
exports.bar = bar_1.default;
//# sourceMappingURL=index.js.map

不仅仅是index.js会注入__importDefault ,所有经过tsc编译的ts文件,都会注入__importDefault。

// foo.js
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
const query_string_1 = __importDefault(require("query-string"));

经过__importDefault 转换后,变为

const query_string_1 = __importDefault( exports );

转换后

const query_string_1 = { default: exports };
query_string_1.default.stringify(body) // 这里就没问题了。

如何理解ts编译配置esModuleInterop?

除了默认引入缺少default的情况,按照namespace方式引入的情况,也需要配置esModuleInterop去兼容。

先来看看ts官方文档:https://www.typescriptlang.org/tsconfig#esModuleInterop

默认情况下,esModuleInterop关闭,ts按照CommonJS/AMD/UMD模块处理为es6模块一样去处理。有两种情况下不能这样去处理:

  • ❌ import * as moment from “moment” 当做const moment = require(“moment”)
  • ❌import moment from "moment"当做const moment = require(“moment”).default

开启后可以避免这2个问题:

import * as fs from "fs";
import _ from "lodash";
fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);

禁用时(直接require):

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const lodash_1 = require("lodash");
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);

开启时(辅助导入函数__importStar, __importDefault):

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const lodash_1 = __importDefault(require("lodash"));
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);

再来看一下知乎上一位前端同学的文章:https://zhuanlan.zhihu.com/p/148081795

esm引入cjs可以interop(互操作)的核心思想是:esm有default,而cjs没有,为cjs模块增加default。

引用一段作者的话,很精简:

目前很多常用的包是基于 cjs / UMD 开发的,而写前端代码一般是写 esm,所以常见的场景是 esm 导入 cjs 的库。但是由于 esm 和 cjs 存在概念上的差异,最大的差异点在于 esm 有 default 的概念而 cjs 没有,所以在 default 上会出问题。TS babel webpack 都有自己的一套处理机制来处理这个兼容问题,核心思想基本都是通过 default 属性的增添和读取

总结

1.如何将esm模块打包为cjs?

module改为commonjs。

2.为什么esm可以通过import引用cjs的包?

babel会把import转为require。

3.如何理解esModuleInterop?

兼容只有umd,cjs方式且没有暴露deault属性的包,添加default属性,从而使得import a from "a"或者import * as a from "a"引入的包,不会报没有default属性。例如query-string@7.1.1这样的包。
保险起见,建议开启这个配置。

4.为什么module为esnext时不会报错?

因为module为esnext时,代码直接就是esModule模式,也就是import, default模式,不会被转为cjs并带一个尾缀default的方式。

可以说,怎么写的,打包出来就是原模原样的。

import webcVCS from "./webcVCS";
import generateAssets from './generateAssets';
export { webcVCS, generateAssets, };
import queryString from 'query-string';
5.以后打包,module怎么配置?
  • esnext: 只在esm环境使用的包
  • commonjs:纯cjs或既在cjs又在esm环境使用的包(esm环境使用一般是由安装包的项目,结合webpack,babel等打包工具支持的)
  • umd: 同commonjs,且需要同时支持cjs,amd, cmd
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tsconfig.json的esModuleInterop使用场景是怎样的? 的相关文章

  • 从字符串中删除货币符号并使用 Javascript 中的单行转换为数字

    我下面有一个字符串 它是以英镑为单位的价格 我想删除货币符号 然后将其转换为我可以用来与另一个值进行比较的数字 价格 例如 X gt Y 14 50 我之前已将字符串转换为用于货币的数字 var priceNum parseFloat pr
  • 了解执行模型和事件循环

    我读过很多关于JavaScript单线程执行模型 事件循环和事件队列的文章 但有一件事尚不清楚 我创建了一个小提琴来说明我的问题 http jsfiddle net yzpmf67f http jsfiddle net yzpmf67f
  • “Pick”仅指一种类型,但在尝试扩展 Pick< 时在此处用作值

    我试图只拥有一些属性ancestor暴露在我的后裔 我尝试通过以下方式实现它Pick export class Base public a public b public c export class PartialDescendant e
  • 通过单击字段启用非活动字段

    是否可以有一组非活动字段 如果单击其中一个字段 则某些字段将变为必填字段并运行某些代码段 举例来说 您显示了三个字段
  • 在 AngularJS 中覆盖运行时的依赖关系

    我有一个服务叫 doggedHttp 它公开了与 http 现在我想创建一个 doggedResource服务是有角度的 resource服务之上 doggedHttp代替 http 换句话说我想注入 doggedHttp as the h
  • 嵌套对象的 AJV 模式验证

    函数返回的对象看起来像这样 answer vehicle type 1 message Car model VW color red 答案 对象始终存在 其他字段基于 vehicle type E g 如果vehicle type 1 则有
  • 如何获取调用函数的“this”值?

    如果我有一个这样的函数 function foo this console log this function bar bar prototype func function foo this var test new bar test f
  • 避免 AngularJS 部分视图在 IE 中缓存

    我正在开发一个 ASP NET MVC 应用程序 它也有一些 angularJS 我有一个主页 其中有不同的选项卡 当您单击它们时 它们会加载角度部分视图 主页是这样的 div class widget div div class widg
  • 在 Chrome 中检索浏览器语言

    我一直在尝试让 momentjs 正确检测浏览器语言并本地化时间显示 按照使用 Moment js 进行区域设置检测 https stackoverflow com questions 25725882 locale detection w
  • RxJS - 从可观察对象中获取最后 n 个元素

    我想从可观察对象中获取最后 3 个元素 假设我的时间线是这样的 a b c d e f g h i j gt where a b c d e f g h i j are emitted values 每当发出新值时 我想立即获取它 因此它可
  • 日期时间的自定义 JavaScriptConverter?

    我有一个对象 它有一个 DateTime 属性 我想通过 AJAX JSON 将该对象从 ashx 处理程序传递回网页 我不想使用第 3 方控件 当我这样做时 new JavaScriptSerializer Serialize DateT
  • Firefox OS 后台服务

    我想构建一个应用程序 用户可以通过它输入一些设置 并且应用程序将启动后台服务来根据这些设置执行一些任务 我只想在模拟器中运行应用程序和后台服务 我知道它需要 认证 模式才能运行后台服务 但我现在不考虑在 Firefox Marketplac
  • 替换 Javascript 中的引号?

    对于我正在制作的网络应用程序 我将收到文本字符串 其中偶尔包含引号 因为我接下来要 document writing 字符串 所以需要将它们更改为撇号或转义 我该怎么做 因为当我尝试时它似乎不起作用 特别是我认为因为字符串的引号阻止了脚本的
  • 反转二进制网络

    如何反转二元方程 以便找到哪些输入将产生给定的输出 Example Inputs i0 through i8 Outputs o0 through o8 Operators XOR AND 二元方程 1 i0 1 i1 0 i2 1 i3
  • 检查 href 中是否存在 jQuery 中的查询字符串

    我目前有一段 jQuery 用于附加带有一些位置信息的 URL jQuery a attr href function return this href location 123 abc 我的问题是大多数链接都有一个 其中使用上面的 就可以
  • 如何在 Javascript 中将字符串数组转换为特定的树结构

    我从后端获取文件路径列表 它代表文件夹结构 如下所示 paths path to file1 doc path to file2 doc foo bar doc 路径的长度是任意的 为了使用文件树组件 角度2树组件 https github
  • 条件顺序仅在 init AngularJS 上执行

    如何在视图初始化时仅运行 orderBy 过滤器一次 我不希望我的列表在运行时被重新排序 li li 使用 orderBy 作为控制器中的过滤器 app controller DemoCtrl scope filter function s
  • 使用 Promise 语法编写同步代码有什么好处吗?

    有同步承诺这样的概念吗 使用 Promise 语法编写同步代码有什么好处吗 try foo bar a b bam catch e handleError e 可以写成类似的东西 但使用同步版本then foo then bar bind
  • 将元素添加到 D3 圆包节点

    我正在尝试制作一个可缩放的圆形包装图 我希望每个子圆圈包含一个较小的图表 该图表始终具有相同的结构 即 4 列 只有条形的高度会改变 我尝试添加一个简单的rect到目前为止我的图表 但矩形没有添加到圆圈中并且是静态的 JS var marg
  • 截断段落前 100 个字符并隐藏段落的其余内容,以通过更多/更少链接显示/隐藏其余内容

    我有一个超过 500 个字符的段落 我只想获取最初的 100 个字符并隐藏其余部分 我还想在 100 个字符旁边插入 更多 链接 单击更多链接时 整个段落应显示并编辑文本 更多 到 更少 单击 更少 时 它应切换行为 段落是动态生成的 我无

随机推荐

  • zookeeper基本概念及使用场景

    zookeeper基本概念及使用场景 Zookeeper作为一种协调分布式应用高性能的调度服务 在ZooKeeper中 节点类型可以分为 持久节点 PERSISTENT 临时节点 EPHEMERAL 时序节点 SEQUENTIAL 具体在节
  • 机器学习项目的实验方法

    发现对于一些机器学习项目 在项目进行中往往会直接就开始编码实现 而没有一套完整的方法论和有实操性的流程 经过一些粗浅的研究 下面写出一些对于此的部分思考 或有参考借鉴之用 实验开始前 需要清楚研究什么 如何收集数据 打算怎样分析 1 研究目
  • Crunchfish发布XR Skeleton手势交互解决方案

    转自 https baijiahao baidu com s id 1677811512042412943 wfr spider for pc 移动交互解决方案开发商Crunchfish日前发布了名为XR Skeleton的手势控制技术 只
  • HttpClient的”javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated”异常

    在开发https应用时 你的测试服务器常常没有一个 有效的 SSL证书 在你的客户端连接测试服务器时 如下的异常会被抛出 javax net ssl SSLPeerUnverifiedException peer not authentic
  • CDN方式使用Vue,VantUI组件方式

    代码示例
  • 这个小工具竟然可以让我保持健康

    Part1背景 我有个同事今天跟我说 他总是忘记喝水 这可怎么办 作为程序员的我们 本就每天久坐缺乏锻炼 如果再忘记喝水 那将会带来多么严重的后果 所以我决定为他做一个小工具来提醒他定时喝水 活动 Part2前期准备 1下载依赖软件 pip
  • python 用逐步回归筛选变量

    在回归分析中 影响因变量y的因素很多 而有些自变量的对目标变量y的影响程度不同 为了建立一个相对最优的回归方程 我们需要筛选掉对目标变量y影响不大的变量 这就涉及到了变量选择问题 逐步回归是通过假设检验的方法来筛选强特征 但如果直接用特征变
  • Springboot线上环境彻底关闭Swagger-UI

    文章目录 概要 整体架构流程 技术细节 小结 概要 Springboot线上环境彻底关闭Swagger UI 整体架构流程 1 SwaggerConfig使用 Profile排除线上环境其他环境生效 2 创建一个控制类使用 Profile仅
  • 一、绘制不同类别特征均值标准差直方图

    1 绘制不同类别植被指数均值标准差直方图 数据类型 不同类别样本植被指数 实验目的 1 计算不同类别各指数的均值和标准差 1 1读取表格中的数据 1 2预处理数据 提取不同类别指数 归一化指数 计算均值标准差 1 3画图 import pa
  • fhog资料整理

    fast hog资料整理 https github com joaofaro FHOG https github com ppaanngggg fhog 这个有两种 1 DPM的 Mat tmp image image convertTo
  • 如何读取字符串中的空格

    根据前面的学习 我们可以知道scanf读取到空格 回车 tab会停止读取 后面的内容会交给后面的scanf读取 那么问题来了 我们如何让scanf语句把字符串中的空格也读取呢 在scanf中使用 参数 这个参数的作用就是读取一个字符集合 中
  • Python+Selenium框架篇1-介绍unittest单元测试框架

    本文介绍什么是unittest单元测试框架 1 什么是unittest框架 unittest框架 原名PyUnit框架 是Python语言自带的单元测试框架 Python2 1及其之后的版本已将unittest作为一个标准模块放入Pytho
  • 【PC】响应式HTML5网页项目

    快速完成网页设计 10个顶尖响应式HTML5网页模板助你一臂之力 为了寻找一个优质的网页模板 网页设计师和开发者往往可能会花上大半天的时间 不过幸运的是 现在的网页设计师和开发人员已经开始共享HTML5 Bootstrap和CSS3中的免费
  • untiy 监听屏幕点击 物体(实现)

    第一种方式 1 百度的第三方法 using System Collections using System Collections Generic using UnityEngine using UnityEngine UI public
  • linux下nginx+php安装

    初始nginx php环境文件复制安装 需要复制的文件列表 usr local nginx usr local php usr local php 5 4 35 data mexue apps mysql etc init d mysqld
  • C#反射:GetManifestResourceStream

    public virtual Stream GetManifestResourceStream string name 参数 name 类型 System String 所请求的清单资源的名称 区分大小写 name格式 项目名称 文件名 通
  • 区块链:单位(Units) 和 全局变量(Globally Available Variables)

    Ether Units 一个整数的后面可以跟一个单位 ether finney szabo或者wei 他们的单位换算如下 1 ether 1000 finney 1 ether 1000000 szabo 1 ether 10 18 wei
  • sql count中加条件

    一般的 我们会在where 或者 having中加条件 count中只是某个字段 今天看到另外一种写法 不知道性能怎么样 select count case when xxx gt 10 and yyy lt 99 then bbb els
  • gitlab的安装过程以及如何修改默认端口

    工欲善其事 必先利其器 此文讲述的是gitlab的安装历程 需要有一定的英文阅读能力者 安装环境 ubuntu Ubuntu 17 04 官方推荐需要的硬件条件 Storage a fast drive 7200 RPM and up or
  • tsconfig.json的esModuleInterop使用场景是怎样的?

    问题场景 npm包改造前 仅支持esm npm包改造后 既支持esm 又支持cjs 为什么改造后 还是会报错 如何理解ts编译配置esModuleInterop 总结 问题场景 遇到一个很有趣的场景 cjs中需要引入原先打包方式为esm方式