博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AMD加载器实现笔记(四)
阅读量:7233 次
发布时间:2019-06-29

本文共 15268 字,大约阅读时间需要 50 分钟。

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

  

  主要用于解决在两个不同模块集中使用一个模块的不同版本,并且保证两个模块集的交互没有冲突。

  

  假设磁盘有如下文件:

  

  当'some/newmodule'请求'foo'模块时,它将从foo1.2.js总得到'foo1.2'模块;当'some/oldmodule'请求'foo'模块时它将从foo1.0中得到'foo1.0'模块。

  在map属性中可以使用任何的module ID前缀,并且mapping对象可以匹配任何别的module ID前缀。

  

  如果出现通配符‘*’,表示任何模块使用这个匹配配置。通配符匹配对象中的模块ID前缀可以被覆盖。

 

  通过上文的解释,可以明白,如果在'some/newmodule'中依赖的foo实际是上依赖的foo1.2。转化成代码逻辑应当是这样的:如果在‘some/module’模块中发现依赖foo模块那就将foo替换成foo1.2。但是在什么地方实现替换好呢?因为模块的定义从define开始,同时只有在define中才能获得模块的绝对路径,所以我们把替换的处理放在define中。那么问题来了,我们的模块大部分都是匿名模块,模块自己如何知道自己的模块Id?所以一定要有一个标记去告诉define函数当前模块的Id,我们知道每一个模块都是一个JavaScript文件,每一个模块都有一个对应的script元素,所以最好的做法是没每一个script都加一个自定义特性,来标记当前元素的模块Id。

  所以在loadJs中要为script加自定义特性:

function loadJS(url, mId) {        var script = document.createElement('script');        script.setAttribute('data-moduleId', mId); //为script元素保留原始模块Id        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);    };

  在define函数中,通过文件的绝对路径,找出对应的script元素,拿到模块Id,判断如果在map中,则进行替换:

global.define = function(id, deps, callback) {        //加上moduleId的支持        if (typeof id !== "string" && arguments.length === 2) {            callback = deps;            deps = id;            id = "";        }        var id = id || getCurrentScript();                var script = document.querySelector('script[src="' + id + '"]');        if (script || id in require.parsedConfig.shim) {            var mId = script ? script.getAttribute('data-moduleId') : id;            var maping = getMapSetting(mId);                        if (maping) {                deps = deps.map(function(dep) {                    return maping[dep] || dep;                });            }        }        if (modules[id]) {            console.error('multiple define module: ' + id);        }                require(deps, callback, id);    };
function getMapSetting(mId) {        if (mId in require.parsedConfig.map) {            return require.parsedConfig[mId];        } else if ('*' in require.parsedConfig.map) {            return require.parsedConfig.map['*'];        } else {            return null;        }    };

  目前为止,我们的加载器已经支持了map属性,完整代码如下:

1 (function(global){  2     global = global || window;  3     modules = {};  4     loadings = [];  5     loadedJs = [];  6     //module: id, state, factory, result, deps;  7     global.require = function(deps, callback, parent){  8         var id = parent || "Bodhi" + Date.now();  9         var cn = 0, dn = deps.length; 10         var args = []; 11          12         var oriDeps = deps.slice();//保留原始dep的模块Id 13          14          // dep为非绝对路径形式,而modules的key仍然需要绝对路径 15         deps = deps.map(function(dep) { 16             if (modules[dep]) { //jquery  17                 return dep; 18             } else if (dep in global.require.parsedConfig.paths) { 19                 return dep; 20             } 21             var rel = ""; 22             if (/^Bodhi/.test(id)) { 23                 rel = global.require.parsedConfig.baseUrl; 24             } else { 25                 var parts = parent.split('/'); 26                 parts.pop(); 27                 rel = parts.join('/'); 28             } 29             return getModuleUrl(dep, rel); 30         }); 31          32         var module = { 33             id: id, 34             deps: deps, 35             factory: callback, 36             state: 1, 37             result: null 38         }; 39         modules[id] = module; 40          41         if (checkCircleRef(id, id)) { 42             return; 43         } 44          45         deps.forEach(function(dep, i) { 46             if (modules[dep] && modules[dep].state === 2) { 47                 cn++ 48                 args.push(modules[dep].result); 49             } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) { 50                 loadJS(dep, oriDeps[i]); 51                 loadedJs.push(dep); 52             } 53         }); 54         if (cn === dn) { 55             callFactory(module); 56         } else { 57             loadings.push(id); 58             checkDeps(); 59         } 60     }; 61      62     global.require.config = function(config) { 63         this.parsedConfig = {}; 64         if (config.baseUrl) { 65             var currentUrl = getCurrentScript(); 66             var parts = currentUrl.split('/'); 67             parts.pop(); 68             var currentDir = parts.join('/'); 69             this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl); 70         } 71         var burl = this.parsedConfig.baseUrl; 72         // 得到baseUrl后,location相对baseUrl定位 73         this.parsedConfig.packages = []; 74         if (config.packages) { 75             for (var i = 0, len = config.packages.length; i < len; i++) { 76                 var pck = config.packages[i]; 77                 var cp = { 78                     name: pck.name, 79                     location: getRoute(burl, pck.location) 80                 } 81                 this.parsedConfig.packages.push(cp); 82             } 83         } 84          85          86         this.parsedConfig.paths = {}; 87         if (config.paths) { 88             for (var p in config.paths) { 89                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]); 90             } 91         } 92          93         this.parsedConfig.map = {}; 94         if (config.map) { 95             this.parsedConfig.map = config.map; 96         } 97          98         this.parsedConfig.shim = {}; 99         //shim 要放在最后处理100         if (config.shim) {101             this.parsedConfig.shim = config.shim;102             for (var p in config.shim) {103                 var item = config.shim[p];104                 define(p, item.deps, function() {105                     var exports;106                     if (item.init) {107                         exports = item.init.apply(item, arguments);108                     }109                     110                     return exports ? exports : item.exports;111                 });112             }113         }114         115         console.log(this.parsedConfig);116     }117     118     global.define = function(id, deps, callback) {119         //加上moduleId的支持120         if (typeof id !== "string" && arguments.length === 2) {121             callback = deps;122             deps = id;123             id = "";124         }125         var id = id || getCurrentScript();126         127         var script = document.querySelector('script[src="' + id + '"]');128         if (script || id in require.parsedConfig.shim) {129             var mId = script ? script.getAttribute('data-moduleId') : id;130             var maping = getMapSetting(mId);131             132             if (maping) {133                 deps = deps.map(function(dep) {134                     return maping[dep] || dep;135                 });136             }137         }138         if (modules[id]) {139             console.error('multiple define module: ' + id);140         }141         142         require(deps, callback, id);143     };144     145     global.define.amd = {};//AMD规范146     147     function getMapSetting(mId) {148         if (mId in require.parsedConfig.map) {149             return require.parsedConfig[mId];150         } else if ('*' in require.parsedConfig.map) {151             return require.parsedConfig.map['*'];152         } else {153             return null;154         }155     };156     157     function checkCircleRef(start, target){158         var m = modules[start];159         if (!m) {160             return false;161         }162         var depModules = m.deps.map(function(dep) {163             return modules[dep] || null;164         });165         166         167         return depModules.some(function(m) {168             if (!m) {169                 return false;170             }171             return m.deps.some(function(dep) {172                 var equal = dep === target;173                 if (equal) {174                     console.error("circle reference: ", target, m.id);175                 }176                 177                 return equal;178             });179         }) ? true : depModules.some(function(m) {180             if (!m) {181                 return false;182             }183             return m.deps.some(function(dep) {184                 return checkCircleRef(dep, target);185             });186         });187         188         //return hasCr ? true: 189     };190     191     function getRoute(base, target) {192         var bts = base.replace(/\/$/, "").split('/');  //base dir193         var tts = target.split('/'); //target parts194         while (isDefined(tts[0])) {195             if (tts[0] === '.') {196                 return bts.join('/') + '/' + tts.slice(1).join('/');197             } else if (tts[0] === '..') {198                 bts.pop();199                 tts.shift();200             } else {201                 return bts.join('/') + '/' + tts.join('/');202             }203         }204     };205     206     function isDefined(v) {207         return v !== null && v !== undefined;208     };209     210     function getModuleUrl(moduleId, relative) {211         function getPackage(nm) {212             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {213                 var pck = require.parsedConfig.packages[i];214                 if (nm === pck.name) {215                     return pck;216                 }217             }218             return false;219         }220         var mts = moduleId.split('/');221         var pck = getPackage(mts[0]);222         if (pck) {223             mts.shift();224             return getRoute(pck.location, mts.join('/'));225         } else if (mts[0] === '.' || mts[0] === '..') {226             return getRoute(relative, moduleId);227         } else {228             return getRoute(require.parsedConfig.baseUrl, moduleId);229         }230     };231     232     function loadJS(url, mId) {233         var script = document.createElement('script');234         script.setAttribute('data-moduleId', mId); //为script元素保留原始模块Id235         script.type = "text/javascript";236         //判断模块是否在paths中定义了路径237         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';238         script.onload = function() {239             var module = modules[url];240             if (module && isReady(module) && loadings.indexOf(url) > -1) {241                 callFactory(module);242             }243             checkDeps();244         };245         var head = document.getElementsByTagName('head')[0];246         head.appendChild(script);247     };248     249     function checkDeps() {250         for (var p in modules) {251             var module = modules[p];252             if (isReady(module) && loadings.indexOf(module.id) > -1) {253                 callFactory(module);254                 checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功255             }256         }257     };258     259     function isReady(m) {260         var deps = m.deps;261         var allReady = deps.every(function(dep) {262             return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;263         })264         if (deps.length === 0 || allReady) {265             return true;266         }267     };268     269     function callFactory(m) {270         var args = [];271         for (var i = 0, len = m.deps.length; i < len; i++) {272             args.push(modules[m.deps[i]].result);273         }274         m.result = m.factory.apply(window, args);275         m.state = 2;276         277         var idx = loadings.indexOf(m.id);278         if (idx > -1) {279             loadings.splice(idx, 1);280         }281     };282     283     function getCurrentScript(base) {284         // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js285         var stack;286         try {287             a.b.c(); //强制报错,以便捕获e.stack288         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL289             stack = e.stack;290             if (!stack && window.opera) {291                 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取292                 stack = (String(e).match(/of linked script \S+/g) || []).join(" ");293             }294         }295         if (stack) {296             /**e.stack最后一行在所有支持的浏览器大致如下:297              *chrome23:298              * at http://113.93.50.63/data.js:4:1299              *firefox17:300              *@http://113.93.50.63/query.js:4301              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP302              *@http://113.93.50.63/data.js:4303              *IE10:304              *  at Global code (http://113.93.50.63/data.js:4:1)305              *  //firefox4+ 可以用document.currentScript306              */307             stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分308             stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符309             return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置310         }311         var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找312         for (var i = nodes.length, node; node = nodes[--i]; ) {313             if ((base || node.className === moduleClass) && node.readyState === "interactive") {314                 return node.className = node.src;315             }316         }317     };318 })(window)
View Code

  

  下面我们看一个demo:

  使用我们的加载器来加载jquery,同时禁用jquery的全局模式$:

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);                console.log($);                return something + " in shim";            }        }    },    map: {        '*': {            'jquery': 'jquery-private'        },        'jquery-private': {            'jquery': 'jquery'        }    },    paths: {        'jquery': "../../Bodhi/src/roots/jquery"    }  });  require([  'bbb',  //'aaa.bbb.ccc',  //'ccc',  //'ddd',  //'fff',  'something'  ], function(aaabbbccc){    console.log('simple loader');    console.log(arguments);  });

  jquery-private代码如下:

define(['jquery'], function(jquery) {    return jquery.noConflict(true);});

  如果某一模块依赖jquery,那么将会加载jquery-private。而在jquery中,因为配置了map和paths,所以jquery-private中的jquery根据paths找到jquery文件,并加载。同时在jquery-private中将禁用了全局模式之后的jquery对象返回给something模块。通过这个配置所有的模块在引用jquery时,实际上是引用了jquery-private模块。

 

转载地址:http://jupfm.baihongyu.com/

你可能感兴趣的文章
XCode4.2.1 使用NavigationController实现View切换
查看>>
如何让NSURLConnection在子线程中运行
查看>>
es6-Generator
查看>>
Python3.6单例模式报错TypeError: object() takes no parameters的解决方法
查看>>
HTML常用标记(选择性,不全)
查看>>
用一辈子去领悟的22条生活真谛
查看>>
1968: [Ahoi2005]COMMON 约数研究
查看>>
discuz 启用html code 显示问题
查看>>
A1027. Colors in Mars (20)
查看>>
[SRM568]DisjointSemicircles
查看>>
9个很有发展潜力的PHP开源项目
查看>>
python中pymysql数据编码的问题
查看>>
HDFS基本原理及数据存取实战
查看>>
j2ee页面静态化方案encache web cache框架详解1
查看>>
php高级注入
查看>>
[硬件]三维点云数据获取
查看>>
nagios安装配置
查看>>
bzoj 2763 [JLOI2011]飞行路线 Dijikstra 分层
查看>>
HEOI2018 游记
查看>>
UITableViewCell 取消选中的蓝色背景
查看>>