webpack針對循環引用以及賦值export的處理注意事項

webpack打包相互引用的模塊

示例項目

(github)javascript

遇到的問題

在有2個或2個以上的文件之間的相互依賴關係構成閉環的時候,有時會出現Can't read Property 'xxx' of undefined或者(0,xxx) is not a function這類的錯誤,好比:java

示例項目中的src/index.js引用src/a.js,而src/a.js中也引用了src/index.js
複製代碼

爲何會出現這樣的問題

這就跟webpack打包後的代碼執行邏輯有關webpack

webpack的頭部啓動代碼中,經過閉包中的installedModules對象,將模塊名或者id做爲對象的key來緩存各個模塊的export的值,經過判斷installedModules上是否緩存了對應模塊的key來判斷是否已經加載了模塊git

// Check if module is in cache
if(installedModules[moduleId]) {
  return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
  i: moduleId,
  l: false,
  exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
複製代碼

但存在一個問題:當模塊還處於第一次執行中的狀態時,若是碰到相互引用的狀況的話,webpack可能會認爲一個沒有徹底加載完成的模塊已經加載完了github

就拿export function.js中的代碼和export const _var.js中的代碼爲例:web

export function.js緩存

/***/ (function(module, exports, __webpack_require__) {
 "use strict";
  
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports._console = _console;// <- 📢注意這裏
  
  var _a = __webpack_require__(2);
  
  var _a2 = _interopRequireDefault(_a);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _console() {
    console.log('this is index.js');
  }
  
  /***/ }),
複製代碼

export const _var.jsbash

/***/ (function(module, exports, __webpack_require__) {
 "use strict";


Object.defineProperty(exports, "__esModule", {
  value: true
});
exports._console = undefined;// <- 📢注意這裏

var _a = __webpack_require__(2);

var _a2 = _interopRequireDefault(_a);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var _console = exports._console = function _console() {
  console.log('this is index.js');
};

/***/ }),
複製代碼

從上面兩段代碼的📢處代碼行能夠看到,使用賦值語句export的代碼打包後,對exports上的屬性的賦值將在import(也就是__webpack_require__)後,另外一種使用申明函數語句export的代碼打包後,對exports上的屬性的賦值將在import(也就是__webpack_require__)前。閉包

這點細微的區別在執行相互引用的代碼時會致使執行結果和你想的不同,試想一下如下的代碼執行過程:函數

  1. 在installedModules對象上設置index.js的key,加載index.js並執行
  2. 遇到import a.js
  3. 在installedModules對象上設置a.js的key,加載a.js並執行
  4. 遇到import index.js
  5. 檢查,發現installedModules上已經存在index.js的key,直接讀對象上緩存的exports(其實這裏可能只在exports聲明瞭屬性名,並無賦值)
  6. 執行exports上的_console函數(若是屬性尚未被賦值就會出錯)

export的方式會影響以上過程的五、6步驟

如何解決

  1. 打破文件間的依賴關係的閉環
  2. 依賴關係閉環的狀況下,只使用export function funcName(){}
相關文章
相關標籤/搜索