/**
|
* ESL (Enterprise Standard Loader)
|
* Copyright 2013 Baidu Inc. All rights reserved.
|
*
|
* @file Browser端标准加载器,符合AMD规范
|
* @author errorrik(errorrik@gmail.com)
|
* Firede(firede@firede.us)
|
*/
|
|
/* jshint ignore:start */
|
var define;
|
var require;
|
var esl;
|
/* jshint ignore:end */
|
|
(function (global) {
|
// "mod"开头的变量或函数为内部模块管理函数
|
// 为提高压缩率,不使用function或object包装
|
|
/**
|
* 模块容器
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var modModules = {};
|
|
/**
|
* 自动定义的模块表
|
*
|
* 模块define factory是用到时才执行,但是以下几种情况需要自动马上执行:
|
* 1. require( [moduleId], callback )
|
* 2. plugin module: require( 'plugin!resource' )
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var autoDefineModules = {};
|
|
|
// 模块状态枚举量
|
var MODULE_PRE_DEFINED = 1;
|
var MODULE_ANALYZED = 2;
|
var MODULE_PREPARED = 3;
|
var MODULE_DEFINED = 4;
|
|
/**
|
* 内建module名称集合
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var BUILDIN_MODULE = {
|
require: globalRequire,
|
exports: 1,
|
module: 1
|
};
|
|
/**
|
* 全局require函数
|
*
|
* @inner
|
* @type {Function}
|
*/
|
var actualGlobalRequire = createLocalRequire();
|
|
// #begin-ignore
|
/**
|
* 超时提醒定时器
|
*
|
* @inner
|
* @type {number}
|
*/
|
var waitTimeout;
|
// #end-ignore
|
|
/* eslint-disable fecs-key-spacing */
|
/* eslint-disable key-spacing */
|
/**
|
* require配置
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var requireConf = {
|
baseUrl : './',
|
paths : {},
|
config : {},
|
map : {},
|
packages : [],
|
// #begin-ignore
|
waitSeconds: 0,
|
// #end-ignore
|
noRequests : {},
|
urlArgs : {}
|
};
|
/* eslint-enable key-spacing */
|
|
/**
|
* 加载模块
|
*
|
* @param {string|Array} requireId 模块id或模块id数组,
|
* @param {Function=} callback 加载完成的回调函数
|
* @return {*} requireId为string时返回模块暴露对象
|
*/
|
function globalRequire(requireId, callback) {
|
// #begin-ignore
|
// #begin assertNotContainRelativeId
|
// 确定require的模块id不包含相对id。用于global require,提前预防难以跟踪的错误出现
|
var invalidIds = [];
|
|
/**
|
* 监测模块id是否relative id
|
*
|
* @inner
|
* @param {string} id 模块id
|
*/
|
function monitor(id) {
|
if (id.indexOf('.') === 0) {
|
invalidIds.push(id);
|
}
|
}
|
|
if (typeof requireId === 'string') {
|
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(', ')
|
);
|
}
|
// #end assertNotContainRelativeId
|
|
// 超时提醒
|
var timeout = requireConf.waitSeconds;
|
if (timeout && (requireId instanceof Array)) {
|
if (waitTimeout) {
|
clearTimeout(waitTimeout);
|
}
|
waitTimeout = setTimeout(waitTimeoutNotice, timeout * 1000);
|
}
|
// #end-ignore
|
|
return actualGlobalRequire(requireId, callback);
|
}
|
|
/**
|
* 版本号
|
*
|
* @type {string}
|
*/
|
globalRequire.version = '1.8.8';
|
|
/**
|
* loader名称
|
*
|
* @type {string}
|
*/
|
globalRequire.loader = 'esl';
|
|
/**
|
* 将模块标识转换成相对的url
|
*
|
* @param {string} id 模块标识
|
* @return {string}
|
*/
|
globalRequire.toUrl = actualGlobalRequire.toUrl;
|
|
// #begin-ignore
|
/**
|
* 超时提醒函数
|
*
|
* @inner
|
*/
|
function waitTimeoutNotice() {
|
var hangModules = [];
|
var missModules = [];
|
var hangModulesMap = {};
|
var missModulesMap = {};
|
var visited = {};
|
|
/**
|
* 检查模块的加载错误
|
*
|
* @inner
|
* @param {string} id 模块id
|
* @param {boolean} hard 是否装载时依赖
|
*/
|
function checkError(id, hard) {
|
if (visited[id] || modIs(id, MODULE_DEFINED)) {
|
return;
|
}
|
|
visited[id] = 1;
|
|
if (!modIs(id, MODULE_PREPARED)) {
|
// HACK: 为gzip后体积优化,不做抽取
|
if (!hangModulesMap[id]) {
|
hangModulesMap[id] = 1;
|
hangModules.push(id);
|
}
|
}
|
|
var mod = modModules[id];
|
if (!mod) {
|
if (!missModulesMap[id]) {
|
missModulesMap[id] = 1;
|
missModules.push(id);
|
}
|
}
|
else if (hard) {
|
if (!hangModulesMap[id]) {
|
hangModulesMap[id] = 1;
|
hangModules.push(id);
|
}
|
|
each(
|
mod.depMs,
|
function (dep) {
|
checkError(dep.absId, dep.hard);
|
}
|
);
|
}
|
}
|
|
/* eslint-disable guard-for-in */
|
for (var id in autoDefineModules) {
|
checkError(id, 1);
|
}
|
/* eslint-enable guard-for-in */
|
|
if (hangModules.length || missModules.length) {
|
throw new Error(
|
'[MODULE_TIMEOUT]Hang( '
|
+ (hangModules.join(', ') || 'none')
|
+ ' ) Miss( '
|
+ (missModules.join(', ') || 'none')
|
+ ' )'
|
);
|
}
|
}
|
// #end-ignore
|
|
/**
|
* 未预定义的模块集合
|
* 主要存储匿名方式define的模块
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var wait4PreDefine = [];
|
|
/**
|
* 完成模块预定义,此时处理的模块是匿名define的模块
|
*
|
* @inner
|
* @param {string} currentId 匿名define的模块的id
|
*/
|
function completePreDefine(currentId) {
|
// HACK: 这里在IE下有个性能陷阱,不能使用任何变量。
|
// 否则貌似会形成变量引用和修改的读写锁,导致wait4PreDefine释放困难
|
each(wait4PreDefine, function (mod) {
|
modPreDefine(
|
currentId,
|
mod.deps,
|
mod.factory
|
);
|
});
|
|
wait4PreDefine.length = 0;
|
modAnalyse(currentId);
|
}
|
|
/**
|
* 定义模块
|
*
|
* @param {string=} id 模块标识
|
* @param {Array=} dependencies 依赖模块列表
|
* @param {Function=} factory 创建模块的工厂方法
|
*/
|
function globalDefine(id, dependencies, factory) {
|
// define(factory)
|
// define(dependencies, factory)
|
// define(id, factory)
|
// define(id, dependencies, factory)
|
if (factory == null) {
|
if (dependencies == null) {
|
factory = id;
|
id = null;
|
}
|
else {
|
factory = dependencies;
|
dependencies = null;
|
if (id instanceof Array) {
|
dependencies = id;
|
id = null;
|
}
|
}
|
}
|
|
if (factory == null) {
|
return;
|
}
|
|
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');
|
}
|
|
if (id) {
|
modPreDefine(id, dependencies, factory);
|
}
|
else {
|
// 纪录到共享变量中,在load或readystatechange中处理
|
// 标准浏览器下,使用匿名define时,将进入这个分支
|
wait4PreDefine[0] = {
|
deps: dependencies,
|
factory: factory
|
};
|
}
|
}
|
|
globalDefine.amd = {};
|
|
/**
|
* 模块配置获取函数
|
*
|
* @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) {
|
// 将模块存入容器
|
//
|
// 模块内部信息包括
|
// -----------------------------------
|
// id: module id
|
// depsDec: 模块定义时声明的依赖
|
// deps: 模块依赖,默认为['require', 'exports', 'module']
|
// factory: 初始化函数或对象
|
// factoryDeps: 初始化函数的参数依赖
|
// exports: 模块的实际暴露对象(AMD定义)
|
// config: 用于获取模块配置信息的函数(AMD定义)
|
// state: 模块当前状态
|
// require: local require函数
|
// depMs: 实际依赖的模块集合,数组形式
|
// depMkv: 实际依赖的模块集合,表形式,便于查找
|
// depRs: 实际依赖的资源集合
|
// depPMs: 用于加载资源的模块集合,key是模块名,value是1,仅用于快捷查找
|
// ------------------------------------
|
if (!modModules[id]) {
|
/* eslint-disable key-spacing */
|
modModules[id] = {
|
id : id,
|
depsDec : dependencies,
|
deps : dependencies || ['require', 'exports', 'module'],
|
factoryDeps : [],
|
factory : factory,
|
exports : {},
|
config : moduleConfigGetter,
|
state : MODULE_PRE_DEFINED,
|
require : createLocalRequire(id),
|
depMs : [],
|
depMkv : {},
|
depRs : [],
|
depPMs : []
|
};
|
/* eslint-enable key-spacing */
|
}
|
}
|
|
/**
|
* 预分析模块
|
*
|
* 首先,完成对factory中声明依赖的分析提取
|
* 然后,尝试加载"资源加载所需模块"
|
*
|
* 需要先加载模块的原因是:如果模块不存在,无法进行resourceId normalize化
|
* modAnalyse完成后续的依赖分析处理,并进行依赖模块的加载
|
*
|
* @inner
|
* @param {string} id 模块id
|
*/
|
function modAnalyse(id) {
|
var mod = modModules[id];
|
if (!mod || modIs(id, MODULE_ANALYZED)) {
|
return;
|
}
|
|
var deps = mod.deps;
|
var factory = mod.factory;
|
var hardDependsCount = 0;
|
|
// 分析function body中的require
|
// 如果包含显式依赖声明,根据AMD规定和性能考虑,可以不分析factoryBody
|
if (typeof factory === 'function') {
|
hardDependsCount = Math.min(factory.length, deps.length);
|
|
// If the dependencies argument is present, the module loader
|
// SHOULD NOT scan for dependencies within the factory function.
|
!mod.depsDec && factory.toString()
|
.replace(/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, '')
|
.replace(/require\(\s*(['"'])([^'"]+)\1\s*\)/g,
|
function ($0, $1, depId) {
|
deps.push(depId);
|
}
|
);
|
}
|
|
var requireModules = [];
|
each(deps, function (depId, index) {
|
var idInfo = parseId(depId);
|
var absId = normalize(idInfo.mod, id);
|
var moduleInfo;
|
var resInfo;
|
|
if (absId && !BUILDIN_MODULE[absId]) {
|
// 如果依赖是一个资源,将其信息添加到module.depRs
|
//
|
// module.depRs中的项有可能是重复的。
|
// 在这个阶段,加载resource的module可能还未defined,
|
// 导致此时resource id无法被normalize。
|
//
|
// 比如对a/b/c而言,下面几个resource可能指的是同一个资源:
|
// - js!../name.js
|
// - js!a/name.js
|
// - ../../js!../name.js
|
//
|
// 所以加载资源的module ready时,需要遍历module.depRs进行处理
|
if (idInfo.res) {
|
resInfo = {
|
id: depId,
|
mod: absId,
|
res: idInfo.res
|
};
|
autoDefineModules[absId] = 1;
|
mod.depPMs.push(absId);
|
mod.depRs.push(resInfo);
|
}
|
|
// 对依赖模块的id normalize能保证正确性,在此处进行去重
|
moduleInfo = mod.depMkv[absId];
|
if (!moduleInfo) {
|
moduleInfo = {
|
id: idInfo.mod,
|
absId: absId,
|
hard: index < hardDependsCount
|
};
|
mod.depMs.push(moduleInfo);
|
mod.depMkv[absId] = moduleInfo;
|
requireModules.push(absId);
|
}
|
}
|
else {
|
moduleInfo = {absId: absId};
|
}
|
|
// 如果当前正在分析的依赖项是define中声明的,
|
// 则记录到module.factoryDeps中
|
// 在factory invoke前将用于生成invoke arguments
|
if (index < hardDependsCount) {
|
mod.factoryDeps.push(resInfo || moduleInfo);
|
}
|
});
|
|
mod.state = MODULE_ANALYZED;
|
modInitFactoryInvoker(id);
|
nativeRequire(requireModules);
|
}
|
|
/**
|
* 对一些需要自动定义的模块进行自动定义
|
*
|
* @inner
|
*/
|
function modAutoInvoke() {
|
/* eslint-disable guard-for-in */
|
for (var id in autoDefineModules) {
|
modUpdatePreparedState(id);
|
modTryInvokeFactory(id);
|
}
|
/* eslint-enable guard-for-in */
|
}
|
|
/**
|
* 更新模块的准备状态
|
*
|
* @inner
|
* @param {string} id 模块id
|
*/
|
function modUpdatePreparedState(id) {
|
var visited = {};
|
update(id);
|
|
function update(id) {
|
if (!modIs(id, MODULE_ANALYZED)) {
|
return false;
|
}
|
if (modIs(id, MODULE_PREPARED) || visited[id]) {
|
return true;
|
}
|
|
visited[id] = 1;
|
var mod = modModules[id];
|
var prepared = true;
|
|
each(
|
mod.depMs,
|
function (dep) {
|
return (prepared = update(dep.absId));
|
}
|
);
|
|
// 判断resource是否加载完成。如果resource未加载完成,则认为未准备好
|
/* jshint ignore:start */
|
prepared && each(
|
mod.depRs,
|
function (dep) {
|
prepared = !!(dep.absId && modIs(dep.absId, MODULE_DEFINED));
|
return prepared;
|
}
|
);
|
/* jshint ignore:end */
|
|
if (prepared) {
|
mod.state = MODULE_PREPARED;
|
}
|
|
return prepared;
|
}
|
}
|
|
/**
|
* 初始化模块定义时所需的factory执行器
|
*
|
* @inner
|
* @param {string} id 模块id
|
*/
|
function modInitFactoryInvoker(id) {
|
var mod = modModules[id];
|
var invoking;
|
|
mod.invokeFactory = invokeFactory;
|
/* eslint-disable max-nested-callbacks */
|
each(
|
mod.depPMs,
|
function (pluginModuleId) {
|
|
modAddDefinedListener(
|
pluginModuleId,
|
function () {
|
each(mod.depRs, function (res) {
|
if (!res.absId && res.mod === pluginModuleId) {
|
res.absId = normalize(res.id, id);
|
nativeRequire([res.absId], modAutoInvoke);
|
}
|
});
|
}
|
);
|
|
}
|
);
|
/* eslint-enable max-nested-callbacks */
|
|
/**
|
* 初始化模块
|
*
|
* @inner
|
*/
|
function invokeFactory() {
|
if (invoking || mod.state !== MODULE_PREPARED) {
|
return;
|
}
|
|
invoking = 1;
|
|
// 拼接factory invoke所需的arguments
|
var factoryReady = 1;
|
var factoryDeps = [];
|
each(
|
mod.factoryDeps,
|
function (dep) {
|
var depId = dep.absId;
|
|
if (!BUILDIN_MODULE[depId]) {
|
modTryInvokeFactory(depId);
|
if (!modIs(depId, MODULE_DEFINED)) {
|
factoryReady = 0;
|
return false;
|
}
|
}
|
|
factoryDeps.push(depId);
|
}
|
);
|
|
if (factoryReady) {
|
try {
|
var args = modGetModulesExports(
|
factoryDeps,
|
{
|
require: mod.require,
|
exports: mod.exports,
|
module: mod
|
}
|
);
|
|
// 调用factory函数初始化module
|
var factory = mod.factory;
|
var exports = typeof factory === 'function'
|
? factory.apply(global, args)
|
: factory;
|
|
if (exports != null) {
|
mod.exports = exports;
|
}
|
|
mod.invokeFactory = null;
|
delete autoDefineModules[id];
|
}
|
catch (ex) {
|
invoking = 0;
|
if (/^\[MODULE_MISS\]"([^"]+)/.test(ex.message)) {
|
// 出错,则说明在factory的运行中,该require的模块是需要的
|
// 所以把它加入强依赖中
|
var hardCirclurDep = mod.depMkv[RegExp.$1];
|
hardCirclurDep && (hardCirclurDep.hard = 1);
|
return;
|
}
|
|
throw ex;
|
}
|
|
// 完成define
|
// 不放在try里,避免后续的运行错误被这里吞掉
|
modDefined(id);
|
}
|
}
|
}
|
|
/**
|
* 判断模块是否完成相应的状态
|
*
|
* @inner
|
* @param {string} id 模块标识
|
* @param {number} state 状态码,使用时传入相应的枚举变量,比如`MODULE_DEFINED`
|
* @return {boolean} 是否完成相应的状态
|
*/
|
function modIs(id, state) {
|
return modModules[id] && modModules[id].state >= state;
|
}
|
|
/**
|
* 尝试执行模块factory函数,进行模块初始化
|
*
|
* @inner
|
* @param {string} id 模块id
|
*/
|
function modTryInvokeFactory(id) {
|
var mod = modModules[id];
|
|
if (mod && mod.invokeFactory) {
|
mod.invokeFactory();
|
}
|
}
|
|
/**
|
* 根据模块id数组,获取其的exports数组
|
* 用于模块初始化的factory参数或require的callback参数生成
|
*
|
* @inner
|
* @param {Array} modules 模块id数组
|
* @param {Object} buildinModules 内建模块对象
|
* @return {Array} 模块exports数组
|
*/
|
function modGetModulesExports(modules, buildinModules) {
|
var args = [];
|
each(
|
modules,
|
function (id, index) {
|
args[index] = buildinModules[id] || modGetModuleExports(id);
|
}
|
);
|
|
return args;
|
}
|
|
/**
|
* 模块定义完成事件监听器容器
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var modDefinedListeners = {};
|
|
/**
|
* 添加模块定义完成时间的监听器
|
*
|
* @inner
|
* @param {string} id 模块标识
|
* @param {Function} listener 监听函数
|
*/
|
function modAddDefinedListener(id, listener) {
|
if (modIs(id, MODULE_DEFINED)) {
|
listener();
|
return;
|
}
|
|
var listeners = modDefinedListeners[id];
|
if (!listeners) {
|
listeners = modDefinedListeners[id] = [];
|
}
|
|
listeners.push(listener);
|
}
|
|
/**
|
* 模块状态切换为定义完成
|
* 因为需要触发事件,MODULE_DEFINED状态切换通过该函数
|
*
|
* @inner
|
* @param {string} id 模块标识
|
*/
|
function modDefined(id) {
|
var listeners = modDefinedListeners[id] || [];
|
var mod = modModules[id];
|
mod.state = MODULE_DEFINED;
|
|
var len = listeners.length;
|
while (len--) {
|
// 这里不做function类型的检测
|
// 因为listener都是通过modOn传入的,modOn为内部调用
|
listeners[len]();
|
}
|
|
// 清理listeners
|
listeners.length = 0;
|
delete modDefinedListeners[id];
|
}
|
|
/**
|
* 获取模块的exports
|
*
|
* @inner
|
* @param {string} id 模块标识
|
* @return {*} 模块的exports
|
*/
|
function modGetModuleExports(id) {
|
if (modIs(id, MODULE_DEFINED)) {
|
return modModules[id].exports;
|
}
|
|
return null;
|
}
|
|
/**
|
* 获取模块
|
*
|
* @param {string|Array} ids 模块名称或模块名称列表
|
* @param {Function=} callback 获取模块完成时的回调函数
|
* @param {string} baseId 基础id,用于当ids是relative id时的normalize
|
* @param {Object} noRequests 无需发起请求的模块集合
|
* @return {Object} 模块对象
|
*/
|
function nativeRequire(ids, callback, baseId, noRequests) {
|
// 根据 https://github.com/amdjs/amdjs-api/wiki/require
|
// It MUST throw an error if the module has not
|
// already been loaded and evaluated.
|
if (typeof ids === 'string') {
|
modTryInvokeFactory(ids);
|
if (!modIs(ids, MODULE_DEFINED)) {
|
throw new Error('[MODULE_MISS]"' + ids + '" is not exists!');
|
}
|
|
return modGetModuleExports(ids);
|
}
|
|
noRequests = noRequests || {};
|
var isCallbackCalled = 0;
|
if (ids instanceof Array) {
|
tryFinishRequire();
|
|
if (!isCallbackCalled) {
|
each(ids, function (id) {
|
if (!(BUILDIN_MODULE[id] || modIs(id, MODULE_DEFINED))) {
|
modAddDefinedListener(id, tryFinishRequire);
|
|
if (!noRequests[id]) {
|
(id.indexOf('!') > 0
|
? loadResource
|
: loadModule
|
)(id, baseId);
|
}
|
|
modAnalyse(id);
|
}
|
});
|
|
modAutoInvoke();
|
}
|
}
|
|
/**
|
* 尝试完成require,调用callback
|
* 在模块与其依赖模块都加载完时调用
|
*
|
* @inner
|
*/
|
function tryFinishRequire() {
|
if (!isCallbackCalled) {
|
var isAllCompleted = 1;
|
each(ids, function (id) {
|
if (!BUILDIN_MODULE[id]) {
|
return (isAllCompleted = !!modIs(id, MODULE_DEFINED));
|
}
|
});
|
|
// 检测并调用callback
|
if (isAllCompleted) {
|
isCallbackCalled = 1;
|
|
(typeof callback === 'function') && callback.apply(
|
global,
|
modGetModulesExports(ids, BUILDIN_MODULE)
|
);
|
}
|
}
|
}
|
}
|
|
/**
|
* 正在加载的模块列表
|
*
|
* @inner
|
* @type {Object}
|
*/
|
var loadingModules = {};
|
|
/**
|
* 加载模块
|
*
|
* @inner
|
* @param {string} moduleId 模块标识
|
*/
|
function loadModule(moduleId) {
|
if (loadingModules[moduleId] || modModules[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 + '.js');
|
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);
|
/* eslint-disable guard-for-in */
|
for (var key in autoDefineModules) {
|
modAnalyse(key);
|
}
|
/* eslint-enable guard-for-in */
|
modAutoInvoke();
|
}
|
}
|
}
|
|
/**
|
* 加载资源
|
*
|
* @inner
|
* @param {string} pluginAndResource 插件与资源标识
|
* @param {string} baseId 当前环境的模块标识
|
*/
|
function loadResource(pluginAndResource, baseId) {
|
if (modModules[pluginAndResource]) {
|
return;
|
}
|
|
var idInfo = parseId(pluginAndResource);
|
var resource = {
|
id: pluginAndResource,
|
state: MODULE_ANALYZED
|
};
|
modModules[pluginAndResource] = resource;
|
|
/**
|
* plugin加载完成的回调函数
|
*
|
* @inner
|
* @param {*} value resource的值
|
*/
|
function pluginOnload(value) {
|
resource.exports = value || true;
|
modDefined(pluginAndResource);
|
}
|
|
/* jshint ignore:start */
|
/**
|
* 该方法允许plugin使用加载的资源声明模块
|
*
|
* @param {string} id 模块id
|
* @param {string} text 模块声明字符串
|
*/
|
pluginOnload.fromText = function (id, text) {
|
autoDefineModules[id] = 1;
|
new Function(text)();
|
completePreDefine(id);
|
};
|
/* jshint ignore:end */
|
|
/**
|
* 加载资源
|
*
|
* @inner
|
* @param {Object} plugin 用于加载资源的插件模块
|
*/
|
function load(plugin) {
|
var pluginRequire = baseId
|
? modModules[baseId].require
|
: actualGlobalRequire;
|
|
plugin.load(
|
idInfo.res,
|
pluginRequire,
|
pluginOnload,
|
moduleConfigGetter.call({id: pluginAndResource})
|
);
|
}
|
|
load(modGetModuleExports(idInfo.mod));
|
}
|
|
/**
|
* 配置require
|
*
|
* @param {Object} conf 配置对象
|
*/
|
globalRequire.config = function (conf) {
|
if (conf) {
|
/* eslint-disable guard-for-in */
|
for (var key in requireConf) {
|
var newValue = conf[key];
|
var oldValue = requireConf[key];
|
|
if (!newValue) {
|
continue;
|
}
|
|
if (key === 'urlArgs' && typeof newValue === 'string') {
|
requireConf.urlArgs['*'] = newValue;
|
}
|
else {
|
// 简单的多处配置还是需要支持,所以配置实现为支持二级mix
|
if (oldValue instanceof Array) {
|
oldValue.push.apply(oldValue, newValue);
|
}
|
else if (typeof oldValue === 'object') {
|
for (var k in newValue) {
|
oldValue[k] = newValue[k];
|
}
|
}
|
else {
|
requireConf[key] = newValue;
|
}
|
}
|
}
|
/* eslint-enable guard-for-in */
|
|
createConfIndex();
|
}
|
|
// 配置信息对象clone返回,避免返回结果对象被用户程序修改可能导致的问题
|
// return clone(requireConf);
|
};
|
|
/**
|
* 对象克隆,支持raw type, Array, raw Object
|
*
|
* @inner
|
* @param {*} source 要克隆的对象
|
* @return {*}
|
*/
|
// function clone(source) {
|
// var result = source;
|
|
// if (source instanceof Array) {
|
// result = [];
|
// each(source, function (item, i) {
|
// result[i] = clone(item);
|
// });
|
// }
|
// else if (typeof source === 'object') {
|
// result = {};
|
// for (var key in source) {
|
// if (source.hasOwnProperty(key)) {
|
// result[key] = clone(source[key]);
|
// }
|
// }
|
// }
|
|
// return result;
|
// }
|
|
// 初始化时需要创建配置索引
|
createConfIndex();
|
|
/**
|
* paths内部索引
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var pathsIndex;
|
|
/**
|
* packages内部索引
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var packagesIndex;
|
|
/**
|
* mapping内部索引
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var mappingIdIndex;
|
|
/**
|
* urlArgs内部索引
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var urlArgsIndex;
|
|
/**
|
* noRequests内部索引
|
*
|
* @inner
|
* @type {Array}
|
*/
|
var noRequestsIndex;
|
|
/**
|
* 将key为module id prefix的Object,生成数组形式的索引,并按照长度和字面排序
|
*
|
* @inner
|
* @param {Object} value 源值
|
* @param {boolean} allowAsterisk 是否允许*号表示匹配所有
|
* @return {Array} 索引对象
|
*/
|
function createKVSortedIndex(value, allowAsterisk) {
|
var index = kv2List(value, 1, allowAsterisk);
|
index.sort(descSorterByKOrName);
|
return index;
|
}
|
|
/**
|
* 创建配置信息内部索引
|
*
|
* @inner
|
*/
|
function createConfIndex() {
|
requireConf.baseUrl = requireConf.baseUrl.replace(/\/$/, '') + '/';
|
|
// create paths index
|
pathsIndex = createKVSortedIndex(requireConf.paths);
|
|
// create mappingId index
|
mappingIdIndex = createKVSortedIndex(requireConf.map, 1);
|
each(
|
mappingIdIndex,
|
function (item) {
|
item.v = createKVSortedIndex(item.v);
|
}
|
);
|
|
// create packages index
|
packagesIndex = [];
|
each(
|
requireConf.packages,
|
function (packageConf) {
|
var pkg = packageConf;
|
if (typeof packageConf === 'string') {
|
pkg = {
|
name: packageConf.split('/')[0],
|
location: packageConf,
|
main: 'main'
|
};
|
}
|
|
pkg.location = pkg.location || pkg.name;
|
pkg.main = (pkg.main || 'main').replace(/\.js$/i, '');
|
pkg.reg = createPrefixRegexp(pkg.name);
|
packagesIndex.push(pkg);
|
}
|
);
|
packagesIndex.sort(descSorterByKOrName);
|
|
// create urlArgs index
|
urlArgsIndex = createKVSortedIndex(requireConf.urlArgs, 1);
|
|
// create noRequests index
|
noRequestsIndex = createKVSortedIndex(requireConf.noRequests);
|
each(noRequestsIndex, function (item) {
|
var value = item.v;
|
var mapIndex = {};
|
item.v = mapIndex;
|
|
if (!(value instanceof Array)) {
|
value = [value];
|
}
|
|
each(value, function (meetId) {
|
mapIndex[meetId] = 1;
|
});
|
});
|
}
|
|
/**
|
* 对配置信息的索引进行检索
|
*
|
* @inner
|
* @param {string} value 要检索的值
|
* @param {Array} index 索引对象
|
* @param {Function} hitBehavior 索引命中的行为函数
|
*/
|
function indexRetrieve(value, index, hitBehavior) {
|
each(index, function (item) {
|
if (item.reg.test(value)) {
|
hitBehavior(item.v, item.k, item);
|
return false;
|
}
|
});
|
}
|
|
/**
|
* 将`模块标识+'.extension'`形式的字符串转换成相对的url
|
*
|
* @inner
|
* @param {string} source 源字符串
|
* @return {string} url
|
*/
|
function toUrl(source) {
|
// 分离 模块标识 和 .extension
|
var extReg = /(\.[a-z0-9]+)$/i;
|
var queryReg = /(\?[^#]*)$/;
|
var extname = '';
|
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, '');
|
}
|
|
var url = id;
|
|
// paths处理和匹配
|
var isPathMap;
|
indexRetrieve(id, pathsIndex, function (value, key) {
|
url = url.replace(key, value);
|
isPathMap = 1;
|
});
|
|
// packages处理和匹配
|
if (!isPathMap) {
|
indexRetrieve(id, packagesIndex, function (value, key, item) {
|
url = url.replace(item.name, item.location);
|
});
|
}
|
|
// 相对路径时,附加baseUrl
|
if (!/^([a-z]{2,10}:\/)?\//i.test(url)) {
|
url = requireConf.baseUrl + url;
|
}
|
|
// 附加 .extension 和 query
|
url += extname + query;
|
|
// urlArgs处理和匹配
|
indexRetrieve(id, urlArgsIndex, function (value) {
|
url += (url.indexOf('?') > 0 ? '&' : '?') + value;
|
});
|
|
return url;
|
}
|
|
/**
|
* 创建local require函数
|
*
|
* @inner
|
* @param {number} baseId 当前module id
|
* @return {Function} local require函数
|
*/
|
function createLocalRequire(baseId) {
|
var requiredCache = {};
|
function req(requireId, callback) {
|
if (typeof requireId === 'string') {
|
if (!requiredCache[requireId]) {
|
requiredCache[requireId] =
|
nativeRequire(normalize(requireId, baseId));
|
}
|
|
return requiredCache[requireId];
|
}
|
else if (requireId instanceof Array) {
|
// 分析是否有resource,取出pluginModule先
|
var pluginModules = [];
|
var pureModules = [];
|
var normalizedIds = [];
|
|
each(
|
requireId,
|
function (id, i) {
|
var idInfo = parseId(id);
|
var absId = normalize(idInfo.mod, baseId);
|
pureModules.push(absId);
|
autoDefineModules[absId] = 1;
|
|
if (idInfo.res) {
|
pluginModules.push(absId);
|
normalizedIds[i] = null;
|
}
|
else {
|
normalizedIds[i] = absId;
|
}
|
}
|
);
|
|
var noRequestModules = {};
|
each(
|
pureModules,
|
function (id) {
|
var meet;
|
indexRetrieve(
|
id,
|
noRequestsIndex,
|
function (value) {
|
meet = value;
|
}
|
);
|
|
if (meet) {
|
if (meet['*']) {
|
noRequestModules[id] = 1;
|
}
|
else {
|
each(pureModules, function (meetId) {
|
if (meet[meetId]) {
|
noRequestModules[id] = 1;
|
return false;
|
}
|
});
|
}
|
}
|
}
|
);
|
|
// 加载模块
|
nativeRequire(
|
pureModules,
|
function () {
|
/* jshint ignore:start */
|
each(normalizedIds, function (id, i) {
|
if (id == null) {
|
normalizedIds[i] = normalize(requireId[i], baseId);
|
}
|
});
|
/* jshint ignore:end */
|
|
nativeRequire(normalizedIds, callback, baseId);
|
},
|
baseId,
|
noRequestModules
|
);
|
}
|
}
|
|
/**
|
* 将[module ID] + '.extension'格式的字符串转换成url
|
*
|
* @inner
|
* @param {string} id 符合描述格式的源字符串
|
* @return {string} url
|
*/
|
req.toUrl = function (id) {
|
return toUrl(normalize(id, baseId));
|
};
|
|
return req;
|
}
|
|
/**
|
* id normalize化
|
*
|
* @inner
|
* @param {string} id 需要normalize的模块标识
|
* @param {string} baseId 当前环境的模块标识
|
* @return {string} normalize结果
|
*/
|
function normalize(id, baseId) {
|
if (!id) {
|
return '';
|
}
|
|
baseId = baseId || '';
|
var idInfo = parseId(id);
|
if (!idInfo) {
|
return id;
|
}
|
|
var resourceId = idInfo.res;
|
var moduleId = relative2absolute(idInfo.mod, baseId);
|
|
each(
|
packagesIndex,
|
function (packageConf) {
|
var name = packageConf.name;
|
if (name === moduleId) {
|
moduleId = name + '/' + packageConf.main;
|
return false;
|
}
|
}
|
);
|
|
// 根据config中的map配置进行module id mapping
|
indexRetrieve(
|
baseId,
|
mappingIdIndex,
|
function (value) {
|
|
indexRetrieve(
|
moduleId,
|
value,
|
function (mdValue, mdKey) {
|
moduleId = moduleId.replace(mdKey, mdValue);
|
}
|
);
|
|
}
|
);
|
|
if (resourceId) {
|
var mod = modGetModuleExports(moduleId);
|
resourceId = mod.normalize
|
? mod.normalize(
|
resourceId,
|
function (resId) {
|
return normalize(resId, baseId);
|
}
|
)
|
: normalize(resourceId, baseId);
|
|
moduleId += '!' + resourceId;
|
}
|
|
return moduleId;
|
}
|
|
/**
|
* 相对id转换成绝对id
|
*
|
* @inner
|
* @param {string} id 要转换的相对id
|
* @param {string} baseId 当前所在环境id
|
* @return {string} 绝对id
|
*/
|
function relative2absolute(id, baseId) {
|
if (id.indexOf('.') === 0) {
|
var basePath = baseId.split('/');
|
var namePath = id.split('/');
|
var baseLen = basePath.length - 1;
|
var nameLen = namePath.length;
|
var cutBaseTerms = 0;
|
var cutNameTerms = 0;
|
|
/* eslint-disable block-scoped-var */
|
pathLoop: for (var i = 0; i < nameLen; i++) {
|
switch (namePath[i]) {
|
case '..':
|
if (cutBaseTerms < baseLen) {
|
cutBaseTerms++;
|
cutNameTerms++;
|
}
|
else {
|
break pathLoop;
|
}
|
break;
|
case '.':
|
cutNameTerms++;
|
break;
|
default:
|
break pathLoop;
|
}
|
}
|
/* eslint-enable block-scoped-var */
|
|
basePath.length = baseLen - cutBaseTerms;
|
namePath = namePath.slice(cutNameTerms);
|
|
return basePath.concat(namePath).join('/');
|
}
|
|
return id;
|
}
|
|
/**
|
* 解析id,返回带有module和resource属性的Object
|
*
|
* @inner
|
* @param {string} id 标识
|
* @return {Object} id解析结果对象
|
*/
|
function parseId(id) {
|
var segs = id.split('!');
|
|
if (segs[0]) {
|
return {
|
mod: segs[0],
|
res: segs[1]
|
};
|
}
|
|
return null;
|
}
|
|
/**
|
* 将对象数据转换成数组,数组每项是带有k和v的Object
|
*
|
* @inner
|
* @param {Object} source 对象数据
|
* @param {boolean} keyMatchable key是否允许被前缀匹配
|
* @param {boolean} allowAsterisk 是否支持*匹配所有
|
* @return {Array.<Object>} 对象转换数组
|
*/
|
function kv2List(source, keyMatchable, allowAsterisk) {
|
var list = [];
|
for (var key in source) {
|
if (source.hasOwnProperty(key)) {
|
var item = {
|
k: key,
|
v: source[key]
|
};
|
list.push(item);
|
|
if (keyMatchable) {
|
item.reg = key === '*' && allowAsterisk
|
? /^/
|
: createPrefixRegexp(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 {HTMLScriptElement} 当前script标签
|
*/
|
function getCurrentScript() {
|
if (currentlyAddingScript) {
|
return currentlyAddingScript;
|
}
|
else if (
|
interactiveScript
|
&& interactiveScript.readyState === 'interactive'
|
) {
|
return interactiveScript;
|
}
|
|
var scripts = document.getElementsByTagName('script');
|
var scriptLen = scripts.length;
|
while (scriptLen--) {
|
var script = scripts[scriptLen];
|
if (script.readyState === 'interactive') {
|
interactiveScript = script;
|
return script;
|
}
|
}
|
}
|
|
var headElement = document.getElementsByTagName('head')[0];
|
var baseElement = document.getElementsByTagName('base')[0];
|
if (baseElement) {
|
headElement = baseElement.parentNode;
|
}
|
|
/**
|
* 向页面中插入script标签
|
*
|
* @inner
|
* @param {HTMLScriptElement} script script标签
|
*/
|
function appendScript(script) {
|
currentlyAddingScript = script;
|
|
// If BASE tag is in play, using appendChild is a problem for IE6.
|
// See: http://dev.jquery.com/ticket/2709
|
baseElement
|
? headElement.insertBefore(script, baseElement)
|
: headElement.appendChild(script);
|
|
currentlyAddingScript = null;
|
}
|
|
/**
|
* 创建id前缀匹配的正则对象
|
*
|
* @inner
|
* @param {string} prefix id前缀
|
* @return {RegExp} 前缀匹配的正则对象
|
*/
|
function createPrefixRegexp(prefix) {
|
return new RegExp('^' + prefix + '(/|$)');
|
}
|
|
/**
|
* 循环遍历数组集合
|
*
|
* @inner
|
* @param {Array} source 数组源
|
* @param {function(Array,Number):boolean} iterator 遍历函数
|
*/
|
function each(source, iterator) {
|
if (source instanceof Array) {
|
for (var i = 0, len = source.length; i < len; i++) {
|
if (iterator(source[i], i) === false) {
|
break;
|
}
|
}
|
}
|
}
|
|
/**
|
* 根据元素的k或name项进行数组字符数逆序的排序函数
|
*
|
* @inner
|
* @param {Object} a 要比较的对象a
|
* @param {Object} b 要比较的对象b
|
* @return {number} 比较结果
|
*/
|
function descSorterByKOrName(a, b) {
|
var aValue = a.k || a.name;
|
var bValue = b.k || b.name;
|
|
if (bValue === '*') {
|
return -1;
|
}
|
|
if (aValue === '*') {
|
return 1;
|
}
|
|
return bValue.length - aValue.length;
|
}
|
|
// 暴露全局对象
|
if (!define) {
|
define = globalDefine;
|
|
// 可能碰到其他形式的loader,所以,不要覆盖人家
|
if (!require) {
|
require = globalRequire;
|
}
|
|
// 如果存在其他版本的esl,在define那里就判断过了,不会进入这个分支
|
// 所以这里就不判断了,直接写
|
esl = globalRequire;
|
}
|
})(this);
|