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等待所有的依賴加載完畢,計算出模塊定義函數正確調用順序,然后依次調用它們。

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

require 實戰

下面我將化整為零的去講解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

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

rquire 壓縮

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.jspaths 屬性定義的模塊為參考,然后到要合並的模塊只能中去匹配依賴,匹配到了就合並到當前文件中。如果參與合並的所有模塊有某些依賴順序上的調整,則可以通過 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的配置參數講解的很詳細


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM