AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器。但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置。这篇文章中我们来添加对config的中baseUrl和packages的支持。API设计如下:

 require.config({
baseUrl: "./",
packages: [{
name: "more",
location: "./more"
}, {
name: "mass",
location: "../"
}, {
name: "wab",
location: "../../../"
}]
});

  主要原则是将baseUrl和packages中location的路径转化为绝对路径。核心算法如下:

  翻译成代码则为:

 function getRoute(base, target) {
var bts = base.replace(/\/$/, "").split('/'); //base dir
var tts = target.split('/'); //target parts
while (isDefined(tts[0])) {
if (tts[0] === '.') {
return bts.join('/') + '/' + tts.slice(1).join('/');
} else if (tts[0] === '..') {
bts.pop();
tts.shift();
} else {
return bts.join('/') + '/' + tts.join('/');
}
}
};

  

  剩下的处理就变得简单起来,首先得到baseUrl的绝对路径,然后根据baseUrl得到各个package中location的绝对路径。代码如下:

global.require.config = function(config) {
this.parsedConfig = {};
if (config.baseUrl) {
var currentUrl = getCurrentScript();
var parts = currentUrl.split('/');
parts.pop();
var currentDir = parts.join('/');
this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
}
var burl = this.parsedConfig.baseUrl;
// 得到baseUrl后,location相对baseUrl定位
this.parsedConfig.packages = [];
if (config.packages) {
for (var i = 0, len = config.packages.length; i < len; i++) {
var pck = config.packages[i];
var cp = {
name: pck.name,
location: getRoute(burl, pck.location)
}
this.parsedConfig.packages.push(cp);
}
} console.log(this.parsedConfig);
}

  

  到了这里模块的依赖模块Id就不用再使用绝对路径了,可以按照正常AMD规范中的来了。如:

define(["aaa",
"bbb",
"ccc",
"fff"],function(a,b,c,f){
$.log("已加载ddd模块", 7);
return {
bbb: b,
ddd: "ddd",
length: arguments.length
}
})

  那么问题来了,这个时候module仓库的key该变成生么样子呢?继续保持原来的绝对路径形式,还是使用上文中的moduleId(aaa、bbb、ccc、fff)。答案是前者;使用后者的话,如果一个依赖是相对路径,比如:"./utils",可能会有多个模块都依赖这个id,但这些模块未必是需要同一个utils文件。所以我们程序中对require函数需要做一些修改,将deps中的moduleId转化为绝对路径。

  // dep为非绝对路径形式,而modules的key仍然需要绝对路径
deps = deps.map(function(dep) {
var rel = "";
if (/^Bodhi/.test(id)) {
rel = global.require.parsedConfig.baseUrl;
} else {
var parts = parent.split('/');
parts.pop();
rel = parts.join('/');
}
return getModuleUrl(dep, rel);
});

  getModuleUrl函数的处理方式为:

  • 如果dep在某一package中,则将package的location作为参考目录
  • 如果dep像相对路径,则将baseUrl作为参考目录
  • 以上两种除外,则使用baseUrl来拼接路径

  代码如下:

 function getModuleUrl(moduleId, relative) {
function getPackage(nm) {
for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
var pck = require.parsedConfig.packages[i];
if (nm === pck.name) {
return pck;
}
}
return false;
}
var mts = moduleId.split('/');
var pck = getPackage(mts[0]);
if (pck) {
mts.shift();
return getRoute(pck.location, mts.join('/'));
} else if (mts[0] === '.' || mts[0] === '..') {
return getRoute(relative, moduleId);
} else {
return getRoute(require.parsedConfig.baseUrl, moduleId);
}
}

  

  到目前为止我们加载器已经支持了config中的baseUrl和packages,下篇文章我们让它来支持paths与shim。

  加载器整体代码如下:

(function(global){
global.$ = {
log: function(m) {
console.log(m);
}
};
global = global || window;
modules = {};
loadings = [];
loadedJs = [];
//module: id, state, factory, result, deps;
global.require = function(deps, callback, parent){
var id = parent || "Bodhi" + Date.now();
var cn = 0, dn = deps.length;
var args = []; // dep为非绝对路径形式,而modules的key仍然需要绝对路径
deps = deps.map(function(dep) {
var rel = "";
if (/^Bodhi/.test(id)) {
rel = global.require.parsedConfig.baseUrl;
} else {
var parts = parent.split('/');
parts.pop();
rel = parts.join('/');
}
return getModuleUrl(dep, rel);
}); var module = {
id: id,
deps: deps,
factory: callback,
state: 1,
result: null
};
modules[id] = module; deps.forEach(function(dep) {
if (modules[dep] && modules[dep].state === 2) {
cn++
args.push(modules[dep].result);
} else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
loadJS(dep);
loadedJs.push(dep);
}
});
if (cn === dn) {
callFactory(module);
} else {
loadings.push(id);
checkDeps();
}
}; global.require.config = function(config) {
this.parsedConfig = {};
if (config.baseUrl) {
var currentUrl = getCurrentScript();
var parts = currentUrl.split('/');
parts.pop();
var currentDir = parts.join('/');
this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
}
var burl = this.parsedConfig.baseUrl;
// 得到baseUrl后,location相对baseUrl定位
this.parsedConfig.packages = [];
if (config.packages) {
for (var i = 0, len = config.packages.length; i < len; i++) {
var pck = config.packages[i];
var cp = {
name: pck.name,
location: getRoute(burl, pck.location)
}
this.parsedConfig.packages.push(cp);
}
} console.log(this.parsedConfig);
} global.define = function(deps, callback) {
var id = getCurrentScript();
if (modules[id]) {
console.error('multiple define module: ' + id);
} require(deps, callback, id);
}; function getRoute(base, target) {
var bts = base.replace(/\/$/, "").split('/'); //base dir
var tts = target.split('/'); //target parts
while (isDefined(tts[0])) {
if (tts[0] === '.') {
return bts.join('/') + '/' + tts.slice(1).join('/');
} else if (tts[0] === '..') {
bts.pop();
tts.shift();
} else {
return bts.join('/') + '/' + tts.join('/');
}
}
}; function isDefined(v) {
return v !== null && v !== undefined;
} function getModuleUrl(moduleId, relative) {
function getPackage(nm) {
for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
var pck = require.parsedConfig.packages[i];
if (nm === pck.name) {
return pck;
}
}
return false;
}
var mts = moduleId.split('/');
var pck = getPackage(mts[0]);
if (pck) {
mts.shift();
return getRoute(pck.location, mts.join('/'));
} else if (mts[0] === '.' || mts[0] === '..') {
return getRoute(relative, moduleId);
} else {
return getRoute(require.parsedConfig.baseUrl, moduleId);
}
} function loadJS(url) {
var script = document.createElement('script');
script.type = "text/javascript";
//var url = getModuleUrl(mId, rel);
script.src = url + '.js';
script.onload = function() {
var module = modules[url];
if (module && isReady(module) && loadings.indexOf(url) > -1) {
callFactory(module);
}
checkDeps();
};
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
}; function checkDeps() {
for (var p in modules) {
var module = modules[p];
if (isReady(module) && loadings.indexOf(module.id) > -1) {
callFactory(module);
checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
}
}
}; function isReady(m) {
var deps = m.deps;
var allReady = deps.every(function(dep) {
return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
})
if (deps.length === 0 || allReady) {
return true;
}
}; function callFactory(m) {
var args = [];
for (var i = 0, len = m.deps.length; i < len; i++) {
args.push(modules[m.deps[i]].result);
}
m.result = m.factory.apply(window, args);
m.state = 2; var idx = loadings.indexOf(m.id);
if (idx > -1) {
loadings.splice(idx, 1);
}
}; function getCurrentScript(base) {
// 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
var stack;
try {
a.b.c(); //强制报错,以便捕获e.stack
} catch (e) { //safari的错误对象只有line,sourceId,sourceURL
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 stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
}
var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
for (var i = nodes.length, node; node = nodes[--i]; ) {
if ((base || node.className === moduleClass) && node.readyState === "interactive") {
return node.className = node.src;
}
}
};
})(window)

  测试:

require([
'bbb',
'aaa.bbb.ccc',
'ccc',
'ddd',
'fff'
], function(aaabbbccc){
console.log('simple loader');
console.log(arguments);
});

  输出结果:

AMD加载器实现笔记(二)的更多相关文章

  1. AMD加载器实现笔记(一)

    之前研究过AMD,也写过一篇关于AMD的文章<以代码爱好者角度来看AMD与CMD>.代码我是有看过的,基本的原理也都明白,但实际动手去实现却是没有的.因为今年计划的dojo教程<静静 ...

  2. AMD加载器实现笔记(五)

    前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善.到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖. 所谓环形依赖,指的是模块A的所有依赖项的依赖 ...

  3. AMD加载器实现笔记(四)

    继续这一系列的内容,到目前为止除了AMD规范中config的map.config参数外,我们已经全部支持其他属性了.这一篇文章中,我们来为增加对map的支持.同样问题,想要增加map的支持首先要知道m ...

  4. AMD加载器实现笔记(三)

    上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持. 要添加shim与paths,第一要务当然是了解他们的语义与用法.先来看 ...

  5. AngularJs2与AMD加载器(dojo requirejs)集成

    现在是西太平洋时间凌晨,这个问题我鼓捣了一天,都没时间学英语了,英语太差,相信第二天我也看不懂了,直接看结果就行. 核心原理就是require在AngularJs2编译过程中是关键字,而在浏览器里面运 ...

  6. Promise实现简易AMD加载器

    在最新的Chrome和FF中已经 实现了Promise.有了Promise我们用数行代码即可实现一个简易AMD模式的加载器 var registry = { promises: { }, resolv ...

  7. JavaScript AMD 模块加载器原理与实现

    关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...

  8. KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器

    无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...

  9. 构建服务端的AMD/CMD模块加载器

    本文原文地址:http://trock.lofter.com/post/117023_1208040 . 引言:  在前端开发领域,相信大家对AMD/CMD规范一定不会陌生,尤其对requireJS. ...

随机推荐

  1. win7+vs2010+opencv2.4.6配置

    记录一下配置,省的以后还到处去找: (一) 添加环境变量://第一次使用opencv的话需要加环境变量:” %opencv%\build\x86\vc10\bin”和”%opencv%\build\c ...

  2. C1000k 新思路:用户态 TCP/IP 协议栈

    现在的服务器支撑上百万个并发 TCP 连接已经不是新闻(余锋2010年的演讲,ideawu 的 iComet 开源项目,WhatsApp 做到了 2.5M).实现 C1000k 的常规做法是调整内核参 ...

  3. 不容错过!2016年度优秀UI/UX设计文章

    本文整理了一些2016年度最受欢迎的文章,例如有关UI / UX设计的理论知识,书籍和工具,如何做出更好的设计的方法和建议,以及新的设计趋势. 1. 2017年用户体验设计趋势 我们期待着2017年用 ...

  4. php和syslog

    syslog是Linux系统默认的日志守护进程.使用syslog可以方便把指定的事件写入特定文件中,可以让任何事件都登录到一台或多台服务器上. 1.简单例子,先说一下syslog怎么使用,以php为例 ...

  5. 配置Chrome Driver

    书中使用Firefox driver出现莫名问题,大概是firefox的版本太新了,懒得降级处理,故学习配置Chome driver进行测试. 1.到http://chromedriver.stora ...

  6. oracle 11gr2 官方文档下载

    http://www.oracle.com/technetwork/database/enterprise-edition/documentation/index.html

  7. (python) 标准模块sys和os的使用

    一.sys模块 包含了系统的相关的功能.我们来学习sys.argv,它包含命令行参数. 例子:定义了一个add函数,用来实现两个整数的相加. #! coding=utf-8 # usersys.py ...

  8. HDU2471_History of Languages

    有意思的题目,给出两个自动机,判断这个两个自动机是否是等价的? 两个自动机是等价的,那么他们可接受的字符串集合一定是完全一样的. 于是我们可以从(0,0)开始bfs,每次看看在两个自动机上走到的两个点 ...

  9. jQuery选择器引擎和Sizzle介绍

    一.前言 Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中.我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍. ...

  10. Oracle 设置表空间自增长

    Oracle修改表空间大小 使用Oracle10g建立数据库后,向数据库中导入了部分数据,第二天继续向数据库中导入数据表时发生错误: 查了很多资料发现原来是Oracle表空间限制,导致无法继续导入数据 ...