模块系统
常用模块系统分为ADM、CommonJS、UMD。ES6也实现了模块的导入导出。
导入导出
ES6
在项目中引入es6/c.ts,即可在浏览器的console中查看输出数据。
es6/a.ts
export let a = 1
let b = 2
let c = 3
export { b, c }
export interface P {
x: number
y: number
}
export function f() {
console.log('func')
}
function g() {}
export { g as G }
export default function () {
console.log('default')
}
export { str as hello } from './b'
es6/b.ts
export let str = 'hello'
es6/c.ts
import { a } from './a'
import { b, c } from './a'
import { P } from './a'
import { f } from './a'
import DefaultFunc from './a'
import * as All from './a'
console.log(a)
console.log(b, c)
let p: P = {
x: 1,
y: 1,
}
f()
DefaultFunc()
console.log(All)
CommonJS
node无法直接执行,需要安装一个ts-node
然后通过命令行执行 ts-node node/c.ts
查看结果
node/a.ts
let a = {
x: 1,
y: 2,
}
module.exports = a
node/b.ts
exports.b = 2
exports.c = 3
// module.exports = {}; // 将会覆盖前面两个导出,只允许一个顶级导出 或 多个次级导出
node/c.ts
let c1 = require('./a')
let c2 = require('./b')
console.log(c1) // { x: 1, y: 2 }
console.log(c2) // { b: 2, c: 3 }
差异总结
ES6可以有default,也可以有多个单独导出;CommonJS只允许一个顶级导出 或 多个次级导出
配置项目
在tsconfig.json中有三个参数和模块处理相关:target
、module
、esModuleInterop
。
target和module
这里先介绍target和module两个属性:
- target:ts代码被编译成什么版本,在项目中默认是es5,在命令行中默认是es3
- module: 编译成什么样的模块系统,默认是commonjs
注:tsc命令如果如果指定了具体文件,将不会读tsconfig.json,需要单独在命令行指定参数
下面例子分别测试了 target 和 module 模块的编译输出,可以非常直观看到差别:
$ tsc ./es6/a.ts -t es3
$ tsc ./es6/a.ts -t es5
$ tsc ./es6/a.ts -t es
$ tsc ./es6/a.ts -m 'amd'
$ tsc ./es6/a.ts -m 'umd'
模块兼容问题
我们的代码是用ES6书写的,最终编译成了CommonJS。根据前面的介绍,ES6是可以有顶级导出(export default)和顶级导入的,而CommonJS是没有的,编译器是如何抹平这种差异的呢。
通过阅读编译后的代码,可以看到顶级导出成了exports的default属性,同样在使用的地方也通过default来调用:
// 导出模块编译后代码
function default_1() {
console.log('default');
}
exports["default"] = default_1;
// 导入模块编译后代码
var a_4 = require("./a");
a_4["default"]();
如果单纯都是用ES6自然没问题,如果要在代码中混用ES6 和 CommonJS,则会出现问题,例如:
node/c.ts
let c3 = require('../es6/a')
console.log(c3)
c3.default()
我们发现,期望默认导入的是函数,结果输出是一个对象,要通过 c3.default() 才可以达到效果,自然不方便。
混用模块的建议
为此提供了建议
1)不要混用两类模块系统
2)使用TS提供的export =
语法,可以被ES6模块识别
es6/d.ts
export = function () {
console.log('func')
}
node/c.ts
import c4 = require('../es6/d')
c4(); // func
围绕兼容性的话题,在简单提供几个例子,有src/test.ts文件,假定我们使用 tsc src/test.ts -m commonjs
编译它,文件内容 和 编译产生的结果在注释中:
src/test.ts
let li: string = 'hello';
module.exports = li
// 编译结果:module.exports = li
export = li;
// 编译结果:module.exports = li;
export defaul li
// exports["default"] = li;
可见,module.exports
和 export =
编译结果完全一样。
但是如果我们尝试ES6模块中,使用impor方式导入上述三种声明的导出,第一个是导出会无法被识别。因此:CommonJS模块代码为了更好的兼容性,建议使用 export =
的语法。
src/main.ts
import li from './test.ts' // 'src/test.ts' is not a module
import li from './test.ts' // ok
import li from './test.ts' // ok
esModuleInterop
上述例子中,我们通过 export =
语法 和 import = require
语法实现了在CommonJS中使用ES6顶级导出。如果想在导入的时候,更ES6的风格,即采用如下方式,必须要讲tsconfig.json中 的 esModuleInterop设置为true。
import c4 from '../es6/d';
c4(); // func