博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
动手实现一个AMD模块加载器(二)
阅读量:7037 次
发布时间:2019-06-28

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

在上一篇文章中,我们已经基本完成了模块加载器的基本功能,接下来来完成一下路径解析的问题。

在之前的功能中,我们所有的模块默认只能放在同级目录下,而在实际项目中,我们的js很有可能位于多个目录,甚至是CDN中,所以现在这种路径解析是非常不合理的,因此我们需要将每个模块的name转化为一个绝对路径,这样才是一个比较完美的解决方案。

借鉴部分requirejs的思想,我们可以通过配置来配置一个baseUrl,当没有配置这个baseUrl的时候,我们认为这个baseUrl就是html页面的地址,所以我们需要对外暴露一个config方法,如下:

var cfg = {  baseUrl: location.href.replace(/(\/)[^\/]+$/g, function(s, s1){    return s1  })}function config(obj) {  obj && merge(cfg, obj);}function merge(obj1, obj2) {  if(obj1 && obj2) {    for(var key in obj2) {      obj1[key] = obj2[key]    }  }}loadjs.config = config;复制代码

上面的代码中,我们定义了一个基本的全局配置对象cfg、一个用来合并对象属性的merge方法和一个用来支持配置的config方法。但是显然这个时候配置baseUrl的时候需要使用一个绝对路径。但是在实际中我们可能更会使用的是一个相对路径,例如../或者./或者/这个需求是非常正常的,因此我们需要也支持这些实现。首先我们先来写这些的匹配的正则表达式,为了之后的使用我们同时也写出检测完整路径(包括http、https和file协议)

var fullPathRegExp = /^[(https?\:\/\/) | (file\:\/\/\/)]/;  var absoPathRegExp = /^\//;  var relaPathRegExp = /^\.\//;  var relaPathBackRegExp = /^\.\.\//;复制代码

同时将这些判断写进一个outputPath方法中。

function outputPath(baseUrl, path) {    if (relaPathRegExp.test(path)) {      if(/\.\.\//g.test(path)) {        var pathArr = baseUrl.split('/');        var backPath = path.match(/\.\.\//g);        var joinPath = path.replace(/[(^\./)|(\.\.\/)]+/g, '');        var num = pathArr.length - backPath.length;        return pathArr.splice(0, num).join('/').replace(/\/$/g, '') + '/' +joinPath;      } else {        return baseUrl.replace(/\/$/g, '') + '/' + path.replace(/[(^\./)]+/g, '');      }    } else if (fullPathRegExp.test(path)) {      return path;    } else if (absoPathRegExp.test(path)) {      return baseUrl.replace(/\/$/g, '') + path;    } else {      return baseUrl.replace(/\/$/g, '') + '/' + path;    }  }复制代码

这里可能需要关注的一个相对路径的问题,因为有可能是需要返回上一级目录的,即形如./../../的形式,因此也应该处理这种情况。另外之所以在这里都是要匹配baseUrl的最后一个斜杠/,是因为提供的这个很有可能带有斜杠,也很有可能不带斜杠。

最后使用config方法配置的时候,通过判断提供的path来做相应的处理,修改config方法如下:

function config(obj) {    if(obj){     if(obj.baseUrl) {       obj.baseUrl = outputPath(cfg.baseUrl, obj.baseUrl);     }      merge(cfg, obj);    }   }复制代码

最后我们修改一下每个模块名为这个模块的绝对路径,这样我们就不必再修改loadScript方法了,我们在loadMod方法中修改name参数,增加代码:

name = outputPath(cfg.baseUrl, name);复制代码

我们再来优化一下,毕竟如果我们每一个模块都要使用./或者../之类的,很多模块下这是要崩溃的,所以我们依旧是借鉴requirejs的方法,允许使用config方法来配置path属性这个问题,当我们配置了一个app的path之后我们认为在模块引用的时候,如果遇到app开头则需要替换这个path。

所以先来看config方法修改如下:

function config(obj) {    if(obj){     if(obj.baseUrl) {       obj.baseUrl = outputPath(cfg.baseUrl, obj.baseUrl);     }     if(obj.path) {       var base = obj.baseUrl || cfg.baseUrl;       for(var key in obj.path) {         obj.path[key] = outputPath(base, obj.path[key]);       }     }      merge(cfg, obj);    }  }复制代码

因此在loadMod方法中同时也应该检测cfg.path中是否含有这个属性,这时候会比较复杂,因此单独抽出为一个函数来说是比较好的处理方式,单独抽出为一个replaceName方法,如下:

function replaceName(name) {    if(fullPathRegExp.test(name) || absoPathRegExp.test(name) || relaPathRegExp.test(name)) {      return outputPath(cfg.baseUrl, name);    } else {      var prefix = name.split('/')[0] || name;      if(cfg.path[prefix]) {        if(name.split('/').length === 0) {         return cfg.path[prefix];        } else {          var endPath = name.split('/').slice(1).join('/');          return outputPath(cfg.path[prefix], endPath);        }      }    }  }复制代码

这样,我们只需要在loadMod方法中调用这个方法就可以了。

我们再优化一下,我们完全可以在define中将name替换为一个绝对路径,同时在主模块加载依赖的时候,将依赖替换为绝对路径即可,因此我们可以在定义模块的时候就将这个这个路径替换好。
不过这个时候我们需要明白的是,在定义模块的时候是一个类似单词,而声明依赖的时候则有可能含有路径,如何在模块声明的时候正确解析路径呢?
很明显我们可以使用一个变量来做这个事情,这个变量存储着所有模块名和依赖这个模块时的声明。那么我们就应该在use方法加载模块的时候将这些变量名添加到这个变量名之下,之后再define中进行转化,那么最后我们的整个代码如下:

(function(root){  var modMap = {};  var moduleMap = {};  var cfg = {    baseUrl: location.href.replace(/(\/)[^\/]+$/g, function(s, s1){      return s1    }),    path: {    }  };  var fullPathRegExp = /^[(https?\:\/\/) | (file\:\/\/\/)]/;  var absoPathRegExp = /^\//;  var relaPathRegExp = /^\.\//;  var relaPathBackRegExp = /^\.\.\//;  function outputPath(baseUrl, path) {    if (relaPathRegExp.test(path)) {      if(/\.\.\//g.test(path)) {        var pathArr = baseUrl.split('/');        var backPath = path.match(/\.\.\//g);        var joinPath = path.replace(/[(^\./)|(\.\.\/)]+/g, '');        var num = pathArr.length - backPath.length;        return pathArr.splice(0, num).join('/').replace(/\/$/g, '') + '/' +joinPath;      } else {        return baseUrl.replace(/\/$/g, '') + '/' + path.replace(/[(^\./)]+/g, '');      }    } else if (fullPathRegExp.test(path)) {      return path;    } else if (absoPathRegExp.test(path)) {      return baseUrl.replace(/\/$/g, '') + path;    } else {      return baseUrl.replace(/\/$/g, '') + '/' + path;    }  }  function replaceName(name) {    if(fullPathRegExp.test(name) || absoPathRegExp.test(name) || relaPathRegExp.test(name) || relaPathBackRegExp.test(name)) {      return outputPath(cfg.baseUrl, name);    } else {      var prefix = name.split('/')[0] || name;      if(cfg.paths[prefix]) {        if(name.split('/').length === 0) {         return cfg.paths[prefix];        } else {;          var endPath = name.split('/').slice(1).join('/');          return outputPath(cfg.paths[prefix], endPath);        }      } else {        return outputPath(cfg.baseUrl, name);      }    }  }  function fixUrl(name) {    return name.split('/')[name.split('/').length-1]  }  function config(obj) {    if(obj){     if(obj.baseUrl) {       obj.baseUrl = outputPath(cfg.baseUrl, obj.baseUrl);     }     if(obj.paths) {       var base = obj.baseUrl || cfg.baseUrl;       for(var key in obj.paths) {         obj.paths[key] = outputPath(base, obj.paths[key]);       }     }      merge(cfg, obj);    }  }  function merge(obj1, obj2) {    if(obj1 && obj2) {      for(var key in obj2) {        obj1[key] = obj2[key]      }    }  }  function use(deps, callback) {    if(deps.length === 0) {      callback();    }    var depsLength = deps.length;    var params = [];    for(var i = 0; i < deps.length; i++) {      moduleMap[fixUrl(deps[i])] = deps[i];      deps[i] = replaceName(deps[i]);      (function(j){        loadMod(deps[j], function(param) {          depsLength--;          params[j] = param;          if(depsLength === 0) {            callback.apply(null, params);          }        })      })(i)    }  }  function loadMod(name, callback) {    if(!modMap[name]) {      modMap[name] = {        status: 'loading',        oncomplete: []      };      loadscript(name, function() {        use(modMap[name].deps, function() {          execMod(name, callback, Array.prototype.slice.call(arguments, 0));        })      });    } else if(modMap[name].status === 'loading') {      modMap[name].oncomplete.push(callback);    } else if (!modMap[name].exports){      use(modMap[name].deps, function() {        execMod(name, callback, Array.prototype.slice.call(arguments, 0));      })    }else {      callback(modMap[name].exports);    }  }  function execMod(name, callback, params) {    var exp = modMap[name].callback.apply(null, params);    modMap[name].exports = exp;    callback(exp);    execComplete(name);  }  function execComplete(name) {    for(var i = 0; i < modMap[name].oncomplete.length; i++) {      modMap[name].oncomplete[i](modMap[name].exports);    }  }  function loadscript(name, callback) {    var doc = document;    var node = doc.createElement('script');    node.charset = 'utf-8';    node.src = name + '.js';    node.id = 'loadjs-js-' + (Math.random() * 100).toFixed(3);    doc.body.appendChild(node);    node.onload = function() {      callback();    }  }  function define(name, deps, callback) {    if(moduleMap[name]) {      name=moduleMap[name]    }     name = replaceName(name);    deps = deps.map(function(ele, i) {      return replaceName(ele);     });    modMap[name] = modMap[name] || {};    modMap[name].deps = deps;    modMap[name].status = 'loaded';    modMap[name].callback = callback;    modMap[name].oncomplete = modMap[name].oncomplete || [];  }  var loadjs = {    define: define,    use: use,    config: config  };  root.define = define;  root.loadjs = loadjs;  root.modMap = modMap;})(window);复制代码

我们进行一下测试:

loadjs.config({      baseUrl:'./static',      paths: {        app: './app'      }    });    loadjs.use(['app/b', 'a'], function(b) {      console.log('main');      console.log(b.equil(1,2));    })复制代码
define('a', ['app/c'], function(c) {  console.log('a');  console.log(c.sqrt(4));  return {    add: function(a, b) {      return a + b;    }  }});复制代码
define('c', ['http://ce.sysu.edu.cn/hope/Skin/js/jquery.min.js'], function() {  console.log('c');  return {    sqrt: function(a) {      return Math.sqrt(a)    }  }});复制代码
define('b', ['c'], function(c) {  console.log('b');  console.log(c.sqrt(9));  return {    equil: function(a,b) {      return a===b;    }  }});复制代码

打开浏览器我们可以看到正常输出,如下:

1111

说明我们的所做的路径解析工作是正确的。

系列文章:

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

你可能感兴趣的文章
开源分布式存储SeaweedFS
查看>>
Servlet容器原型(二)——一个简单的连接器
查看>>
Quartz和UIKit坐标系
查看>>
Path Sum
查看>>
Spring使用Cache、整合Ehcache
查看>>
Quartz定时任务调度cron表达式时间格式
查看>>
ubuntu 安装mysql环境(离线压缩包方式)
查看>>
HTML <legend> 标签
查看>>
使用express配置前端代码服务器
查看>>
oracel设置自增ID。
查看>>
Maven com.sun.jdmk:jmxtools:jar 下载不下来
查看>>
DevExpress之Skin自定义使用
查看>>
可变参数
查看>>
[日推荐]『饿了么外卖服务』饿了么官方小程序,无需下载安装!
查看>>
Maven的学习资料收集--(四)使用Maven构建Web项目-测试
查看>>
redis安装与配置
查看>>
粒子群算法
查看>>
JDBC高效批量处理
查看>>
问题导向VS目标导向
查看>>
分布式之数据库和缓存双写一致性方案解析(三)
查看>>