JavaScript Module (2) — Browserify (CommonJS Bundler)

由於網路上的範例大部分都不太可用,所以有這篇。希望在使用到最少套件下,減少變因與學習難度。

本篇工具需求: 請安裝 npm (Node package manager) , Git , Node.js(browserify執行環境)

最早也是最有名的前端模組工具,非RequireJS莫屬。他是採用AMD格式,異步載入各種模組。具體的用法,可以參考我上一篇文章。RequireJS的問題在于各種參數設置過于繁瑣,不容易學習,很難完全掌握。而且,實際應用中,往往還需要在Server端,將所有模組合並一個檔案後,再統一載入,但模組一多,人工步驟相對眼花撩亂。

鑒於 CommonJS 的模組讀取方式在 Node.js 幫助之下大放異彩,使用簡單的require('XXXX')語法就能載入XXXX套件,但是require不支援瀏覽器環境,於是就有人想出辦法想要在瀏覽器也能使用 CommonJS 這種簡單的模組管理方法來替代 RequireJS。這個工具叫做Browserify。當然這是專門用在Node.js環境之下的模組管理工具。

相較於RequireJS的非同步載入模組,CommonJS因為本來就設定在後端使用,所以也設計成同步方式載入模組。

在這篇可以學到 一個Common JS 模組的定義語法(與AMD比較),以及 Browserify 的基本使用方法。

Browserify的運作原理是把require到的模組以及依賴的模組會合併(bundling)成一個檔案,最後由<script>方式統一載入。這樣就是同步的載入。

Browserify 13.0.1

底下的範例有:

  1. A Very Simple CommonJS Module
  2. A CommonJS Module depends on jQuery (Bundle)
  3. CommonJS-Incompatible JS file(Shim)

Ex 1 : A Very Simple CommonJS Module

The files of Ex1
  1. 首先要安裝 Browserify。請在任一資料夾打開進入 Git Bash 後, 使用 npm 安裝。

指令:

npm install browserify -g 
-g : 全域安裝broserify這個npm套件。

2. 撰寫一個最簡單的 module

module.exports = XXXX
是 CommonJS 模組定義的方法,只匯出必要的Object。
myModule1.js and myModule2.js

3. 撰寫主要程式進入點JS檔案

require('./XXXXX')  
是 CommonJS 匯入模組的方法。
main.js

4. 使用Browserify 整合所有必要模組檔案。

browserify  main.js > bundle.js
自動靜態分析所有require的檔案

5. 在網頁 index.html 載入 bundle.js

index.html

接這打開網頁,F12進入Console,可以看到add()後的結果。最簡單的Browserify範例就這麼完成了。比RequireJS簡單許多。

大家一定很好奇bundle.js到底裝了什麼?

bundle.js

Ex2 : A CommonJS Module depends on jQuery (Bundled)

這個範例是如何使用在browserify的方案下使用jquery語法。並且使用jQuery的npm module。現在的jQuery都已經有含CommonJS與AMD(for RequireJS)的 雙重定義,所以要如何使用jQuery呢?

  1. 先產生 package.json檔 (npm專案設定檔),以便日後安裝模組與設定browser。
npm init
輸入後打上專案名稱example_browserify_npm_jquery,之後一直按enter略過。反正只是範例。

之後就有 package.json這個檔案。

2. 用NPM為專案安裝 jQuery套件。

npm install jquery -save
-save : 代表專案與jquery相依,並將套件裝在專案目錄內。

安裝完畢後,package.json會多出一個jquery 相依設定。

package.json

3. 為Module加入jQuery範例程式碼。

myModule1.js and myModule2.js

加入簡單操作DOM之語法。

$(function(){
//.....write your code relate to jquery here
});
請使用此$(function(){ ...}包裝方法。
$(function(){ …}); is a shortcut for $(document).ready(function(){ … });

4. 在主要程式進入點JS檔案宣告載入jquery套件。

global.$ = require('jquery');
使用CommonJS語法載入jquery。為什麼是global,詳情請見Ex2詳細說明。
main.js

4. 使用Browserify 整合所有必要模組檔案。

browserify  main.js > bundle.js
自動靜態分析所有require的檔案 

5. 在網頁 index.html 載入 bundle.js

index.html

接這打開網頁,結果如下。

打開bundle.js,你會看到jQuery已經包在bundle.js裡面了。

詳細說明:

  1. 前情提要: 
    Browser(前端)的全域變數為 window 物件,而Node.js的全域變數為 global 物件。
  2. 在main.js為什麼使用 global.$ = require('jquery') 而不是 require('jquery') 或 $ = require('jquery') 呢?

這得要看 bundle.js是怎麼包裝的?

2–1.全域變數的無效:

先說說為什麼不能用 require('jquery') ?

由於jQuery在1.7板之後已經加入了AMD與CommonJS的支援,也就是開始判斷當下的jQuery是在CommonJS下的環境(如Node.js),亦或是AMD(Require.js)環境,甚至什麼模組定義都沒有的環境底下去操作jQuery。

./node_modules/jquery/dist/jquery.js (v3.0.0)

把jquery.js的source code翻出來看,在CommonJS環境下因為有module 與 module.export 有沒有定義,有的話會在 factory( global, true) 的變數帶入,由於 factory 第二參數是 noGlobal,在jquery的判斷(如下圖)之下,在CommonJS環境是不會把jQuery object 賦予給瀏覽器專有的全域物件 window。

所以在main.js裡光只有require(‘jquery’)的話,是無法讓其他模組也能用jquery的,必須要讓window也有 window.$ 才可以在其他模組也能用 $ 操作其他DOM的。

./node_modules/jquery/dist/jquery.js (v3.0.0)

題外話: AMD 的define也在jquery內。

./node_modules/jquery/dist/jquery.js (v3.0.0)

更多可參考 http://warmsea.net/blog/post/38/

2–2. 那為什麼是global?

當然我們可以直接寫成

window.$ = require('jquery')

此行在瀏覽器可用,但是如果這些code是在純CommonJS環境下(如Node.js) 執行的話,window物件就無法使用。

由於Browserify在bundle main.js檔案時候會帶入global變數,如下:

bundle.js

而global變數在Browser的執行環境下,Browserify 判斷為window物件(如下圖)。所以把require(‘jquery’)的return值給global變數既能符合CommonJS規範,也能把值間接的設定給window物件。

bundle.js

Ex3 : CommonJS-Incompatible JS file (Shim)

前情提要:

有沒有一種狀況是我們寫的js檔目前沒有CommonJS版本,或是根本沒有按照任何模組的方法去寫,但是在這個js檔內又使用到了 CommonJS 模組,這時候該怎麼辦? 答案是,我們可以用browserify-shim 來達到自動轉換成CommonJS模組的目標。

browserify-shim 的存在就是為了讓不相容於CommonJS的 Javascript 也能被Browserify所分析並bundle起來。
  1. 先產生 package.json檔 (npm專案設定檔),以便日後安裝模組與設定browser。
npm init
輸入後打上專案名稱example_browserify_shim_jquery,之後一直按enter略過。反正只是範例。

並用NPM為專案安裝 jQuery套件。

npm install jquery -save
-save : 代表專案與jquery相依,並將套件裝在專案目錄內。

2. 之後要安裝最重要 browserify-shim 套件。請輸入指令。

npm install browserify-shim --save-dev
--save-dev : 把套件安裝在專案目錄資料夾內,並且記錄在package.json內。

3. 打開package.json後,我們要加上一些東西。

package.json

就是加上這些設定:

    "browser": {
"myModule2": "./myModule2.js"
},
"browserify": {
"transform": [ "browserify-shim" ]
},
"browserify-shim": {
"jquery": "$",
"myModule2": {
"exports": "myMath2",
"depends": [ "jquery:$" ]
}
}

其中先為browserify設定轉換器,本範例是browserify-shim,當然還有其他種類的轉換器可以使用。

    "browserify": {
"transform": [ "browserify-shim" ]
}

4. 加上main.js 與 不相容CommonJS的myModule2。

myModule1.js & myModule2.js

範例內 myModule1.js 是相容於CommonJS的檔案。而myModule2.js是不相容於CommonJS的檔案,也就是沒有寫上 module.exports = myMath2;。

既然沒有 module.export 那麼就只能靠設定檔來告知Browserify-shim怎麼export變數。

首先要先設定myModule2這個JS檔案在哪裡。

    "browser": {
"myModule2": "./myModule2.js"
}

再來是設定export 與相依性,因為myModule2.js有用到jQuery的$。

"browserify-shim": {
"jquery": "$",
"myModule2": {
"exports": "myMath2",
"depends": [ "jquery:$" ]
}
}
1. "jquery": "$" --> 代表 jquery export $ 
2. 不相容CommonJS的 myModule2 通常分成exports與depends設定。myMath2是myModule2.js裡要export的function。 而depends裡列出來的module也會被一併合併到bundle.js。
更多設定請參考: https://github.com/thlorenz/browserify-shim#a-expose-global-variables-via-global

所以 main.js 裡寫了什麼

main.js

就是一般CommonJS的寫法,使用require載入不相容的myModule2模組。

誤解1: 不需要global.$ = require(‘jquery’);? 因為我們透過browserify-shim等同加入的這行設定。 →這個說法不對,因為只有export $這個變數,如果沒有人require一樣是不會載入jQuery。

5. 使用Browserify 整合所有必要模組檔案。

browserify  main.js > bundle.js
自動靜態分析所有require的檔案

6. 在網頁 index.html 載入 bundle.js

index.html

接這打開網頁,結果如下。

打開bundle.js,你會看到jQuery已經包在bundle.js裡面了。

bundle.js

可以看到 就算沒有 module.exports = myMath2; ,Browserify轉出來的bundle.js也會多了

browserify_shim__define__module__export__(typeof myMath2 != "undefined" ? myMath2 : window.myMath2);

如需要本篇範例檔請留言告知。

Reference:

https://github.com/substack/browserify-handbook

http://acgtofe.com/posts/2015/06/modular-javascript-with-browserify

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.