首页 > 其他分享 >tsconfig.json的esModuleInterop使用场景是怎样的?

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

时间:2022-10-07 22:01:24浏览次数:57  
标签:__ exports default json esModuleInterop cjs esm tsconfig mod

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

问题场景

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

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

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

import foo from "./foo";
import bar from './bar';
export

支持的方式为

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。

但是​[email protected]​的index.js,并没有暴露default。

转换后

const query_string_1 = exports;
// [email protected]
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官方文档:​​www.typescriptlang.org/tsconfig#es…​

默认情况下,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);

再来看一下知乎上一位前端同学的文章:​​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属性。例如​[email protected]​这样的包。 保险起见,建议开启这个配置。

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

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

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

import webcVCS from "./webcVCS";
import generateAssets from './generateAssets';
export
import queryString from 'query-string';

5.以后打包,module怎么配置?

  • esnext: 只在esm环境使用的包
  • commonjs:纯cjs或既在cjs又在esm环境使用的包(esm环境使用一般是由安装包的项目,结合webpack,babel等打包工具支持的)
  • umd: 同commonjs,且需要同时支持cjs,amd, cmd

标签:__,exports,default,json,esModuleInterop,cjs,esm,tsconfig,mod
From: https://blog.51cto.com/u_15725382/5735150

相关文章

  • Microsoft 365 开发篇:如何使用JSON Format来定制New Item的页面布局
    Blog链接:​​​https://blog.51cto.com/13969817​​我们使用SharePointOnline作为企业数据存储和协作办公管理平台时,往往根据用户的实际需求基于SharePointOnline做了很......
  • httpclient实现HttpGet请求传body的json参数的!
    原文来自:https://admins.blog.csdn.net/article/details/109809386前言最近调用公司项目一个接口时,发现该接口是一个Get请求,入参在Body中(json格式)。场景如下:A服务需发送h......
  • HttpClient发送Post请求传递json、普通参数
    importcom.alibaba.fastjson.JSONObject;importorg.apache.http.HttpEntity;importorg.apache.http.NameValuePair;importorg.apache.http.client.entity.UrlEncod......
  • Springboot 之 Filter 实现 Gzip 压缩超大 json 对象
    简介在项目中,存在传递超大json数据的场景。直接传输超大json数据的话,有以下两个弊端占用网络带宽,而有些云产品就是按照带宽来计费的,间接浪费了钱传输数据大导致......
  • JSON
    json在线解析:https://www.sojson.com/json基础入门:https://blog.csdn.net/Rao_Limon/article/details/80011601学习网站:http://c.biancheng.net/json/what-is-json.html......
  • jsonschema2pojo 基于json schema 生成代码
    jsonschema2pojo是一个很不错的基于jsonschema生成代码的包以及工具(maven扩展)jsonschema2pojo特点支持基本的jsonschema操作支持java扩展,比如别名,继承扩展接口外......
  • quicklib json序列
    quicklibjson序列quicklib面向MODEL的JSON序列。unitUnit2;///<author>cxg2022-6-14</author>interfaceusesquick.Json.Serializer,Quick.MemoryCache.Seri......
  • openmetadata jsonschema 处理的一些变动
    openmetadata估计是因为quicktype使用复杂费事,最近版本quicktype的集成已经废弃了,都使用jsonschema2pojo了实际上使用jsonschema2pojo对于java项目来说也比较好,毕竟......
  • Android中的JSON详细总结
    1、JSON(JavaScriptObjectNotation)定义:一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。业内主流技术为其提供了完整的解决方案(有点类似于正则表达式,获得......
  • vue res.data接收到的是字符串的处理方式,先转化成Json格式再解析
    api.postWachPay(param).then(res=>{this.html=res.data;letdata=JSON.parse(res.data)console......