本文共 12670 字,大约阅读时间需要 42 分钟。
上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持。
要添加shim与paths,第一要务当然是了解他们的语义与用法。先来看shim,shim翻译成中文是“垫片”的意思。在AMD中主要用途是把不支持AMD的某些变量包装AMD模块。shim是一个哈希对象,key为包装后的模块Id,value是关于这个包装模块的一些配置,主要配置项如下:
举个例子:
这个配置的目的是想将window.some.thing这个全局变量包装成id为‘some/thing’的模块。模块的依赖项Id为'a'、'b',输出值为some.thing但因为定义了init函数,所以最后模块的输出值变成了some.thing + 'another'。
因为shim是要将全局变量包装成模块,所以直接用shim中的配置项来定义模块即可。上例中的配置最终将转化为:
1 define('some/thing', ['a', 'b'], function(a, b) {2 var initResult = shimItem.init(a, b);3 return initResult === undefined ? shimItem.exports : initResult; 4 });但前面我的加载器中,define只支持匿名模块,现在我们让它来支持显示定义模块Id:
global.define = function(id, deps, callback) { //加上moduleId的支持 if (typeof id !== "string" && arguments.length === 2) { callback = deps; deps = id; id = ""; } var id = id || getCurrentScript(); if (modules[id]) { console.error('multiple define module: ' + id); } require(deps, callback, id); };完成后我们在require.config函数中加入对shim的支持。
//shim 要放在最后处理 if (config.shim) { for (var p in config.shim) { var item = config.shim[p]; define(p, item.deps, function() { var exports; if (item.init) { exports = item.init.apply(item, arguments); } return exports ? exports : item.exports; }); } }于此同时,require中也要改一下,现在已经不能一股脑的将dep都转化为绝对路径了。
// dep为非绝对路径形式,而modules的key仍然需要绝对路径 deps = deps.map(function(dep) { if (modules[dep]) { //jquery return dep; } else if (dep in global.require.parsedConfig.paths) { return 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); });
到此,shim已经完美支持了。
下面轮到了paths,paths也是一个对象,格式为{模块Id:模块所在路径}。当其他模块引用该模块时,该模块的加载地址使用paths中所配置的地址,这个地址可以是绝对的,也可以是相对路径(相对于baseUrl)。首先要在require.config函数中解析path路径
this.parsedConfig.paths = {}; if (config.paths) { for (var p in config.paths) { this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]); } }然后当依赖模块Id在paths中有配置时,那就需要从paths中拿到加载地址,所以需要修改loadJs中代码
function loadJS(url) { var script = document.createElement('script'); script.type = "text/javascript"; //判断模块是否在paths中定义了路径 script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : 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); };
同时如果dep在paths中有配置,也不能将dep转化为绝对路径(require函数)
这里面需要注意的是,模块的Id必须与路径所在文件中定义的模块的id保持一致。以jquery为例:
require.config({ baseUrl: "./", packages: [{ name: "more", location: "./more" }, { name: "mass", location: "../" }, { name: "wab", location: "../../../" }], paths: { 'jquery': "../../Bodhi/src/roots/jquery" } });当在paths中配置了jquery模块时,那么路径对应的文件一定要定义jquery模块,如jquery源码中用define定义jquery模块:
1 if ( typeof define === "function" && define.amd ) {2 define( "jquery", [], function() {3 return jQuery;4 });5 }本文内容结束,目前为止loader加载器的源码为:
(function(global){ 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) { if (modules[dep]) { //jquery return dep; } else if (dep in global.require.parsedConfig.paths) { return 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); } } this.parsedConfig.paths = {}; if (config.paths) { for (var p in config.paths) { this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]); } } //shim 要放在最后处理 if (config.shim) { for (var p in config.shim) { var item = config.shim[p]; define(p, item.deps, function() { var exports; if (item.init) { exports = item.init.apply(item, arguments); } return exports ? exports : item.exports; }); } } console.log(this.parsedConfig); } global.define = function(id, deps, callback) { //加上moduleId的支持 if (typeof id !== "string" && arguments.length === 2) { callback = deps; deps = id; id = ""; } var id = id || getCurrentScript(); if (modules[id]) { console.error('multiple define module: ' + id); } require(deps, callback, id); }; global.define.amd = {};//AMD规范 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"; //判断模块是否在paths中定义了路径 script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : 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)测试demo:
window.something = "Bodhi"; require.config({ baseUrl: "./", packages: [{ name: "more", location: "./more" }, { name: "mass", location: "../" }, { name: "wab", location: "../../../" }], shim: { "something": { "deps": ['jquery'], exports: 'something', init: function(jq, ol) { console.log(jq); return something + " in shim"; } } }, paths: { 'jquery': "../../Bodhi/src/roots/jquery" } }); require([ 'bbb', 'aaa.bbb.ccc', 'ccc', 'ddd', 'fff', 'something' ], function(aaabbbccc){ console.log('simple loader'); console.log(arguments); });
转载地址:http://tckgo.baihongyu.com/