关于javascript:tsconfigjson的esModuleInterop使用场景是怎样的

  • 问题场景
  • 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.or…

默认状况下,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/…

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理