/** | |
* ESL (Enterprise Standard Loader) | |
* Copyright 2013 Baidu Inc. All rights reserved. | |
* | |
* @file Browser端标准加载器,符合AMD规范 | |
* @author errorrik(errorrik@gmail.com) | |
* Firede(firede@firede.us) | |
*/ | |
var define; | |
var require; | |
(function ( global ) { | |
// "mod"开头的变量或函数为内部模块管理函数 | |
// 为提高压缩率,不使用function或object包装 | |
/** | |
* 模块容器 | |
* | |
* @inner | |
* @type {Object} | |
*/ | |
var modModules = {}; | |
var MODULE_STATE_PRE_DEFINED = 1; | |
var MODULE_STATE_PRE_ANALYZED = 2; | |
var MODULE_STATE_ANALYZED = 3; | |
var MODULE_STATE_READY = 4; | |
var MODULE_STATE_DEFINED = 5; | |
/** | |
* 全局require函数 | |
* | |
* @inner | |
* @type {Function} | |
*/ | |
var actualGlobalRequire = createLocalRequire( '' ); | |
/** | |
* 超时提醒定时器 | |
* | |
* @inner | |
* @type {number} | |
*/ | |
var waitTimeout; | |
/** | |
* 加载模块 | |
* | |
* @param {string|Array} requireId 模块id或模块id数组, | |
* @param {Function=} callback 加载完成的回调函数 | |
* @return {*} | |
*/ | |
function require( requireId, callback ) { | |
assertNotContainRelativeId( requireId ); | |
// 超时提醒 | |
var timeout = requireConf.waitSeconds; | |
if ( isArray( requireId ) && timeout ) { | |
if ( waitTimeout ) { | |
clearTimeout( waitTimeout ); | |
} | |
waitTimeout = setTimeout( waitTimeoutNotice, timeout * 1000 ); | |
} | |
return actualGlobalRequire( requireId, callback ); | |
} | |
/** | |
* 将模块标识转换成相对的url | |
* | |
* @param {string} id 模块标识 | |
* @return {string} | |
*/ | |
require.toUrl = toUrl; | |
/** | |
* 超时提醒函数 | |
* | |
* @inner | |
*/ | |
function waitTimeoutNotice() { | |
var hangModules = []; | |
var missModules = []; | |
var missModulesMap = {}; | |
var hasError; | |
for ( var id in modModules ) { | |
if ( !modIsDefined( id ) ) { | |
hangModules.push( id ); | |
hasError = 1; | |
} | |
each( | |
modModules[ id ].realDeps || [], | |
function ( depId ) { | |
if ( !modModules[ depId ] && !missModulesMap[ depId ] ) { | |
hasError = 1; | |
missModules.push( depId ); | |
missModulesMap[ depId ] = 1; | |
} | |
} | |
); | |
} | |
if ( hasError ) { | |
throw new Error( '[MODULE_TIMEOUT]Hang( ' | |
+ ( hangModules.join( ', ' ) || 'none' ) | |
+ ' ) Miss( ' | |
+ ( missModules.join( ', ' ) || 'none' ) | |
+ ' )' | |
); | |
} | |
} | |
/** | |
* 尝试完成模块定义的定时器 | |
* | |
* @inner | |
* @type {number} | |
*/ | |
var tryDefineTimeout; | |
/** | |
* 定义模块 | |
* | |
* @param {string=} id 模块标识 | |
* @param {Array=} dependencies 依赖模块列表 | |
* @param {Function=} factory 创建模块的工厂方法 | |
*/ | |
function define() { | |
var argsLen = arguments.length; | |
if ( !argsLen ) { | |
return; | |
} | |
var id; | |
var dependencies; | |
var factory = arguments[ --argsLen ]; | |
while ( argsLen-- ) { | |
var arg = arguments[ argsLen ]; | |
if ( isString( arg ) ) { | |
id = arg; | |
} | |
else if ( isArray( arg ) ) { | |
dependencies = arg; | |
} | |
} | |
// 出现window不是疏忽 | |
// esl设计是做为browser端的loader | |
// 闭包的global更多意义在于: | |
// define和require方法可以被挂到用户自定义对象中 | |
var opera = window.opera; | |
// IE下通过current script的data-require-id获取当前id | |
if ( | |
!id | |
&& document.attachEvent | |
&& (!(opera && opera.toString() === '[object Opera]')) | |
) { | |
var currentScript = getCurrentScript(); | |
id = currentScript && currentScript.getAttribute('data-require-id'); | |
} | |
// 处理依赖声明 | |
// 默认为['require', 'exports', 'module'] | |
dependencies = dependencies || ['require', 'exports', 'module']; | |
if ( id ) { | |
modPreDefine( id, dependencies, factory ); | |
// 在不远的未来尝试完成define | |
// define可能是在页面中某个地方调用,不一定是在独立的文件被require装载 | |
if ( tryDefineTimeout ) { | |
clearTimeout( tryDefineTimeout ); | |
} | |
tryDefineTimeout = setTimeout( modPreAnalyse, 10 ); | |
} | |
else { | |
// 纪录到共享变量中,在load或readystatechange中处理 | |
wait4PreDefines.push( { | |
deps : dependencies, | |
factory : factory | |
} ); | |
} | |
} | |
define.amd = {}; | |
/** | |
* 获取相应状态的模块列表 | |
* | |
* @inner | |
* @param {number} state 状态码 | |
* @return {Array} | |
*/ | |
function modGetByState( state ) { | |
var modules = []; | |
for ( var key in modModules ) { | |
var module = modModules[ key ]; | |
if ( module.state == state ) { | |
modules.push( module ); | |
} | |
} | |
return modules; | |
} | |
/** | |
* 模块配置获取函数 | |
* | |
* @inner | |
* @return {Object} 模块配置对象 | |
*/ | |
function moduleConfigGetter() { | |
var conf = requireConf.config[ this.id ]; | |
if ( conf && typeof conf === 'object' ) { | |
return conf; | |
} | |
return {}; | |
} | |
/** | |
* 预定义模块 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @param {Array.<string>} dependencies 显式声明的依赖模块列表 | |
* @param {*} factory 模块定义函数或模块对象 | |
*/ | |
function modPreDefine( id, dependencies, factory ) { | |
if ( modExists( id ) ) { | |
return; | |
} | |
var module = { | |
id : id, | |
deps : dependencies, | |
factory : factory, | |
exports : {}, | |
config : moduleConfigGetter, | |
state : MODULE_STATE_PRE_DEFINED, | |
hardDeps : {} | |
}; | |
// 将模块预存入defining集合中 | |
modModules[ id ] = module; | |
} | |
/** | |
* 预分析模块 | |
* | |
* 首先,完成对factory中声明依赖的分析提取 | |
* 然后,尝试加载"资源加载所需模块" | |
* | |
* 需要先加载模块的原因是:如果模块不存在,无法进行resourceId normalize化 | |
* modAnalyse完成后续的依赖分析处理,并进行依赖模块的加载 | |
* | |
* @inner | |
* @param {Object} modules 模块对象 | |
*/ | |
function modPreAnalyse() { | |
var pluginModuleIds = []; | |
var pluginModuleIdsMap = {}; | |
var modules = modGetByState( MODULE_STATE_PRE_DEFINED ); | |
each( | |
modules, | |
function ( module ) { | |
// 处理实际需要加载的依赖 | |
var realDepends = module.deps.slice( 0 ); | |
module.realDeps = realDepends; | |
// 分析function body中的require | |
// 如果包含显式依赖声明,为性能考虑,可以不分析factoryBody | |
// AMD规范的说明是`SHOULD NOT`,所以这里还是分析了 | |
var factory = module.factory; | |
var requireRule = /require\(\s*(['"'])([^'"]+)\1\s*\)/g; | |
var commentRule = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg; | |
if ( isFunction( factory ) ) { | |
factory.toString() | |
.replace( commentRule, '' ) | |
.replace( requireRule, function ( $0, $1, $2 ) { | |
realDepends.push( $2 ); | |
}); | |
} | |
// 分析resource加载的plugin module id | |
each( | |
realDepends, | |
function ( dependId ) { | |
var idInfo = parseId( dependId ); | |
if ( idInfo.resource ) { | |
var plugId = normalize( idInfo.module, module.id ); | |
if ( !pluginModuleIdsMap[ plugId ] ) { | |
pluginModuleIds.push( plugId ); | |
pluginModuleIdsMap[ plugId ] = 1; | |
} | |
} | |
} | |
); | |
module.state = MODULE_STATE_PRE_ANALYZED; | |
} | |
); | |
nativeRequire( pluginModuleIds, function () { | |
modAnalyse( modules ); | |
} ); | |
} | |
/** | |
* 分析模块 | |
* 对所有依赖id进行normalize化,完成分析,并尝试加载其依赖的模块 | |
* | |
* @inner | |
* @param {Array} modules 模块对象列表 | |
*/ | |
function modAnalyse( modules ) { | |
var requireModules = []; | |
each( | |
modules, | |
function ( module ) { | |
if ( module.state !== MODULE_STATE_PRE_ANALYZED ) { | |
return; | |
} | |
var id = module.id; | |
// 对参数中声明的依赖进行normalize | |
var depends = module.deps; | |
var hardDepends = module.hardDeps; | |
var hardDependsCount = isFunction( module.factory ) | |
? module.factory.length | |
: 0; | |
each( | |
depends, | |
function ( dependId, index ) { | |
dependId = normalize( dependId, id ); | |
depends[ index ] = dependId; | |
if ( index < hardDependsCount ) { | |
hardDepends[ dependId ] = 1; | |
} | |
} | |
); | |
// 依赖模块id normalize化,并去除必要的依赖。去除的依赖模块有: | |
// 1. 内部模块:require/exports/module | |
// 2. 重复模块:dependencies参数和内部require可能重复 | |
// 3. 空模块:dependencies中使用者可能写空 | |
var realDepends = module.realDeps; | |
var len = realDepends.length; | |
var existsDepend = {}; | |
while ( len-- ) { | |
// 此处和上部分循环存在重复normalize,因为deps和realDeps是重复的 | |
// 为保持逻辑分界清晰,就不做优化了先 | |
var dependId = normalize( realDepends[ len ], id ); | |
if ( !dependId | |
|| dependId in existsDepend | |
|| dependId in BUILDIN_MODULE | |
) { | |
realDepends.splice( len, 1 ); | |
} | |
else { | |
existsDepend[ dependId ] = 1; | |
realDepends[ len ] = dependId; | |
// 将实际依赖压入加载序列中,后续统一进行require | |
requireModules.push( dependId ); | |
} | |
} | |
module.realDepsIndex = existsDepend; | |
module.state = MODULE_STATE_ANALYZED; | |
modWaitDependenciesLoaded( module ); | |
modInvokeFactoryDependOn( id ); | |
} | |
); | |
nativeRequire( requireModules ); | |
} | |
/** | |
* 等待模块依赖加载完成 | |
* 加载完成后尝试调用factory完成模块定义 | |
* | |
* @inner | |
* @param {Object} module 模块对象 | |
*/ | |
function modWaitDependenciesLoaded( module ) { | |
var id = module.id; | |
module.invokeFactory = invokeFactory; | |
invokeFactory(); | |
// 用于避免死依赖链的死循环尝试 | |
var checkingLevel = 0; | |
/** | |
* 判断依赖加载完成 | |
* | |
* @inner | |
* @return {boolean} | |
*/ | |
function checkInvokeReadyState() { | |
checkingLevel++; | |
var isReady = 1; | |
var tryDeps = []; | |
each( | |
module.realDeps, | |
function ( depId ) { | |
if ( !modIsAnalyzed( depId ) ) { | |
isReady = 0; | |
} | |
else if ( !modIsDefined( depId ) ) { | |
switch ( modHasCircularDependency( id, depId ) ) { | |
case CIRCULAR_DEP_UNREADY: | |
case CIRCULAR_DEP_NO: | |
isReady = 0; | |
break; | |
case CIRCULAR_DEP_YES: | |
if ( module.hardDeps[ depId ] ) { | |
tryDeps.push( depId ); | |
} | |
break; | |
} | |
} | |
return !!isReady; | |
} | |
); | |
// 只有当其他非循环依赖都装载了,才去尝试触发硬依赖模块的初始化 | |
isReady && each( | |
tryDeps, | |
function ( depId ) { | |
modTryInvokeFactory( depId ); | |
} | |
); | |
isReady = isReady && tryDeps.length === 0; | |
isReady && (module.state = MODULE_STATE_READY); | |
checkingLevel--; | |
return isReady; | |
} | |
/** | |
* 初始化模块 | |
* | |
* @inner | |
*/ | |
function invokeFactory() { | |
if ( module.state == MODULE_STATE_DEFINED | |
|| checkingLevel > 1 | |
|| !checkInvokeReadyState() | |
) { | |
return; | |
} | |
// 调用factory函数初始化module | |
try { | |
var factory = module.factory; | |
var exports = isFunction( factory ) | |
? factory.apply( | |
global, | |
modGetModulesExports( | |
module.deps, | |
{ | |
require : createLocalRequire( id ), | |
exports : module.exports, | |
module : module | |
} | |
) | |
) | |
: factory; | |
if ( typeof exports != 'undefined' ) { | |
module.exports = exports; | |
} | |
module.state = MODULE_STATE_DEFINED; | |
module.invokeFactory = null; | |
} | |
catch ( ex ) { | |
if ( /^\[MODULE_MISS\]"([^"]+)/.test( ex.message ) ) { | |
// 出错说明在factory的运行中,该require的模块是需要的 | |
// 所以把它加入硬依赖中 | |
module.hardDeps[ RegExp.$1 ] = 1; | |
return; | |
} | |
throw ex; | |
} | |
modInvokeFactoryDependOn( id ); | |
modFireDefined( id ); | |
} | |
} | |
/** | |
* 根据模块id数组,获取其的exports数组 | |
* 用于模块初始化的factory参数或require的callback参数生成 | |
* | |
* @inner | |
* @param {Array} modules 模块id数组 | |
* @param {Object} buildinModules 内建模块对象 | |
* @return {Array} | |
*/ | |
function modGetModulesExports( modules, buildinModules ) { | |
var args = []; | |
each( | |
modules, | |
function ( moduleId, index ) { | |
args[ index ] = | |
buildinModules[ moduleId ] | |
|| modGetModuleExports( moduleId ); | |
} | |
); | |
return args; | |
} | |
var CIRCULAR_DEP_UNREADY = 0; | |
var CIRCULAR_DEP_NO = 1; | |
var CIRCULAR_DEP_YES = 2; | |
/** | |
* 判断source是否处于target的依赖链中 | |
* | |
* @inner | |
* @return {number} | |
*/ | |
function modHasCircularDependency( source, target, meet ) { | |
if ( !modIsAnalyzed( target ) ) { | |
return CIRCULAR_DEP_UNREADY; | |
} | |
meet = meet || {}; | |
meet[ target ] = 1; | |
if ( target == source ) { | |
return CIRCULAR_DEP_YES; | |
} | |
var module = modGetModule( target ); | |
var depends = module && module.realDeps; | |
if ( depends ) { | |
var len = depends.length; | |
while ( len-- ) { | |
var dependId = depends[ len ]; | |
if ( meet[ dependId ] ) { | |
continue; | |
} | |
var state = modHasCircularDependency( source, dependId, meet ); | |
switch ( state ) { | |
case CIRCULAR_DEP_UNREADY: | |
case CIRCULAR_DEP_YES: | |
return state; | |
} | |
} | |
} | |
return CIRCULAR_DEP_NO; | |
} | |
/** | |
* 让依赖自己的模块尝试初始化 | |
* | |
* @inner | |
* @param {string} id 模块id | |
*/ | |
function modInvokeFactoryDependOn( id ) { | |
for ( var key in modModules ) { | |
var realDeps = modModules[ key ].realDepsIndex || {}; | |
realDeps[ id ] && modTryInvokeFactory( key ); | |
} | |
} | |
/** | |
* 尝试执行模块factory函数,进行模块初始化 | |
* | |
* @inner | |
* @param {string} id 模块id | |
*/ | |
function modTryInvokeFactory( id ) { | |
var module = modModules[ id ]; | |
if ( module && module.invokeFactory ) { | |
module.invokeFactory(); | |
} | |
} | |
/** | |
* 模块定义完成的事件监听器 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var modDefinedListener = []; | |
/** | |
* 模块定义完成事件监听器的移除索引 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var modRemoveListenerIndex = []; | |
/** | |
* 模块定义完成事件fire层级 | |
* | |
* @inner | |
* @type {number} | |
*/ | |
var modFireLevel = 0; | |
/** | |
* 派发模块定义完成事件 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
*/ | |
function modFireDefined( id ) { | |
modFireLevel++; | |
each( | |
modDefinedListener, | |
function ( listener ) { | |
listener && listener( id ); | |
} | |
); | |
modFireLevel--; | |
modSweepDefinedListener(); | |
} | |
/** | |
* 清理模块定义完成事件监听器 | |
* modRemoveDefinedListener时只做标记 | |
* 在modFireDefined执行清除动作 | |
* | |
* @inner | |
* @param {Function} listener 模块定义监听器 | |
*/ | |
function modSweepDefinedListener() { | |
if ( modFireLevel < 1 ) { | |
modRemoveListenerIndex.sort( | |
function ( a, b ) { return b - a; } | |
); | |
each( | |
modRemoveListenerIndex, | |
function ( index ) { | |
modDefinedListener.splice( index, 1 ); | |
} | |
); | |
modRemoveListenerIndex = []; | |
} | |
} | |
/** | |
* 移除模块定义监听器 | |
* | |
* @inner | |
* @param {Function} listener 模块定义监听器 | |
*/ | |
function modRemoveDefinedListener( listener ) { | |
each( | |
modDefinedListener, | |
function ( item, index ) { | |
if ( listener == item ) { | |
modRemoveListenerIndex.push( index ); | |
} | |
} | |
); | |
} | |
/** | |
* 添加模块定义监听器 | |
* | |
* @inner | |
* @param {Function} listener 模块定义监听器 | |
*/ | |
function modAddDefinedListener( listener ) { | |
modDefinedListener.push( listener ); | |
} | |
/** | |
* 判断模块是否存在 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @return {boolean} | |
*/ | |
function modExists( id ) { | |
return id in modModules; | |
} | |
/** | |
* 判断模块是否已定义完成 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @return {boolean} | |
*/ | |
function modIsDefined( id ) { | |
return modExists( id ) | |
&& modModules[ id ].state == MODULE_STATE_DEFINED; | |
} | |
/** | |
* 判断模块是否已分析完成 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @return {boolean} | |
*/ | |
function modIsAnalyzed( id ) { | |
return modExists( id ) | |
&& modModules[ id ].state >= MODULE_STATE_ANALYZED; | |
} | |
/** | |
* 获取模块的exports | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @return {*} | |
*/ | |
function modGetModuleExports( id ) { | |
if ( modIsDefined( id ) ) { | |
return modModules[ id ].exports; | |
} | |
return null; | |
} | |
/** | |
* 获取模块 | |
* | |
* @inner | |
* @param {string} id 模块标识 | |
* @return {Object} | |
*/ | |
function modGetModule( id ) { | |
return modModules[ id ]; | |
} | |
/** | |
* 添加资源 | |
* | |
* @inner | |
* @param {string} resourceId 资源标识 | |
* @param {*} value 资源对象 | |
*/ | |
function modAddResource( resourceId, value ) { | |
modModules[ resourceId ] = { | |
exports: value || true, | |
state: MODULE_STATE_DEFINED | |
}; | |
modInvokeFactoryDependOn( resourceId ); | |
modFireDefined( resourceId ); | |
} | |
/** | |
* 内建module名称集合 | |
* | |
* @inner | |
* @type {Object} | |
*/ | |
var BUILDIN_MODULE = { | |
require : require, | |
exports : 1, | |
module : 1 | |
}; | |
/** | |
* 未预定义的模块集合 | |
* 主要存储匿名方式define的模块 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var wait4PreDefines = []; | |
/** | |
* 完成模块预定义 | |
* | |
* @inner | |
*/ | |
function completePreDefine( currentId ) { | |
var preDefines = wait4PreDefines.slice( 0 ); | |
wait4PreDefines.length = 0; | |
wait4PreDefines = []; | |
// 预定义模块: | |
// 此时处理的模块都是匿名define的模块 | |
each( | |
preDefines, | |
function ( module ) { | |
var id = module.id || currentId; | |
modPreDefine( id, module.deps, module.factory ); | |
} | |
); | |
modPreAnalyse(); | |
} | |
/** | |
* 获取模块 | |
* | |
* @param {string|Array} ids 模块名称或模块名称列表 | |
* @param {Function=} callback 获取模块完成时的回调函数 | |
* @return {Object} | |
*/ | |
function nativeRequire( ids, callback, baseId ) { | |
callback = callback || new Function(); | |
baseId = baseId || ''; | |
// 根据 https://github.com/amdjs/amdjs-api/wiki/require | |
// It MUST throw an error if the module has not | |
// already been loaded and evaluated. | |
if ( isString( ids ) ) { | |
if ( !modIsDefined( ids ) ) { | |
throw new Error( '[MODULE_MISS]"' + ids + '" is not exists!' ); | |
} | |
return modGetModuleExports( ids ); | |
} | |
if ( !isArray( ids ) ) { | |
return; | |
} | |
if ( ids.length === 0 ) { | |
callback(); | |
return; | |
} | |
var isCallbackCalled = 0; | |
modAddDefinedListener( tryFinishRequire ); | |
each( | |
ids, | |
function ( id ) { | |
if ( id in BUILDIN_MODULE ) { | |
return; | |
} | |
( id.indexOf( '!' ) > 0 | |
? loadResource | |
: loadModule | |
)( id, baseId ); | |
} | |
); | |
tryFinishRequire(); | |
/** | |
* 尝试完成require,调用callback | |
* 在模块与其依赖模块都加载完时调用 | |
* | |
* @inner | |
*/ | |
function tryFinishRequire() { | |
if ( isCallbackCalled ) { | |
return; | |
} | |
var visitedModule = {}; | |
/** | |
* 判断是否所有模块都已经加载完成,包括其依赖的模块 | |
* | |
* @inner | |
* @param {Array} modules 直接模块标识列表 | |
* @return {boolean} | |
*/ | |
function isAllInited( modules ) { | |
var allInited = 1; | |
each( | |
modules, | |
function ( id ) { | |
if ( visitedModule[ id ] ) { | |
return; | |
} | |
visitedModule[ id ] = 1; | |
if ( BUILDIN_MODULE[ id ] ) { | |
return; | |
} | |
if ( | |
!modIsDefined( id ) | |
|| !isAllInited( modGetModule( id ).realDeps ) | |
) { | |
allInited = 0; | |
return false; | |
} | |
} | |
); | |
return allInited; | |
} | |
// 检测并调用callback | |
if ( isAllInited( ids ) ) { | |
isCallbackCalled = 1; | |
modRemoveDefinedListener( tryFinishRequire ); | |
callback.apply( | |
global, | |
modGetModulesExports( ids, BUILDIN_MODULE ) | |
); | |
} | |
} | |
} | |
/** | |
* 正在加载的模块列表 | |
* | |
* @inner | |
* @type {Object} | |
*/ | |
var loadingModules = {}; | |
/** | |
* 加载模块 | |
* | |
* @inner | |
* @param {string} moduleId 模块标识 | |
*/ | |
function loadModule( moduleId ) { | |
if ( loadingModules[ moduleId ] ) { | |
return; | |
} | |
if ( modExists( moduleId ) ) { | |
modAnalyse( [ modGetModule( moduleId ) ] ); | |
return; | |
} | |
loadingModules[ moduleId ] = 1; | |
// 创建script标签 | |
// | |
// 这里不挂接onerror的错误处理 | |
// 因为高级浏览器在devtool的console面板会报错 | |
// 再throw一个Error多此一举了 | |
var script = document.createElement( 'script' ); | |
script.setAttribute( 'data-require-id', moduleId ); | |
script.src = toUrl( moduleId ) ; | |
script.async = true; | |
if ( script.readyState ) { | |
script.onreadystatechange = loadedListener; | |
} | |
else { | |
script.onload = loadedListener; | |
} | |
appendScript( script ); | |
/** | |
* script标签加载完成的事件处理函数 | |
* | |
* @inner | |
*/ | |
function loadedListener() { | |
var readyState = script.readyState; | |
if ( | |
typeof readyState == 'undefined' | |
|| /^(loaded|complete)$/.test( readyState ) | |
) { | |
script.onload = script.onreadystatechange = null; | |
script = null; | |
completePreDefine( moduleId ); | |
delete loadingModules[ moduleId ]; | |
} | |
} | |
} | |
/** | |
* 加载资源 | |
* | |
* @inner | |
* @param {string} pluginAndResource 插件与资源标识 | |
* @param {string} baseId 当前环境的模块标识 | |
*/ | |
function loadResource( pluginAndResource, baseId ) { | |
var idInfo = parseId( pluginAndResource ); | |
var pluginId = idInfo.module; | |
var resourceId = idInfo.resource; | |
/** | |
* plugin加载完成的回调函数 | |
* | |
* @inner | |
* @param {*} value resource的值 | |
*/ | |
function pluginOnload( value ) { | |
modAddResource( pluginAndResource, value ); | |
} | |
/** | |
* 该方法允许plugin使用加载的资源声明模块 | |
* | |
* @param {string} name 模块id | |
* @param {string} body 模块声明字符串 | |
*/ | |
pluginOnload.fromText = function ( id, text ) { | |
new Function( text )(); | |
completePreDefine( id ); | |
}; | |
/** | |
* 加载资源 | |
* | |
* @inner | |
* @param {Object} plugin 用于加载资源的插件模块 | |
*/ | |
function load( plugin ) { | |
if ( !modIsDefined( pluginAndResource ) ) { | |
plugin.load( | |
resourceId, | |
createLocalRequire( baseId ), | |
pluginOnload, | |
moduleConfigGetter.call( { id: pluginAndResource } ) | |
); | |
} | |
} | |
if ( !modIsDefined( pluginId ) ) { | |
nativeRequire( [ pluginId ], load ); | |
} | |
else { | |
load( modGetModuleExports( pluginId ) ); | |
} | |
} | |
/** | |
* require配置 | |
* | |
* @inner | |
* @type {Object} | |
*/ | |
var requireConf = { | |
baseUrl : './', | |
paths : {}, | |
config : {}, | |
map : {}, | |
packages : [], | |
waitSeconds : 0, | |
urlArgs : {} | |
}; | |
/** | |
* 混合当前配置项与用户传入的配置项 | |
* | |
* @inner | |
* @param {string} name 配置项名称 | |
* @param {Any} value 用户传入配置项的值 | |
*/ | |
function mixConfig( name, value ) { | |
var originValue = requireConf[ name ]; | |
var type = typeof originValue; | |
if ( type == 'string' || type == 'number' ) { | |
requireConf[ name ] = value; | |
} | |
else if ( isArray( originValue ) ) { | |
each( value, function ( item ) { | |
originValue.push( item ); | |
} ); | |
} | |
else { | |
for ( var key in value ) { | |
originValue[ key ] = value[ key ]; | |
} | |
} | |
} | |
/** | |
* 配置require | |
* | |
* @param {Object} conf 配置对象 | |
*/ | |
require.config = function ( conf ) { | |
// 简单的多处配置还是需要支持 | |
// 所以实现更改为二级mix | |
for ( var key in requireConf ) { | |
if ( conf.hasOwnProperty( key ) ) { | |
var confItem = conf[ key ]; | |
if ( key == 'urlArgs' && isString( confItem ) ) { | |
defaultUrlArgs = confItem; | |
} | |
else { | |
mixConfig( key, confItem ); | |
} | |
} | |
} | |
createConfIndex(); | |
}; | |
// 初始化时需要创建配置索引 | |
createConfIndex(); | |
/** | |
* 创建配置信息内部索引 | |
* | |
* @inner | |
*/ | |
function createConfIndex() { | |
requireConf.baseUrl = requireConf.baseUrl.replace( /\/$/, '' ) + '/'; | |
createPathsIndex(); | |
createMappingIdIndex(); | |
createPackagesIndex(); | |
createUrlArgsIndex(); | |
} | |
/** | |
* packages内部索引 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var packagesIndex; | |
/** | |
* 创建packages内部索引 | |
* | |
* @inner | |
*/ | |
function createPackagesIndex() { | |
packagesIndex = []; | |
each( | |
requireConf.packages, | |
function ( packageConf ) { | |
var pkg = packageConf; | |
if ( isString( packageConf ) ) { | |
pkg = { | |
name: packageConf.split('/')[ 0 ], | |
location: packageConf, | |
main: 'main' | |
}; | |
} | |
pkg.location = pkg.location || pkg.name; | |
pkg.main = (pkg.main || 'main').replace(/\.js$/i, ''); | |
packagesIndex.push( pkg ); | |
} | |
); | |
packagesIndex.sort( createDescSorter( 'name' ) ); | |
} | |
/** | |
* paths内部索引 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var pathsIndex; | |
/** | |
* 创建paths内部索引 | |
* | |
* @inner | |
*/ | |
function createPathsIndex() { | |
pathsIndex = kv2List( requireConf.paths ); | |
pathsIndex.sort( createDescSorter() ); | |
} | |
/** | |
* 默认的urlArgs | |
* | |
* @inner | |
* @type {string} | |
*/ | |
var defaultUrlArgs; | |
/** | |
* urlArgs内部索引 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var urlArgsIndex; | |
/** | |
* 创建urlArgs内部索引 | |
* | |
* @inner | |
*/ | |
function createUrlArgsIndex() { | |
urlArgsIndex = kv2List( requireConf.urlArgs ); | |
urlArgsIndex.sort( createDescSorter() ); | |
} | |
/** | |
* mapping内部索引 | |
* | |
* @inner | |
* @type {Array} | |
*/ | |
var mappingIdIndex; | |
/** | |
* 创建mapping内部索引 | |
* | |
* @inner | |
*/ | |
function createMappingIdIndex() { | |
mappingIdIndex = []; | |
mappingIdIndex = kv2List( requireConf.map ); | |
mappingIdIndex.sort( createDescSorter() ); | |
each( | |
mappingIdIndex, | |
function ( item ) { | |
var key = item.k; | |
item.v = kv2List( item.v ); | |
item.v.sort( createDescSorter() ); | |
item.reg = key == '*' | |
? /^/ | |
: createPrefixRegexp( key ); | |
} | |
); | |
} | |
/** | |
* 将`模块标识+'.extension'`形式的字符串转换成相对的url | |
* | |
* @inner | |
* @param {string} source 源字符串 | |
* @return {string} | |
*/ | |
function toUrl( source ) { | |
// 分离 模块标识 和 .extension | |
var extReg = /(\.[a-z0-9]+)$/i; | |
var queryReg = /(\?[^#]*)$/i; | |
var extname = '.js'; | |
var id = source; | |
var query = ''; | |
if ( queryReg.test( source ) ) { | |
query = RegExp.$1; | |
source = source.replace( queryReg, '' ); | |
} | |
if ( extReg.test( source ) ) { | |
extname = RegExp.$1; | |
id = source.replace( extReg, '' ); | |
} | |
// 模块标识合法性检测 | |
if ( !MODULE_ID_REG.test( id ) ) { | |
return source; | |
} | |
var url = id; | |
// paths处理和匹配 | |
var isPathMap; | |
each( pathsIndex, function ( item ) { | |
var key = item.k; | |
if ( createPrefixRegexp( key ).test( id ) ) { | |
url = url.replace( key, item.v ); | |
isPathMap = 1; | |
return false; | |
} | |
} ); | |
// packages处理和匹配 | |
if ( !isPathMap ) { | |
each( | |
packagesIndex, | |
function ( packageConf ) { | |
var name = packageConf.name; | |
if ( createPrefixRegexp( name ).test( id ) ) { | |
url = url.replace( name, packageConf.location ); | |
return false; | |
} | |
} | |
); | |
} | |
// 相对路径时,附加baseUrl | |
if ( !/^([a-z]{2,10}:\/)?\//i.test( url ) ) { | |
url = requireConf.baseUrl + url; | |
} | |
// 附加 .extension 和 query | |
url += extname + query; | |
var isUrlArgsAppended; | |
/** | |
* 为url附加urlArgs | |
* | |
* @inner | |
* @param {string} args urlArgs串 | |
*/ | |
function appendUrlArgs( args ) { | |
if ( !isUrlArgsAppended ) { | |
url += ( url.indexOf( '?' ) > 0 ? '&' : '?' ) + args; | |
isUrlArgsAppended = 1; | |
} | |
} | |
// urlArgs处理和匹配 | |
each( urlArgsIndex, function ( item ) { | |
if ( createPrefixRegexp( item.k ).test( id ) ) { | |
appendUrlArgs( item.v ); | |
return false; | |
} | |
} ); | |
defaultUrlArgs && appendUrlArgs( defaultUrlArgs ); | |
return url; | |
} | |
/** | |
* 创建local require函数 | |
* | |
* @inner | |
* @param {number} baseId 当前module id | |
* @return {Function} | |
*/ | |
function createLocalRequire( baseId ) { | |
var requiredCache = {}; | |
function req( requireId, callback ) { | |
if ( isString( requireId ) ) { | |
var requiredModule; | |
if ( !( requiredModule = requiredCache[ requireId ] ) ) { | |
requiredModule = nativeRequire( | |
normalize( requireId, baseId ), | |
callback, | |
baseId | |
); | |
requiredCache[ requireId ] = requiredModule; | |
} | |
return requiredModule; | |
} | |
else if ( isArray( requireId ) ) { | |
// 分析是否有resource使用的plugin没加载 | |
var unloadedPluginModules = []; | |
each( | |
requireId, | |
function ( id ) { | |
var idInfo = parseId( id ); | |
var pluginId = normalize( idInfo.module, baseId ); | |
if ( idInfo.resource && !modIsDefined( pluginId ) ) { | |
unloadedPluginModules.push( pluginId ); | |
} | |
} | |
); | |
// 加载模块 | |
nativeRequire( | |
unloadedPluginModules, | |
function () { | |
var ids = []; | |
each( | |
requireId, | |
function ( id ) { | |
ids.push( normalize( id, baseId ) ); | |
} | |
); | |
nativeRequire( ids, callback, baseId ); | |
}, | |
baseId | |
); | |
} | |
} | |
/** | |
* 将[module ID] + '.extension'格式的字符串转换成url | |
* | |
* @inner | |
* @param {string} source 符合描述格式的源字符串 | |
* @return {string} | |
*/ | |
req.toUrl = function ( id ) { | |
return toUrl( normalize( id, baseId ) ); | |
}; | |
return req; | |
} | |
/** | |
* id normalize化 | |
* | |
* @inner | |
* @param {string} id 需要normalize的模块标识 | |
* @param {string} baseId 当前环境的模块标识 | |
* @return {string} | |
*/ | |
function normalize( id, baseId ) { | |
if ( !id ) { | |
return ''; | |
} | |
var idInfo = parseId( id ); | |
if ( !idInfo ) { | |
return id; | |
} | |
var resourceId = idInfo.resource; | |
var moduleId = relative2absolute( idInfo.module, baseId ); | |
each( | |
packagesIndex, | |
function ( packageConf ) { | |
var name = packageConf.name; | |
var main = name + '/' + packageConf.main; | |
if ( name == moduleId | |
) { | |
moduleId = moduleId.replace( name, main ); | |
return false; | |
} | |
} | |
); | |
moduleId = mappingId( moduleId, baseId ); | |
if ( resourceId ) { | |
var module = modGetModuleExports( moduleId ); | |
resourceId = module && module.normalize | |
? module.normalize( | |
resourceId, | |
function ( resId ) { | |
return normalize( resId, baseId ); | |
} | |
) | |
: normalize( resourceId, baseId ); | |
return moduleId + '!' + resourceId; | |
} | |
return moduleId; | |
} | |
/** | |
* 相对id转换成绝对id | |
* | |
* @inner | |
* @param {string} id 要转换的id | |
* @param {string} baseId 当前所在环境id | |
* @return {string} | |
*/ | |
function relative2absolute( id, baseId ) { | |
if ( /^\.{1,2}/.test( id ) ) { | |
var basePath = baseId.split( '/' ); | |
var namePath = id.split( '/' ); | |
var baseLen = basePath.length - 1; | |
var nameLen = namePath.length; | |
var cutBaseTerms = 0; | |
var cutNameTerms = 0; | |
pathLoop: for ( var i = 0; i < nameLen; i++ ) { | |
var term = namePath[ i ]; | |
switch ( term ) { | |
case '..': | |
if ( cutBaseTerms < baseLen ) { | |
cutBaseTerms++; | |
cutNameTerms++; | |
} | |
else { | |
break pathLoop; | |
} | |
break; | |
case '.': | |
cutNameTerms++; | |
break; | |
default: | |
break pathLoop; | |
} | |
} | |
basePath.length = baseLen - cutBaseTerms; | |
namePath = namePath.slice( cutNameTerms ); | |
basePath.push.apply( basePath, namePath ); | |
return basePath.join( '/' ); | |
} | |
return id; | |
} | |
/** | |
* 确定require的模块id不包含相对id。用于global require,提前预防难以跟踪的错误出现 | |
* | |
* @inner | |
* @param {string|Array} requireId require的模块id | |
*/ | |
function assertNotContainRelativeId( requireId ) { | |
var invalidIds = []; | |
/** | |
* 监测模块id是否relative id | |
* | |
* @inner | |
* @param {string} id 模块id | |
*/ | |
function monitor( id ) { | |
if ( /^\.{1,2}/.test( id ) ) { | |
invalidIds.push( id ); | |
} | |
} | |
if ( isString( requireId ) ) { | |
monitor( requireId ); | |
} | |
else { | |
each( | |
requireId, | |
function ( id ) { | |
monitor( id ); | |
} | |
); | |
} | |
// 包含相对id时,直接抛出错误 | |
if ( invalidIds.length > 0 ) { | |
throw new Error( | |
'[REQUIRE_FATAL]Relative ID is not allowed in global require: ' | |
+ invalidIds.join( ', ' ) | |
); | |
} | |
} | |
/** | |
* 模块id正则 | |
* | |
* @const | |
* @inner | |
* @type {RegExp} | |
*/ | |
var MODULE_ID_REG = /^[-_a-z0-9\.]+(\/[-_a-z0-9\.]+)*$/i; | |
/** | |
* 解析id,返回带有module和resource属性的Object | |
* | |
* @inner | |
* @param {string} id 标识 | |
* @return {Object} | |
*/ | |
function parseId( id ) { | |
var segs = id.split( '!' ); | |
if ( MODULE_ID_REG.test( segs[ 0 ] ) ) { | |
return { | |
module : segs[ 0 ], | |
resource : segs[ 1 ] || '' | |
}; | |
} | |
return null; | |
} | |
/** | |
* 基于map配置项的id映射 | |
* | |
* @inner | |
* @param {string} id 模块id | |
* @param {string} baseId 当前环境的模块id | |
* @return {string} | |
*/ | |
function mappingId( id, baseId ) { | |
each( | |
mappingIdIndex, | |
function ( item ) { | |
if ( item.reg.test( baseId ) ) { | |
each( item.v, function ( mapData ) { | |
var key = mapData.k; | |
var rule = createPrefixRegexp( key ); | |
if ( rule.test( id ) ) { | |
id = id.replace( key, mapData.v ); | |
return false; | |
} | |
} ); | |
return false; | |
} | |
} | |
); | |
return id; | |
} | |
/** | |
* 将对象数据转换成数组,数组每项是带有k和v的Object | |
* | |
* @inner | |
* @param {Object} source 对象数据 | |
* @return {Array.<Object>} | |
*/ | |
function kv2List( source ) { | |
var list = []; | |
for ( var key in source ) { | |
if ( source.hasOwnProperty( key ) ) { | |
list.push( { | |
k: key, | |
v: source[ key ] | |
} ); | |
} | |
} | |
return list; | |
} | |
// 感谢requirejs,通过currentlyAddingScript兼容老旧ie | |
// | |
// For some cache cases in IE 6-8, the script executes before the end | |
// of the appendChild execution, so to tie an anonymous define | |
// call to the module name (which is stored on the node), hold on | |
// to a reference to this node, but clear after the DOM insertion. | |
var currentlyAddingScript; | |
var interactiveScript; | |
/** | |
* 获取当前script标签 | |
* 用于ie下define未指定module id时获取id | |
* | |
* @inner | |
* @return {HTMLDocument} | |
*/ | |
function getCurrentScript() { | |
if ( currentlyAddingScript ) { | |
return currentlyAddingScript; | |
} | |
else if ( | |
interactiveScript | |
&& interactiveScript.readyState == 'interactive' | |
) { | |
return interactiveScript; | |
} | |
else { | |
var scripts = document.getElementsByTagName( 'script' ); | |
var scriptLen = scripts.length; | |
while ( scriptLen-- ) { | |
var script = scripts[ scriptLen ]; | |
if ( script.readyState == 'interactive' ) { | |
interactiveScript = script; | |
return script; | |
} | |
} | |
} | |
} | |
/** | |
* 向页面中插入script标签 | |
* | |
* @inner | |
* @param {HTMLScriptElement} script script标签 | |
*/ | |
function appendScript( script ) { | |
currentlyAddingScript = script; | |
var doc = document; | |
(doc.getElementsByTagName('head')[0] || doc.body).appendChild( script ); | |
currentlyAddingScript = null; | |
} | |
/** | |
* 创建id前缀匹配的正则对象 | |
* | |
* @inner | |
* @param {string} prefix id前缀 | |
* @return {RegExp} | |
*/ | |
function createPrefixRegexp( prefix ) { | |
return new RegExp( '^' + prefix + '(/|$)' ); | |
} | |
/** | |
* 判断对象是否数组类型 | |
* | |
* @inner | |
* @param {*} obj 要判断的对象 | |
* @return {boolean} | |
*/ | |
function isArray( obj ) { | |
return obj instanceof Array; | |
} | |
/** | |
* 判断对象是否函数类型 | |
* | |
* @inner | |
* @param {*} obj 要判断的对象 | |
* @return {boolean} | |
*/ | |
function isFunction( obj ) { | |
return typeof obj == 'function'; | |
} | |
/** | |
* 判断是否字符串 | |
* | |
* @inner | |
* @param {*} obj 要判断的对象 | |
* @return {boolean} | |
*/ | |
function isString( obj ) { | |
return typeof obj == 'string'; | |
} | |
/** | |
* 循环遍历数组集合 | |
* | |
* @inner | |
* @param {Array} source 数组源 | |
* @param {function(Array,Number):boolean} iterator 遍历函数 | |
*/ | |
function each( source, iterator ) { | |
if ( isArray( source ) ) { | |
for ( var i = 0, len = source.length; i < len; i++ ) { | |
if ( iterator( source[ i ], i ) === false ) { | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* 创建数组字符数逆序排序函数 | |
* | |
* @inner | |
* @param {string} property 数组项对象名 | |
* @return {Function} | |
*/ | |
function createDescSorter( property ) { | |
property = property || 'k'; | |
return function ( a, b ) { | |
var aValue = a[ property ]; | |
var bValue = b[ property ]; | |
if ( bValue == '*' ) { | |
return -1; | |
} | |
if ( aValue == '*' ) { | |
return 1; | |
} | |
return bValue.length - aValue.length; | |
}; | |
} | |
// 暴露全局对象 | |
global.define = define; | |
global.require = require; | |
})( this ); |