基於 Webpack 和 ES6 打造 JavaScript 類庫

CSS魔法發表於2016-01-14

兩個月前,我曾釋出了一篇基於 webpack 的 React 起步教程。你眼前的這篇文章跟那一篇差不多,只不過不包含 React 那一塊。這篇教程稍微簡單一些,但仍然會有一些棘手的部分。因此,我特意建了一個全新的程式碼倉庫 webpack-library-starter,把建立一個 JavaScript 類庫所需的所有素材都放了進去。

首先,我們說的 “類庫” 是指什麼

在 JavaScript 語境中,我對類庫的定義是 “提供了特定功能的一段代段”。一個類庫只做一件事,並且把這件事做好。在理想情況下,它不依賴其它類庫或框架。jQuery 就是一個很好的例子。React 或者 Vue.js 也可以認為是一個類庫。

一個類庫應該:

  • 可以在瀏覽器環境下使用。也就是說,可以通過 <script> 標籤來引入這個類庫。
  • 可以通過 npm 來安裝。
  • 相容 ES6(ES2015) 的模組系統、CommonJS 和 AMD 模組規範。

用什麼來開發這個類庫並不重要,重要的是我們最終產出的檔案。它只要滿足上述要求就行。儘管如此,我還是比較喜歡用原生 JavaScript 寫成的類庫,因為這樣更方便其它人貢獻程式碼。

目錄結構

我一般選擇如下的目錄結構:

其中 src 目錄用於存放原始碼檔案,而 lib 目錄用於存放最終編譯的結果。這意味著類庫的入口檔案應該放在 lib 目錄下,而不是 src 目錄下。

起步動作

我確實很喜歡最新的 ES6 規範。但壞訊息是它身上綁了一堆的附加工序。也許將來某一天我們可以擺脫轉譯過程,所寫即所得;但現在還不行。通常我們需要用到 Babel 來完成轉譯這件事。Babel 可以把我們的 ES6 檔案轉換為 ES5 格式,但它並不打算處理打包事宜。或者換句話說,如果我們有以下檔案:

然後我們用上 Babel,那我們將會得到:

或者再換句話說,Babel 並不解析程式碼中的 import 或 require 指令。因此,我們需要一個打包工具,而你應該已經猜到了,我的選擇正是 webpack。最終我想達到的效果是這樣的:

npm 命令

在執行任務方面,npm 提供了一套不錯的機制——scripts(指令碼)。我們至少需要註冊以下三個指令碼:

  • npm run build – 這個指令碼用來生成這個類庫的最終壓縮版檔案。
  • npm run dev – 跟 build 類似,但它並不壓縮程式碼;此外還需要啟動一個監視程式。
  • npm run test – 用來執行測試。

構建開發版本

npm run dev 需要呼叫 webpack 並生成 lib/library.js 檔案。我們從 webpack 的配置檔案開始著手:

即使你還沒有使用 webpack 的經驗,你或許也可以看明白這個配置檔案做了些什麼。我們定義了這個編譯過程的輸入(entry)和輸出(output)。那個 module 屬性指定了每個檔案在處理過程中將被哪些模組處理。在我們的這個例子中,需要用到 Babel 和 ESLint,其中 ESLint 用來校驗程式碼的語法和正確性。

這裡有一個坑,花了我不少的時間。這個坑是關於 librarylibraryTarget 和 umdNamedDefine 屬性的。最開始我沒有把它們寫到配置中,結果編譯結果就成了下面這個樣子:

經過 webpack 編譯之後的檔案差不多都是這個樣子。它採用的方式跟 Browserify 很類似。編譯結果是一個自呼叫的函式,它會接收應用程式中所用到的所有模組。每個模組都被存放到到 modules 陣列中。上面這段程式碼只包含了一個模組,而 __webpack_require__(0) 實際上相當於執行 src/index.js 檔案中的程式碼。

光是得到這樣一個打包檔案,並沒有滿足我們在文章開頭所提到的所有需求,因為我們還沒有匯出任何東西。這個檔案的執行結果在網頁中必定會被丟棄。不過,如果我們加上 librarylibraryTarget 和umdNamedDefine,就可以讓 webpack 在檔案頂部注入一小段非常漂亮的程式碼片斷:

把 libraryTarget 設定為 umd 表示採用 通用模組定義 來生成最終結果。而且這段程式碼確實可以識別不同的執行環境,併為我們的類庫提供一個妥當的初始化機制。

構建生產環境所需的版本

對 webpack 來說,開發階段與生產階段之間唯一的區別在於壓縮。執行 npm run build 應該生成一個壓縮版——library.min.js。webpack 有一個不錯的內建外掛可以做到這一點:

只要我們把 UglifyJsPlugin 加入到 plugins 陣列中,它就可以完成這個任務。此外,還一些事情有待明確。我們還需要某種條件判斷邏輯,來告訴 webpack 需要生成哪一種型別(“開發階段” 還是 “生產階段”)的打包檔案。一個常見的做法是定義一個環境變數,並將它通過命令列傳進去。比如這樣:

(請留意 --watch 選項。它會讓 webpack 監視檔案變化並持續執行構建任務。)

測試

我通常採用 Mocha 和 Chai 來執行測試——測試環節是這篇起步教程特有的內容。這裡同樣存在一個棘手的問題,就是如何讓 Mocha 正確識別用 ES6 寫的測試檔案。不過謝天謝地,Babel 再次解決了這個問題。

這裡最關鍵的部分在於 --compilers 這個選項。它允許我們在執行測試檔案之前預先處理這個檔案。

其它配置檔案

在最新的 6.x 版本中,Babel 發生了一些重大的變化。現在,在指定哪些程式碼轉換器將被啟用時,我們需要面對一種叫作 presets 的東西。最簡單配置的方法就是寫一個 .babelrc 檔案:

ESLint 也需要一個類似的配置檔案,叫作 .eslintrc

相關連結

這篇起步教程還可以在 GitHub 上找到:github.com/krasimir/webpack-library-starter

用到的專案如下:

具體依賴如下:


譯註

是不是意猶未盡?其實準確來說,這篇文章是作者對 webpack-library-starter 專案的一個簡要解說,講解了程式碼之外的背景知識。

因此,作為學習者,光讀文章是遠遠不夠的,我們真正需要的是研讀這個專案提供的原始碼,並且動手實際操作和演練,如此方能掌握要領。加油!

相關文章