webpack組織模塊的原理 - 打包Library

以前一篇文章分析了Webpack打包JS模塊的基本原理,所介紹的案例是最多見的一種狀況,即多個JS模塊和一個入口模塊,打包成一個bundle文件,能夠直接被瀏覽器或者其它JavaScript引擎執行,至關於直接編譯生成一個完整的可執行的文件。不過還有一種很常見的狀況,就是咱們要構建發佈一個JavaScript的庫,好比你在npm社區發佈本身的庫,這時Webpack就須要相應的配置,編譯生成的代碼也會略有不一樣。javascript

和以前一篇文章同樣,本文主要分析的是Webpack的生成代碼,並結合它來講明編譯庫時Webpack那些關於library的配置選項的具體做用,相應的官方文檔在這裏vue

編寫JS的庫

咱們仍是從簡單的案例開始,咱們隨便編寫一個簡單的庫util.jsjava

import $ from 'jquery'

function sayHello() {
  console.log("Hello");
}

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

export default {
  sayHello: sayHello,
  hideImages: hideImages
}

提供了兩個函數,固然它們之間毫無關係,也實際上沒有任何卵用,純粹是僅供教學參考。。。jquery

接下來寫Webpack的配置:webpack

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

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

但僅僅這樣是不夠的,這樣輸出的文件就是一個當即執行的函數,最後會返回util.js的exports,參照上一篇文章中分析,最後生成的bundle代碼結構大體是這樣的:web

(function(modules) {
  var installedModules = {};
  
  function webpack_require(moduleId) {
     // ...
  }

  return webpack_require('./util.js');
}) ({
  './util.js': generated_util,
  '/path/to/jquery.js': generated_jquery
});

它若是執行完,那就結束了,將util.js的export部分返回而已,而咱們須要的是將這個返回值交給編譯後的文件的module.export,這樣編譯後的文件就成了一個能夠被別人import的庫。因此咱們但願獲得的編譯文件應該是這樣的:npm

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
});

要獲得這樣的結果,Webpack配置output部分須要加入library信息:segmentfault

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

  library: 'util',
  libraryTarget: commonjs2
}

這裏最重要的就是libraryTarget,咱們如今採用commonjs2的格式,就會獲得上面的編譯結果,也就是說Webpack會library把最後的輸出以CommonJS的形式export出來,這樣就實現了一個庫的發佈。瀏覽器

其它發佈格式

除了commonjs2,libraryTarget還有其它選項:ide

var (默認值,發佈爲全局變量)
commonjs
commonjs2
amd
umd

使用不一樣的選項,編譯出來的文件就可以在不一樣JavaScript執行環境中使用。在這裏咱們直接看萬金油umd格式的輸出是怎麼樣的:

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

比以前的commonjs2的狀況要複雜得多,由於它須要處理各類不一樣的case,但其實後面的部分都是同樣的,最重要的就是最前面的幾行,這是umd模塊的標準寫法。它運行傳入的factory函數,實際上就是加載模塊的函數,而後把返回的結果根據不一樣的運行環境交給相應的對象。例如var,那就會把結果設置爲一個全局變量,這用於瀏覽器經過<script>標籤直接導入該JS文件;若是是CommonJS,它則交給exports對象;若是是AMD環境,它也是用標準的AMD的寫法。這樣這個發佈出來的JS庫就能夠在任意的環境中都能被其餘人使用。

targetExport控制輸出內容

若是用umd格式打包,可能會有一個坑須要注意,若是你的庫的源代碼是用ES6格式export default來輸出的,正如上面的例子util.js,你直接把編譯後的JS庫文件放到瀏覽器中使用,能夠是<script>,或者RequireJS,可能得不到你想要的結果。這是由於你的JS文件返回給你的對象是這樣的:

{
  'default': {
    sayHello: sayHello,
    hideImages: hideImages
  }
}

而不是你所指望的:

{
  sayHello: sayHello,
  hideImages: hideImages
}

不只是瀏覽器,在不支持ES6的模塊系統中一樣會出這個問題,就是由於它們並不認識default。因此你編譯後的JS文件實際上應當只輸出default,這就須要在Webpack配置裏用targetExport來控制:

library: 'util',
libraryTarget: umd,
targetExport: 'default'

這樣上面的模塊加載函數factory會在返回值後面加一個['default'],這樣就只返回exports的default部分。

這個坑在umd格式下其實仍是挺容易踩到的,例如你發佈一個Vue組件,.vue文件中的JavaScript部分通常都是把Component對象以export default的格式導出的,就像這樣:

export default {
  name: 'xxx',
  data() {
    return // ...
  },
  props: {
    // ...
  }
  methods: {
    // ...
  }
}

若是你把編譯後的JS文件直接放在瀏覽器裏運行,而且用CDN的方式加載Vue,你會發現Vue沒法識別這個Component,由於你獲得的這個對象多了一層沒必要要的default

你可能會問若是我把輸出內容改爲了default,會不會影響這個模塊在ES6環境下的使用?通常來講是不會的。以前一篇文章裏已經談到,Webpack的生成代碼在引入一個模塊時,會經過一個叫__esModule的值來設置和判斷它是否是ES6格式的export,如今若是隻導出default部分,那麼這個對象是被視爲非ES6的,由於它不含__esModule。這樣其它模塊經過import來引入這個模塊時,會引入整個對象,這實際上變相地就等價於只引入原模塊的export default部分。

固然以上討論的前提是,你全部須要export的內容所有都在export default裏。若是你既有default,又有正常的export,那編譯後的文件只導出default部分顯然是不行的。

相關文章
相關標籤/搜索