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

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

 上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持。

  要添加shim与paths,第一要务当然是了解他们的语义与用法。先来看shim,shim翻译成中文是“垫片”的意思。在AMD中主要用途是把不支持AMD的某些变量包装AMD模块。shim是一个哈希对象,key为包装后的模块Id,value是关于这个包装模块的一些配置,主要配置项如下:

  • deps:定义模块需要的依赖项的moduleId数组
  • exports:模块输出值
  • init:如果它的返回值不是undefined,则返回值作为‘some/thind’模块的返回值,否则以exports作为模块的返回值。

  举个例子:

  

  这个配置的目的是想将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/

你可能感兴趣的文章
如何设置同一单据的单据头字段各行合并显示吗?
查看>>
HAProxy负载均衡代理
查看>>
汇编中参数的传递和堆栈修正【转载】
查看>>
web安全实践系列导航
查看>>
[Oracle]GoldenGate官方文档
查看>>
第 42 章 WMware
查看>>
动感的网页相册
查看>>
Nginx的反向代理与负载均衡
查看>>
redis之(十四)redis的主从复制的原理
查看>>
Velocity入门指南
查看>>
ntp redhat
查看>>
sum(case when status=1 then 1 else 0 end) 的意思
查看>>
Win7硬盘安装方法
查看>>
python - 列表
查看>>
UIVisualEffectView用法
查看>>
springmvc+mybatis整合cms+UC浏览器文章功能
查看>>
docker安装(centos6.5_x86_64)
查看>>
mysql悲观锁与乐观锁
查看>>
ubuntu下python2-python3版共存,创建django项目出现的问题
查看>>
2018.4.3三周第二次课
查看>>