export default 为什么忽然没用了?

前言

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

//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

这里抛出一个疑问?webpack

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

接下来咱们从 export defalut -> babel -> add-module-exports来逐步的了解下为何git

export default 做用是什么

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

相似这样
导出函数github

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

导出对象web

//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}}

导出defaultexpress

//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 属性,能够吗?segmentfault

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, 而且经过解构能够取的到呢?babel

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
相关文章
相关标签/搜索