(function(
userConfig,
defaultConfig
){
/*
summary: 概述
This is the "source loader" and is the entry point for Dojo during development. You may also load Dojo with
any AMD-compliant loader via the package main module dojo/main.
这个是一个源文件加载器,在dojo的开发过程中,它是整个应用程序的入口。 你也可以通过其它遵循AMD规范的加载器来加载Dojo, 如requirejs. 如果你采用第三方的加截器,可以不用使用本文件,而是直接加载 dojo/main
模块
description: 描述
This is the "source loader" for Dojo. It provides an AMD-compliant loader that can be configured
to operate in either synchronous or asynchronous modes. After the loader is defined, dojo is loaded
IAW the package main module dojo/main. In the event you wish to use a foreign loader, you may load dojo as a package
via the package main module dojo/main and this loader is not required; see dojo/package.json for details.
这是Dojo的一个源文件加载器,它遵循 AMD规范,可以配置成同步或异步模式。当加载器被定义好后(即用于浏览器,nodejs环境), dojo的主体会被加载进来,这主要是依据 dojo/main模块。如果想使用一个外部的
加载器(如requirejs), 你可以直接将 dojo/main进行加载,而这个loader不必加载;需要了解更多,请查看 dojo/package.json;
In order to keep compatibility with the v1.x line, this loader includes additional machinery that enables
the dojo.provide, dojo.require et al API. This machinery is loaded by default, but may be dynamically removed
via the has.js API and statically removed via the build system.
考虑到向前兼容dojo的版本, 这个加载器包含一些额外机制,如使 dojo.provide, dojo.requre; 这些机制默认情况下是包含的,但可以通过 has.js 的API动态删除或通过 dojo的build system 手动删除。
This loader includes sniffing machinery to determine the environment; the following environments are supported:
这个加载器也包含嗅探机制去发现当前使用的环境, 以下三种会被支持。
- browser
- node.js
- rhino Java 编写的一个解释器,由 Mozilla 开发
This is the so-called "source loader". As such, it includes many optional features that may be discarded by
building a customized version with the build system.
这就是所谓的源文件加载器,如上陈述, 它可能通过 build system可以去除掉一些功能,而形成你自己想要的版本。.
Design and Implementation Notes 设计和实现日记
This function defines an AMD-compliant (http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition)
loader that can be configured to operate in either synchronous or asynchronous modes.
这个功能定议一个遵循AMD规范(http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition)的加载器。 可以配置成同步或异步加载模式。
! 有空了解下AMD规范
here is a road map of the contents:
以下是这个内容的路线图。
1. Small library for use implementing the loader. 加载器会使用到的一个很小的库,即工具函数。
2. Define the has.js API; this is used throughout the loader to bracket features.定义一个has.js API; 整个加载器都会使用到它来特征检测
3. Define the node.js and rhino sniffs and sniff. 定义侦测node.js 及rhino 的嗅探器
4. Define the loader's data. 定义一个loader的数据模型
5. Define the configuration machinery. 实现配置机制
6. Define the script element sniffing machinery and sniff for configuration data. 定义一个角本元素嗅探机制,并且将嗅探的结果作为配置数据
7. Configure the loader IAW the provided user, default, and sniffing data. 依据提供的用户,默认参数及嗅探到的信息,配置一个加载器
8. Define the global require function. 定义一个全局加载函数
9. Define the module resolution machinery 定义一个模块解析机制.
10. Define the module and plugin module definition machinery 定义一个模块及插件开发规范
11. Define the script injection machinery. 定义一个角本注入机制
12. Define the window load detection. 定义一个window 加载侦测
13. Define the logging API. 定义一个日志API
14. Define the tracing API. 定义一个跟踪API, 即调试用的API
15. Define the AMD define function. 实现AMD规范中需要定义的函数
16. Define the dojo v1.x provide/require machinery--so called "legacy" modes. 为了兼容之前老的dojo, 定义provide/ require。
17. Publish global variables. 发布一些全局变量
Language and Acronyms and Idioms
束语
moduleId: a CJS(CommonJS) module identifier, (used for public APIs 对外的CommonJS 模块标识符
mid: moduleId (used internally) 对内的模块ID(标识符)
packageId: a package identifier (used for public APIs) 对外的包标识符
pid: packageId (used internally); the implied system or default package has pid==="" 对内的包id, 如是是默认的包,则pid为空字符
pack: package is used internally to reference a package object (since javascript has reserved words including "package") 包对象
prid: plugin resource identifier 插件标识符
referenceModule: 参照模块,用代码来表示更好理解, require['dojo/dom'], 而 dom里面依赖 define(["./sniff", "./_base/window"], sniff, window. 在加载sniff时, referenceModule代表的就是dom 模块.
The integer constant 1 is used in place of true and 0 in place of false 常量1及0 ,分别用作ture及false的占位符
*/
var
noop = function(){
},
isEmpty = function(it){
for(var p in it){
return 0;
}
return 1;
},
toString = {}.toString,
isFunction = function(it){
return toString.call(it) == "[object Function]";
},
isString = function(it){
return toString.call(it) == "[object String]";
},
isArray = function(it){
return toString.call(it) == "[object Array]";
},
forEach = function(vector, callback){
if(vector){
for(var i = 0; i < vector.length;){
callback(vector[i++]);
}
}
},
mix = function(dest, src){
for(var p in src){
dest[p] = src[p];
}
return dest;
},
makeError = function(error, info){
return mix(new Error(error), {src:"dojoLoader", info:info});
},
uidSeed = 1,
uid = function(){
// Returns a unique identifier (within the lifetime of the document) of the form /_d+/. 返回一个唯一标识符(文档的生命周期内),格式为 “_数字” 形式
return "_" + uidSeed++;
},
// FIXME: how to doc window.require() api FIXME 标识此注释的内容代码需要修正 TODO 标识此处功能需要实现, 这里说的是需要修正 require的api 文档
// this will be the global require function;
// 全局的 require 函数
req = function(
config,
//(object, optional) hash of configuration properties 传入的第一个值为配置属性的散列对象(可选)
dependencies, //(array of commonjs.moduleId, optional) list of modules to be loaded before applying callback 传入的第二个参数为,在调用回调函数之前,需要加载的模块id, 该模块id遵循commonjs模范(可选)
callback
//(function, optional) lambda expression to apply to module values implied by dependencies,传入一个匿名函数,当依赖的模块加载完后,会调用这个函数
){
return contextRequire(config, dependencies, callback, 0, req);
},
// the loader uses the has.js API to control feature inclusion/exclusion; define then use throughout loader 通过has.js API来包含或者排除某些特征
global = this,
doc = global.document,
element = doc && doc.createElement("DiV"),
has = req.has = function(name){
return isFunction(hasCache[name]) ? (hasCache[name] = hasCache[name](global, doc, element)) : hasCache[name];
},
hasCache = has.cache = defaultConfig.hasCache;
has.add = function(name, test, now, force){ // 添加某个测试。
(hasCache[name]===undefined || force) && (hasCache[name] = test); // 测试缓存中还没有测试结果,或者要强制覆盖原有的测试, hasCache[name]=test;
return now && has(name); //指定第三个参数为true时,立即返回检测的结果。
};
/*
以下测试是否为 nodejs环境,如果是,加载./_base/configNode.js的文件。
如果用户的配置中有指定has对象,并且存在"host-node", 那么直接返回用户指定的值。 惹没有指定,则测试是否有全局对象process, 并测试它的版本是否为node.
例:
nodejs环境
hasCache['host-node'] = 1
在这里,没有设定has.add的第三个参数。所以只是设置了hasCache['host-node']的值,而不返回,在if中,会通过has("host-node")来再获得相应的值。
*/
has.add("host-node", userConfig.has && "host-node" in userConfig.has ?
userConfig.has["host-node"] :
(typeof process == "object" && process.versions && process.versions.node && process.versions.v8));
if(has("host-node")){
// fixup the default config for node.js environment
require("./_base/configNode.js").config(defaultConfig);
// remember node's require (with respect to baseUrl==dojo's root)
defaultConfig.loaderPatch.nodeRequire = require;
}
/*
用于检测rhino环境
*/
has.add("host-rhino", userConfig.has && "host-rhino" in userConfig.has ?
userConfig.has["host-rhino"] :
(typeof load == "function" && (typeof Packages == "function" || typeof Packages == "object")));
if(has("host-rhino")){
// owing to rhino's lame feature that hides the source of the script, give the user a way to specify the baseUrl...
for(var baseUrl = userConfig.baseUrl || ".", arg, rhinoArgs = this.arguments, i = 0; i < rhinoArgs.length;){
arg = (rhinoArgs[i++] + "").split("=");
if(arg[0] == "baseUrl"){
baseUrl = arg[1];
break;
}
}
load(baseUrl + "/_base/configRhino.js");
rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs);
}
// userConfig has tests override defaultConfig has tests; do this after the environment detection because
// the environment detection usually sets some has feature values in the hasCache.
// 用户配置中的has测试会覆盖 defaultConfig 中的 has测试。 但这会发现在环境侦测之后, 因为环境侦没通常会在hasCache里设置特征检测的值。
// 总体的意思就是在确定loader使用环境后,用 userConfig中的has 替换defaultConfig指定的has测试
for(var p in userConfig.has){
has.add(p, userConfig.has[p], 0, 1);
}
//
// define the loader data 定义加载器的数据
//
// the loader will use these like symbols if the loader has the traceApi; otherwise
// define magic numbers so that modules can be provided as part of defaultConfig
/*
如果有指定traceApi(跟踪功能), 定义的变量相当于标识的符号,表示模块是在请求,到达,正在执行,执行完成等各种状态。否则,定义的数字,模块可以作为defaultConfig的一部分。
以下变量指定模块当前的状态
*/
var
requested = 1,
arrived = 2,
nonmodule = 3,
executing = 4,
executed = 5;
if(has("dojo-trace-api")){
// these make debugging nice; don't do it for production code 使调试变得容易,不要将他用于产品代码。
requested = "requested";
arrived = "arrived";
nonmodule = "not-a-module";
executing = "executing";
executed = "executed";
}
var legacyMode = 0,
sync = "sync",
xd = "xd",
syncExecStack = [],
dojoRequirePlugin = 0,
checkDojoRequirePlugin = noop,
transformToAmd = noop,
getXhr;
// 是否可以使用1.7之前的加载器
if(has("dojo-sync-loader")){
req.isXdUrl = noop;
req.initSyncLoader = function(dojoRequirePlugin_, checkDojoRequirePlugin_, transformToAmd_){
// the first dojo/_base/loader loaded gets to define these variables; they are designed to work
// in the presence of zero to many mapped dojo/_base/loaders
if(!dojoRequirePlugin){
dojoRequirePlugin = dojoRequirePlugin_;
checkDojoRequirePlugin = checkDojoRequirePlugin_;
transformToAmd = transformToAmd_;
}
return {
sync:sync,
requested:requested,
arrived:arrived,
nonmodule:nonmodule,
executing:executing,
executed:executed,
syncExecStack:syncExecStack,
modules:modules,
execQ:execQ,
getModule:getModule,
injectModule:injectModule,
setArrived:setArrived,
signal:signal,
finishExec:finishExec,
execModule:execModule,
dojoRequirePlugin:dojoRequirePlugin,
getLegacyMode:function(){return legacyMode;},
guardCheckComplete:guardCheckComplete
};
};
if(has("dom")){
// in legacy sync mode, the loader needs a minimal XHR library
var locationProtocol = location.protocol,
locationHost = location.host;
req.isXdUrl = function(url){
if(/^\./.test(url)){
// begins with a dot is always relative to page URL; therefore not xdomain
return false;
}
if(/^\/\//.test(url)){
// for v1.6- backcompat, url starting with // indicates xdomain
return true;
}
// get protocol and host
// \/+ takes care of the typical file protocol that looks like file:///drive/path/to/file
// locationHost is falsy if file protocol => if locationProtocol matches and is "file:", || will return false
var match = url.match(/^([^\/\:]+\:)\/+([^\/]+)/);
return match && (match[1] != locationProtocol || (locationHost && match[2] != locationHost));
};
// note: to get the file:// protocol to work in FF, you must set security.fileuri.strict_origin_policy to false in about:config
has.add("dojo-xhr-factory", 1);
has.add("dojo-force-activex-xhr", has("host-browser") && !doc.addEventListener && window.location.protocol == "file:");
has.add("native-xhr", typeof XMLHttpRequest != "undefined");
if(has("native-xhr") && !has("dojo-force-activex-xhr")){
getXhr = function(){
return new XMLHttpRequest();
};
}else{
// if in the browser an old IE; find an xhr
for(var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], progid, i = 0; i < 3;){
try{
progid = XMLHTTP_PROGIDS[i++];
if(new ActiveXObject(progid)){
// this progid works; therefore, use it from now on
break;
}
}catch(e){
// squelch; we're just trying to find a good ActiveX progid
// if they all fail, then progid ends up as the last attempt and that will signal the error
// the first time the client actually tries to exec an xhr
}
}
getXhr = function(){
return new ActiveXObject(progid);
};
}
req.getXhr = getXhr;
has.add("dojo-gettext-api", 1);
req.getText = function(url, async, onLoad){
var xhr = getXhr();
xhr.open('GET', fixupUrl(url), false);
xhr.send(null);
if(xhr.status == 200 || (!location.host && !xhr.status)){
if(onLoad){
onLoad(xhr.responseText, async);
}
}else{
throw makeError("xhrFailed", xhr.status);
}
return xhr.responseText;
};
}
}else{
req.async = 1;
}
//
// loader eval
//
var eval_ =
// use the function constructor so our eval is scoped close to (but not in) in the global space with minimal pollution 使用函数的构造函数,便我们定义的eval形成闭包,不会污染全局作域用。
new Function('return eval(arguments[0]);');
req.eval =
function(text, hint){
return eval_(text + "\r\n@ sourceURL=" + hint);
};
//
// loader micro events API
// loader 内部事件系统
var listenerQueues = {},
error = "error",
signal = req.signal = function(type, args){
var queue = listenerQueues[type];
// notice we run a copy of the queue; this allows listeners to add/remove
// other listeners without affecting this particular signal
forEach(queue && queue.slice(0), function(listener){
listener.apply(null, isArray(args) ? args : [args]);
});
},
on = req.on = function(type, listener){
// notice a queue is not created until a client actually connects
var queue = listenerQueues[type] || (listenerQueues[type] = []);
queue.push(listener);
return {
remove:function(){
for(var i = 0; i
if(queue[i]===listener){
queue.splice(i, 1);
return;
}
}
}
};
};
// configuration machinery; with an optimized/built defaultConfig, all configuration machinery can be discarded
// lexical variables hold key loader data structures to help with minification; these may be completely,
// one-time initialized by defaultConfig for optimized/built versions
// 实现loader的配置机制;
var
aliases
// a vector of pairs of [regexs or string, replacement] => (alias, actual)
= [],
paths
// CommonJS paths
= {},
pathsMapProg
// list of (from-path, to-path, regex, length) derived from paths;
// a "program" to apply paths; see computeMapProg
= [],
packs
// a map from packageId to package configuration object; see fixupPackageInfo
= {},
map = req.map
// AMD map config variable; dojo/_base/kernel needs req.map to figure out the scope map
= {},
mapProgs
// vector of quads as described by computeMapProg; map-key is AMD map key, map-value is AMD map value
= [],
modules
// A hash:(mid) --> (module-object) the module namespace
//
// pid: the package identifier to which the module belongs (e.g., "dojo"); "" indicates the system or default package
// mid: the fully-resolved (i.e., mappings have been applied) module identifier without the package identifier (e.g., "dojo/io/script")
// url: the URL from which the module was retrieved
// pack: the package object of the package to which the module belongs
// executed: 0 => not executed; executing => in the process of traversing deps and running factory; executed => factory has been executed
// deps: the dependency vector for this module (vector of modules objects)
// def: the factory for this module
// result: the result of the running the factory for this module
// injected: (0 | requested | arrived) the status of the module; nonmodule means the resource did not call define
// load: plugin load function; applicable only for plugins
//
// Modules go through several phases in creation:
//
// 1. Requested: some other module's definition or a require application contained the requested module in
//
its dependency vector or executing code explicitly demands a module via req.require.
//
// 2. Injected: a script element has been appended to the insert-point element demanding the resource implied by the URL
//
// 3. Loaded: the resource injected in [2] has been evaluated.
//
// 4. Defined: the resource contained a define statement that advised the loader about the module. Notice that some
//
resources may just contain a bundle of code and never formally define a module via define
//
// 5. Evaluated: the module was defined via define and the loader has evaluated the factory and computed a result.
= {},
cacheBust
// query string to append to module URLs to bust browser cache
= "",
cache
// hash:(mid | url)-->(function | string)
//
// A cache of resources. The resources arrive via a config.cache object, which is a hash from either mid --> function or
// url --> string. The url key is distinguished from the mid key by always containing the prefix "url:". url keys as provided
// by config.cache always have a string value that represents the contents of the resource at the given url. mid keys as provided
// by config.cache always have a function value that causes the same code to execute as if the module was script injected.
//
// Both kinds of key-value pairs are entered into cache via the function consumePendingCache, which may relocate keys as given
// by any mappings *iff* the config.cache was received as part of a module resource request.
//
// Further, for mid keys, the implied url is computed and the value is entered into that key as well. This allows mapped modules
// to retrieve cached items that may have arrived consequent to another namespace.
//
= {},
urlKeyPrefix
// the prefix to prepend to a URL key in the cache.
= "url:",
pendingCacheInsert
// hash:(mid)-->(function)
//
// Gives a set of cache modules pending entry into cache. When cached modules are published to the loader, they are
// entered into pendingCacheInsert; modules are then pressed into cache upon (1) AMD define or (2) upon receiving another
// independent set of cached modules. (1) is the usual case, and this case allows normalizing mids given in the pending
// cache for the local configuration, possibly relocating modules.
= {},
dojoSniffConfig
// map of configuration variables
// give the data-dojo-config as sniffed from the document (if any)
= {},
insertPointSibling
// the nodes used to locate where scripts are injected into the document
= 0;
// 确保在 dojo在 build时可配置。提供配置API,这样可以将map或者paths中的值处理为 mapProgs, pathsMapProgs, 主要是在编译时,这些配置信息都是存放在profile 文件里,可以查看profile文件的格式。 Ensures that the build is configurable
if(has("dojo-config-api")){
var consumePendingCacheInsert = function(referenceModule){
var p, item, match, now, m;
for(p in pendingCacheInsert){
item = pendingCacheInsert[p];
match = p.match(/^url\:(.+)/);
if(match){
cache[urlKeyPrefix + toUrl(match[1], referenceModule)] = item;
}else if(p=="*now"){
now = item;
}else if(p!="*noref"){
m = getModuleInfo(p, referenceModule, true);
cache[m.mid] = cache[urlKeyPrefix + m.url] = item;
}
}
if(now){
now(createRequire(referenceModule));
}
pendingCacheInsert = {};
},
escapeString = function(s){
return s.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(c){ return "\\" + c; });
},
computeMapProg = function(map, dest){
// This routine takes a map as represented by a JavaScript object and initializes dest, a vector of
// quads of (map-key, map-value, refex-for-map-key, length-of-map-key), sorted decreasing by length-
// of-map-key. The regex looks for the map-key followed by either "/" or end-of-string at the beginning
// of a the search source. Notice the map-value is irrelevant to the algorithm
/*
这段程序获得一个由Javascript 对像表示的map配置数据, 然后会初始化dest(存放结果的数组),dest中存放的值都是四维向量(map-key, map-value, map-key的正则表达式, map-key的长度)
程序会对dest中的值,根据map-key的长度进行降序排序. 正则表达式用于搜索目标对像的开头是否是may-key + "/" 或者may-key + "$" (完全匹配). 注意,map-value跟算法没有什么关系。
*/
/*
map: {
myOldApp: {
dojo: "dojo16",
dijit: "dijit16",
dojox: "dojox16"
}
}
[["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]
paths:{
"a/b": "myApp/ core/widget"
}
[["a/b", "myApp/ core/widget", RegExp /^a\/b(\/|$)/, 3]]
*/
dest.splice(0, dest.length);
for(var p in map){
dest.push([
p,
map[p],
new RegExp("^" + escapeString(p) + "(\/|$)"),
p.length]);
}
dest.sort(function(lhs, rhs){ return rhs[3] - lhs[3]; });
return dest;
},
computeAliases = function(config, dest){
/*
将键值对,转化为正则表达式形式的键值对
aliases:[
["text", "dojo/text"]
]
[[RegExp /^text$/, "dojo/text"]]
*/
forEach(config, function(pair){
// take a fixed-up copy...
//如果是字符串,则构造一个正则表达式,如果是函数或者正则表达式,直接使用键。
dest.push([isString(pair[0]) ? new RegExp("^" + escapeString(pair[0]) + "$") : pair[0], pair[1]]);
});
},
fixupPackageInfo = function(packageInfo){
// calculate the precise (name, location, main, mappings) for a package 计算每个包的准确信息,包的信息包括,名字,位置, 主文件名称, 映射情况。
/*
packages: [
{location:"lib/dojo"}, // 标注1
{ name: "dojo16", location: "lib/dojo16" },
{ name: "dijit16", location: "lib/dijit16" },
{ name: "dojox16", location: "lib/dojox16" },
{ name: "dojo", location: "lib/dojo" },
{ name: "dijit", location: "lib/dijit" },
{ name: "dojox", location: "lib/dojox" },
{name:"doh", location:"lib/util/doh"},
{ name: "myOldApp", location: "myOldApp", packageMap:{dojo:"dojo16"} },
{ name: "my", location: "my" }
]
*/
var name = packageInfo.name;
if(!name){
// packageInfo must be a string that gives the name
// 如果没有在包中指定name, 则整个包对像作为包的名称, 如标注1, name= {location:"lib/dojo"}
name = packageInfo;
packageInfo = {name:name};
}
packageInfo = mix({main:"main"}, packageInfo); //mix(dest,src)
packageInfo.location = packageInfo.location ? packageInfo.location : name; //如果没有指定包的位置,刚location为包的名称。
// packageMap is deprecated in favor of AMD map, packageMap已经被弃用,被map配置替代
if(packageInfo.packageMap){
map[name] = packageInfo.packageMap; //将packageMap值作为 map对像中的一个值。
}
if(!packageInfo.main.indexOf("./")){
packageInfo.main = packageInfo.main.substring(2); //如果main以"./"开头,则去掉"./"
}
// now that we've got a fully-resolved package object, push it into the configuration
// 现在我们获得一个完全解析后的一个包对像, 即原始的包对像可能只有名称和location,处理后的包对像,都有 name, location, main属性,有的还有packageMap,packageMap不是很重要,在这里只是将他的值添加到map里。
packs[name] = packageInfo;
},
delayedModuleConfig
// module config cannot be consumed until the loader is completely initialized; therefore, all
// module config detected during booting is memorized and applied at the end of loader initialization
// TODO: this is a bit of a kludge; all config should be moved to end of loader initialization, but
// we'll delay this chore and do it with a final loader 1.x cleanup after the 2.x loader prototyping is complete
= [],
config = function(config, booting, referenceModule){
for(var p in config){
if(p=="waitSeconds"){
req.waitms = (config[p] || 0) * 1000; //设置加载一个模块的超时时间, 单位为秒.
}
if(p=="cacheBust"){
cacheBust = config[p] ? (isString(config[p]) ? config[p] : (new Date()).getTime() + "") : ""; //添加到URL后缀的字符串。以打断浏览器缓存。
}
if(p=="baseUrl" || p=="combo"){
req[p] = config[p]; //设置baseUrl和 combo的值( dojo-combo-api是启动一些老版本的API, 可以不用设置)
}
if(has("dojo-sync-loader") && p=="async"){
// falsy or "sync" => legacy sync loader
// "xd" => sync but loading xdomain tree and therefore loading asynchronously (not configurable, set automatically by the loader)
// "legacyAsync" => permanently in "xd" by choice
// "debugAtAllCosts" => trying to load everything via script injection (not implemented)
// otherwise, must be truthy => AMD
// legacyMode: sync | legacyAsync | xd | false
//配置加载器的工作模式,AMD, 同步,或者跨域异步
var mode = config[p];
req.legacyMode = legacyMode = (isString(mode) && /sync|legacyAsync/.test(mode) ? mode : (!mode ? sync : false));
req.async = !legacyMode;
}
if(config[p]!==hasCache){
//将defautlConfig, dojoConfig, dojoSniffConfig中的配置数据存入rawConfig.
// 将原生配置存入hasCache
// accumulate raw config info for client apps which can use this to pass their own config
req.rawConfig[p] = config[p];
p!="has" && has.add("config-"+p, config[p], 0, booting);
}
}
// make sure baseUrl exists 确保存在 baseUrl
if(!req.baseUrl){
req.baseUrl = "./";
}
// make sure baseUrl ends with a slash 确保baseUrl是以"/"结尾,即表示一个文件夹路径
if(!/\/$/.test(req.baseUrl)){
req.baseUrl += "/";
}
// now do the special work for has, packages, packagePaths, paths, aliases, and cache
// 开始具体的配置工作,如has, packages, packagePaths, paths, aliases, mapProgs, pathsMapProg.
// 如果在userConfig,defaultConfig中指定了has测试, 则把相应的has测试添加到hasCache中。
for(p in config.has){
/*
var dojoConfig={
has: {
"dojo-firebug": true
}
}
*/
has.add(p, config.has[p], 0, booting);
}
//如果指定了packages, 对每个包进行处理,使每个包有固定的格式{name:**, location:**, main:**}, 如果有packageMap属性,则把值添加到map配置里. 详细请查看fixupPackageInfo.
forEach(config.packages, fixupPackageInfo);
// packagePath已经被弃用,会在2.0移除.
/*
var dojoConfig = {
packagePaths:{
"path/to/some/place":[
"myPackage",
{
name:"yourPackage",
main:"base"
}
]
}
}
相当于
packages:[{
name:"myPackage",
location:"path/to/some/place/myPackage"
},{
name:"yourPackage",
location:"path/to/some/place/youPackage"
}]
*/
for(baseUrl in config.packagePaths){
forEach(config.packagePaths[baseUrl], function(packageInfo){
var location = baseUrl + "/" + packageInfo;
//如果只指定了一个字符串,如说明中的"myPackage"
if(isString(packageInfo)){
packageInfo = {name:packageInfo};
}
packageInfo.location = location;
fixupPackageInfo(packageInfo);
});
}
// notice that computeMapProg treats the dest as a reference; therefore, if/when that variable
// is published (see dojo-publish-privates), the published variable will always hold a valid value.
// this must come after all package processing since package processing may mutate map
/*
注意computeMapProg 会将结果直接保存到dest, 如果变量被发布(可以通过require直接获得), 那么发布的变量会直接获得这个有效值
这个步骤必须在所有的package处理完成后才能进行,因为在包的处理过程中,会改变map的值。可以看上面的 packageMap.
map: {
myOldApp: {
dojo: "dojo16",
dijit: "dijit16",
dojox: "dojox16"
}
}
[["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]
*/
computeMapProg(mix(map, config.map), mapProgs);
/*
对上面的 may-key 进一步处理,即 Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"} 也转化为一个四维向量数组
[["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]
*/
forEach(mapProgs, function(item){
item[1] = computeMapProg(item[1], []);
if(item[0]=="*"){
mapProgs.star = item;
}
});
/*
计算路径映射
paths:{
"a/b": "myApp/ core/widget"
}
[["a/b", "myApp/ core/widget", RegExp /^a\/b(\/|$)/, 3]]
*/
computeMapProg(mix(paths, config.paths), pathsMapProg);
// aliases
/*
aliases:[
["text", "dojo/text"]
]
[[RegExp /^text$/, "dojo/text"]]
1. 如果是字符串,别名需要跟目标字符串完全匹配
2. 别名可以采用正则表达式的方式.
*/
computeAliases(config.aliases, aliases);
//给某个模块传递配置。如果指定了booting为1,刚在loader启动时,不进行模块配置。
// 在实际开发中,好像都不会对模块进行配置。可以忽略delayedModuleConfig 和在 dojoConfig={ config:{模块名称: 配置}}
if(booting){
delayedModuleConfig.push({config:config.config});
}else{
for(p in config.config){
var module = getModule(p, referenceModule);
module.config = mix(module.config || {}, config.config[p]);
}
}
// push in any new cache values
if(config.cache){
/*
不能理解 cache, pedingCaccheInsert, consumePendingCacheInsert.
没有找到相关的使用例子.
*/
consumePendingCacheInsert();
pendingCacheInsert = config.cache;
if(config.cache["*noref"]){
consumePendingCacheInsert();
}
}
signal("config", [config, req.rawConfig]);
};
//
// execute the various sniffs; userConfig can override and value
//
// 可以获得浏览器 user agent的信息
if(has("dojo-cdn") || has("dojo-sniff")){
// the sniff regex looks for a src attribute ending in dojo.js, optionally preceded with a path.
// match[3] returns the path to dojo.js (if any) without the trailing slash. This is used for the
// dojo location on CDN deployments and baseUrl when either/both of these are not provided
// explicitly in the config data; this is the 1.6- behavior.
var scripts = doc.getElementsByTagName("script"),
i = 0,
script, dojoDir, src, match;
while(i < scripts.length){
script = scripts[i++];
if((src = script.getAttribute("src")) && (match = src.match(/(((.*)\/)|^)dojo\.js(\W|$)/i))){
// sniff dojoDir and baseUrl
dojoDir = match[3] || "";
defaultConfig.baseUrl = defaultConfig.baseUrl || dojoDir;
// remember an insertPointSibling
insertPointSibling = script;
}
// sniff configuration on attribute in script element
if((src = (script.getAttribute("data-dojo-config") || script.getAttribute("djConfig")))){
dojoSniffConfig = req.eval("({ " + src + " })", "data-dojo-config");
// remember an insertPointSibling
insertPointSibling = script;
}
// sniff requirejs attribute
if(has("dojo-requirejs-api")){
if((src = script.getAttribute("data-main"))){
dojoSniffConfig.deps = dojoSniffConfig.deps || [src];
}
}
}
}
// 禁止将 doh(单元测试)里的配置, 混合到dojoSniffConfig ( dojo.js 中 data-dojo-config扫描到的配置)
if(has("dojo-test-sniff")){
// pass down doh.testConfig from parent as if it were a data-dojo-config
try{
if(window.parent != window && window.parent.require){
var doh = window.parent.require("doh");
doh && mix(dojoSniffConfig, doh.testConfig);
}
}catch(e){}
}
// configure the loader; let the user override defaults
req.rawConfig = {};
config(defaultConfig, 1);
// do this before setting userConfig/sniffConfig to allow userConfig/sniff overrides
if(has("dojo-cdn")){
packs.dojo.location = dojoDir;
if(dojoDir){
dojoDir += "/";
}
packs.dijit.location = dojoDir + "../dijit/";
packs.dojox.location = dojoDir + "../dojox/";
}
config(userConfig, 1);
config(dojoSniffConfig, 1);
}else{
// no config API, assume defaultConfig has everything the loader needs...for the entire lifetime of the application
paths = defaultConfig.paths;
pathsMapProg = defaultConfig.pathsMapProg;
packs = defaultConfig.packs;
aliases = defaultConfig.aliases;
mapProgs = defaultConfig.mapProgs;
modules = defaultConfig.modules;
cache = defaultConfig.cache;
cacheBust = defaultConfig.cacheBust;
// remember the default config for other processes (e.g., dojo/config)
req.rawConfig = defaultConfig;
}
if(has("dojo-combo-api")){
// 已被弃用
req.combo = req.combo || {add:noop};
var
comboPending = 0,
combosPending = [],
comboPendingTimer = null;
}
// build the loader machinery iaw configuration, including has feature tests 依据配置数据, 和特征检测,建立加载器的核功能
var
injectDependencies = function(module){
// checkComplete!=0 holds the idle signal; we're not idle if we're injecting dependencies
/*
方法签名:需要注入依赖链接的模块对像
*/
guardCheckComplete(function(){
forEach(module.deps, injectModule);
if(has("dojo-combo-api") && comboPending && !comboPendingTimer){
comboPendingTimer = setTimeout(function() {
comboPending = 0;
comboPendingTimer = null;
req.combo.done(function(mids, url) {
var onLoadCallback= function(){
// defQ is a vector of module definitions 1-to-1, onto mids
runDefQ(0, mids);
checkComplete();
};
combosPending.push(mids);
injectingModule = mids;
req.injectUrl(url, onLoadCallback, mids);
injectingModule = 0;
}, req);
}, 0);
}
});
},
contextRequire = function(a1, a2, a3, referenceModule, contextRequire){
/*
a1: 一般为config object, 配置对像, 可以通过require对loader进行配置.
a2: 函数的依赖数组。
a3: 加载完依赖后,要执行的回调函数
referenceModule: 引用模块,举例来说,如dom.js里面, define(["./sniff","./_base/window"], 当要加载dom模块时,需要先加载sniff及 _base/window, 那么在这时相对于sniff时,引用模块就是dojo/dom, 而dom的引用模块是全局函数require,那么引用模块为0
contextRequire: 加载器的上下文,如果是在全局中使用require来加载模块,那么contextRequire为require函数。如果在定义模块时,如定义一个"my/app", define(['dom','require'],function(dom,require){require(require('dojo/on')}),即使用局部加载器,那么contextRequire为"my/app"模块的require函数, 上下文是通过createContext方法创建。
*/
var module, syntheticMid;
/*
1. a1 如果是一个模块mid字符串,则返回相对应的模块
2. a1 不是字符串,也不是数组(依赖或者要加载的模块列表。则为配置对像或者函数。require({ aliases:[["text", "dojo/text"]]})
3. a1 第一个参数为数组,代表直接要加载的模块。
*/
if(isString(a1)){
// 方法签名是一个模块模块标识符,用于检测模块是否在Modules
/*
getModule获得一个模块信息,第一个参数是模块 MID, 第二个为引用模块(在模块标识符解析时,如果模块标识符为相对路径,那么先获得引用模块的路径,在计算相对路径)。 第三个参数是指, mid是否存在Modules, 如果没有存在,则立即反回false,而不会把空上模块信息添加到Modules.
*/
module = getModule(a1, referenceModule, true);
if(module && module.executed){
return module.result;
}
throw makeError("undefinedModule", a1);
}
if(!isArray(a1)){
// a1 是一个配置对像, 调用上一节讲过的配置机制中的config方法。
config(a1, 0, referenceModule);
// 处理参数, 如果a2为请求加载的模块列表,a3为回调函数,而a1经过上一步骤已经完成了配置,将 a2,a3的位置提前, 方便下一步的处理; (a2, a3) may be (dependencies, callback)
a1 = a2;
a2 = a3;
}
if(isArray(a1)){
// 方法签名为 (requestList [,callback])
// 如果第一个参数为数组(1. require(['dojo/dom',function(){}], 2. require({}, 'dojo/dom',function(){}), 第二种情况会经过上一步处理参数。
if(!a1.length){
a2 && a2(); //如果数组为空数组,直接调用回调函数。
}else{
syntheticMid = "require*" + uid(); //每一个require都会有一个唯一标识.
// 解析数组中的模块请求
for(var mid, deps = [], i = 0; i < a1.length;){
mid = a1[i++];
deps.push(getModule(mid, referenceModule)); //根据mid获取一个模块, 并添加到依赖列表,如果是在define中定义的依赖,则referenceModule为定义的模块。referenceModule主要是提供相对路径
}
// construct a synthetic module to control execution of the requestList, and, optionally, callback
/*
构造一个人造模块(不是真实定义的模块),用于控制执行请求的模块链表(deps), 及回调函数。
可以这样理解,Dojo loader中都是以模块为操作对象,指定模块的依赖,工厂函数,状态(注入,加载,执行)。 如果仅仅通过 require(['dojo/dom'],function(dom){}), 而不构造一个人造模块。那么需要判断分别为require, define定义不同的加载依赖链接的方法。
makeModuleInfo:
方法签名(pid, mid, pack, url)
return: {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0}; //executed: 模块未执行, def: 模块的工厂函数
*/
module = mix(makeModuleInfo("", syntheticMid, 0, ""), {
injected: arrived, //在模块中添加角本注入状态
deps: deps, //将 require中的请求链接,做为人造模块的依赖链表
def: a2 || noop, //如果有指定回调函数,则工厂函数为回调函数,否则为空函数
require: referenceModule ? referenceModule.require : req, // 指定加载器。如有指定了引用模块,那么调用局部加载器。如果没有指定,调用全局加载器
gc: 1 //garbage collect 垃级回收。在加载完依赖和执行完回调函数后,销毁这个构造的模块。
});
//将构造的这个模块添加到模块数组中.(modules 变量在上一节的配置机制里定义)
modules[module.mid] = module;
// checkComplete!=0 holds the idle signal; we're not idle if we're injecting dependencies
/*
注入依赖列表。
*/
injectDependencies(module);
// try to immediately execute
// if already traversing a factory tree, then strict causes circular dependency to abort the execution; maybe
// it's possible to execute this require later after the current traversal completes and avoid the circular dependency.
// ...but *always* insist on immediate in synch mode
/*
尝试直接运行模块的工厂函数。
strict 主要是在遍历工厂函数时(checkComplete), 避免循环依赖.可以通过下面的例子来理解.
require(['my/app'],function(app){
})
** my/app.js
define(['./app1'],function(app1){
require(['dojo/dom'])
console.log('a')
})
** my/app1.js
define(['require'],function(require){
require(["./app"])
})
第一个require因为没有遍历到工厂函数组成的树对像。即还没有调用checkComplete(), 所以checkCompleteGuard为0
第二个 require, 即app1.js里面的require. 因为正在处于app1 模块的树对像,所以checkCompleteGuard 为 1. 可以查看 checkComplete().
checkComplete 会在guardCheckComplete中,先将checkCompleteGuard++, 然后遍历所有的exeQ中的模块,并执行模块execModule(运行工厂方法)。当执行到app1模块时,发现require(["./app"]).
那么就会调用require方法,构造一个人造模块require*_1 此时checkCompleteGuard为1, leagacyMode!=sync.所以 strict 为1.
在执行require*_1时,exeModule要先执行my/app模块的工厂函数
*/
var strict = checkCompleteGuard && legacyMode!=sync;
guardCheckComplete(function(){
execModule(module, strict);
});
if(!module.executed){
// some deps weren't on board or circular dependency detected and strict; therefore, push into the execQ
execQ.push(module);
}
checkComplete();
}
}
return contextRequire;
},
createRequire = function(module){
if(!module){
return req;
}
var result = module.require;
if(!result){
result = function(a1, a2, a3){
return contextRequire(a1, a2, a3, module, result);
};
module.require = mix(result, req);
result.module = module;
result.toUrl = function(name){
return toUrl(name, module);
};
result.toAbsMid = function(mid){
return toAbsMid(mid, module);
};
if(has("dojo-undef-api")){
result.undef = function(mid){
req.undef(mid, module);
};
}
if(has("dojo-sync-loader")){
result.syncLoadNls = function(mid){
var nlsModuleInfo = getModuleInfo(mid, module),
nlsModule = modules[nlsModuleInfo.mid];
if(!nlsModule || !nlsModule.executed){
cached = cache[nlsModuleInfo.mid] || cache[urlKeyPrefix + nlsModuleInfo.url];
if(cached){
evalModuleText(cached);
nlsModule = modules[nlsModuleInfo.mid];
}
}
return nlsModule && nlsModule.executed && nlsModule.result;
};
}
}
return result;
},
execQ =
// The list of modules that need to be evaluated.
[],
defQ =
// 每次调用了define函数后,都会把这个define函数的参数,组成一个数组,添加到defQ中,格式为:[mid, dependencs, facotry], 然后在runDefQ中会弹出数组的元素。
[],
waiting =
// The set of modules upon which the loader is waiting for definition to arrive
{},
setRequested = function(module){
module.injected = requested;
waiting[module.mid] = 1;
if(module.url){
waiting[module.url] = module.pack || 1;
}
startTimer();
},
setArrived = function(module){
module.injected = arrived;
delete waiting[module.mid];
if(module.url){
delete waiting[module.url];
}
if(isEmpty(waiting)){
clearTimer();
has("dojo-sync-loader") && legacyMode==xd && (legacyMode = sync);
}
},
execComplete = req.idle =
// says the loader has completed (or not) its work
function(){
return !defQ.length && isEmpty(waiting) && !execQ.length && !checkCompleteGuard;
},
runMapProg = function(targetMid, map){
// search for targetMid in map; return the map item if found; falsy otherwise
if(map){
for(var i = 0; i < map.length; i++){
if(map[i][2].test(targetMid)){
return map[i];
}
}
}
return 0;
},
compactPath = function(path){
/*
路径处理函数,返回一个简洁的路径
传入的path可能为 MID, 或者referenceModule+".." +MID;
*/
var result = [],
segment, lastSegment;
path = path.replace(/\\/g, '/').split('/'); // 将path中的反斜线,夫换为 linux下的路径形式
while(path.length){
/*
1. path 中包含 ".." 时去除掉 紧靠 ".." 的路径 如 dojo/dom/../sniff 会变为dojo/sniff;
2. path 中包含 "." 时,直接跳过
*/
segment = path.shift();
if(segment==".." && result.length && lastSegment!=".."){
result.pop();
lastSegment = result[result.length - 1];
}else if(segment!="."){
result.push(lastSegment= segment);
} // else ignore "."
}
return result.join("/");
},
makeModuleInfo = function(pid, mid, pack, url){
/*
方法签名:
pid: String, 包的标识符
mid: String,模块标识符
pack: Object, 包的详细信息 { main="main", name="dojo", location="lib/dojo"}
url: String, 模块解析后的地址
return: Object
{pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0}; //executed: 模块未执行, def: 模块的工厂函数
*/
if(has("dojo-sync-loader")){
var xd= req.isXdUrl(url);
return {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0, isXd:xd, isAmd:!!(xd || (packs[pid] && packs[pid].isAmd))};
}else{
return {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0}; //executed: 模块未执行, def: 模块的工厂函数
}
},
getModuleInfo_ = function(mid, referenceModule, packs, modules, baseUrl, mapProgs, pathsMapProg, aliases, alwaysCreate){
// 模块标识符解析函数, 将一个字符解析为实际的路径。
// 通过传递参数来代替使用词法变量(查看词法作用域,例如,不是modules, 不是直接访问loader的本地变量), 使得 getModuleInfo_ 可以被loader单独使用(例如, builder)
// 在这种情况下 (builder) 会非常有用。 getModuleInfo 不会返回已以存在的模块引用, 而总是创建一个新的。
// arguments are passed instead of using lexical variables so that this function my be used independent of the loader (e.g., the builder)
// alwaysCreate is useful in this case so that getModuleInfo never returns references to real modules owned by the loader
/*
mid: String 模块Id标识符
referenceModule: Object 参考模块
packs: Dojo Config里面的packages包对象。
modules: 已经存在的模块,
baseUrl: 根路径,
mapProgs: 配置变量map或者packageMap,
pathsMapProg: 配置变量paths,
aliases 配置变量aliases的对象
alwaysCreate: 如果指定为true, 一直创建新的文档,默认为undefined.
*/
/*
当指定了map对象时,
packages:[
{name:"dojo16", location:"lib/dojo16"},
{name:"dijit16", location:"lib/dijit16"},
{name:"dojox16", location:"lib/dojox16"},
{name:"dojo",location:"lib/dojo"},
{name:"myOldApp",location:"myOldApp"},
{name:"my", location:"my"}
],
map:{
myOldApp:{
dojo:"dojo16",
dijit:"dijit16",
dojox:"dojox16"
}
}
输出如下, console.log(mapProgs)
[["myOldApp", [["dijit", "dijit16", RegExp /^dijit(\/|$)/, 5], ["dojox", "dojox16", RegExp /^dojox(\/|$)/, 5], ["dojo", "dojo16", RegExp /^dojo(\/|$)/, 4]], RegExp /^myOldApp(\/|$)/, 8]]
*/
var pid, pack, midInPackage, mapItem, url, result, isRelative, requestedMid;
requestedMid = mid;
isRelative = /^\./.test(mid); // 以 "." 开头的mid为相对路径,主要用于package.
if(/(^\/)|(\:)|(\.js$)/.test(mid) || (isRelative && !referenceModule)){
// absolute path or protocol of .js filetype, or relative path but no reference module and therefore relative to page
// whatever it is, it's not a module but just a URL of some sort
// note: pid===0 indicates the routine is returning an unmodified mid
/*
绝对路径,以 "/"开头,或者是一种协议如http:,或者是以 .js 结尾的文件类型, 或者是 相对路径( 以 ".")开头,而没有referenceModule.
这里请求的可能是任意数量的javascript代码,而不是一AMD规范的模块。
注意: 如果 pid===0 标志着程序会返回一个未修改的mid.
*/
return makeModuleInfo(0, mid, 0, mid);
}else{
// relative module ids are relative to the referenceModule; get rid of any dots 以"."开头的对头模块标识是相对于 referenceModule(解释参考术语名词). 而忽略其它的点。
mid = compactPath(isRelative ? (referenceModule.mid + "/../" + mid) : mid);
if(/^\./.test(mid)){
// require['dojo/../.d/dom'] 不知道这个函数是不是要处理这种极端的问题
throw makeError("irrationalPath", mid);
}
// at this point, mid is an absolute mid
// map the mid
/*
!** 待解决
*/
if(referenceModule){
mapItem = runMapProg(referenceModule.mid, mapProgs);
}
mapItem = mapItem || mapProgs.star;
mapItem = mapItem && runMapProg(mid, mapItem[1]);
if(mapItem){
mid = mapItem[1] + mid.substring(mapItem[3]);
}
match = mid.match(/^([^\/]+)(\/(.+))?$/);
pid = match ? match[1] : "";
if((pack = packs[pid])){
mid = pid + "/" + (midInPackage = (match[3] || pack.main));
}else{
pid = "";
}
// search aliases
var candidateLength = 0,
candidate = 0;
forEach(aliases, function(pair){
var match = mid.match(pair[0]);
if(match && match.length>candidateLength){
candidate = isFunction(pair[1]) ? mid.replace(pair[0], pair[1]) : pair[1];
}
});
if(candidate){
return getModuleInfo_(candidate, 0, packs, modules, baseUrl, mapProgs, pathsMapProg, aliases, alwaysCreate);
}
result = modules[mid];
if(result){
return alwaysCreate ? makeModuleInfo(result.pid, result.mid, result.pack, result.url) : modules[mid];
}
}
// get here iff the sought-after module does not yet exist; therefore, we need to compute the URL given the
// fully resolved (i.e., all relative indicators and package mapping resolved) module id
/*
此时 寻找加载的模块还没有存在,因些我们需要对过完全解析的模块id(如,完成所有的相对标识及包映射) 来计算它的URL
*/
// note: pid!==0 indicates the routine is returning a url that has .js appended unmodified mid 注意: 当 pid!==0时, 表明程序会返回未变的mid, 并在mid后添加.js的URL
mapItem = runMapProg(mid, pathsMapProg); // 配置变量中的paths.
if(mapItem){
url = mapItem[1] + mid.substring(mapItem[3]);
}else if(pid){
url = pack.location + "/" + midInPackage;
}else if(has("config-tlmSiblingOfDojo")){
url = "../" + mid;
}else{
url = mid;
}
// if result is not absolute, add baseUrl
if(!(/(^\/)|(\:)/.test(url))){
url = baseUrl + url;
}
url += ".js";
return makeModuleInfo(pid, mid, pack, compactPath(url));
},
getModuleInfo = function(mid, referenceModule, fromPendingCache){
return getModuleInfo_(mid, referenceModule, packs, modules, req.baseUrl, fromPendingCache ? [] : mapProgs, fromPendingCache ? [] : pathsMapProg, fromPendingCache ? [] : aliases);
},
resolvePluginResourceId = function(plugin, prid, referenceModule){
return plugin.normalize ? plugin.normalize(prid, function(mid){return toAbsMid(mid, referenceModule);}) : toAbsMid(prid, referenceModule);
},
dynamicPluginUidGenerator = 0,
getModule = function(mid, referenceModule, immediate){
// compute and optionally construct (if necessary) the module implied by the mid with respect to referenceModule
var match, plugin, prid, result;
match = mid.match(/^(.+?)\!(.*)$/);
if(match){
// name was !
plugin = getModule(match[1], referenceModule, immediate);
if(has("dojo-sync-loader") && legacyMode == sync && !plugin.executed){
injectModule(plugin);
if(plugin.injected===arrived && !plugin.executed){
guardCheckComplete(function(){
execModule(plugin);
});
}
if(plugin.executed){
promoteModuleToPlugin(plugin);
}else{
// we are in xdomain mode for some reason
execQ.unshift(plugin);
}
}
if(plugin.executed === executed && !plugin.load){
// executed the module not knowing it was a plugin
promoteModuleToPlugin(plugin);
}
// if the plugin has not been loaded, then can't resolve the prid and must assume this plugin is dynamic until we find out otherwise
if(plugin.load){
prid = resolvePluginResourceId(plugin, match[2], referenceModule);
mid = (plugin.mid + "!" + (plugin.dynamic ? ++dynamicPluginUidGenerator + "!" : "") + prid);
}else{
prid = match[2];
mid = plugin.mid + "!" + (++dynamicPluginUidGenerator) + "!waitingForPlugin";
}
result = {plugin:plugin, mid:mid, req:createRequire(referenceModule), prid:prid};
}else{
result = getModuleInfo(mid, referenceModule);
}
return modules[result.mid] || (!immediate && (modules[result.mid] = result));
},
toAbsMid = req.toAbsMid = function(mid, referenceModule){
//将给定的mid, 通过模块标识符解析后,获得一个绝的模块标识符。
return getModuleInfo(mid, referenceModule).mid;
},
toUrl = req.toUrl = function(name, referenceModule){
/*
获得一个资源的路径
name: string, 由模块标识符作为前缀,如在dojo包内有一张图片,地址为js/lib/dojo/main.jpg, 那么指定的名称可以为 dojo/main.jpg.
referenceModule: 哪个模块调用了req.toUrl, 如 my/app.js 里面调用require.toUrl("./main.jpg"), 那么 referenceModule为 my/app;
*/
var moduleInfo = getModuleInfo(name+"/x", referenceModule),
url= moduleInfo.url;
return fixupUrl(moduleInfo.pid===0 ?
// if pid===0, then name had a protocol or absolute path; either way, toUrl is the identify function in such cases
name :
// "/x.js" since getModuleInfo automatically appends ".js" and we appended "/x" to make name look like a module id
url.substring(0, url.length-5)
);
},
nonModuleProps = {
injected: arrived,
executed: executed,
def: nonmodule,
result: nonmodule
},
makeCjs = function(mid){
return modules[mid] = mix({mid:mid}, nonModuleProps);
},
cjsRequireModule = makeCjs("require"),
cjsExportsModule = makeCjs("exports"),
cjsModuleModule = makeCjs("module"),
runFactory = function(module, args){
req.trace("loader-run-factory", [module.mid]);
var factory = module.def,
result;
has("dojo-sync-loader") && syncExecStack.unshift(module);
if(has("config-dojo-loader-catches")){
try{
result= isFunction(factory) ? factory.apply(null, args) : factory;
}catch(e){
signal(error, module.result = makeError("factoryThrew", [module, e]));
}
}else{
result= isFunction(factory) ? factory.apply(null, args) : factory;
}
module.result = result===undefined && module.cjs ? module.cjs.exports : result;
has("dojo-sync-loader") && syncExecStack.shift(module);
},
abortExec = {},
defOrder = 0,
promoteModuleToPlugin = function(pluginModule){
var plugin = pluginModule.result;
pluginModule.dynamic = plugin.dynamic;
pluginModule.normalize = plugin.normalize;
pluginModule.load = plugin.load;
return pluginModule;
},
resolvePluginLoadQ = function(plugin){
// plugins is a newly executed module that has a loadQ waiting to run
// step 1: traverse the loadQ and fixup the mid and prid; remember the map from original mid to new mid
// recall the original mid was created before the plugin was on board and therefore it was impossible to
// compute the final mid; accordingly, prid may or may not change, but the mid will definitely change
var map = {};
forEach(plugin.loadQ, function(pseudoPluginResource){
// manufacture and insert the real module in modules
var prid = resolvePluginResourceId(plugin, pseudoPluginResource.prid, pseudoPluginResource.req.module),
mid = plugin.dynamic ? pseudoPluginResource.mid.replace(/waitingForPlugin$/, prid) : (plugin.mid + "!" + prid),
pluginResource = mix(mix({}, pseudoPluginResource), {mid:mid, prid:prid, injected:0});
if(!modules[mid]){
// create a new (the real) plugin resource and inject it normally now that the plugin is on board
injectPlugin(modules[mid] = pluginResource);
} // else this was a duplicate request for the same (plugin, rid) for a nondynamic plugin
// pluginResource is really just a placeholder with the wrong mid (because we couldn't calculate it until the plugin was on board)
// mark is as arrived and delete it from modules; the real module was requested above
map[pseudoPluginResource.mid] = modules[mid];
setArrived(pseudoPluginResource);
delete modules[pseudoPluginResource.mid];
});
plugin.loadQ = 0;
// step2: replace all references to any placeholder modules with real modules
var substituteModules = function(module){
for(var replacement, deps = module.deps || [], i = 0; i
replacement = map[deps[i].mid];
if(replacement){
deps[i] = replacement;
}
}
};
for(var p in modules){
substituteModules(modules[p]);
}
forEach(execQ, substituteModules);
},
finishExec = function(module){
req.trace("loader-finish-exec", [module.mid]);
module.executed = executed;
module.defOrder = defOrder++;
has("dojo-sync-loader") && forEach(module.provides, function(cb){ cb(); });
if(module.loadQ){
// the module was a plugin
promoteModuleToPlugin(module);
resolvePluginLoadQ(module);
}
// remove all occurrences of this module from the execQ
for(i = 0; i < execQ.length;){
if(execQ[i] === module){
execQ.splice(i, 1);
}else{
i++;
}
}
// delete references to synthetic modules
if (/^require\*/.test(module.mid)) {
delete modules[module.mid];
}
},
circleTrace = [],
execModule = function(module, strict){
// run the dependency vector, then run the factory for module
// 运行依赖向量, 然后运行模块的工厂函数
if(module.executed === executing){
req.trace("loader-circular-dependency", [circleTrace.concat(module.mid).join("->")]);
return (!module.def || strict) ? abortExec : (module.cjs && module.cjs.exports);
}
// at this point the module is either not executed or fully executed
if(!module.executed){
/*
依赖的模块刚刚请求完成,即完成角本注入,module.injected == "requested" 还不是received. 所以 def == 0
*/
if(!module.def){
return abortExec; //反回 {}
}
var mid = module.mid,
deps = module.deps || [],
arg, argResult,
args = [],
i = 0;
if(has("dojo-trace-api")){
circleTrace.push(mid);
req.trace("loader-exec-module", ["exec", circleTrace.length, mid]);
}
// for circular dependencies, assume the first module encountered was executed OK
// modules that circularly depend on a module that has not run its factory will get
// the pre-made cjs.exports===module.result. They can take a reference to this object and/or
// add properties to it. When the module finally runs its factory, the factory can
// read/write/replace this object. Notice that so long as the object isn't replaced, any
// reference taken earlier while walking the deps list is still valid.
module.executed = executing;
while((arg = deps[i++])){
argResult = ((arg === cjsRequireModule) ? createRequire(module) :
((arg === cjsExportsModule) ? module.cjs.exports :
((arg === cjsModuleModule) ? module.cjs :
execModule(arg, strict))));
if(argResult === abortExec){
module.executed = 0;
req.trace("loader-exec-module", ["abort", mid]);
has("dojo-trace-api") && circleTrace.pop();
return abortExec;
}
args.push(argResult);
}
runFactory(module, args);
finishExec(module);
has("dojo-trace-api") && circleTrace.pop();
}
// at this point the module is guaranteed fully executed
return module.result;
},
checkCompleteGuard = 0,
guardCheckComplete = function(proc){
try{
checkCompleteGuard++;
proc();
}finally{
checkCompleteGuard--;
}
if(execComplete()){
signal("idle", []);
}
},
checkComplete = function(){
// keep going through the execQ as long as at least one factory is executed
// plugins, recursion, cached modules all make for many execution path possibilities
if(checkCompleteGuard){
return;
}
guardCheckComplete(function(){
checkDojoRequirePlugin();
for(var currentDefOrder, module, i = 0; i < execQ.length;){
currentDefOrder = defOrder;
module = execQ[i];
execModule(module);
if(currentDefOrder!=defOrder){
// defOrder was bumped one or more times indicating something was executed (note, this indicates
// the execQ was modified, maybe a lot (for example a later module causes an earlier module to execute)
checkDojoRequirePlugin();
i = 0;
}else{
// nothing happened; check the next module in the exec queue
i++;
}
}
});
};
if(has("dojo-undef-api")){
req.undef = function(moduleId, referenceModule){
// In order to reload a module, it must be undefined (this routine) and then re-requested. 为了重新加载一个模块, 它必须是未定义的(当前程序), 然后在重新加载。
// This is useful for testing frameworks (at least). 至少在测试框架时很有用。
var module = getModule(moduleId, referenceModule);
setArrived(module);
mix(module, {def:0, executed:0, injected:0, node:0});
};
}
// 以下是支持加载跨域的模块(支持加载不同域名下的模块)。
if(has("dojo-inject-api")){
if(has("dojo-loader-eval-hint-url")===undefined){
has.add("dojo-loader-eval-hint-url", 1);
}
var fixupUrl= function(url){
url += ""; // make sure url is a Javascript string (some paths may be a Java string)
return url + (cacheBust ? ((/\?/.test(url) ? "&" : "?") + cacheBust) : "");
},
injectPlugin = function(
module
){
// injects the plugin module given by module; may have to inject the plugin itself
var plugin = module.plugin;
if(plugin.executed === executed && !plugin.load){
// executed the module not knowing it was a plugin
promoteModuleToPlugin(plugin);
}
var onLoad = function(def){
module.result = def;
setArrived(module);
finishExec(module);
checkComplete();
};
if(plugin.load){
plugin.load(module.prid, module.req, onLoad);
}else if(plugin.loadQ){
plugin.loadQ.push(module);
}else{
// the unshift instead of push is important: we don't want plugins to execute as
// dependencies of some other module because this may cause circles when the plugin
// loadQ is run; also, generally, we want plugins to run early since they may load
// several other modules and therefore can potentially unblock many modules
plugin.loadQ = [module];
execQ.unshift(plugin);
injectModule(plugin);
}
},
// for IE, injecting a module may result in a recursive execution if the module is in the cache
cached = 0,
injectingModule = 0,//表示当前正在注入的模块对像, 即正在创建一个script元素,并把它添到insertPointSibling之前。添加完后把injectingModule赋值回 0.
injectingCachedModule = 0,
evalModuleText = function(text, module){
// see def() for the injectingCachedModule bracket; it simply causes a short, safe circuit
if(has("config-stripStrict")){
text = text.replace(/"use strict"/g, '');
}
injectingCachedModule = 1;
if(has("config-dojo-loader-catches")){
try{
if(text===cached){
cached.call(null);
}else{
req.eval(text, has("dojo-loader-eval-hint-url") ? module.url : module.mid);
}
}catch(e){
signal(error, makeError("evalModuleThrew", module));
}
}else{
if(text===cached){
cached.call(null);
}else{
req.eval(text, has("dojo-loader-eval-hint-url") ? module.url : module.mid);
}
}
injectingCachedModule = 0;
},
injectModule = function(module){
// Inject the module. In the browser environment, this means appending a script element into
// the document; in other environments, it means loading a file.
//
// If in synchronous mode, then get the module synchronously if it's not xdomainLoading.
/*
注入模块。 在浏览器环境下, 它会在文档中添加一个script 元素, 在其它环境下,它会直接加载一个文件。
如果是同步模式下, 而没有指定跨域加载,则会以同步的方法获得一个模块。
*/
var mid = module.mid,
url = module.url;
if(module.executed || module.injected || waiting[mid] || (module.url && ((module.pack && waiting[module.url]===module.pack) || waiting[module.url]==1))){
return;
}
setRequested(module); //设置inject的状态为requested, 并将模块添加到waiting对像中。表明这个模块正在等到下载(从服务器请求这个模块文件)
// dojo-combo-api 已弃用
if(has("dojo-combo-api")){
var viaCombo = 0;
if(module.plugin && module.plugin.isCombo){
// a combo plugin; therefore, must be handled by combo service
// the prid should have already been converted to a URL (if required by the plugin) during
// the normalize process; in any event, there is no way for the loader to know how to
// to the conversion; therefore the third argument is zero
req.combo.add(module.plugin.mid, module.prid, 0, req);
viaCombo = 1;
}else if(!module.plugin){
viaCombo = req.combo.add(0, module.mid, module.url, req);
}
if(viaCombo){
comboPending= 1;
return;
}
}
// 如果模块为插件,比如dojo/text!my/app.html, getModule方法会返回一个包含 plugin属性的对像,了解更多,请查看getModule方法。
if(module.plugin){
injectPlugin(module);
return;
} // else a normal module (not a plugin)
// 模块在加载完成后的回调函数。
var onLoadCallback = function(){
runDefQ(module); //运行模块的工厂函数
if(module.injected !== arrived){
// the script that contained the module arrived and has been executed yet
// nothing was added to the defQ (so it wasn't an AMD module) and the module
// wasn't marked as arrived by dojo.provide (so it wasn't a v1.6- module);
// therefore, it must not have been a module; adjust state accordingly
if(has("dojo-enforceDefine")){
signal(error, makeError("noDefine", module));
return;
}
setArrived(module);
mix(module, nonModuleProps);
req.trace("loader-define-nonmodule", [module.url]);
}
if(has("dojo-sync-loader") && legacyMode){
// must call checkComplete even in for sync loader because we may be in xdomainLoading mode;
// but, if xd loading, then don't call checkComplete until out of the current sync traversal
// in order to preserve order of execution of the dojo.required modules
!syncExecStack.length && checkComplete();
}else{
checkComplete();
}
};
/*
cache 对像是的配置机制中定义的对像,用于缓存已加载的资源。可以查看cache变量声明时的注释。
*/
cached = cache[mid] || cache[urlKeyPrefix + module.url];
if(cached){
req.trace("loader-inject", ["cache", module.mid, url]);
evalModuleText(cached, module);
onLoadCallback();
return;
}
if(has("dojo-sync-loader") && legacyMode){
if(module.isXd){
// switch to async mode temporarily; if current legacyMode!=sync, then is must be one of {legacyAsync, xd, false}
legacyMode==sync && (legacyMode = xd);
// fall through and load via script injection
}else if(module.isAmd && legacyMode!=sync){
// fall through and load via script injection
}else{
// mode may be sync, xd/legacyAsync, or async; module may be AMD or legacy; but module is always located on the same domain
var xhrCallback = function(text){
if(legacyMode==sync){
// the top of syncExecStack gives the current synchronously executing module; the loader needs
// to know this if it has to switch to async loading in the middle of evaluating a legacy module
// this happens when a modules dojo.require's a module that must be loaded async because it's xdomain
// (using unshift/shift because there is no back() methods for Javascript arrays)
syncExecStack.unshift(module);
evalModuleText(text, module);
syncExecStack.shift();
// maybe the module was an AMD module
runDefQ(module);
// legacy modules never get to defineModule() => cjs and injected never set; also evaluation implies executing
if(!module.cjs){
setArrived(module);
finishExec(module);
}
if(module.finish){
// while synchronously evaluating this module, dojo.require was applied referencing a module
// that had to be loaded async; therefore, the loader stopped answering all dojo.require
// requests so they could be answered completely in the correct sequence; module.finish gives
// the list of dojo.requires that must be re-applied once all target modules are available;
// make a synthetic module to execute the dojo.require's in the correct order
// compute a guaranteed-unique mid for the synthetic finish module; remember the finish vector; remove it from the reference module
// TODO: can we just leave the module.finish...what's it hurting?
var finishMid = mid + "*finish",
finish = module.finish;
delete module.finish;
def(finishMid, ["dojo", ("dojo/require!" + finish.join(",")).replace(/\./g, "/")], function(dojo){
forEach(finish, function(mid){ dojo.require(mid); });
});
// unshift, not push, which causes the current traversal to be reattempted from the top
execQ.unshift(getModule(finishMid));
}
onLoadCallback();
}else{
text = transformToAmd(module, text);
if(text){
evalModuleText(text, module);
onLoadCallback();
}else{
// if transformToAmd returned falsy, then the module was already AMD and it can be script-injected
// do so to improve debugability(even though it means another download...which probably won't happen with a good browser cache)
injectingModule = module;
req.injectUrl(fixupUrl(url), onLoadCallback, module);
injectingModule = 0;
}
}
};
req.trace("loader-inject", ["xhr", module.mid, url, legacyMode!=sync]);
if(has("config-dojo-loader-catches")){
try{
req.getText(url, legacyMode!=sync, xhrCallback);
}catch(e){
signal(error, makeError("xhrInjectFailed", [module, e]));
}
}else{
req.getText(url, legacyMode!=sync, xhrCallback);
}
return;
}
} // else async mode or fell through in xdomain loading mode; either way, load by script injection
req.trace("loader-inject", ["script", module.mid, url]);
injectingModule = module;
req.injectUrl(fixupUrl(url), onLoadCallback, module);
injectingModule = 0;
},
defineModule = function(module, deps, def){
/*
定义一个模块
1. 跟def函数的区别:def函数即全局define函数。只是构造一个数组[mid, dependences, factory], 并添加到defQ中。
2. 跟getModule的区别, getModule是获得一个对像 {pid="dojo", mid="dojo/dom", pack={...}, more...} 并把这个对像存入到 modules对像中, 而没有依赖deps,工厂函数def。
*/
req.trace("loader-define-module", [module.mid, deps]);
if(has("dojo-combo-api") && module.plugin && module.plugin.isCombo){
// the module is a plugin resource loaded by the combo service
// note: check for module.plugin should be enough since normal plugin resources should
// not follow this path; module.plugin.isCombo is future-proofing belt and suspenders
module.result = isFunction(def) ? def() : def;
setArrived(module);
finishExec(module);
return module;
}
var mid = module.mid;
if(module.injected === arrived){
signal(error, makeError("multipleDefine", module));
return module;
}
mix(module, {
deps: deps,
def: def,
cjs: { //CommonJS
id: module.mid,
uri: module.url,
exports: (module.result = {}),
setExports: function(exports){
module.cjs.exports = exports;
},
config:function(){
return module.config;
}
}
});
// resolve deps with respect to this module
for(var i = 0; deps[i]; i++){
deps[i] = getModule(deps[i], module);
}
// 可以忽略
if(has("dojo-sync-loader") && legacyMode && !waiting[mid]){
// the module showed up without being asked for; it was probably in a