摘要:它的關(guān)鍵能力,是模塊及依賴管理。其次,保持局部變量風格。我們很習慣通過和這樣的全局變量來訪問這樣的庫,但如果使用,它們都應只作為局部變量這里的就只存在于這個文件的代碼范圍內(nèi)獨立的作用域。
引言 1. manually
以前,我新開一個網(wǎng)頁項目,然后想到要用jQuery,我會打開瀏覽器,然后找到j(luò)Query的官方網(wǎng)站,點擊那個醒目的“Download jQuery”按鈕,下載到.js文件,然后把它丟在項目目錄里。在需要用到它的地方,這樣用引入它:
2. Bower
后來,我開始用[Bower][]這樣的包管理工具。所以這個過程變成了:先打開命令行用bower安裝jQuery。
bower install jquery
再繼續(xù)用引入它。
3. npm&Browserify
現(xiàn)在,我又有了新的選擇,大概是這樣:
命令行用npm安裝jQuery。
npm install jquery
在需要用到它的JavaScript代碼里,這樣引入它:
var $ = require("jquery");
沒錯,這就是使用npm的包的一般方法。但特別的是,這個npm的包是我們熟知的jquery,而它將用在瀏覽器中。
[Browserify][],正如其名字所體現(xiàn)的動作那樣,讓原本屬于服務(wù)器端的Node及npm,在瀏覽器端也可使用。
顯然,上面的過程還沒結(jié)束,接下來是Browserify的工作(假定上面那段代碼所在的文件叫main.js):
browserify main.js -o bundle.js
最后,用引用Browserify生成的bundle.js文件。
這就是依托Browserify建立起來的第三選擇。
等下,怎么比以前變復雜了?
CommonJS風格的模塊及依賴管理其實,在這個看起來更復雜的過程中,require()具有非凡的意義。
Browserify并不只是一個讓你輕松引用JavaScript包的工具。它的關(guān)鍵能力,是JavaScript模塊及依賴管理。(這才是為師的主業(yè))
就模塊及依賴管理這個問題而言,已經(jīng)有RequireJS[]這些優(yōu)秀的作品。而現(xiàn)在,Browserify又給了我們新的選擇。
Browserify參照了Node中的模塊系統(tǒng),約定用require()來引入其他模塊,用module.exports來引出模塊。在我看來,Browserify不同于RequireJS和Sea.js的地方在于,它沒有著力去提供一個“運行時”的模塊加載器,而是強調(diào)進行預編譯。預編譯會帶來一個額外的過程,但對應的,你也不再需要遵循一定規(guī)則去加一層包裹。因此,相比較而言,Browserify提供的組織方式更簡潔,也更符合CommonJS規(guī)范。
像寫Node那樣去組織你的JavaScript,Browserify會讓它們在瀏覽器里正常運行的。
安裝及使用 命令行形式命令行形式是官方貼出來的用法,因為看起來最簡單。
Browserify本身也是npm,通過npm的方式安裝:
npm install -g browserify
這里-g的參數(shù)表示全局,所以可以在命令行內(nèi)直接使用。接下來,運行browserify命令到你的.js文件(比如entry.js):
browserify entry.js -o bundle.js
Browserify將遞歸分析你的代碼中的require(),然后生成編譯后的文件(這里的bundle.js)。在編譯后的文件內(nèi),所有JavaScript模塊都已合并在一起且建立好了依賴關(guān)系。最后,你在html里引用這個編譯后的文件(喂,和引言里的一樣啊):
有關(guān)這個編譯命令的配置參數(shù),請參照[node-browserify#usage][]。如果你想要做比較精細的配置,命令行形式可能會不太方便。這種時候,推薦結(jié)合Gulp使用。
+ Gulp形式結(jié)合Gulp使用時,你的Browserify只安裝在某個項目內(nèi):
npm install browserify --save-dev
建議加上后面的--save-dev以保存到你項目的package.json里。
接下來是gulpfile.js的部分,下面是一個簡單示例:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require("vinyl-source-stream"); var buffer = require("vinyl-buffer"); gulp.task("browserify", function () { var b = browserify({ entries: "./javascripts/src/main.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./javascripts/dist")); });
可以看到,Browserify是獨立的,我們需要直接使用它的API,并將它加入到Gulp的任務(wù)中。
在上面的代碼中,debug: true是告知Browserify在運行同時生成內(nèi)聯(lián)sourcemap用于調(diào)試。引入gulp-sourcemaps并設(shè)置loadMaps: true是為了讀取上一步得到的內(nèi)聯(lián)sourcemap,并將其轉(zhuǎn)寫為一個多帶帶的sourcemap文件。vinyl-source-stream用于將Browserify的bundle()的輸出轉(zhuǎn)換為Gulp可用的[vinyl][](一種虛擬文件格式)流。vinyl-buffer用于將vinyl流轉(zhuǎn)化為buffered vinyl文件(gulp-sourcemaps及大部分Gulp插件都需要這種格式)。
這樣配置好之后,直接運行gulp browserify就可以得到結(jié)果了,可能像這樣:
如果你的代碼比較多,可能像上圖這樣一次編譯需要1s以上,這是比較慢的。這種時候,推薦使用[watchify][]。它可以在你修改文件后,只重新編譯需要的部分(而不是Browserify原本的全部編譯),這樣,只有第一次編譯會花些時間,此后的即時變更刷新則十分迅速。
有關(guān)更多Browserify + Gulp的示例,請參考[Gulp Recipes][]。
特性及簡要原理使用Browserify來組織JavaScript,有什么要注意的地方嗎?
要回答這個問題,我們先看看Browserify到底做了什么。下面是一個比較詳細的例子。
項目內(nèi)現(xiàn)在用到2個.js文件,它們存在依賴關(guān)系,其內(nèi)容分別是:
name.js:
module.exports = "aya";
main.js:
var name = require("./name"); console.log("Hello! " + name);
然后對main.js運行Browserify,得到的bundle.js的文件內(nèi)容是這樣的:
(function e(t, n, r) { // ... })({ 1: [function (require, module, exports) { var name = require("./name"); console.log("Hello! " + name); }, {"./name": 2}], 2: [function (require, module, exports) { module.exports = "aya"; }, {}] }, {}, [1]) //# sourceMappingURL=bundle.js.map
請先忽略掉省略號里的部分。然后,它的結(jié)構(gòu)就清晰多了。可以看到,整體是一個立即執(zhí)行的函數(shù)([IIFE][]),該函數(shù)接收了3個參數(shù)。其中第1個參數(shù)比較復雜,第2、3個參數(shù)在這里分別是{}和[1]。
模塊map第1個參數(shù)是一個Object,它的每一個key都是數(shù)字,作為模塊的id,每一個數(shù)字key對應的值是長度為2的數(shù)組。可以看出,前面的main.js中的代碼,被function(require, module, exports){}這樣的結(jié)構(gòu)包裝了起來,然后作為了key1數(shù)組里的第一個元素。類似的,name.js中的代碼,也被包裝,對應到key2。
數(shù)組的第2個元素,是另一個map對應,它表示的是模塊的依賴。main.js在key1,它依賴name.js,所以它的數(shù)組的第二個元素是{"./name": 2}。而在key2的name.js,它沒有依賴,因此其數(shù)組第二個元素是空Object{}。
因此,這第1個復雜的參數(shù),攜帶了所有模塊的源碼及其依賴關(guān)系,所以叫做模塊map。
包裝前面提到,原有的文件中的代碼,被包裝了起來。為什么要這樣包裝呢?
因為,瀏覽器原生環(huán)境中,并沒有require()。所以,需要用代碼去實現(xiàn)它(RequireJS和Sea.js也做了這件事)。這個包裝函數(shù)提供的3個參數(shù),require、module、exports,正是由Browserify實現(xiàn)了特定功能的3個關(guān)鍵字。
緩存第2個參數(shù)幾乎總是空的{}。它如果有的話,也是一個模塊map,表示本次編譯之前被加載進來的來自于其他地方的內(nèi)容。現(xiàn)階段,讓我們忽略它吧。
入口模塊第3個參數(shù)是一個數(shù)組,指定的是作為入口的模塊id。前面的例子中,main.js是入口模塊,它的id是1,所以這里的數(shù)組就是[1]。數(shù)組說明其實還可以有多個入口,比如運行多個測試用例的場景,但相對來說,多入口的情況還是比較少的。
實現(xiàn)功能還記得前面忽略掉的省略號里的代碼嗎?這部分代碼將解析前面所說的3個參數(shù),然后讓一切運行起來。這段代碼是一個函數(shù),來自于browser-pack項目的[prelude.js][]。令人意外的是,它并不復雜,而且寫有豐富的注釋,很推薦你自行閱讀。
所以,到底要注意什么?到這里,你已經(jīng)看過了Browserify是如何工作的。是時候回到前面的問題了。首先,在每個文件內(nèi),不再需要自行包裝。
你可能已經(jīng)很習慣類似下面這樣的寫法:
;(function(){ // Your code here. }());
但你已經(jīng)了解到,Browserify的編譯會將你的代碼封裝在局部作用域內(nèi),所以,你不再需要自己做這個事情,像這樣會更好:
// Your code here.
類似的,如果你想用"use strict";啟用嚴格模式,直接寫在外面就可以了,這表示在某個文件的代碼范圍內(nèi)啟用嚴格模式。
其次,保持局部變量風格。我們很習慣通過window.jQuery和window.$這樣的全局變量來訪問jQuery這樣的庫,但如果使用Browserify,它們都應只作為局部變量:
var $ = require("jquery"); $("#alice").text("Hello!");
這里的$就只存在于這個文件的代碼范圍內(nèi)(獨立的作用域)。如果你在另一個文件內(nèi)要使用jQuery,需要按照同樣的方式去require()。
然而,新的問題又來了,既然jQuery變成了這種局部變量的形式,那我們熟悉的各種jQuery插件要如何使用呢?
browserify-shim你一定熟悉這樣的jQuery插件使用方式:
很多jQuery插件是這樣做的:默認window.jQuery存在,然后取這個全局變量,把自己添加到j(luò)Query中。顯然,這在Browserify的組織方式里是沒法用的。
為了讓這樣的“不兼容Browserify”(其實是不兼容CommonJS)的JavaScript模塊(如插件)也能為Browserify所用,于是有了[browserify-shim][]。
下面,以jQuery插件[jquery.pep.js][]為例,請看browserify-shim的使用方法。
使用示例安裝browserify-shim:
npm install browserify-shim --save-dev
然后在package.json中做如下配置:
"browserify": { "transform": [ "browserify-shim" ] }, "browser": { "jquery.pep" : "./vendor/jquery.pep.js" }, "browserify-shim": { "jquery.pep" : { "depends": ["jquery:jQuery"] } }
最后是.js中的代碼:
var $ = require("jquery"); require("jquery.pep"); $(".move-box").pep();
完成!到此,經(jīng)過Browserify編譯后,將可以正常運行這個jQuery插件。
這是一個怎樣的過程呢?
在本例中,jQuery使用的是npm里的,而jquery.pep.js使用的是一個自己下載的文件(它與很多jQuery插件一樣,還沒有發(fā)布到npm)。查看jquery.pep.js源碼,注意到它用了這樣的包裝:
;(function ( $, window, undefined ) { // ... }(jQuery, window));
可以看出,它默認當前環(huán)境中已存在一個變量jQuery(如果不存在,則報錯)。package.json中的"depends": ["jquery:jQuery"] 是為它添加依賴聲明,前一個jquery表示require("jquery"),后一個jQuery則表示將其命名為jQuery(賦值語句)。這樣,插件代碼運行的時候就可以正常找到jQuery變量,然后將它自己添加到j(luò)Query中。
實際上,browserify-shim的配置并不容易。針對代碼包裝(盡管都不兼容CommonJS,但也存在多種情況)及使用場景的不同,browserify-shim有不同的解決方案,本文在此只介紹到這。
關(guān)于配置的更多說明,請參照browserify-shim官方文檔[]。此外,如果你覺得browserify-shim有些難以理解或者對它的原理也有興趣,推薦閱讀[這篇Stack Overflow上的回答][]。
當然,對于已經(jīng)處理了CommonJS兼容的庫或插件(比如已經(jīng)發(fā)布到npm),browserify-shim是不需要的。
其實還有的更多transform在前面browserify-shim的例子中,"browserify": {"transform": [ "browserify-shim" ]}其實是Browserify的配置。可以看出,browserify-shim只是Browserify的其中一種transform。在它之外,還有[很多的transform][]可用,分別應對不同的需求,使Browserify的體系更為完善。
比如,還記得本文引言里的Bower嗎?[debowerify][]可以讓通過Bower安裝的包也可以用require()引用。npm和bower同為包管理工具,Browserify表示你們都是我的翅膀。
Browserify是靜態(tài)分析編譯工具,因此不支持動態(tài)require()。例如,下面這樣是不可以的:
var lang = "zh_cn"; var i18n = require("./" + lang);文檔資料
有關(guān)Browserify更詳細的說明文檔,請看[browserify-handbook][]。
結(jié)語我覺得Browserify很有趣,它用了這樣一個名字,讓你覺得它好像只是一個Node的瀏覽器端轉(zhuǎn)化工具。為此,它還完成了Node中大部分核心庫的瀏覽器端實現(xiàn)。但實際上,它走到了更遠的地方,并在JavaScript模塊化開發(fā)這個重要的領(lǐng)域中,創(chuàng)立了一個全新的體系。
喜歡CommonJS的簡潔風格?請嘗試Browserify!
(重新編輯自我的博客,原文地址:http://acgtofe.com/posts/2015/06/modular-javascript-with-browserify)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85773.html
摘要:在開發(fā)大型的項目中,可能會使用到管理的模塊化工具。說道,學習過的同學會比較熟悉,是服務(wù)器模塊的規(guī)范,采用了這個規(guī)范。可能是未來模塊化解決方案的首選。 本文章記錄本人在學習 JavaScript 中理解到的一些東西,加深記憶和并且整理記錄下來,方便之后的復習。 在開發(fā)大型的web項目中,可能會使用到管理js的模塊化工具。但是在前端輪子漫天飛的時代。那一款js模塊化工具真正適合我...
摘要:可維護性根據(jù)定義,每個模塊都是獨立的。良好設(shè)計的模塊會盡量與外部的代碼撇清關(guān)系,以便于獨立對其進行改進和維護。這標志模塊化編程正式誕生。的模塊系統(tǒng),就是參照規(guī)范實現(xiàn)的。對象就代表模塊本身。 javascript模塊化及webpack基本介紹 JavaScript 模塊化發(fā)展歷程 什么是模塊化 ? 為什么要做Javascript模塊化? JavaScript 模塊化發(fā)展歷程 什么是模...
摘要:本文特此給大家介紹下如何使用配合來構(gòu)建基于的前端項目。最后,在目錄下會生成最終的項目文件。執(zhí)行單元測試本例中使用進行單元測試。 隨著React、Angular2、Redux等前沿的前端框架越來越流行,使用webpack、gulp等工具構(gòu)建前端自動化項目也隨之變得越來越重要。鑒于目前業(yè)界普遍更流行使用webpack來構(gòu)建es6(ECMAScript 2015)前端項目,網(wǎng)上的相關(guān)教程也比...
摘要:本節(jié)將學習是如何利用形成一套完整的前端工作流模式的。你也可以使用下面命令來強制安裝所有模塊,不管該模塊之前是否安裝過由于國內(nèi)墻的原因,使用安裝會非常緩慢,慢到想切,不過還好,我們可以使用淘寶提供的國內(nèi)鏡像進行下載。 本節(jié)將學習 Laravel 是如何利用 Sass, NPM, Gulp形成一套完整的前端工作流模式的。 一、句法強大的樣式表Sass Sass 是一種可用于編寫CSS的語言...
閱讀 455·2023-04-25 23:00
閱讀 3486·2021-11-22 13:54
閱讀 1886·2021-10-27 14:14
閱讀 1478·2019-08-30 13:59
閱讀 3503·2019-08-23 16:15
閱讀 1948·2019-08-23 16:06
閱讀 3315·2019-08-23 15:26
閱讀 1246·2019-08-23 13:48