模块系统
常用模块系统分为ADM、CommonJS、UMD。ES6也实现了模块的导入导出。
导入导出
ES6
在项目中引入es6/c.ts,即可在浏览器的console中查看输出数据。
es6/a.ts
export let a = 1let b = 2let c = 3export { b, c }export interface P {x: numbery: 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 = 2exports.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 = liexport = 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 moduleimport li from './test.ts' // okimport li from './test.ts' // ok
esModuleInterop
上述例子中,我们通过 export =语法 和 import = require语法实现了在CommonJS中使用ES6顶级导出。如果想在导入的时候,更ES6的风格,即采用如下方式,必须要讲tsconfig.json中 的 esModuleInterop设置为true。
import c4 from '../es6/d';c4(); // func
