forked from RubyLouvre/avalon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path17 loader.js
669 lines (638 loc) · 23.6 KB
/
17 loader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
/*********************************************************************
* AMD加载器 *
**********************************************************************/
//https://www.devbridge.com/articles/understanding-amd-requirejs/
//http://maxogden.com/nested-dependencies.html
var modules = avalon.modules = {
"domReady!": {
exports: avalon,
state: 3
},
"avalon": {
exports: avalon,
state: 4
}
}
//Object(modules[id]).state拥有如下值
// undefined 没有定义
// 1(send) 已经发出请求
// 2(loading) 已经被执行但还没有执行完成,在这个阶段define方法会被执行
// 3(loaded) 执行完毕,通过onload/onreadystatechange回调判定,在这个阶段checkDeps方法会执行
// 4(execute) 其依赖也执行完毕, 值放到exports对象上,在这个阶段fireFactory方法会执行
modules.exports = modules.avalon
new function() {
var loadings = [] //正在加载中的模块列表
var factorys = [] //放置define方法的factory函数
var rjsext = /\.js$/i
var name2url = {}
function makeRequest(name, config) {
//1. 去掉资源前缀
var res = "js"
name = name.replace(/^(\w+)\!/, function(a, b) {
res = b
return ""
})
if (res === "ready") {
log("debug: ready!已经被废弃,请使用domReady!")
res = "domReady"
}
//2. 去掉querystring, hash
var query = ""
name = name.replace(rquery, function(a) {
query = a
return ""
})
//3. 去掉扩展名
var suffix = "." + res
var ext = /js|css/.test(suffix) ? suffix : ""
name = name.replace(/\.[a-z0-9]+$/g, function(a) {
if (a === suffix) {
ext = a
return ""
} else {
return a
}
})
var req = avalon.mix({
query: query,
ext: ext,
res: res,
name: name,
toUrl: toUrl
}, config)
req.toUrl(name)
return req
}
function fireRequest(req) {
var name = req.name
var res = req.res
//1. 如果该模块已经发出请求,直接返回
var module = modules[name]
var urlNoQuery = name && req.urlNoQuery
if (module && module.state >= 3) {
return name
}
module = modules[urlNoQuery]
if (module && module.state >= 3) {
innerRequire(module.deps || [], module.factory, urlNoQuery)
return urlNoQuery
}
if (name && !module) {
module = modules[urlNoQuery] = {
id: urlNoQuery,
state: 1 //send
}
var wrap = function(obj) {
resources[res] = obj
obj.load(name, req, function(a) {
if (arguments.length && a !== void 0) {
module.exports = a
}
module.state = 4
checkDeps()
})
}
if (!resources[res]) {
innerRequire([res], wrap)
} else {
wrap(resources[res])
}
}
return name ? urlNoQuery : res + "!"
}
//核心API之一 require
var requireQueue = []
var isUserFirstRequire = false
innerRequire = avalon.require = function(array, factory, parentUrl, defineConfig) {
if (!isUserFirstRequire) {
requireQueue.push(avalon.slice(arguments))
if (arguments.length <= 2) {
isUserFirstRequire = true
var queue = requireQueue.splice(0, requireQueue.length), args
while (args = queue.shift()) {
innerRequire.apply(null, args)
}
}
return
}
if (!Array.isArray(array)) {
avalon.error("require方法的第一个参数应为数组 " + array)
}
var deps = [] // 放置所有依赖项的完整路径
var uniq = {}
var id = parentUrl || "callback" + setTimeout("1")
defineConfig = defineConfig || {}
defineConfig.baseUrl = kernel.baseUrl
var isBuilt = !!defineConfig.built
if (parentUrl) {
defineConfig.parentUrl = parentUrl.substr(0, parentUrl.lastIndexOf("/"))
defineConfig.mapUrl = parentUrl.replace(rjsext, "")
}
if (isBuilt) {
var req = makeRequest(defineConfig.defineName, defineConfig)
id = req.urlNoQuery
} else {
array.forEach(function(name) {
var req = makeRequest(name, defineConfig)
var url = fireRequest(req) //加载资源,并返回该资源的完整地址
if (url) {
if (!uniq[url]) {
deps.push(url)
uniq[url] = "司徒正美" //去重
}
}
})
}
var module = modules[id]
if (!module || module.state !== 4) {
modules[id] = {
id: id,
deps: isBuilt ? array.concat() : deps,
factory: factory || noop,
state: 3
}
}
if (!module) {
//如果此模块是定义在另一个JS文件中, 那必须等该文件加载完毕, 才能放到检测列队中
loadings.push(id)
}
checkDeps()
}
//核心API之二 require
innerRequire.define = function(name, deps, factory) { //模块名,依赖列表,模块本身
if (typeof name !== "string") {
factory = deps
deps = name
name = "anonymous"
}
if (!Array.isArray(deps)) {
factory = deps
deps = []
}
var config = {
built: !isUserFirstRequire, //用r.js打包后,所有define会放到requirejs之前
defineName: name
}
var args = [deps, factory, config]
factory.require = function(url) {
args.splice(2, 0, url)
if (modules[url]) {
modules[url].state = 3 //loaded
var isCycle = false
try {
isCycle = checkCycle(modules[url].deps, url)
} catch (e) {
}
if (isCycle) {
avalon.error(url + "模块与之前的模块存在循环依赖,请不要直接用script标签引入" + url + "模块")
}
}
delete factory.require //释放内存
innerRequire.apply(null, args) //0,1,2 --> 1,2,0
}
//根据标准,所有遵循W3C标准的浏览器,script标签会按标签的出现顺序执行。
//老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。
//较新的浏览器中(IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,
//下载可以是并行的,但是执行顺序还是按照标签出现的顺序。
//但如果script标签是动态插入的, 就未必按照先请求先执行的原则了,目测只有firefox遵守
//唯一比较一致的是,IE10+及其他标准浏览器,一旦开始解析脚本, 就会一直堵在那里,直接脚本解析完毕
//亦即,先进入loading阶段的script标签(模块)必然会先进入loaded阶段
var url = config.built ? "unknown" : getCurrentScript()
if (url) {
var module = modules[url]
if (module) {
module.state = 2
}
factory.require(url)
} else {//合并前后的safari,合并后的IE6-9走此分支
factorys.push(factory)
}
}
//核心API之三 require.config(settings)
innerRequire.config = kernel
//核心API之四 define.amd 标识其符合AMD规范
innerRequire.define.amd = modules
//==========================对用户配置项进行再加工==========================
var allpaths = kernel["orig.paths"] = {}
var allmaps = kernel["orig.map"] = {}
var allpackages = kernel["packages"] = []
var allargs = kernel["orig.args"] = {}
avalon.mix(plugins, {
paths: function(hash) {
avalon.mix(allpaths, hash)
kernel.paths = makeIndexArray(allpaths)
},
map: function(hash) {
avalon.mix(allmaps, hash)
var list = makeIndexArray(allmaps, 1, 1)
avalon.each(list, function(_, item) {
item.val = makeIndexArray(item.val)
})
kernel.map = list
},
packages: function(array) {
array = array.concat(allpackages)
var uniq = {}
var ret = []
for (var i = 0, pkg; pkg = array[i++]; ) {
pkg = typeof pkg === "string" ? {name: pkg} : pkg
var name = pkg.name
if (!uniq[name]) {
var url = pkg.location ? pkg.location : joinPath(name, pkg.main || "main")
url = url.replace(rjsext, "")
ret.push(pkg)
uniq[name] = pkg.location = url
pkg.reg = makeMatcher(name)
}
}
kernel.packages = ret.sort()
},
urlArgs: function(hash) {
if (typeof hash === "string") {
hash = {"*": hash}
}
avalon.mix(allargs, hash)
kernel.urlArgs = makeIndexArray(allargs, 1)
},
baseUrl: function(url) {
if (!isAbsUrl(url)) {
var baseElement = head.getElementsByTagName("base")[0]
if (baseElement) {
head.removeChild(baseElement)
}
var node = DOC.createElement("a")
node.href = url
url = getFullUrl(node, "href")
if (baseElement) {
head.insertBefore(baseElement, head.firstChild)
}
}
if (url.length > 3)
kernel.baseUrl = url
},
shim: function(obj) {
for (var i in obj) {
var value = obj[i]
if (Array.isArray(value)) {
value = obj[i] = {
deps: value
}
}
if (!value.exportsFn && (value.exports || value.init)) {
value.exportsFn = makeExports(value)
}
}
kernel.shim = obj
}
})
//==============================内部方法=================================
function checkCycle(deps, nick) {
//检测是否存在循环依赖
for (var i = 0, id; id = deps[i++]; ) {
if (modules[id].state !== 4 &&
(id === nick || checkCycle(modules[id].deps, nick))) {
return true
}
}
}
function checkFail(node, onError, fuckIE) {
var id = trimQuery(node.src) //检测是否死链
node.onload = node.onreadystatechange = node.onerror = null
if (onError || (fuckIE && modules[id] && !modules[id].state)) {
setTimeout(function() {
head.removeChild(node)
node = null // 处理旧式IE下的循环引用问题
})
log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state))
} else {
return true
}
}
function checkDeps() {
//检测此JS模块的依赖是否都已安装完毕,是则安装自身
loop: for (var i = loadings.length, id; id = loadings[--i]; ) {
var obj = modules[id],
deps = obj.deps
if (!deps)
continue
for (var j = 0, key; key = deps[j]; j++) {
var k = name2url[key]
if (k) {
key = deps[j] = k
}
if (Object(modules[key]).state !== 4) {
continue loop
}
}
//如果deps是空对象或者其依赖的模块的状态都是2
if (obj.state !== 4) {
loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
fireFactory(obj.id, obj.deps, obj.factory)
checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好
}
}
}
var rreadyState = DOC.documentMode >= 8 ? /loaded/ : /complete|loaded/
function loadJS(url, id, callback) {
//通过script节点加载目标模块
var node = DOC.createElement("script")
node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点
var timeID
var supportLoad = "onload" in node
var onEvent = supportLoad ? "onload" : "onreadystatechange"
function onload() {
if (!"1"[0] && !timeID) {
return timeID = setTimeout(onload, 150)
}
if (supportLoad || rreadyState.test(node.readyState)) {
clearTimeout(timeID)
var factory = factorys.pop()
factory && factory.require(id)
if (callback) {
callback()
}
if (checkFail(node, false, !supportLoad)) {
log("debug: 已成功加载 " + url)
id && loadings.push(id)
checkDeps()
}
}
}
node[onEvent] = onload
node.onerror = function() {
checkFail(node, true)
}
head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null
node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错
log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围
}
var resources = innerRequire.plugins = {
//三大常用资源插件 js!, css!, text!, ready!
ready: {
load: noop
},
js: {
load: function(name, req, onLoad) {
var url = req.url
var id = req.urlNoQuery
var shim = kernel.shim[name.replace(rjsext, "")]
if (shim) { //shim机制
innerRequire(shim.deps || [], function() {
var args = avalon.slice(arguments)
loadJS(url, id, function() {
onLoad(shim.exportsFn ? shim.exportsFn.apply(0, args) : void 0)
})
})
} else {
loadJS(url, id)
}
}
},
css: {
load: function(name, req, onLoad) {
var url = req.url
var node = DOC.createElement("link")
node.rel = "stylesheet"
node.href = url
head.insertBefore(node, head.firstChild)
log("debug: 已成功加载 " + url)
onLoad()
}
},
text: {
load: function(name, req, onLoad) {
var url = req.url
var xhr = getXHR()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var status = xhr.status;
if (status > 399 && status < 600) {
avalon.error(url + " 对应资源不存在或没有开启 CORS")
} else {
log("debug: 已成功加载 " + url)
onLoad(xhr.responseText)
}
}
}
xhr.open("GET", url, true)
if ("withCredentials" in xhr) {//这是处理跨域
xhr.withCredentials = true
}
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")//告诉后端这是AJAX请求
xhr.send()
log("debug: 正准备加载 " + url)
}
}
}
innerRequire.checkDeps = checkDeps
var rquery = /(\?[^#]*)$/
function trimQuery(url) {
return (url || "").replace(rquery, "")
}
function isAbsUrl(path) {
//http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
return /^(?:[a-z]+:)?\/\//i.test(String(path))
}
function getFullUrl(node, src) {
return"1"[0] ? node[src] : node.getAttribute(src, 4)
}
function getCurrentScript() {
// inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js
var stack
try {
a.b.c() //强制报错,以便捕获e.stack
} catch (e) { //safari5的sourceURL,firefox的fileName,它们的效果与e.stack不一样
stack = e.stack
if (!stack && window.opera) {
//opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
}
}
if (stack) {
/**e.stack最后一行在所有支持的浏览器大致如下:
*chrome23:
* at http://113.93.50.63/data.js:4:1
*firefox17:
*@http://113.93.50.63/query.js:4
*opera12:http://www.oldapps.com/opera.php?system=Windows_XP
*@http://113.93.50.63/data.js:4
*IE10:
* at Global code (http://113.93.50.63/data.js:4:1)
* //firefox4+ 可以用document.currentScript
*/
stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
return trimQuery(stack.replace(/(:\d+)?:\d+$/i, "")) //去掉行号与或许存在的出错字符起始位置
}
var nodes = head.getElementsByTagName("script") //只在head标签中寻找
for (var i = nodes.length, node; node = nodes[--i]; ) {
if (node.className === subscribers && node.readyState === "interactive") {
var url = getFullUrl(node, "src")
return node.className = trimQuery(url)
}
}
}
function fireFactory(id, deps, factory) {
var module = Object(modules[id])
module.state = 4
for (var i = 0, array = [], d; d = deps[i++]; ) {
d = name2url[d] || d
if (d === "exports") {
var obj = module.exports || (module.exports = {})
array.push(obj)
} else {
array.push(modules[d].exports)
}
}
var ret = factory.apply(window, array)
if (ret !== void 0) {
module.exports = ret
}
delete module.factory
return ret
}
function toUrl(id) {
if (id.indexOf(this.res + "!") === 0) {
id = id.slice(this.res.length + 1) //处理define("css!style",[], function(){})的情况
}
var url = id
//1. 是否命中paths配置项
var usePath = 0
var baseUrl = this.baseUrl
var rootUrl = this.parentUrl || baseUrl
eachIndexArray(id, kernel.paths, function(value, key) {
url = url.replace(key, value)
usePath = 1
})
//2. 是否命中packages配置项
if (!usePath) {
eachIndexArray(id, kernel.packages, function(value, key, item) {
url = url.replace(item.name, item.location)
})
}
//3. 是否命中map配置项
if (this.mapUrl) {
eachIndexArray(this.mapUrl, kernel.map, function(array) {
eachIndexArray(url, array, function(mdValue, mdKey) {
url = url.replace(mdKey, mdValue)
rootUrl = baseUrl
})
})
}
var ext = this.ext
if (ext && usePath && url.slice(-ext.length) === ext) {
url = url.slice(0, -ext.length)
}
//4. 转换为绝对路径
if (!isAbsUrl(url)) {
rootUrl = this.built || /^\w/.test(url) ? baseUrl : rootUrl
url = joinPath(rootUrl, url)
}
//5. 还原扩展名,query
var urlNoQuery = url + ext
url = urlNoQuery + this.query
//6. 处理urlArgs
eachIndexArray(id, kernel.urlArgs, function(value) {
url += (url.indexOf("?") === -1 ? "?" : "&") + value;
})
this.url = url
return this.urlNoQuery = urlNoQuery
}
function makeIndexArray(hash, useStar, part) {
//创建一个经过特殊算法排好序的数组
var index = hash2array(hash, useStar, part)
index.sort(descSorterByName)
return index
}
function makeMatcher(prefix) {
return new RegExp('^' + prefix + '(/|$)')
}
function makeExports(value) {
return function() {
var ret
if (value.init) {
ret = value.init.apply(window, arguments)
}
return ret || (value.exports && getGlobal(value.exports))
}
}
function hash2array(hash, useStar, part) {
var array = [];
for (var key in hash) {
if (hash.hasOwnProperty(key)) {
var item = {
name: key,
val: hash[key]
}
array.push(item)
item.reg = key === "*" && useStar ? /^/ : makeMatcher(key)
if (part && key !== "*") {
item.reg = new RegExp('\/' + key.replace(/^\//, "") + '(/|$)')
}
}
}
return array
}
function eachIndexArray(moduleID, array, matcher) {
array = array || []
for (var i = 0, el; el = array[i++]; ) {
if (el.reg.test(moduleID)) {
matcher(el.val, el.name, el)
return false
}
}
}
// 根据元素的name项进行数组字符数逆序的排序函数
function descSorterByName(a, b) {
var aaa = a.name
var bbb = b.name
if (bbb === "*") {
return -1
}
if (aaa === "*") {
return 1
}
return bbb.length - aaa.length
}
var rdeuce = /\/\w+\/\.\./
function joinPath(a, b) {
if (a.charAt(a.length - 1) !== "/") {
a += "/"
}
if (b.slice(0, 2) === "./") { //相对于兄弟路径
return a + b.slice(2)
}
if (b.slice(0, 2) === "..") { //相对于父路径
a += b
while (rdeuce.test(a)) {
a = a.replace(rdeuce, "")
}
return a
}
if (b.slice(0, 1) === "/") {
return a + b.slice(1)
}
return a + b
}
function getGlobal(value) {
if (!value) {
return value
}
var g = window
value.split(".").forEach(function(part) {
g = g[part]
})
return g
}
var mainNode = DOC.scripts[DOC.scripts.length - 1]
var dataMain = mainNode.getAttribute("data-main")
if (dataMain) {
plugins.baseUrl(dataMain)
var href = kernel.baseUrl
kernel.baseUrl = href.slice(0, href.lastIndexOf("/") + 1)
loadJS(href.replace(rjsext, "") + ".js")
} else {
var loaderUrl = trimQuery(getFullUrl(mainNode, "src"))
kernel.baseUrl = loaderUrl.slice(0, loaderUrl.lastIndexOf("/") + 1)
}
}