1.3 萬 Star!直接在瀏覽器端組織 JS 代碼的魔法工具
【導(dǎo)語】:Browserify 是一個(gè)開源的 JS?編譯工具,可以讓你使用類似于 node 的 require() 的方式來組織瀏覽器端的 JS?代碼,通過預(yù)編譯讓前端 JS?可以直接使用 Node NPM 安裝的一些庫。
簡介
Browserify 是一個(gè)開源的?JavaScript 工具,它可以讓你像在 node 中那樣,在瀏覽器中也可以使用?require('module')?來加載模塊。換句話說,它可以讓服務(wù)端的 CommonJs 的模塊運(yùn)行在瀏覽器端。

開源地址
Browserify 在 GitHub 上已有 13.5k Star。
https://github.com/browserify/browserify
安裝
如果你想在命令行中使用,可以直接全局安裝:
npm?install?-g?browserify

基礎(chǔ)使用
簡單上手
我們先來看一個(gè)簡單的例子。假設(shè)我們有一個(gè)文件 index.js,引入了一個(gè)外部模塊?uniq。首先我們安裝這個(gè)模塊
npm?install?uniq
接著在 index.js 使用這個(gè)模塊
const?uniq??=?require("uniq");
console.info(uniq([1,2,3,1,2,3]))
我們知道,這個(gè) index.js 文件是可以直接在 node 環(huán)境下運(yùn)行的,但是在瀏覽器上運(yùn)行會有什么效果呢?
我們來試一下效果,在 index.html 中手動引入這個(gè)文件:
<script?src="./index.js">script>
打開網(wǎng)頁,我們可以看到控制臺報(bào)錯(cuò)了:

其實(shí)很容易理解,我們根本沒有定義這個(gè)?require,瀏覽器當(dāng)然也不會認(rèn)識它了。這時(shí)候我們的主角 Browserify 就派上用場了,我們在當(dāng)前路徑下執(zhí)行如下命令:
browserify?index.js?>?app.js
然后修改我們 index.html 引入的腳本為 app.js:
<script?src="./app.js">script>
接著,我們刷新一下頁面,可以看到正常顯示了去重后的數(shù)組:
外部依賴
如果你想在 script 標(biāo)簽中直接使用第三方模塊,或者我們自己定義的模塊,你可以使用?--require?選項(xiàng)參數(shù),它的簡寫為?-r。
承接上例,假設(shè)我們的 index.js 導(dǎo)出了一個(gè)對象:
module.exports?=?{
??name:?"Jerry",
??techs:?["Vue",?"React",?"Webpack",?"Vue"]
}
并且我們想在 index.html 中直接使用 uniq 模塊和 index.js 導(dǎo)出的對象。此時(shí)我們需要修改我們的命令:
browserify?-r?uniq?-r?index.js:myModule?>?app.js
上述命令的意思是,將 uniq 打包成一個(gè)可 require 的模塊,將 index.js 也打包成一個(gè)可 require 的模塊,并且這個(gè)模塊的名字叫 myModule。
接著在 index.html 中修改 script 標(biāo)簽:
<script?src="./app.js">script>
<script>
??const?uniq??=?require("uniq");
??const?myModule?=?require("myModule");
??console.info(`Got?unique?techs?[${uniq(myModule.techs)}]?from?${myModule.name}`)
script>

可以看到,輸出內(nèi)容跟我們預(yù)期的一致。
生成 sourcemap
Browserify 也可以生成 sourcemap 方便我們開發(fā)中定位問題,它的使用很簡單,只需要加上?--debug?選項(xiàng)即可(簡寫為?-d)。我們修改之前的命令:
browserify?-d?-r?uniq?-r?index.js:myModule?>?app.js?
然后看一下生成的 app.js 文件:

可以看到在最后生成了 sourcemap 內(nèi)容,在開發(fā)中可以方便的借助于它來進(jìn)行調(diào)試了。
當(dāng)然,如果你想將生成的 sourcemap 與我們的 js 文件分離開,可以借助于?exorcist?來實(shí)現(xiàn)(exorcist 原意為“驅(qū)魔人”,這里指代的是,將文件中的 sourcemap 部分的內(nèi)容“驅(qū)趕”到另一個(gè)單獨(dú)文件中去,更多用法請參考:exorcist[1]):
修改我們的命令為:
browserify?-d?-r?uniq?-r?index.js:myModule?|?npx?exorcist?./app.js.map?>?./app.js?
執(zhí)行后你就會發(fā)現(xiàn),你的 app.js 和 它對應(yīng)的 sourcemap 文件已經(jīng)分離了:

多文件打包
當(dāng)多個(gè)頁面同時(shí)用到某一個(gè)相同的文件時(shí),我們希望單獨(dú)抽離出這個(gè)文件,再結(jié)合緩存機(jī)制,可以實(shí)現(xiàn)高效的加載。在 browserify 中我們可以通過?--external(簡寫 -x) 和?--require(簡寫 -r) 來實(shí)現(xiàn)。
比如我們此時(shí)有如下文件:
utils.js(公用的工具庫):
function?add(a,?b)?{
??return?a?+?b;
}
function?minus(a,?b)?{
??return?a?-?b;
}
function?upper(str)?{
??return?String(str).toLocaleUpperCase()
}
module.exports?=?{
??add,
??minus,
??upper
}
sum.js:
const?{?add?}?=?require("./utils")
console.info(add(1,2))
upper.js:
const?{?upper?}?=?require("./utils")
console.info(upper("hello"))
我們想將 utils.js 單獨(dú)打包成一個(gè)文件,然后 sum.js 和 upper.js 的打包文件中不用包含這個(gè) utils.js 的代碼,而且直接 require 引入,我們需要如下三個(gè)命令:
//?單獨(dú)打包?utils.js?文件為?common.js?文件
browserify?-r?utils.js?-o?./lib/common.js
//?打包?sum.js,并且指定其使用的?utils.js?為外部模塊(即不需要將這個(gè)?utils.js?打包進(jìn)來)
browserify?-x?utils.js?sum.js?-o?./lib/sum.js
//?打包?upper.js,并且指定其使用的?utils.js?為外部模塊(即不需要將這個(gè)?utils.js?打包進(jìn)來)
browserify?-x?utils.js?upper.js?-o?./lib/upper.js
依次執(zhí)行后會在當(dāng)目錄下生成 lib 文件夾,里面有三個(gè)打包后的文件:
lib/common.js:
require=(function(){function?r(e,n,t){function?o(i,f){if(!n[i]){if(!e[i]){var?c="function"==typeof?require&&require;if(!f&&c)return?c(i,!0);if(u)return?u(i,!0);var?a=new?Error("Cannot?find?module?'"+i+"'");throw?a.code="MODULE_NOT_FOUND",a}var?p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var?n=e[i][1][r];return?o(n||r)},p,p.exports,r,e,n,t)}return?n[i].exports}for(var?u="function"==typeof?require&&require,i=0;ireturn?o}return?r})()({"/src/03-multiple/utils.js":[function(require,module,exports){
function?add(a,?b)?{
??return?a?+?b;
}
function?minus(a,?b)?{
??return?a?-?b;
}
function?upper(str)?{
??return?String(str).toLocaleUpperCase()
}
module.exports?=?{
??add,
??minus,
??upper
}
},{}]},{},[]);
lib/sum.js
(function(){function?r(e,n,t){function?o(i,f){if(!n[i]){if(!e[i]){var?c="function"==typeof?require&&require;if(!f&&c)return?c(i,!0);if(u)return?u(i,!0);var?a=new?Error("Cannot?find?module?'"+i+"'");throw?a.code="MODULE_NOT_FOUND",a}var?p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var?n=e[i][1][r];return?o(n||r)},p,p.exports,r,e,n,t)}return?n[i].exports}for(var?u="function"==typeof?require&&require,i=0;ireturn?o}return?r})()({1:[function(require,module,exports){
const?{?add?}?=?require("./utils")
console.info(add(1,2))
},{"./utils":"/src/03-multiple/utils.js"}]},{},[1]);
lib/upper.js
(function(){function?r(e,n,t){function?o(i,f){if(!n[i]){if(!e[i]){var?c="function"==typeof?require&&require;if(!f&&c)return?c(i,!0);if(u)return?u(i,!0);var?a=new?Error("Cannot?find?module?'"+i+"'");throw?a.code="MODULE_NOT_FOUND",a}var?p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var?n=e[i][1][r];return?o(n||r)},p,p.exports,r,e,n,t)}return?n[i].exports}for(var?u="function"==typeof?require&&require,i=0;ireturn?o}return?r})()({1:[function(require,module,exports){
const?{?upper?}?=?require("./utils")
console.info(upper("hello"))
},{"./utils":"/src/03-multiple/utils.js"}]},{},[1]);
可以看到,common.js 中包含著公有的方法,sum.js 和 upper.js 中只包含著自己的業(yè)務(wù)邏輯,而外部模塊的使用是通過 require 來引入的。我們來驗(yàn)證一下是否可以運(yùn)行:
新建一個(gè) sum.html 文件,引入:
<script?src="./lib/common.js">script>
<script?src="./lib/sum.js">script>
運(yùn)行結(jié)果如下圖,可以看到正確的輸出了結(jié)果。

封裝 npm scripts
在開發(fā)過程中,我們通常會將一些常用的構(gòu)建命令封裝成 npm 腳本去執(zhí)行,這樣可以減小我們輸錯(cuò)腳本內(nèi)容的幾率,我們在 package.json 文件中增加 scripts
{
??"scripts":?{
????"build":?"browserify?index.js?>?app.js"
??}
}
接著我們執(zhí)行?npm run build?就可以完成同樣的任務(wù)了,這也是最佳實(shí)踐的一種。
進(jìn)階用法
使用 ES Module
如果你使用的是 import 或者 export 這樣的 ES6 的模塊化語言來引入其他模塊的話,browserify 也提供了插件支持這樣的語法。我們需要安裝如下依賴:
npm?install?--save-dev?@babel/core?@babel/preset-env?babelify
然后添加 babel.config.json 配置文件來配置 babel:
{
??"presets":?["@babel/preset-env"]
}
假設(shè)此時(shí)我們的 index.js 文件如下:
import?uniq?from?"uniq";
console.info(uniq([1,2,3,1,2,3]))
那么要像讓 browserify 正確的打包,需要如下命令:
browserify?index.js?>?app.es.js?-t?babelify
代碼壓縮
假設(shè)我們的 index.js 中出現(xiàn)了沒有使用的變量或者方法,這些代碼永遠(yuǎn)不會被執(zhí)行,那么打包進(jìn)最終的生成文件無疑是一種浪費(fèi)。browserify 也提供了這樣的插件來優(yōu)化我們的生成代碼,比如:刪除未使用的變量,刪除無效的代碼,壓縮代碼等。
我們需要安裝這個(gè)插件
npm?install?--save-dev?tinyify
假設(shè)我們的 index.js 是這樣的:
const?name?=?'Hello';
const?version?=?1.1;
function?add(a,?b)?{
??return?a?+?b;
}
function?upper(str)?{
??return?str.toUpperCase()
}
console.info(add(name,?version))
首先我們看一下不使用插件的打包輸出:
browserify?index.js?>?app.big.js
生成的 app.big.js 文件內(nèi)容:
(function(){function?r(e,n,t){function?o(i,f){if(!n[i]){if(!e[i]){var?c="function"==typeof?require&&require;if(!f&&c)return?c(i,!0);if(u)return?u(i,!0);var?a=new?Error("Cannot?find?module?'"+i+"'");throw?a.code="MODULE_NOT_FOUND",a}var?p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var?n=e[i][1][r];return?o(n||r)},p,p.exports,r,e,n,t)}return?n[i].exports}for(var?u="function"==typeof?require&&require,i=0;ireturn?o}return?r})()({1:[function(require,module,exports){
const?name?=?'Hello';
const?version?=?1.1;
function?add(a,?b)?{
??return?a?+?b;
}
function?upper(str)?{
??return?str.toUpperCase()
}
console.info(add(name,?version))
},{}]},{},[1]);
我們可以看到,index.js 中的代碼原樣的被包裹在匿名函數(shù)中。并且我們的源代碼中沒有使用其他模塊,但是打包后的文件還是模塊引入之類的代碼打包進(jìn)來了。我們使用 tinyify 插件再看看:
browserify?index.js?>?app.big.js?-p?tinyfiy
生成的 app.small.js 文件內(nèi)容如下:
console.info("Hello1.1");
對,你沒有看錯(cuò),就是這么一行代碼!簡單粗暴!其實(shí)讀者回過頭來看一下 index.js 的代碼,無非也就是這么一行輸出代碼而已。所以,借助于 tinyify 插件,我們可以有效的減少打包文件的體積,提升程序加載速度與運(yùn)行效率。
監(jiān)聽文件修改
在開發(fā)過程中,我們不希望每次修改文件都要去手動的執(zhí)行構(gòu)建腳本,所以需要這樣一個(gè)工具來監(jiān)聽文件的修改,一有變動立即執(zhí)行構(gòu)建邏輯。我們可以使用 watchify 來實(shí)現(xiàn)這樣的效果。首先安裝這個(gè)模塊:
npm?install?watchify
接著只要運(yùn)行如下的命令即可:
watchify?index.js?-o?app.js
當(dāng)我們的 index.js 文件修改時(shí),app.js 就會被重新生成,刷新瀏覽器后就可以看到最新的結(jié)果了。
我們可以將上述三個(gè)腳本封裝成 npm scripts 來使用:
{
??"scripts":?{
????"dev":?"watchify?index.js?-o?app.js",
????"build":?"browserify?index.js?-o?app.js?-p?tinyify",
????"build:es":?"browserify?index.js?>?app.es.js?-t?babelify?-p?tinify",
??}
}
好了,關(guān)于 browserify 的使用就介紹到這里,如果想了解更多,可以去翻閱它的官網(wǎng)[2]掌握更多用法。
參考資料
exorcist:?https://github.com/thlorenz/exorcist
[2]?官網(wǎng):?http://browserify.org/
-?EOF -?
更多優(yōu)秀開源項(xiàng)目(點(diǎn)擊下方圖片可跳轉(zhuǎn))
如果覺得本文介紹的開源項(xiàng)目不錯(cuò),歡迎轉(zhuǎn)發(fā)推薦給更多人。
分享、點(diǎn)贊和在看
支持我們分享更多優(yōu)秀開源項(xiàng)目,謝謝!



