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

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

 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);  });

 输出结果:

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

你可能感兴趣的文章
Zabbix“专家坐诊”第10期问答汇总
查看>>
生产管理软件改进生产流程
查看>>
MaxCompute新功能发布
查看>>
3.文件拷贝
查看>>
Kubernetes弹性伸缩全场景解读(五) - 定时伸缩组件发布与开源
查看>>
100. bootstrap 弹出对话框bootbox.confirm
查看>>
wps怎么转图片?
查看>>
多人聊天
查看>>
科略教育—企业管理运营八大基本法
查看>>
I/O多路转接之select
查看>>
IPScan的作用
查看>>
我的友情链接
查看>>
mysql主从复制
查看>>
etc目录下配置文件详解
查看>>
配置 yum 源的两种方法
查看>>
A10 负载均衡模拟器下载安装及license免费激活详细介绍
查看>>
SQL Server数据库的存储过程中定义的临时表,真的有必要显式删除(drop table #tableName)吗?...
查看>>
双向链表的几种实现
查看>>
我的友情链接
查看>>
Ubuntu快捷键
查看>>