天天看點

requireJS 從概念到實戰

requireJS 可以很輕易的将一個項目中的JavaScript代碼分割成若幹個子產品(module)。并且requireJS推薦一個子產品就是一個檔案,是以,你将獲得一些零碎的具有互相依賴關系的JS檔案。子產品化的好處也淺顯意見,那就是大大增強代碼的可讀性、易維護性、可擴充性、減少全局污染等。

目錄:

基本概念

requireJS的曆史發展

子產品化的優點

require 實戰

    引入requireJS

    參數配置

    加載配置檔案

    定義子產品

    簡單的值對

    非依賴的函數式定義

    依賴的函數式定義

    載入子產品

    子產品的傳回值

          return 方式

          exports導出

    非标準子產品定義

    常用參數

          urlArgs

          scriptType

          waitSeconds

          deps

          callback

          config

          map

          packages

rquire 壓縮

其它問題

    1. timeout逾時問題

    2. 循環依賴問題

    3. CDN回退

    4. 定義AMD插件

    5. 關于require的預定義子產品

    6. 關于R.js壓縮非本地檔案的問題

    7. 關于R.js - shim功能的說明

    8. 關于require加載CSS的問題

基本概念##

因為自身設計的不足,JavaScript 這門語言實際上并沒有子產品化這種概念與機制,是以想實作如JAVA,PHP等一些背景語言的子產品化開發,那麼我們必須借助 requireJS 這個前端模拟子產品化的插件,雖然我們不需要去了解它的實作原理,但是大緻去了解它是如何工作的,我相信這會讓我們更容易上手。

requireJS使用head.appendChild()将每一個依賴加載為一個script标簽。
requireJS等待所有的依賴加載完畢,計算出子產品定義函數正确調用順序,然後依次調用它們。
           

在說JavaScript子產品化之前,我們不得不提CommonJS(原名叫ServerJs),這個社群可謂大牛雲集,他們為NodeJS制定過子產品化規範 Modules/1.0 ,并得到了廣泛的支援。在為JavaScript定制子產品化規範時,讨論的都是在 Modules/1.0 上進行改進,但是 Modules/1.0 是專門為服務端制定的規範,是以要想套用在客服端環境的JS上,情況就會有很大的不同,例如,對于服務端加載一個JS檔案,其耗費的時間幾乎都是可以忽略不計的,因為這些都是基于本地環境,而在用戶端浏覽器上加載一個檔案,都會發送一個HTTP請求,并且還可能會存在跨域的情況,也就是說資源的加載,到執行,是會存在一定的時間消耗與延遲。

是以社群的成員們意識到,要想在浏覽器環境中也能子產品化開發,則需要對現有規範進行更改,而就在社群讨論制定規範的時候内部發生了比較大的分歧,分裂出了三個主張,漸漸的形成三個不同的派别:

1.Modules/1.x派
這一波人認為,在現有基礎上進行改進即可滿足浏覽器端的需要,既然浏覽器端需要function包裝,需要異步加載,那麼新增一個方案,能把現有子產品轉化為适合浏覽器端的就行了,有點像“保皇派”。基于這個主張,制定了Modules/Transport(http://wiki.commonjs.org/wiki/Modules/Transport)規範,提出了先通過工具把現有子產品轉化為複合浏覽器上使用的子產品,然後再使用的方案。
	browserify就是這樣一個工具,可以把nodejs的子產品編譯成浏覽器可用的子產品。(Modules/Transport規範晦澀難懂,我也不确定browserify跟它是何關聯,有知道的朋友可以講一下)
	目前的最新版是Modules/1.1.1(http://wiki.commonjs.org/wiki/Modules/1.1.1),增加了一些require的屬性,以及子產品内增加module變量來描述子產品資訊,變動不大。
	 
2. Modules/Async派
這一波人有點像“革新派”,他們認為浏覽器與伺服器環境差别太大,不能沿用舊的子產品标準。既然浏覽器必須異步加載代碼,那麼子產品在定義的時候就必須指明所依賴的子產品,然後把本子產品的代碼寫在回調函數裡。子產品的加載也是通過下載下傳-回調這樣的過程來進行,這個思想就是AMD的基礎,由于“革新派”與“保皇派”的思想無法達成一緻,最終從CommonJs中分裂了出去,獨立制定了浏覽器端的js子產品化規範AMD(Asynchronous Module Definition)(https://github.com/amdjs/amdjs-api/wiki/AMD)
	 
3. Modules/2.0派
這一波人有點像“中間派”,既不想丢掉舊的規範,也不想像AMD那樣推到重來。他們認為,Modules/1.0固然不适合浏覽器,但它裡面的一些理念還是很好的,(如通過require來聲明依賴),新的規範應該相容這些,AMD規範也有它好的地方(例如子產品的預先加載以及通過return可以暴漏任意類型的資料,而不是像commonjs那樣exports隻能為object),也應采納。最終他們制定了一個Modules/Wrappings(http://wiki.commonjs.org/wiki/Modules/Wrappings)規範,此規範指出了一個子產品應該如何“包裝”,包含以下内容:
           

實際上這三個流派誰都沒有勝過誰,反而是最後的AMD,CMD 規範紮根在這三個流派之上,吸取它們提出的優點不斷得到壯大。

總的來說AMD,CMD都是從commonJS規範中結合浏覽器現實情況,并且吸收三大流派的優點而誕生。其中CMD是國内大牛制定的規範,其實作的工具是seaJS,而AMD則是國外大牛制定的,其實作技術則是requireJS

既然我們已經詳細的了解了“前端子產品化”的曆史與發展,那麼我們也要大緻了解子產品開發的好處,畢竟這是我們學習的動力。

1. 作用域污染
	小明定義了 var name = 'xiaoming';
	N ~ 天之後:
	小王又定義了一個 var name = 'xiaowang';

2.  防止代碼暴漏可被修改:
	為了解決全局變量的污染,早期的前端的先驅們則是以對象封裝的方式來寫JS代碼:
	var utils = {
		'version':'1.3'
	};
	然而這種方式不可以避免的是對象中的屬性可被直接修改:utils.version = 2.0 。

3. 維護成本的提升。
   如果代碼毫無子產品化可言,那麼小明今天寫的代碼,若幹天再讓小明自己去看,恐怕也無從下手。


4. 複用與效率
   子產品與非子產品的目的就是為了複用,提高效率
           

總的來說,前端的子產品化就是在眼瞎與手殘的過程進行發展的,大緻我們可以總結一下幾時代:

  1. 無序(洪荒時代) :自由的書寫代碼。
  2. 函數時代 :将代碼關入了籠子之中。
  3. 面向對象的方式。
  4. 匿名自執行函數:其典型的代表作就是JQ。
  5. 僞子產品開發(CMD/AMD)
  6. 子產品化開發(還未誕生的ES6标準)

我們相信未來必将更加光明,但是回顧現在,特别是在國内的市場環境中IE浏覽器依然占據半壁江山,是以基于ES6的子產品特性依然任重道遠,是以,在光明還未播撒的時刻,就讓我們率先點燃一朵火苗照亮自己,而這朵火苗就是 ———— requireJS

下面我将化整為零的去講解requireJS在一個項目的具體使用方式以及需要注意的事項。

引入requireJS

通過

<script>

标簽,将require.js 檔案引入到目前的 HTML 頁面中

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>RequireJS 實戰</title>
</head>
<body>
	<script src="js/require.js"></script>
</body>
</html>
           

參數配置###

requireJS 常用的方法與指令也就兩個,是以requireJS使用起來非常簡單。

  • require
  • define

其中

define

是用于定義子產品,而

require

是用于載入子產品以及載入配置檔案。

在requireJS中一個檔案就是一個子產品,并且檔案名就是該子產品的ID,其表現則是以key/value的鍵值對格式,key即子產品的名稱(子產品ID),而value則是檔案(子產品)的位址,是以多個子產品便有多個鍵值對值,這些鍵值對再加上一些常用的參數,便是require的配置參數,這些配置參數我們通常會單獨儲存在一個JS檔案中,友善以後修改、調用,是以這個檔案我們也稱之為“配置檔案”。

下面是requireJS的基本參數配置:

//index.html
<script>
require.config({
	baseUrl:'js/',
	paths:{
		'jquery':'http://xxxx.xxx.com/js/jquery.min',
		'index':'index'
	}
});

require(['index']);
</script>
           

require.config()

是用于配置參數的核心方法,它接收一個有固定格式與屬性的對象作為參數,這個對象便是我們的配置對象。

在配置對象中

baseUrl

定義了基準目錄,它會與

paths

中子產品的位址自動進行拼接,構成該子產品的實際位址,并且當配置參數是通過

script

标簽嵌入到html檔案中時,baseUrl預設的指向路徑就是該html檔案所處的位址。

paths

屬性的值也是一個對象,該對象儲存的就是子產品key/value值。其中key便是子產品的名稱與ID,一般使用檔案名來命名,而value則是子產品的位址,在requireJS中,當子產品是一個JS檔案時,是可以省略 .js 的擴充名,比如 “index.js” 就可以直接寫成 “index” 而當定義的子產品不需要與

baseUrl

的值進行拼接時,可以通過

"/"

http://

以及

.js

的形式來繞過

baseUrl

的設定。

示例:

require.config({
	baseUrl:'js/',
	paths:{
		'jquery':'http://xxx.xxxx.com/js/jquery.min',
		'index':'index'
	}
});
require(['index']);
           

實際上,除了可以在require.js加載完畢後,通過

require.config()

方法去配置參數,我們也可以在require.js加載之前,定義一個全局的對象變量 require 來事先定義配置參數。然後在require.js被浏覽器加載完畢後,便會自動繼承之前配置的參數。

<script>
	var require = {
		baseUrl: 'js/',
		paths: {
			'jquery': 'http://xxx.xxxx.com/js/jquery.min',
			'index': 'index'
		},
		deps:[index]
	};
</script>
<script src="js/require.js"></script>
           

不論是在require.js加載之前定義配置參數,還是之後來定義,這都是看看我們需求而言的,這裡我們舉例的配置參數都是放入到script标簽中,然後嵌入到HTML頁面的内嵌方式,在實際使用時,我們更多的則是将該段配置提取出來單獨儲存在一個檔案中,并将其取名為

app.js

,而這個

app.js

便是我們後面常說到的配置檔案。

另外還有一個“接口檔案”的概念,requireJS中,所謂接口檔案指的便是require.js加載完畢後第一個加載的子產品檔案。

加載配置檔案

現在我們知道require的配置有兩種加載方式,一種是放入到script标簽嵌入到html檔案中,另一種則是作為配置檔案

app.js

來獨立的引入。

獨立的引入配置檔案也有兩種方式,一種是通過script标簽加載外部JS檔案形式:

<script src="js/require.js"></script>
<script src="js/app.js"></script>
           

另一種方式則是使用 require 提供的

data-main

屬性,該屬性是直接寫在引入require.js的script标簽上,在require.js 加載完畢時,會自動去加載配置檔案 app.js。

html<script data-main="js/app" src="js/require.js"></script>

data-main

去加載入口檔案,便會使配置對象中的

baseUrl

屬性預設指向位址改為 app.js 所在的位置,相比之下我更加推薦這種方式,因為它更可能的友善快捷。

當我們的項目足夠的龐大時,我也會推薦将入口檔案作為一個普通的子產品,然後在這個子產品中,根據業務的不同再去加載不同的配置檔案。

//define.js
define(['app1','app2','app3','app4'],function(app1,app2,app3,app4){
    if(page == 'app1'){
        require.config(app1);
    }else if(page == 'app2'){
        require.config(app2);
    }else if(page == 'app3'){
        require.config(app3);
    }else{
        require.config(app4);
    }
})
           

當然關于子產品的定義和載入我們後面會詳細的講解到,這裡隻需要有一個概念即可。

定義子產品

在我們選擇requireJS來子產品化開發我們的項目或者頁面時,就要明确的知道我們以後所編寫的代碼或者是某段功能,都是要放在一個個定義好的子產品中。

下面是requireJS定義子產品的方法格式:

define([id,deps,] callback);

ID:子產品的ID,預設的便是檔案名,一般無需使用者自己手動指定。

deps:目前子產品是以依賴的子產品數組,數組的每個數組元素便是子產品名或者叫子產品ID。

callback:子產品的回調方法,用于儲存子產品具體的功能與代碼,而這個回調函數又接收一個或者多個參數,這些參數會與子產品數組的每個數組元素一一對應,即每個參數儲存的是對應子產品傳回值。

根據

define()

使用時參數數量的不同,可以定義以下幾種子產品類型:

簡單的值對####

當所要定義的子產品沒有任何依賴也不具有任何的功能,隻是單純的傳回一組鍵值對形式的資料時,便可以直接将要傳回的資料對象寫在

define

方法中:

define({
    'color':'red',
    'size':'13px',
    'width':'100px'
});
           

這種隻為儲存資料的子產品,我們稱之為“值對”子產品,實際上值對子產品不僅可以用于儲存資料,還可以儲存我們的配置參數,然後在不同的業務場景下去加載不同的配置參數檔案。

//app1.js
define({
    baseUrl:'music/js/',
    paths:{
        msuic:'music',
        play:'play'
    }
});
           
//app2.js
define({
    baseUrl:'video/js/',
    paths:{
        video:'video',
        play:'play'
    }
});
           

非依賴的函數式定義####

如果一個子產品沒有任何的依賴,隻是單純的執行一些操作,那麼便可以直接将函數寫在

define

define(function(require,exports,modules){
    // do something
    return {
    'color':'red',
    'size':'13px'
    }
});
           

依賴的函數式定義####

這種帶有依賴的函數式子產品定義,也是我們平時常用到的,這裡我們就結合執行個體,通過上面所舉的

index

子產品為例:

//index.js
define(['jquery','./utils'], function($) {
    $(function() {
        alert($);
    });
});

           

從上面的示例中我們可以看出

index

子產品中,依賴了 'jquery' 子產品,并且在子產品的回調函數中,通過

$

形參來接收

jquery

子產品傳回的值,除了

jquery

子產品,index子產品還依賴了

utils

子產品,因為該子產品沒有在配置檔案中定義,是以這裡以附加路徑的形式單獨引入進來的。

載入子產品

在說載入子產品之前,我們先聊聊“子產品依賴”。子產品與子產品之間存在着互相依賴的關系,是以就決定了不同的加載順序,比如子產品A中使用到的一個函數是定義在子產品B中的,我們就可以說子產品A依賴子產品B,同時也說明了在載入子產品時,其順序也是先子產品A,再子產品B。

在require中,我們可以通過

require()

方法去載入子產品。其使用格式如下:

require(deps[,callback]);

deps:所要載入的子產品數組。

callback:子產品載入後執行的回調方法。

這裡就讓我們依然使用上述的 index 子產品為例來說明

require.config({
        paths:{
            'index':'index'
        }
    });
    
    require(['index']);
           

requireJS 通過

require([])

方法去載入子產品,并執行子產品中的回調函數,其值是一個數組,數組中的元素便是要載入的子產品名稱也就是子產品ID,這裡我們通過

require(['index'])

方法載入了 index 這個子產品,又因為該子產品依賴了 jquery 子產品,是以接着便會繼續載入jquery子產品,當jquery子產品加載完成後,便會将自身的方法傳遞給形參

$

最後執行子產品的回調方法,alert出$參數具體内容。

這裡我們可以小小的總結一下,實作子產品的載入除了

require([],fn)

的主動載入方法,通過依賴也可以間接載入對應的子產品,但是相比較而言require方式載入子產品在使用上更加靈活,它不僅可以隻載入子產品不執行回調,也可以載入子產品然後執行回調,還可以在所定義的子產品中,按需載入所需要用到的子產品,并且将子產品傳回的對象或方法中儲存在一個變量中,以供使用。

這種按需載入子產品,也叫就近依賴模式,它的使用要遵循一定的使用場景:

當子產品是非依賴的函數式時,可以直接使用

define(function(require,exports,modules){
	var utils = require('utils');
	utils.sayHellow('hellow World')
})
           

當子產品是具有依賴的函數式時,隻能夠以回調的形式處理。

define(['jquery'], function($) {
	$(function() {
		require(['utils'],function(utils){
			utils.sayHellow('Hellow World!');
		});
	});
});
           

當然聰明伶俐的你,一定會想到這樣更好的辦法:

define(['jquery','require','exports','modules'], function($,require,exports,modules) {
	$(function() {
	    //方式一
		require(['utils'],function(utils){
			utils.sayHellow('Hellow World!');
		});
		//方式二:
		var utils = require('utils');
    	utils.sayHellow('hellow World')
	});
});
           

子產品的傳回值

require中定義的子產品不僅可以傳回一個對象作為結果,還可以傳回一個函數作為結果。實作子產品的傳回值主要有兩種方法:

return 方式####

// utils.js

define(function(require,exports,modules){
    function sayHellow(params){
        alert(params);
    }

    return sayHellow
});

// index.js
define(function(require,exports,modules){
	var sayHellow = require('utils');
	sayHellow('hellow World');
})

           

如果通過return 傳回多種結果的情況下:

// utils.js

define(function(require,exports,modules){
    function sayHellow(params){
        alert(params);
    }
    
    function sayBye(){
        alert('bye-bye!');
    }
    
    return {
        'sayHellow':sayHellow,
        'sayBye':sayBye
    }
});

// index.js
define(function(require,exports,modules){
	var utils = require('utils');
	utils.sayHellow('hellow World');
})

           

exports導出####

// utils.js

define(function(require,exports,modules){
    function sayHellow(params){
        alert(params);
    }
    exports.sayHellow = sayHellow;
})

// index.js
define(function(require,exports,modules){
	var utils = require('utils');
	utils.sayHellow('hellow World');
});

           

這裡有一個注意的地方,那就是非依賴性的子產品,可以直接在子產品的回調函數中,加入以下三個參數:

require:加載子產品時使用。

exports:導出子產品的傳回值。

modules:定義子產品的相關資訊以及參數。

非标準子產品定義

require.config()

方法的配置對象中有一個

shim

屬性,它的值是一個對象,可以用于聲明非标準子產品的依賴和傳回值。

所謂的 “非标準子產品” 指的是那些不符合的AMD規範的JS插件。

下面我們先看看基本的

shim

配置參數:

require.config({
	baseUrl:'js/',
	paths:{
		'jquery':'http://xxx.xxxx.com/js/jquery.min',
		'index':'index',
		'say':'say',
		'bar':'bar',
		'tools':'tools'
	},
	shim:{
		'tools':{
			deps:['bar'],
			exports:'tool'
		},
		'say':{
			deps:['./a','./b'],
			init:function(){
				return {
					'sayBye':sayBye,
					'sayHellow':sayHellow
				}
			}
		}
	}
});

require(['index']);
           

這裡需要注意的是如果所加載的子產品檔案是符合AMD規範,比如通過

define

進行定義的,那麼require預設的優先級将是标準的,隻有在不符合标準的情況下才會采用shim中定義的參數。

在 index 子產品執行時:

define(['jquery','tool','say'],function($,tool,say){
	tool.drag();
	say.sayHellow();
	say.sayBye();
})
           

上面的示例中,關于

shim

中有三個重要的屬性,它們分别是:

deps: 用于聲明目前非标準子產品所依賴的其它子產品,值是一個數組,數組元素是子產品的名稱或者是ID。

exports:用于定義非标準子產品的全局變量或者方法。值一般是一個字元串。

init:用于初始,處理,非标準子產品的全局變量或者是方法,常用于當非标準子產品存在多個全局變量以及方法,值是一個函數。

常用參數

require.config

中還存在其他的常用屬性設定。

urlArgs

RequireJS擷取資源時附加在URL後面的額外的query參數。作為浏覽器或伺服器未正确配置時的“cache bust”手段很有用。使用cache bust配置的一個示例:

javascript:;urlArgs: "bust=" + (new Date()).getTime()

在開發中這很有用,但請記得在部署到生成環境之前移除它。

scriptType

指定RequireJS将script标簽插入document時所用的type=""值。預設為“text/javascript”。想要啟用Firefox的JavaScript 1.8特性,可使用值“text/javascript;version=1.8”。

waitSeconds

通過該參數可以設定requireJS在加載腳本時的逾時時間,它的預設值是7,即如果一個腳本檔案加載時長超過7秒鐘,便會放棄等待該腳本檔案,進而報出timeout逾時的錯誤資訊,考慮到國内網絡環境不穩定的因素,是以這裡我建議設定為0。當然一般不需要去改動它,除非到了你需要的時候。

deps

用于聲明require.js在加載完成時便會自動加載的子產品,值是一個數組,數組元素便是子產品名。

callback

當deps中的自動加載子產品加載完畢時,觸發的回調函數。

config

config屬性可以為子產品配置額外的參數設定,其使用格式就是以子產品名或者子產品ID為key,然後具體的參數為value。

//app.js
require.config({
	baseUrl:'js/',
	paths:{
		'jquery':'http://xx.xxxx.com/js/jquery.min',
		'index':'index'
	},
	config:{
		'index':{
			'size':13,
			'color':'red'
		}
	}
});

//index.js
define(['jquery','module'],function($,module){
	console.log(module.config().size)
});
           

這裡要引起我們注意的地方就是依賴的'module'子產品,它是一個預先定義好的值,引入該值,在目前子產品下便可以調用module對象,從該對象中執行

config()

方法便可以生成改子產品的參數對象。

map

[略],暫時還未弄明白其具體使用方式,後續會繼續保持關注,如果你知曉其作用,麻煩你一定要與我聯系。

packages

RequireJS 會将完整項目的JavaScript代碼輕易的分割成苦幹個子產品(module),這樣,你将獲得一些具有互相依賴關系的JavaScript檔案。在開發環境中,這種方式可以讓我們的代碼更具有子產品化與易維護性。但是,在生産環境中将所有的JavaScript檔案分離,這是一個不好的做法。這會導緻很多次請求(requests),即使這些檔案都很小,也會浪費很多時間。是以我們可以通過合并這些腳本檔案壓縮檔案的大小,以減少請求的次數與資源的體積來達到節省加載時間的目的,是以這裡我們就要提到一個關于requireJS 延伸,那就是 r.js。

r.js 是一個獨立的項目,它作用在nodeJS環境中,可以實作對JS代碼的合并壓縮。

使用r.js 要具有以下幾個條件:

  1. r.js 源檔案
  2. bulid.js (即屬于r.js的配置檔案)
  3. nodeJS 環境

r.js 可以直接丢在項目的根目錄上,build.js 是 r.js 的配置檔案,由開發者自己建立,與r.js同目錄。其一般的目錄結構如下:

[project]
  /js
  /css
  /images
index.html
r.js
build.js
           

r.js 下載下傳

nodeJS環境,以及r.js都好辦,重要的就是掌握配置檔案的使用 -- build.js,下面我們就詳細的說說它。

({
    //(選填)app的頂級目錄。如果指定該參數,說明您的所有檔案都在這個目錄下面(包括baseUrl和dir都以這個為根目錄)。如果不指定,則以baseUrl參數為準
    appDir: './', 
    
     // 輸出目錄。如果不指定,預設會建立一個build目錄
    dir: 'pack',
    
     // 子產品所在預設相對目錄,如果appDir有指定,則baseUrl相對于appDir。
    baseUrl: 'js/',
    paths: {
        'index': 'index',
        'a': 'a',
        'b': 'b',
        'c': 'c'

    },
    
    //過濾規則,比對到的檔案将不會被輸出到輸出目錄去
    fileExclusionRegExp:   /^(r|build)\.js|.*\.scss$/, 
    
     /*
        JS 檔案優化方式,目前支援以下幾種:
        uglify: (預設) 使用 UglifyJS 來壓縮代碼
        closure: 使用 Google's Closure Compiler 的簡單優化模式
        closure.keepLines: 使用 closure,但保持換行
        none: 不壓縮代碼
    */
    optimize: 'none',
   
   /*
    允許優化CSS,參數值:
    “standard”: @import引入并删除注釋,删除空格和換行。删除換行在IE可能會出問題,取決于CSS的類型
    “standard.keepLines”: 和”standard”一樣但是會保持換行
    “none”: 跳過CSS優化
    “standard.keepComments”: 保持注釋,但是去掉換行(r.js 1.0.8+)
    “standard.keepComments.keepLines”: 保持注釋和換行(r.js 1.0.8+)
    “standard.keepWhitespace”: 和”standard”一樣但保持空格
    */
    optimizeCss:   '“standard”', 
    

    // 是否忽略 CSS 資源檔案中的 @import 指令
    cssImportIgnore: null,
    
    //參與壓縮的主子產品,預設情況下會将paths子產品中定義的子產品都壓縮合并到改子產品中,通過exclude 可以排除參與壓縮的子產品,其中子產品的位址都是相對于baseUrl的位址。
    modules: [{ 
        name: 'index',
        exclude: ['c']
    }],
    
    // 包裹子產品
    wrap: true,
    
    // 自定義包裹子產品,顧名思義就是使用特定内容去包裹modules指定的合并子產品内容,如此一來 define/require 就不再是全局變量,在 end 中可以暴露一些全局變量供整個函數使用
    wrap: {
         start: "(function() {",
         end: "}(window));"
     },

    removeCombined: false,
    
    //如果shim配置在requirejs運作過程中被使用的話,需要在這裡重複聲明,這樣才能将依賴子產品正确引入。
    shim: {} 
    
     // 載入requireJS 的配置檔案,進而使用其中的paths 以及 shim 屬性值。通過指定該屬性,可以省去我們在bulid.js中重複定義 paths 與 shim屬性。
    mainConfigFile:"js/app.js",
})

           

以上環節都準備好了之後,就可以在終端中允許打包壓縮指令:

node r.js -o build.js

當執行該指令後,

r.js

會将自身所在目錄的所有資源連同目錄重新拷貝一份到輸出目錄(dir)中。然後再輸出目錄進行最後的合并與壓縮操作。

timeout逾時問題

該問題一般是

waitSeconds

屬性值導緻,解決的方法有兩個,一個是将

waitSeconds

的值設定更長時間,比如17s,另一個就是将其值設定為0,讓其永不逾時。

循環依賴問題

何為循環依賴?

如果存在兩個子產品,moduleA 與 moduleB, 如果 moduleA 依賴 moduleB ,moduleB也依賴了moduleA,并且這中情況下,便是循環依賴。

循環依賴導緻的問題!

如果兩個子產品循環依賴,并且A中有調用B中的方法,而B中也有調用A中的方法,那麼此時,A調用B正常,但是B中調用A方法,則會傳回

undefined

異常。

如何解決循環依賴的問題?

require([],fn)

解決

此時在子產品B中,我們通過引入 require 依賴,然後再通過

require()

方法去載入子產品A,并在回調中去執行。

define(['require','jquery'],function(require,$){

	function bFunction(){
		alert('this is b module');
	}

	require(['moduleA'],function(m){
		m() // 執行傳遞過來方法
	});

	return bFunction;
});

           

這裡要引起我們注意的地方就是依賴的'module'子產品,它是一個預先定義好的值,引入該值,在目前子產品下便可以調用

require

方法。

exports

define(['exports','jquery'],function(exports,$){

	function bFunction(){
		exports.aFunction();
		alert('this is b module');
	}

	exports.bFunction = bFunction;
});
           

相同的這裡依賴的

module

子產品也是一個預先定義好的值,,引入該值,在目前子產品下便可以調用

exports

對象設定目前子產品的傳回值。

而通過

exports

所解決的循環依賴問題,有一個需要注意的地方,那就是方法的執行必須要放入到目前定義方法的回調中,因為我們不能确定 moduleA 與 moduleB的加載順序。

CDN回退

如果我們不确定一個子產品的加載正确,我們可以在

require.config()

方法中将子產品的位址替換為一個數組,數組的元素,便是同一子產品的多個位址。

requirejs.config({
    paths: {
        jquery: [
            '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',
            'lib/jquery'
        ]
    }
});
           

定義AMD插件

有時候我們自己編寫的一款插件,我們需要它能夠在任何環境中都能起作用,比如在引入requireJS的AMD環境下可以作為符合AMD規範的插件,進行子產品式加載調用,而在普通的浏覽器環境中也可以正常的執行。

想實作這一功能,其實很簡單,隻需要按照下例格式去編寫插件即可。

// say.js 基于JQ擴充的插件。

(function(win, factory) {
	if ('function' === typeof define && define.amd) {
		define(['jquery'], function($) {
			return new factory(win, $)
		});
	} else {
		factory(win, $);
	}
}(window, function(win, $) {

	var say = function(value) {
		alert(value);
	}

	if ('function' === typeof define && define.amd) {
		return say;
	} else if ($ && 'function' === typeof $) {
		$.say = function(v) {
			return new say(v);
		}
	} else {
		win.say = function(v) {
			return new say(v);
		}
	}

}));

// index.js
define(['say'],function(say){
    say('hellow requireJS');
})

           

關于require的預定義子產品

關于這個問題,我們上面也有說到,這裡就進行一次總結。

我們可以這樣了解,對于 requireJS 來說,除了我們自己使用

require.config()

定義的子產品,它内部也有自己預先定義好的子產品,比如:

require

,

exports

modules

,在使用時,我們無需在

require.config()

去中定義,而是可以直接在依賴中引入使用,比如:

//index.js
define(['jquery','config','require','exports','module'],function($,config,require,exports,module){
  $(function(){
      require.config(config); // 載入配置檔案
      exports.data = 'index Module Return Value' //定義子產品的傳回值。
      modules.config().color; // 接受在配置檔案中為該子產品配置的參數資料。
  })
});
           

關于R.js壓縮非本地檔案的問題###

r.js

中是無法合并壓縮遠端檔案的,它隻能操作本地檔案,是以這就帶來一個問題,當我們進行子產品的壓縮合并時,若某個子產品存在着對遠端子產品(檔案)的依賴時,使用

r.js

進行操作便會報錯,雖然可以将這個遠端檔案拷貝到本地來解決這一問題,但是如果像一些公用的資源例如JQ插件等,如果讓每個項目都在本地放入一個

common

資源包,這就脫離了我們的實際意義。

({
    paths:{
        jquery:'http://xxx.com/js/jquery.min'
    }
})
           

此時進行打包的時候在就會報錯。但是如果我們不在

paths

中去聲明

jquery

子產品,當打包的時候,

r.js

發現其它子產品有依賴

jquery

的,但是你又沒有在

build.js

中聲明,依然會報錯阻礙運作。

那麼有沒有一個好的辦法呢?比如雖然聲明了

jquery

子產品,但是值卻不是遠端的檔案,本地也不存在該檔案,更不會報錯。答案是有的,那就是對(不需要參與壓縮合并的)遠端的資源子產品設定值為 empty:`。

({
    paths:{
        jquery:'empty:'
    }
})
           

或者是在執行指令時,指定參數值為空:

node r.js -o build.js paths.jquery=empty:

關于R.js - shim功能的說明###

R.js 用于合并多個子產品(多個檔案),以及壓縮檔案中的JS代碼,也就是說在這個合并後的檔案中會包含多個

define

定義的子產品,而這個合并後的檔案也就是這個頁面的入口檔案,并且rquire的config配置也會在其中。

子產品的合并,對于R.js來言,它會以

build.js

paths

屬性定義的子產品為參考,然後到要合并的子產品隻能中去比對依賴,比對到了就合并到目前檔案中。如果參與合并的所有子產品有某些依賴順序上的調整,則可以通過

shim

屬性去調整合并時的前後順序。

//build.js
({
    'paths':{
        'a':'a',
        'b':'b'
    },
    'shim':{
        'a':{
            'deps':['b']
        }
    },
    wrapShim:true
})
           

此時合并到主檔案後,

b

子產品的内容就會在

a

子產品之前。

define('b',[],function(){}),
define('a',[],function(){})
           

最後強調一點,對于通過

exclude

屬性排除合并的子產品,使用

shim

并不會産生作用,因為它隻對合并在一個檔案中的子產品有效。

關于require加載CSS的問題###

requireJS不僅僅隻加載JS檔案,實際上它還可以加載CSS樣式檔案,但是這需要借助一個requireJS插件才能實作。

下載下傳位址:require-css.js

使用上,有兩種方式,一種在配置參數中進行聲明:

var require = {
    baseUrl:'js/',
    paths:{
        'index':'index',
        'a':'a'
    },
    shim:{
        'a':{
            deps:['css!../css/a.css']
        }
    },
    deps:['index']
};
           
//index.js
define(['a']); // 載入子產品不執行任何操作。
           

另一種是直接在子產品中進行依賴聲明

define(['css!../css/a.css']);
           

最後說下我個人對

css!../css/index.css

的了解吧,首先

!

是插件與插件參數的分割符号,是以"css"就是插件的子產品名,requireJS會先去檢查

css

這個子產品是否有在配置檔案中聲明,如果沒有則會預設在

baseUrl

指向的路徑下去載入,而分隔符右邊的 '../css/a.css' 就是插件要使用的參數,這裡便是要載入的css檔案位址。

http://requirejs.org/docs/api.html // 這是require的英文版官網,建議來這裡學習,中文版太多翻譯問題。

https://segmentfault.com/a/1190000002401665 //對require的配置參數講解的很詳細

如果覺得本文對您有幫助或者您心情好~可以支付寶(左)或微信(右)支援一下,就當給作者贊助杯咖啡錢了

requireJS 從概念到實戰

 ~~: