前言
前几天团队小伙伴问我一个问题,我在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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。