• AMD加载器实现笔记(四)


      继续这一系列的内容,到目前为止除了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 dir
    193         var tts = target.split('/'); //target parts
    194         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元素保留原始模块Id
    235         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.js
    285         var stack;
    286         try {
    287             a.b.c(); //强制报错,以便捕获e.stack
    288         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
    289             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:1
    299              *firefox17:
    300              *@http://113.93.50.63/query.js:4
    301              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
    302              *@http://113.93.50.63/data.js:4
    303              *IE10:
    304              *  at Global code (http://113.93.50.63/data.js:4:1)
    305              *  //firefox4+ 可以用document.currentScript
    306              */
    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模块。

  • 相关阅读:
    关于supervisor无法监控golang代码的解决方法
    [2017BUAA软工]提问回顾
    [2017BUAA软工]个人阅读作业+总结
    解决nginx+uWSGI部署Django时遇到的static文件404的问题
    [2017BUAA软工]个人项目心得体会:数独
    Week2 Programming Assignment: Linear Regression
    eclipse如何同步自己的preference(oomph preference recorder)
    javac
    Apache Maven Compiler Plugin
    数据库与数据仓库的区别
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5153769.html
Copyright © 2020-2023  润新知