13

前言

前几天团队小伙伴问我一个问题,我在ts文件中使用解构导出了一个变量,在其他地方 import 进来发现是 undefined,类似这样

//a.ts
export const a = {
   a1: 1,
   a2: 2
}

export const b = {
    b1: 1
}

export default {
 ...a,
 b
}
// b.ts
import { a1 } from 'a';
console.log(a1): // undefined

这里抛出一个疑问?

明明使用了 babel-plugin-add-module-exports 兼容了 export default,但是就是取不到?

接下来我们从 export defalut -> babel -> add-module-exports来逐步的了解下为什么

export default 作用是什么

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

类似这样
导出函数

//a.js
export default funcion() {
   //xxx
}
//b.js
import foo from 'a';

导出对象

//c.js
const c = { c1:1, c2:2 }
const d = { d1:1, d2:2 }
export default {
    c,d
}
//d.js
import obj from 'c'
console.log(obj); // {c:{c1:1,c2:2},d:{d1:1,d2:2}}

导出default

//a.js
function foo(){}
export { foo as default};
// 等同于
// export default foo;

// b.js
import { default as foo } from 'a';
// 等同于
// import foo from 'a';

到这里看起来一切很美好,有一个新问题:在d.js里,我想直接拿到 obj 里的 c 属性,可以吗?

const c = { c1:1, c2:2 }
const d = { d1:1, d2:2 }
export default {
    c,d
}
//d.mjs
import {c} from 'c'
console.log(c); // 报错了

// terminal
node --experimental-modules d.js
/*
import {c} from 'c';
        ^
SyntaxError: The requested module 'c' does not provide an export named 'c'
*/

其实这样写是错的,因为ES6的import并不是对象解构语法,只是看起来比较像,可以参考MDN对import的描述MDN import。所以import并不能解构一个default对象。

既然import不支持解构一个default对象,那么我们心中又有一个疑问,为什么我们在项目中能够随意的去写 export default, 并且通过解构可以取的到呢?

export default 编译结果

export default 属于 ES6 的语法,为了兼容不支持 ES6 的浏览器,所以需要 babel 编译。接下来我们看看经过babel编译之后,export default变成了什么。

babel 5 时代

在使用babel5的时候,下面代码

//a.js
const a = {};
const b = {};
export default {a,b}
//b.js
import {a} from 'b'
console.log(a)

会被打包为

//a.js
...
let _default = _objectSpread({}, {a, b});
exports.default = _default;
module.exports = exports.default;

//b.js
"use strict";
var _const = require("./a");
console.log(_const.a);

babel 把 esm 解析成了cjs,我们执行 b.js,发现可以取到值,但是在浏览器环境require语法,我们还需要webpack,因为webpack 简单来说是对babel转换后的文件做了一层 require 的包装,所以这里具体不谈webpack做了什么,只讨论babel, webpack具体做了什么可以戳这里查看

webpack启动代码解读
webpack模块化原理

babel 6 时代

项目升级babel 6 之后,发现之前写法取不到值了,上面的 a.js 和 b.js 打包后变为

//a.js
...
let _default = _objectSpread({}, {a, b});
exports.default = _default;
// babel6 去掉了 module.exports = exports.default;

//b.js
"use strict";
var _const = require("./a");
console.log(_const.a);

这个时候 _const 的值为 {default: {a:{},b{}}}
出现这个的原因是因为 Babel 的这个Issue Kill CommonJS default export behaviour,所以 Babel 6通过不再执行module.exports = exports['default']模块转换来更改某些行为。Babel5 是支持export 一个对象,但是到 Babel6 的时候去掉了这个特性。这个时候我们为了兼容老代码,需要一个解决方案,这个时候 babel-plugin-add-module-exports 入场了。

babel-plugin-add-module-exports 入场

babel-plugin-add-module-exports 主要作用是补全 Babel6 去掉的 module.exports = exports.default;
问题来了,项目中配置了babel-plugin-add-module-exports为什么前沿中的代码会有问题呢

babel-plugin-add-module-exports 失效原因

答案很简单,我们发现 babel-plugin-add-module-exports 失效了,深入源码打个log发现会判断是否有 name export,如果有 name export,就不会补上 babel5 export default object的特性。

// hasExportNamed 一直是 true
...
if (hasExportDefault &&  !hasExportNamed) {

path.pushContainer('body', \[types.expressionStatement(types.assignmentExpression('=', types.memberExpression(types.identifier('module'), types.identifier('exports')), types.memberExpression(types.identifier('exports'), types.stringLiteral('default'), true)))\]);

}
...

解决方案:
我们只需要改动代码为

//a.ts
const a = {
   a1: 1,
   a2: 2
}
const b = {
    b1: 1
}
export default {
 ...a,
 b
}
// b.ts
import { a1 } from 'a';
console.log(a1): // 1

我们只需要去掉 export const, 只保留 export default 即可解决这个问题。

有好奇的同学问,为什么有了name export,就不会去补全module.exports = exports.default。看到下面的例子你就明白了

//a.ts
export const a = {
   a1: 1,
   a2: 2
}
const b = {
    b1: 1
}
export default {
 b
}
// b.ts
import { a } from 'a';
console.log(a);

打包后手动加一个 module.exports = exports.default

//a.js
...
let _default = _objectSpread({}, {b});
exports.default = _default;
module.exports = exports.default;

结果可想而知,在b文件require进来的时候,a 找不到了

//b.js
"use strict";
var _const = require("./a");
console.log(_const.a); // undefined

结语

export default配合babel-plugin-add-module-exports给了我们很好的开发体验,但是还是要遵守export default 的基本规则:
虽然es6 export default 导出的内容有工具帮你处理,但是 es6 import 不是解构语法。需要注意的是,在引入一个有默认输出的模块时,这时import命令后面,不使用大括号,不要对引入的内容进行解构。

帮助链接:

关于 import、require、export、module.exports

csywweb
232 声望8 粉丝

最担心的事不是写出ipcode,而是从不开始


下一篇 »
走进AST