-
Notifications
You must be signed in to change notification settings - Fork 8
/
require.js
351 lines (313 loc) · 10.5 KB
/
require.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
/**
* Created by youngwind on 2016/12/12.
*/
let require, define;
(function (global) {
if (global !== window) {
console.error('当前环境非浏览器环境。');
return;
}
let mid = 0; // 模块id
let tid = 0; // 任务id
let modules = {}; // 模块列表集合
let tasks = {};
let mapDepToModuleOrTask = {}; // 依赖→模块映射map
window.modules = modules; // 调试语句
window.tasks = tasks;
window.mapDepToModuleOrTask = mapDepToModuleOrTask; // 调试语句
/**
* 入口文件的函数
* @param dep {Array} 依赖模块数组(可省略)
* @param cb {Function} 成功回调函数
* @param errorFn {Function} 失败回调函数(可省略)
*/
require = function (dep, cb, errorFn) {
// 缺省参数处理
if (isFunction(dep)) {
cb = dep;
dep = undefined;
}
let task = new Task(dep, cb, errorFn);
task.analyzeDep();
};
/**
* 定义一个模块
* @param name {String} 模块名称(可省略)
* @param dep {Array} 依赖模块数组(可省略)
* @param cb {Function} 成功回调函数, 必须带return
* @param errorFn {Function} 失败回调函数(可省略)
*/
define = function (name, dep, cb, errorFn) {
// 缺省参数处理
if (isFunction(name)) {
// 只有传了回调
cb = name;
name = getCurrentModuleName();
} else if (Array.isArray(name) && isFunction(dep)) {
// 传了依赖和回调
cb = dep;
dep = name;
name = getCurrentModuleName();
} else if (isString(name) && Array.isArray(name) && isFunction(cb)) {
// 传了名字,依赖和回调
}
let module = modules[name];
module.name = name;
module.dep = dep;
module.cb = cb;
module.errorFn = errorFn;
module.analyzeDep();
};
// 模块状态的定义
Module.STATUS = {
INITED: 1, // 初始化完成
FETCHING: 2, // 正在网络请求
FETCHED: 3, // 网络请求结束(此状态暂时用不到)
EXECUTING: 4, // 准备开始运算模块
EXECUTED: 5, // 模块运算完毕
ERROR: 6 // 模块发生错误
};
/**
* 模块对象的构造函数
* @param name {String} 模块名
* @param dep {Array} 模块依赖
* @param cb {Function} 成功回调函数
* @param errorFn {Function} 失败回调函数
* @constructor
*/
function Module(name, dep, cb, errorFn) {
this.mid = ++mid;
this.init(name, dep, cb, errorFn);
this.fetch();
}
/**
* 模块初始化
* @param name {String} 模块名
* @param dep {Array} 模块依赖
* @param cb {Function} 成功回调函数
* @param errorFn {Function} 失败回调函数
*/
Module.prototype.init = function (name, dep, cb, errorFn) {
this.name = name;
this.src = moduleNameToModulePath(name);
this.dep = dep;
this.cb = cb;
this.errorFn = errorFn;
this.callHook('INITED');
};
/**
* 启动fetch任务
*/
Module.prototype.fetch = function () {
let scriptNode = document.createElement('script');
scriptNode.type = 'text/javascript';
scriptNode.src = this.src;
scriptNode.onerror = this.fetchFail.bind(this);
document.body.appendChild(scriptNode);
this.callHook('FETCHING');
};
/**
* 模块获取失败
*/
Module.prototype.fetchFail = function () {
console.error(`模块${this.name}获取失败, url为${this.src}`);
this.callHook('ERROR');
};
/**
* 分析模块的依赖
* 1. 计算模块依赖的数量:depCount
* 2. 生成依赖→模块映射表: mapDepToModuleOrTask
*/
Module.prototype.analyzeDep = function () {
let depCount = this.dep ? this.dep.length : 0;
// 处理dep中包含'require'的特殊情况
let requireInDep = (this.dep || []).indexOf('require');
if (requireInDep !== -1) {
depCount--;
this.requireInDep = requireInDep;
this.dep.splice(requireInDep, 1);
}
// 处理循环依赖情况
let cycleArray = this.checkCycle();
if (cycleArray) {
depCount = depCount - cycleArray.length;
}
if (depCount === 0) {
this.execute();
return;
}
Object.defineProperty(this, 'depCount', {
get() {
return depCount;
},
set(newDepCount) {
depCount = newDepCount;
if (newDepCount === 0) {
if (this.mid) {
console.log(`模块${this.name}的依赖已经全部准备好`);
} else if (this.tid) {
console.log(`任务${this.tid}的依赖已经全部准备好`);
}
this.execute();
}
}
});
this.depCount = depCount;
if (!this.depCount) return;
this.dep.forEach((depModuleName) => {
if (!modules[depModuleName]) {
let module = new Module(depModuleName);
modules[module.name] = module;
}
if (!mapDepToModuleOrTask[depModuleName]) {
mapDepToModuleOrTask[depModuleName] = [];
}
mapDepToModuleOrTask[depModuleName].push(this);
});
};
/**
* 检查模块循环依赖
* @returns {Array|undefined} 如果模块出现循环依赖的话,返回循环依赖的模块(以数组形式)
*/
Module.prototype.checkCycle = function () {
let cycleDep = [];
for (let depModuleName of (this.dep || [])) {
if (mapDepToModuleOrTask[this.name] && mapDepToModuleOrTask[this.name].indexOf(modules[depModuleName]) !== -1) {
cycleDep.push(depModuleName);
}
}
return cycleDep.length ? cycleDep : undefined;
};
/**
* 运算模块
*/
Module.prototype.execute = function () {
this.callHook('EXECUTING');
// 根据依赖数组向依赖模块收集exports当做参数
let arg = (this.dep || []).map((dep) => {
return modules[dep].exports;
});
// 插入require到回调函数的参数列表中
if (this.requireInDep !== -1 && this.requireInDep !== undefined) {
arg.splice(this.requireInDep, 0, require);
}
this.exports = this.cb.apply(this, arg);
this.callHook('EXECUTED');
if (this.tid) {
console.log(`任务${this.tid}执行完成`);
} else if (this.mid) {
console.log(`模块${this.name}执行完成`);
}
};
/**
* 状态机:触发模块的状态转移
* 当该模块处于运算完成状态时, 查找依赖→模块映射表, 修改相应的模块的depCount
* @param mStatus {String} 模块的状态
*/
Module.prototype.callHook = function (mStatus) {
let status = Module.STATUS[mStatus];
if (!this.status) {
Object.defineProperty(this, 'status', {
get () {
return status;
},
set (newStatus) {
status = newStatus;
if (status === 5) {
// 该模块已经executed
let depedModules = mapDepToModuleOrTask[this.name];
if (!depedModules) return;
depedModules.forEach((module) => {
setTimeout(() => {
module.depCount--;
});
});
}
}
})
} else {
this.status = status;
}
};
/**
* 任务构造函数
* 任务是与模块不一样的概念。每调用一次require函数,相当于新建一个任务
* 而任务与模块又有类似的地方,所以我让任务继承模块
* @param dep {Array} 依赖数组
* @param cb {Function} 成功回调函数
* @param errorFn {Function} 失败回调函数
* @constructor
*/
function Task(dep, cb, errorFn) {
this.tid = ++tid;
this.init(dep, cb, errorFn);
}
Task.prototype = Object.create(Module.prototype);
/**
* 初始化任务
* @param dep {Array} 依赖数组
* @param cb {Function} 成功回调函数
* @param errorFn {Function} 失败回调函数
*/
Task.prototype.init = function (dep, cb, errorFn) {
this.dep = dep;
this.cb = cb;
this.errorFn = errorFn;
tasks[this.tid] = this;
};
// 启动主入口加载流程
let mainEntryModule = new Module(getMainEntryModuleName());
modules[mainEntryModule.name] = mainEntryModule;
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////下面是工具类函数/////////////////////////////////////
/**
* 获取主入口模块的模块名
* @returns {String} 主入口模块名
*/
function getMainEntryModuleName() {
let dataMain = document.currentScript.getAttribute('data-main');
return modulePathToModuleName(dataMain);
}
/**
* 获取当前正在执行的模块的模块名
* @returns {String}
*/
function getCurrentModuleName() {
let src = document.currentScript.getAttribute('src');
return modulePathToModuleName(src);
}
/**
* 将模块的路径装换成模块名
* @param path {String} 模块路径
* @returns {String} 模块名
*/
function modulePathToModuleName(path) {
let reg = /\w*.js/;
let output = reg.exec(path);
if (!output) {
return path;
} else {
return output[0].split('.')[0];
}
}
/**
* 将模块名转换成模块路径
* @param name {String} 模块名
* @returns {String} 模块路径
*/
function moduleNameToModulePath(name) {
let reg = /\w*.js/;
let output = reg.exec(name);
if (!output) {
return `./${name}.js`;
} else {
return name;
}
}
function isFunction(fn) {
return typeof fn === 'function';
}
function isString(str) {
return typeof str === 'string';
}
})(this);