3

背景

ts可以用于node环境和web环境,或者说在es module出来之前,大部分的包都是遵循commonjs的,而这些遵循commonjs的包现在大多还存在与nodejs当中,也是nodejs迟迟还没有全面支持esm的原因。那么ts怎么兼容commonjs和esm包呢?

如果你不常用ts或者是没有在nodejs中用过ts,那么看到ts 官方的推荐写法,一定会傻眼,竟然是import和require混用……
文件模块的导入
clipboard.png

回顾cjs和esm

我们要理解ts为什么有这么独特的推荐写法,先来回顾一下commonjs和esm的相关知识,并且考虑在ts中他们的互换性

我们有一个commonjs文件

// my-module.js
module.exports = { foo, bar }

两种方式引入并且解构,好像没有什么不同

// index.ts
// cjs
const { foo, bar } = require('my-module')
// esm
import { foo, bar } from 'my-module'

但是我们来看下面这一组

// module
export const foo = 1
export const bar = 2
export default () => {}

// esm
import { foo } from 'module'
import func from 'module'`
// module
module.exports = {
  foo: 1,
  bar: 2,
  default: () => {}
}
// cjs
const module = require('module')
const foo = module.foo
const func = module.default

因此如果我们都是用default的这个function 在commonjs中需要点操作module.default才可以获取到。
比如考虑互操作性,在react中,

import React from 'react'
相当于只导入了default这个属性
const {default: React} = require('react')

因此2018年之前我们在用ts引入React的为了保持一致性会这样写import * as React from 'react'来获取module.exports中的所有内容。

因此在导入commonjs模块的时候ts除了有require混合的写法也可以用import *的写法typescript-import-as-vs-import-require

esModuleInterop

ts2.7出了一个esModuleInterop的配置,支持import d from "cjs"support-for-import-d-from-cjs-from-commonjs-modules-with---esmoduleintero

来简单看下ts如何做到兼容一致性

// common.js
module.exports = {
  default: function greeter(person) {
    return "Hello, " + person;
  },
  person: "common"
};

// esm.js
export default function greeter(person) {
  return "Hello, " + person;
}

export const person = "esm";

// index.ts
import common from "./common";
const common1 = require("./common");
import * as common2 from "./common";

import esm from "./esm";
const esm1 = require("./esm");
import * as esm2 from "./esm";

console.log(common, common1, common2);
console.log(esm, esm1, esm2);

esModuleInterop为false的时候编译的代码

"use strict";
exports.__esModule = true;
var common_1 = require("./common");
var common1 = require("./common");
var common2 = require("./common");
var esm_1 = require("./esm");
var esm1 = require("./esm");
var esm2 = require("./esm");
console.log(common_1["default"], common1, common2);
console.log(esm_1["default"], esm1, esm2);

//[Function: greeter] { default: [Function: greeter], person: 'common' } { default: [Function: greeter], person: 'common' }
//[Function: greeter] { default: [Function: greeter], person: 'esm' } { default: [Function: greeter], person: 'esm' }

可以观察到import common from "./common";输入的结果只有default的方法,跟const common1 = require("./common");输出的结果不一样,并不能保持一致性,这也是前面提到为什么我们要import *或者import 和require同处一等式的原因

esModuleInterop为true时候的编译代码

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
exports.__esModule = true;
var common_1 = __importDefault(require("./common"));
var common1 = require("./common");
var common2 = __importStar(require("./common"));
var esm_1 = __importDefault(require("./esm"));
var esm1 = require("./esm");
var esm2 = __importStar(require("./esm"));
console.log(common_1["default"], common1, common2);
console.log(esm_1["default"], esm1, esm2);

//{ default: [Function: greeter], person: 'common' } { default: [Function: greeter], person: 'common' } { default: { default: [Function: greeter], person: 'common' },
  person: 'common' }
//[Function: greeter] { default: [Function: greeter], person: 'esm' } { default: [Function: greeter], person: 'esm' }

esModuleInterop 为true,我们可以看到加入了__importDefault和__importStar判断引入的模块是不是esm模块再做封装,import common from "./common";输入的结果只有default的方法,跟const common1 = require("./common");输出的结果保持了一致性


那为什么ts不直接推荐使用const common1 = require("./common");方式呢?
我们可以从tslint的no-var-requires中的说明得到结果,因为amd和commonjs方式的require是特定环境的写法,难以进行静态分析

clipboard.png


小结

1.在ts2.7之后的项目+esModuleInterop不需要去考虑引入包的一些兼容
2.即使在ts2.7之前的项目最好也不要用import和require混用的写法,用import * ,因为esm终将是大流

参考链接:

https://stackoverflow.com/que...
https://palantir.github.io/ts...

https://jkchao.github.io/type...

https://www.typescriptlang.or...


Obeing
665 声望108 粉丝

努力地成为一只小牛