seed.js是基于AMD規(guī)范的,我的理解就是一幫子前端看到node.js的模塊化的好處之后,也想要享受這種好處,但是node因?yàn)槭呛蠖苏Z言,所有的資源運(yùn)行在本地機(jī)上面,可以通過系統(tǒng)調(diào)用直接調(diào)用,但是在瀏覽器端的js卻不能直接這么干,先不說沒有require,import,use,export之類的函數(shù),還有就是在瀏覽器沙箱里,請(qǐng)求的文件都是遠(yuǎn)端服務(wù)器上面的,要考慮各個(gè)模塊之間依賴有沒有加載完成,如果沒有加載完成直接運(yùn)行就會(huì)失效.
當(dāng)然,實(shí)現(xiàn)了把一大陀打散模塊化編程,在真正生產(chǎn)環(huán)境上,還要考慮效率的問題,資源可以合并到一個(gè)文件上面,這時(shí)候就需要用到打包工具了,常見的是grunt,gulp之類的,當(dāng)然,其實(shí)他們就是一個(gè)更加復(fù)雜的代碼解析工具,根據(jù)你的配置項(xiàng)進(jìn)行分析,正則匹配,把一些約定的地方進(jìn)行替換,然后可以進(jìn)行壓縮什么的
/*
* seed v1.1.4
* AMD module loader
*
* Copyright (c) 2013-2014 Yiguo Chan
* Released under the MIT Licenses
*
* Gighub : https://github.com/chenmnkken/seed.git
* Mail : chenmnkken@gmail.com
* Date : 2014-09-21
*/
(function( window, undefined ){
'use strict';
var seed = function(){
var rProtocol = /^(file\:.+\:\/|[\w\-]+\:\/\/)/,
rModId = /([^\/?]+?)(\.(?:js|css))?(\?.*)?$/,
rReadyState = /loaded|complete|undefined/,
moduleCache = {}, // 模塊加載時(shí)的隊(duì)列數(shù)據(jù)存儲(chǔ)對(duì)象
modifyCache = {}, // modify的臨時(shí)數(shù)據(jù)存儲(chǔ)對(duì)象
head = document.head ||
document.getElementsByTagName( 'head' )[0] ||
document.documentElement,
modClassName = 'seed_mod_' + ( +new Date() ) + ( Math.random() + '' ).slice( -8 ),
isScriptOnload = !!document.addEventListener,
// 模塊加載器的配置對(duì)象
moduleOptions = {
baseUrl : null,
charset : {}, // 模塊對(duì)應(yīng)的charset存儲(chǔ)對(duì)象
alias : {}
};
var easyModule = {
error : function( msg ){
throw new Error( msg );
},
// 用于合并模塊加載器配置的工具函數(shù)
merge : function( key, options ){
if( options ){
var name;
for( name in options ){
moduleOptions[ key ][ name ] = options[ name ];
}
}
},
// 初始化模塊加載器時(shí)獲取baseUrl(既是當(dāng)前js文件加載的url)
init : function(){
var i = 0,
script, scripts, initMod, initBaseUrl, url;
// firefox支持currentScript屬性
if( document.currentScript ){
script = document.currentScript;
}
else{
// 正常情況下,在頁面加載時(shí),當(dāng)前js文件的script標(biāo)簽始終是最后一個(gè)
scripts = document.getElementsByTagName( 'script' );
script = scripts[ scripts.length - 1 ];
}
initMod = script.getAttribute( 'data-main' );//主入口函數(shù),這樣只需加在tpl里面寫一行就夠了,自動(dòng)會(huì)加載的,或者類似sea.js一樣,直接下面再加幾行seajs.use()這樣
initBaseUrl = script.getAttribute( 'data-baseurl' );
url = script.hasAttribute ? script.src : script.getAttribute( 'src', 4 );
// 如果seed是通過script標(biāo)簽inline添加到頁面中其baseUrl就是當(dāng)前頁面的路徑
url = url || window.location.href;
moduleOptions.baseUrl = initBaseUrl ?
easyModule.mergePath( initBaseUrl, window.location.href ) :
url.slice( 0, url.lastIndexOf('/') + 1 );
// 初始化時(shí)加載data-main中的模塊
if( initMod ){
initMod = initMod.split( ',' );
seedExports.use( initMod );
}
scripts = script = null;
},
// 獲取當(dāng)前運(yùn)行腳本的文件的名稱
// 用于獲取匿名模塊的模塊名
getCurrentScript : function(){
var script, scripts, i, stack;
// 標(biāo)準(zhǔn)瀏覽器(IE10、Chrome、Opera、Safari、Firefox)通過強(qiáng)制捕獲錯(cuò)誤(e.stack)來確定為當(dāng)前運(yùn)行的腳本
// http://www.cnblogs.com/rubylouvre/archive/2013/01/23/2872618.html
try{
// 運(yùn)行一個(gè)不存在的方法強(qiáng)制制造錯(cuò)誤
easyModule.makeerror();
}
// 捕獲錯(cuò)誤
// safari的錯(cuò)誤對(duì)象只有l(wèi)ine,sourceId,sourceURL
catch( e ){
stack = e.stack;
}
if( stack ){
// 取得最后一行,最后一個(gè)空格或@之后的部分
stack = stack.split( /[@ ]/g ).pop();
// 去掉換行符
stack = stack[0] === '(' ? stack.slice( 1, -1 ) : stack.replace( /\s/, '' );
//去掉行號(hào)與或許存在的出錯(cuò)字符起始位置
return stack.replace( /(:\d+)?:\d+$/i, '' ).match( rModId )[1];
}
// IE6-8通過遍歷script標(biāo)簽,判斷其readyState為interactive來確定為當(dāng)前運(yùn)行的腳本
scripts = head.getElementsByTagName( 'script' );
i = scripts.length - 1;
for( ; i >= 0; i-- ){
script = scripts[i];
if( script.className === modClassName && script.readyState === 'interactive' ){
break;
}
}
return script.src.match( rModId )[1];
},
// 將模塊標(biāo)識(shí)(相對(duì)路徑)和基礎(chǔ)路徑合并成新的真正的模塊路徑(不含模塊的文件名)
mergePath : function( id, url ){
var isRootDir = id.charAt(0) === '/',
isHttp = url.slice( 0, 4 ) === 'http',
domain = '',
i = 0,
protocol, isHttp, urlDir, idDir, dirPath, len, dir;
protocol = url.match( rProtocol )[1];
url = url.slice( protocol.length );
// HTTP協(xié)議的路徑含有域名
if( isHttp ){
domain = url.slice( 0, url.indexOf('/') + 1 );
url = isRootDir ? '' : url.slice( domain.length );
}
// 組裝基礎(chǔ)路徑的目錄數(shù)組
urlDir = url.split( '/' );
urlDir.pop();
// 組裝模塊標(biāo)識(shí)的目錄數(shù)組
idDir = id.split( '/' );
idDir.pop();
if( isRootDir ){
idDir.shift();
}
len = idDir.length;
for( ; i < len; i++ ){
dir = idDir[i];
// 模塊標(biāo)識(shí)的目錄數(shù)組中含有../則基礎(chǔ)路徑的目錄數(shù)組刪除最后一個(gè)目錄
// 否則直接將模塊標(biāo)識(shí)的目錄數(shù)組的元素添加到基礎(chǔ)路徑的目錄數(shù)組中
if( dir === '..' ){
urlDir.pop();
}
else if( dir !== '.' ){
urlDir.push( dir );
}
}
// 基礎(chǔ)路徑的目錄數(shù)組轉(zhuǎn)換成目錄字符串
dirPath = urlDir.join( '/' );
// 無目錄的情況不用加斜杠
dirPath = dirPath === '' ? '' : dirPath + '/';
return protocol + domain + dirPath;
},
/*
* 解析模塊標(biāo)識(shí),返回模塊名和模塊路徑
* @parmm { String } 模塊標(biāo)識(shí)
* @param { String } 基礎(chǔ)路徑baseUrl
* @return { Array } [ 模塊名, 模塊路徑 ]
* =====================================================================
* 解析規(guī)則:
* baseUrl = http://easyjs.org/js/
* http://example.com/test.js => [ test, http://example.com/test.js ]
* style.css => [ test, http://easyjs.org/js/style.css ]
* ajax/xhr => [ xhr, http://easyjs.org/js/ajax/xhr.js ]
* ../core => [ core, http://easyjs.org/core.js ]
* test.js => [ test, http://easyjs.org/js/test.js ]
* test => [ test, http://easyjs.org/js/test.js ]
* test.js?v20121202 => [ test, http://easyjs.org/js/test.js?v20121202 ]
* =====================================================================
*/
parseModId : function( id, url ){
var isAbsoluteId = rProtocol.test( id ),
result = id.match( rModId ),
modName = result[1],
suffix = result[2] || '.js',
search = result[3] || '',
baseUrl, modUrl;
// 模塊標(biāo)識(shí)為絕對(duì)路徑時(shí),標(biāo)識(shí)就是基礎(chǔ)路徑
if( isAbsoluteId ){
url = id;
id = '';
}
baseUrl = easyModule.mergePath( id, url );
modUrl = baseUrl + modName + suffix + search;
return [ modName, modUrl ];
},
/*
* 將依賴模塊列表的外部接口(exports)合并成arguments
* @param { Array } 依賴模塊列表
* @return { Array } 返回參數(shù)數(shù)組
*/
getExports : function( deps ){
if( deps ){
var len = deps.length,
module = seedExports.module,
arr = [],
i = 0,
j = 0,
dep;
for( ; i < len; i++ ){
arr[ j++ ] = module[ deps[i] ].exports;
}
return arr;
}
return [];
},
/*
* 測(cè)試該模塊的依賴模塊是否都已加載并執(zhí)行完
* @param { Object } 模塊對(duì)象
* @return { Boolean } 依賴模塊是否都加載并執(zhí)行完
*/
isLoaded : function( mod ){
var deps = mod.deps,
len = deps.length,
module = seedExports.module,
i = 0, depMod;
for( ; i < len; i++ ){
depMod = module[ deps[i] ];
if( depMod.status !== 4 ){
return false;
}
}
return true;
},
factoryHandle : function( name, mod, factory, data ){
// 模塊解析完畢,所有的依賴模塊也都加載完,但還未輸出exports
mod.status = 3;
var args = easyModule.getExports( mod.deps ),
exports = typeof factory === 'function' ? factory.apply( null, args ) : factory;
if( exports !== undefined ){
// 如果有綁定modify方法,將在正式返回exports前進(jìn)行修改
if( modifyCache[name] ){
exports = modifyCache[ name ]( exports );
// 修改后即刪除modify方法
delete modifyCache[ name ];
}
// 存儲(chǔ)exports到當(dāng)前模塊的緩存中
mod.exports = exports;
}
// 當(dāng)前模塊加載并執(zhí)行完畢,exports已可用
mod.status = 4;
if( data ){
data.length--;
}
},
/*
* 觸發(fā)被依賴模塊的factory
* @param { Object } 模塊的緩存對(duì)象
*/
fireFactory : function( useKey ){
var data = moduleCache[ useKey ],
factorys = data.factorys,
result = factorys[0],
args, exports, name, toDepMod;
if( !result ){
return;
}
name = result.name;
toDepMod = seedExports.module[ name ];
if( easyModule.isLoaded(toDepMod) ){
factorys.shift();
easyModule.factoryHandle( name, toDepMod, result.factory, data );
if( factorys.length ){
easyModule.fireFactory( useKey );
}
}
},
/*
* 模塊加載完觸發(fā)的回調(diào)函數(shù)
* @param{ Object } 模塊對(duì)象
*/
complete : function( mod ){
var module = seedExports.module,
useKey = mod.useKey,
keyLen = useKey.length,
k = 0,
namesCache, args, cacheLen, cacheMod, name, data, key, i, j;
for( ; k < useKey.length; k++ ){
key = useKey[k];
data = moduleCache[ key ];
useKey.splice( k--, 1 );
if( !data ){
continue;
}
// 隊(duì)列沒加載完將繼續(xù)加載
if( data.urls.length ){
easyModule.load( key );
}
else if( !data.length ){
namesCache = data.namesCache;
cacheLen = namesCache.length;
args = [];
i = 0;
j = 0;
// 合并模塊的exports為arguments
for( ; i < cacheLen; i++ ){
name = namesCache[i];
cacheMod = module[ name ];
if( cacheMod.status !== 4 ){
return;
}
args[ j++ ] = cacheMod.exports;
}
// 執(zhí)行use的回調(diào)
if( data.callback ){
data.callback.apply( null, args );
}
// 刪除隊(duì)列數(shù)據(jù)
delete moduleCache[ key ];
}
}
},
/*
* 創(chuàng)建script/link元素來動(dòng)態(tài)加載JS/CSS資源
* @param{ String } 模塊的URL
* @param{ String } 模塊名
* @param{ String } 用來訪問存儲(chǔ)在moduleCache中的數(shù)據(jù)的屬性名
* @return { HTMLElement } 用于添加到head中來進(jìn)行模塊加載的元素
*/
create : function( url, name, useKey ){
var charset = moduleOptions.charset[ name ],
mod = seedExports.module[ name ],
script, link;
mod.status = 1;//1,2,3,4用于存狀態(tài)標(biāo)識(shí)符
// CSS模塊的處理
if( ~url.indexOf('.css') ){
link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.href = url;
if( charset ){
link.charset = charset;
}
link.onload = link.onerror = function(){
link = link.onload = link.onerror = null;
mod.status = 4;
moduleCache[ useKey ].length--;
easyModule.fireFactory( useKey );
easyModule.complete( mod );
};
return link;
}
// JS模塊的處理
script = document.createElement( 'script' );
script.className = modClassName;
script.async = true;
if( charset ){
script.charset = charset;
}
if( isScriptOnload ){
script.onerror = function(){
script.onerror = script.onload = null;
head.removeChild( script );
script = null;
easyModule.error( '[' + name + '] module failed to load, the url is ' + url + '.' );
};
}
script[ isScriptOnload ? 'onload' : 'onreadystatechange' ] = function(){
if( isScriptOnload || rReadyState.test(script.readyState) ){
script[ isScriptOnload ? 'onload' : 'onreadystatechange' ] = null;
head.removeChild( script );
script = null;
// 加載成功
easyModule.complete( mod );
}
};
script.src = url;
return script;
},
/*
* 加載模塊
* @param { String } 用來訪問存儲(chǔ)在moduleCache中的數(shù)據(jù)的屬性名
*/
load : function( useKey ){
var data = moduleCache[ useKey ],
module = seedExports.module,
names = data.names.shift(),
urls = data.urls.shift(),
len = urls.length,
i = 0,
mod, script;
for( ; i < len; i++ ){
mod = module[ names[i] ];
if( mod.useKey === undefined ){
mod.useKey = [];
}
mod.useKey.push( useKey );
if( module[names[i]].status === undefined ){
script = easyModule.create( urls[i], names[i], useKey );
head.insertBefore( script, head.firstChild );
}
else{
data.length--;
}
}
}
};
var seedExports = {
version : '1.1.2',
module : {},
use : function( ids, fn ){
ids = typeof ids === 'string' ? [ ids ] : ids;
var alias = moduleOptions.alias,
module = seedExports.module,
len = ids.length,
isLoaded = false,
namesCache = [],
modNames = [],
modUrls = [],
j = 0,
mod, modName, result, useKey, args, name, i, id;
for( i = 0; i < len; i++ ){
id = ids[i];
// 獲取解析后的模塊名和url
result = easyModule.parseModId( alias[id] || id, moduleOptions.baseUrl );
modName = alias[ id ] ? id : result[0];
mod = module[ modName ];
if( !mod ){
mod = module[ modName ] = {};
isLoaded = false;
}
else if( mod.status === 4 ){
isLoaded = true;
}
// 將模塊名和模塊路徑添加到隊(duì)列中
modNames[ modNames.length++ ] = modName;
modUrls[ modUrls.length++ ] = mod.url = result[1];
}
// 生成隊(duì)列的隨機(jī)屬性名
useKey = modNames.join( '_' ) + '_' + ( +new Date() ) + ( Math.random() + '' ).slice( -8 );
// 復(fù)制模塊名,在輸出exports時(shí)會(huì)用到
namesCache = namesCache.concat( modNames );
// 在模塊都合并的情況下直接執(zhí)行callback
if( isLoaded ){
len = namesCache.length;
args = [];
// 合并模塊的exports為arguments
for( i = 0; i < len; i++ ){
name = namesCache[i];
mod = module[ name ];
if( mod.status !== 4 ){
easyModule.error( '[' + name + '] module failed to use.' );
}
args[ j++ ] = mod.exports;
}
// 執(zhí)行use的回調(diào)
if( fn ){
fn.apply( null, args );
}
return;
}
// 添加隊(duì)列
moduleCache[ useKey ] = {
length : namesCache.length,
namesCache : namesCache,
names : [ modNames ],
urls : [ modUrls ],
callback : fn,
factorys : [],
deps : {}
};
// 開始加載
easyModule.load( useKey );
},
/*
* 給模塊添加modify方法以便在正式返回exports前進(jìn)行修改
* @param { String } 模塊名
* @param { Function } 修改exports的函數(shù),該函數(shù)至少要有一個(gè)返回值
*/
modify : function( name, fn ){
modifyCache[ name ] = fn;
},
/*
* 修改模塊加載器的配置
* @param { Object }
*/
config : function( options ){
var baseUrl, isHttp;
if( options.baseUrl ){
baseUrl = options.baseUrl;
isHttp = baseUrl.slice( 0, 4 ) === 'http';
if( isHttp ){
moduleOptions.baseUrl = baseUrl;
}
// 相對(duì)路徑的baseUlr是基于HTML頁面所在的路徑(無論是http地址還是file地址)
else{
moduleOptions.baseUrl = easyModule.mergePath( baseUrl, window.location.href );
}
}
easyModule.merge( 'charset', options.charset );
easyModule.merge( 'alias', options.alias );
}
};
/*
* 定義模塊的全局方法(AMD規(guī)范)
* @param { String } 模塊名
* @param { Array } 依賴模塊列表,單個(gè)可以用字符串形式傳參,多個(gè)用數(shù)組形式傳參
* @param { Function } 模塊的內(nèi)容
* factory的參數(shù)對(duì)應(yīng)依賴模塊的外部接口(exports)
*/
window.define = function( name, deps, factory ){
//類似于jquery的超級(jí)方法$
if( typeof name !== 'string' ){
if( typeof name === 'function' ){
factory = name;
}
else{
factory = deps;
deps = name;
}
name = easyModule.getCurrentScript();
}
else if( deps !== undefined && factory === undefined ){
factory = deps;
deps = null;
}
var alias = moduleOptions.alias,//moduleOptions是一個(gè)配置項(xiàng)的字面量
module = seedExports.module,
mod = module[ name ],
isRepeat = false,
isLoaded = true,
names = [],
urls = [],
insertIndex = 0,
pullIndex = 0,
useKey, data, modUrl, factorys, baseUrl, depMod, depName, result, exports, args, depsData, repeatDepsData, i, repeatName, dep;
// 在模塊都合并的情況下直接執(zhí)行factory
if( !mod ){
mod = module[ name ] = {};
if( deps ){
mod.deps = deps;
}
easyModule.factoryHandle( name, mod, factory );
return;
}
useKey = mod.useKey[0];
data = moduleCache[ useKey ];
modUrl = mod.url;
// 開始解析模塊內(nèi)容
mod.status = 2;
mod.deps = [];
// 如果有依賴模塊,先加載依賴模塊
if( deps && deps.length ){
// 依賴模塊的baseUrl是當(dāng)前模塊的baseUrl
baseUrl = modUrl.slice( 0, modUrl.lastIndexOf('/') + 1 );
factorys = data.factorys;
depsData = data.deps[ name ] = {};
// 遍歷依賴模塊列表,如果該依賴模塊沒加載過,
// 則將該依賴模塊名和模塊路徑添加到當(dāng)前模塊加載隊(duì)列的數(shù)據(jù)去進(jìn)行加載
for( i = 0; i < deps.length; i++ ){
dep = deps[i];
result = easyModule.parseModId( alias[dep] || dep, baseUrl );
depName = alias[ dep ] ? dep : result[0];
depMod = module[ depName ];
mod.deps.push( depName );
depsData[ depName ] = true;
if( depMod ){
if( depMod.status !== 4 ){
// 獲取第一個(gè)重復(fù)依賴的模塊名,會(huì)在稍后進(jìn)行factorys的順序調(diào)整
if( !isRepeat ){
isRepeat = true;
repeatName = depName;
}
isLoaded = false;
}
deps.splice( i--, 1 );
continue;
}
else{
depMod = module[ depName ] = {};
}
isLoaded = false;
data.length++;
names[ names.length++ ] = depName;
urls[ urls.length++ ] = depMod.url = result[1];
}
// 只要當(dāng)前模塊有一個(gè)依賴模塊沒加載完就將當(dāng)前模塊的factory添加到factorys中
if( !isLoaded ){
factorys.unshift({
name : name,
factory : factory
});
// 有重復(fù)依賴時(shí)將調(diào)整factorys的順序
if( repeatName ){
repeatDepsData = data.deps[ repeatName ];
for( i = factorys.length - 1; i >= 0; i-- ){
result = factorys[i].name;
if( result === repeatName ){
pullIndex = i;
if( !repeatDepsData ){
break;
}
}
if( repeatDepsData && repeatDepsData[result] ){
insertIndex = i;
break;
}
}
// 將重復(fù)模塊的factory插入到該模塊最后一個(gè)依賴模塊的factory后
factorys.splice( insertIndex + 1, 0, factorys.splice(pullIndex, 1)[0] );
// 將當(dāng)前模塊的factory插入到重復(fù)模塊的factory后
factorys.splice( insertIndex + 1, 0, factorys.shift() );
}
}
if( names.length ){
data.names.unshift( names );
data.urls.unshift( urls );
}
}
// 該模塊無依賴模塊就直接執(zhí)行其factory
if( isLoaded ){
easyModule.factoryHandle( name, mod, factory, data );
}
easyModule.fireFactory( useKey );
// 無依賴列表將刪除依賴列表的數(shù)組
if( !mod.deps.length ){
delete mod.deps;
}
};//把define方法 暴露給window
/***
* jQuery里這段代碼對(duì)應(yīng)
* if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
* define( "jquery", [], function () { return jQuery; } );
* }
* ***/
window.define.amd = {
jQuery : true
};
// 初始化模塊加載器
easyModule.init();
return seedExports;
};
window.seed = seed();//定義了一個(gè)構(gòu)造函數(shù)seed(),然后暴露給全局global的屬性實(shí)例化
})( window );
下面是打包工具的代碼:這幾天慢慢的分析
/*
* seedCombo for seed v1.1.0
*/
var rExistId = /define\(\s*['"][^\[\('"\{]+['"]\s*,?/,
rProtocol = /^(http(?:s)?\:\/\/|file\:.+\:\/)/,
rModId = /([^\\\/?]+?)(\.(?:js|css))?(\?.*)?$/,
rRightEnd = /,?\s*(function\s*\(.*|\{.*)/,
rPullDeps = /((?:define|seed\.use)\(.*)/g,
rDeps = /((?:define|seed\.use)\([^\[\(\{]*\[)([^\]]+)/,
rDefine = /define\(/,
fs = require( 'fs' ),
path = require( 'path' ),
depsCache = {},
seed = {
use : function( ids ){
return typeof ids === 'string' ? [ ids ] : ids;
}
},
define = function( name, deps ){
if( Array.isArray(name) ){
deps = name;
}
return deps;
},
// 分析模塊的依賴,將依賴模塊的模塊標(biāo)識(shí)組成一個(gè)數(shù)組以便合并
parseDeps = function( key, mods, encoding ){
var cache = depsCache[ key ],
deps = [];
mods.forEach(function( modUrl ){
var baseUrl = path.resolve( modUrl, '..' ),
content, literals;
if( !~modUrl.indexOf('.js') ){
modUrl += '.js'
}
// 讀取文件
try{
content = fs.readFileSync( modUrl, encoding );
}
catch( error ){
console.log( 'Read file ' + error );
return;
}
// 將define(), use()用正則提取出來
literals = content.match( rPullDeps );
literals.forEach(function( literal ){
var arr;
// define('hello', ['hello1'], function(){ => define('hello', ['hello1'])
// use('hello', function(){ => use('hello')
literal = literal.replace(rRightEnd, ')');
// 然后用eval去執(zhí)行處理過的define和use獲取到依賴模塊的標(biāo)識(shí)
arr = eval( literal );
if( arr && arr.length ){
// 為依賴模塊解析真實(shí)的模塊路徑
arr.forEach(function( item, i ){
arr[i] = path.resolve( baseUrl, item );
});
deps = deps.concat( arr );
}
});
});
if( deps.length ){
cache.ids = deps.concat( cache.ids );
// 遞歸調(diào)用直到所有的依賴模塊都添加完
parseDeps( key, deps, encoding );
}
},
formatDeps = function( _, define, deps ){
var arr = deps.split( ',' ),
len = arr.length,
i = 0,
item, index;
for( ; i < len; i++ ){
item = arr[i];
item = item.replace( /['"]/g, '' ).trim();
index = item.lastIndexOf( '/' );
arr[i] = ~index ? item.slice( index + 1 ) : item;
}
return define + "'" + arr.join("','") + "'";
},
// 合并內(nèi)容
comboContent = function( key, baseUrl, encoding, format ){
var cache = depsCache[ key ],
unique = cache.unique,
ids = cache.ids;
ids.forEach(function( id ){
var modName = id.match( rModId )[1],
modUrl = path.resolve( __dirname, id ),
content;
if( !~modUrl.indexOf('.js') ){
modUrl += '.js'
}
content = fs.readFileSync( modUrl, encoding );
// 非use()的情況下防止重復(fù)合并
if( !~content.indexOf('use(') ){
if( unique[modUrl] ){
return;
}
unique[ modUrl ] = true;
}
// utf-8 編碼格式的文件可能會(huì)有 BOM 頭,需要去掉
if( encoding === 'UTF-8' && content.charCodeAt(0) === 0xFEFF ){
content = content.slice( 1 );
}
// 格式化
if( typeof format === 'function' ){
content = format( content );
}
// 為匿名模塊添加模塊名
if( !rExistId.test(content) ){
content = content.replace( rDefine, "define('" + modName + "'," );
}
// 格式化依賴模塊列表 ['../hello5'] => ['hello5']
content = content.replace( rDeps, formatDeps );
// 合并
cache.contents += content + '\n';
console.log( 'Combo the [' + modName + '] success.' );
});
},
// 寫入文件
writeFile = function( key, mod, uglifyUrl ){
var output = mod.output,
contents = depsCache[ key ].contents,
uglify, jsp, pro, ast;
// 壓縮文件
if( uglifyUrl ){
uglify = require( uglifyUrl );
jsp = uglify.parser;
pro = uglify.uglify;
ast = jsp.parse( contents );
ast = pro.ast_mangle( ast );
ast = pro.ast_squeeze( ast );
contents = pro.gen_code( ast );
}
// 合并好文本內(nèi)容后的回調(diào)
if( typeof complete === 'function' ){
contents = complete( contents );
}
// 寫入文件
try{
fs.writeFileSync( output, contents, mod.encoding );
}
catch( error ){
console.log( 'Output file ' + error );
return;
}
console.log( '\n' );
console.log( '============================================================' );
console.log( 'Output the [' + output + '] success.' );
console.log( '============================================================' );
console.log( '\n' );
delete depsCache[ key ];
},
seedCombo = function( options ){
var modules = options.modules,
baseUrl = path.resolve();
modules.forEach(function( mod ){
var encoding = mod.encoding = ( mod.encoding || 'UTF-8' ).toUpperCase(),
randomKey = ( +new Date() + '' ) + ( Math.random() + '' ).slice( -8 );
depsCache[ randomKey ] = {
ids : [],
unique : {},
contents : ''
};
mod.input.forEach(function( id ){
var modUrl = path.resolve( baseUrl, id );
depsCache[ randomKey ].ids.push( modUrl );
parseDeps( randomKey, [modUrl], encoding );
});
comboContent( randomKey, baseUrl, encoding, mod.format );
writeFile( randomKey, mod, options.uglifyUrl );
});
};
exports.seedCombo = seedCombo;