webpack組織模塊的原理 - external模塊

這篇文章討論Webpack打包library時常常須要用到的一個選項external,它用於避免將一些很通用的模塊打包進你發佈的library裏,而是選擇把它們聲明成external的模塊,在你的library被上層使用後,在最後階段由Webpack統一把這個external的依賴模塊打包進來。javascript

external選項通常都是用在打包library上面,若是不是library而是一個最終的app的發佈JS文件,那external也沒有什麼意義。關於Webpack打包library的分析和一些選項的做用,我在前一篇文章作了討論。java

external選項

咱們仍然使用前一篇文章的例子,定義一個庫util.jsnode

import $ from 'jquery'

function hideImages() {
  $('img').hide();
}

export default {
  "hideImages": hideImages
}

咱們使用Webpack打包發佈這個庫:jquery

// 入口文件
entry: {
  util: './util.js',
}

// 輸出文件
output: {
  path: './dist',
  filename: '[name].dist.js'

  library: 'util',
  libraryTarget: commonjs2,
  targetExport: 'default'
}

這樣打包出來的util.dist.js文件會把jquery的代碼完整地注入進去,由於你的源代碼使用到了它。可是這每每並非咱們但願的,由於jquery是很通用的模塊,在一個app中,極可能其它的庫也會用到它,最頂層的入口文件app也可能用到它,若是每個庫模塊的發佈版本都將jquery原封不動地打包進了本身的bundle,最後拼到一塊兒,在最終的app發佈代碼裏就會有不少份jquery的複製,固然這可能並不會影響它的正常功能,可是會佔據很大的代碼體積。webpack

因此一般狀況下當你的庫須要依賴到例如jquerybootstrap這樣的通用JS模塊時,咱們能夠不將它打包進bundle,而是在Webpack的配置中聲明externalweb

externals: {
  jquery: {
    root: 'jquery',
    commonjs: 'jquery',
    commonjs2: 'jquery',
    amd: 'jquery',
  },
},

這就是在告訴Webpack:請不要將這個模塊注入編譯後的JS文件裏,對於我源代碼裏出現的任何import/require這個模塊的語句,請將它保留npm

咱們能夠看一下編譯後的bundle文件的結構:json

module.exports = (function(modules) {
  var installedModules = {};
  function webpack_require(moduleId) {
     // ...
  }
  return webpack_require('./util.js');
}) ({
  './util.js': generated_util,
  // '/path/to/jquery.js': generated_jquery 本來有這一行,如今被刪去。
});

能夠看到jquery模塊沒有被打包進bundle文件,而對於util,它的生成代碼即generated_util函數中關於import jquery相關的語句也被保留了原意:bootstrap

function generated_util(module, exports, webpack_require) {
  var $ = require('jquery');
  // util的其它源代碼
  // ...
}

固然也並不是徹底沒有修改,例如將import的改回了傳統的require關鍵詞,由於咱們這裏用的是CommonJS風格的打包方式。不過這些都是次要的,關鍵是它保留了require這個關鍵詞,而沒有使用webpack_require將jquery真的引入進來。這就是說,當前的這個JS文件的模塊管理系統中是沒有jquery的,它是一個external的模塊,須要在這個JS文件被其它人引用而且在上層編譯時,jquery纔可能被真的引入進來,到那個時候這裏的require關鍵詞纔會被替換爲webpack_requiresegmentfault

對於external的依賴模塊,一般你能夠這樣作,例如你使用npm發佈你的庫,你能夠將jquery在package.json文件中添加到dependencies,這樣別人npm install你發佈的庫時,jquery也會被自動下載到node_modules供別人打包使用。

umd格式下的打包

若是咱們使用umd格式打包,咱們能夠看到在不一樣環境中,external模塊是如何發揮做用的:

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')  // commonjs2
    module.exports = factory(require('jquery'));
  else if(typeof define === 'function' && define.amd)
    define("util", ['jquery'], factory);  // amd
  else if(typeof exports === 'object')
    exports["util"] = factory(require('jquery'));  // commonjs
  else
    root["util"] = factory(root['jquery']);  // var
}) (window, function(__webpack_external_module_jquery__) {
  return (function(modules) {
    var installedModules = {};
    function webpack_require(moduleId) {
       // ...
    }
    return webpack_require('./util.js');
  }) ({
    './util.js': generated_util,
  });
}

generated_util也相應地增長一個參數__webpack_external_module_jquery__

function generated_util(module, exports, webpack_require,
                        __webpack_external_module_jquery__) {
  var $ = __webpack_external_module_jquery__;
  // util的其它源代碼
  // ...
}

這樣的寫法彷佛結構和上面的CommonJS的編譯版本不太同樣,但實際上本質是同樣的。由於如今umd要照顧到不一樣的運行環境,因此它把require('jquery')提早了,做爲factory的參數傳入。對於每種運行環境,各有各的作法:

  • CommonJS:保留require('jquery')語句。
  • AMD:在define中將jquery定義爲依賴模塊。
  • Var:從全局域中取出jquery變量,這須要jquery在該模塊以前就已經被加載。

而後不論是哪一種狀況,它們都將載入後的jquery模塊做爲參數傳入factory函數,這樣就能正確加載util模塊了。

以上涉及到Webpack生成代碼的部分可能有點繞,須要你比較瞭解Webpack打包模塊的機制和原理,關於這部分我在這篇文章裏作了詳細討論。

總結

以上就是關於Webpack的external選項的使用,而且從編譯後的JS代碼分析了它究竟是如何起做用的。我想閱讀Webpack相關的生成代碼仍是很重要的,這樣纔算是真正地理解了external的機制,在碰到一些坑時才能知道怎麼去debug。

相關文章
相關標籤/搜索