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
相關文章
相關標籤/搜索