共计 3523 个字符,预计需要花费 9 分钟才能阅读完成。
背景
ts 可以用于 node 环境和 web 环境,或者说在 es module 出来之前,大部分的包都是遵循 commonjs 的,而这些遵循 commonjs 的包现在大多还存在与 nodejs 当中,也是 nodejs 迟迟还没有全面支持 esm 的原因。那么 ts 怎么兼容 commonjs 和 esm 包呢?
如果你不常用 ts 或者是没有在 nodejs 中用过 ts,那么看到 ts 官方的推荐写法,一定会傻眼,竟然是 import 和 require 混用……
文件模块的导入
回顾 cjs 和 esm
我们要理解 ts 为什么有这么独特的推荐写法,先来回顾一下 commonjd 和 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"; | |
import esm from "./esm"; | |
const common1 = require("./common"); | |
const esm1 = require("./esm"); | |
import * as common2 from "./common"; | |
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 esm_1 = require("./esm"); | |
var common1 = require("./common"); | |
var esm1 = require("./esm"); | |
var common2 = require("./common"); | |
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 esm_1 = __importDefault(require("./esm")); | |
var common1 = require("./common"); | |
var esm1 = require("./esm"); | |
var common2 = __importStar(require("./common")); | |
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");
输出的结果保持了一致性
小结
1. 在 ts2.7 之后的项目 +esModuleInterop 不需要去考虑引入包的一些兼容
2. 即使在 ts2.7 之前的项目最好也不要用 import 和 require 混用的写法,用 import *,因为 esm 终将是大流