動(dòng)手實(shí)現(xiàn)一個(gè)AMD模塊加載器(二)

在上一篇文章中侄泽,我們已經(jīng)基本完成了模塊加載器的基本功能,接下來來完成一下路徑解析的問題正勒。

在之前的功能中得院,我們所有的模塊默認(rèn)只能放在同級(jí)目錄下,而在實(shí)際項(xiàng)目中章贞,我們的js很有可能位于多個(gè)目錄祥绞,甚至是CDN中,所以現(xiàn)在這種路徑解析是非常不合理的鸭限,因此我們需要將每個(gè)模塊的name轉(zhuǎn)化為一個(gè)絕對(duì)路徑蜕径,這樣才是一個(gè)比較完美的解決方案。

借鑒部分requirejs的思想里覆,我們可以通過配置來配置一個(gè)baseUrl丧荐,當(dāng)沒有配置這個(gè)baseUrl的時(shí)候,我們認(rèn)為這個(gè)baseUrl就是html頁(yè)面的地址喧枷,所以我們需要對(duì)外暴露一個(gè)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;

上面的代碼中,我們定義了一個(gè)基本的全局配置對(duì)象cfg隧甚、一個(gè)用來合并對(duì)象屬性的merge方法和一個(gè)用來支持配置的config方法车荔。但是顯然這個(gè)時(shí)候配置baseUrl的時(shí)候需要使用一個(gè)絕對(duì)路徑。但是在實(shí)際中我們可能更會(huì)使用的是一個(gè)相對(duì)路徑戚扳,例如../或者./或者/這個(gè)需求是非常正常的忧便,因此我們需要也支持這些實(shí)現(xiàn)。首先我們先來寫這些的匹配的正則表達(dá)式帽借,為了之后的使用我們同時(shí)也寫出檢測(cè)完整路徑(包括http珠增、https和file協(xié)議)

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

同時(shí)將這些判斷寫進(jìn)一個(gè)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;
    }
  }

這里可能需要關(guān)注的一個(gè)相對(duì)路徑的問題砍艾,因?yàn)橛锌赡苁切枰祷厣弦患?jí)目錄的蒂教,即形如./../../的形式,因此也應(yīng)該處理這種情況脆荷。另外之所以在這里都是要匹配baseUrl的最后一個(gè)斜杠/凝垛,是因?yàn)樘峁┑倪@個(gè)很有可能帶有斜杠,也很有可能不帶斜杠蜓谋。
最后使用config方法配置的時(shí)候梦皮,通過判斷提供的path來做相應(yīng)的處理,修改config方法如下:

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

最后我們修改一下每個(gè)模塊名為這個(gè)模塊的絕對(duì)路徑桃焕,這樣我們就不必再修改loadScript方法了剑肯,我們?cè)趌oadMod方法中修改name參數(shù),增加代碼:

name = outputPath(cfg.baseUrl, name);

我們?cè)賮韮?yōu)化一下观堂,畢竟如果我們每一個(gè)模塊都要使用./或者../之類的退子,很多模塊下這是要崩潰的岖妄,所以我們依舊是借鑒requirejs的方法,允許使用config方法來配置path屬性這個(gè)問題寂祥,當(dāng)我們配置了一個(gè)app的path之后我們認(rèn)為在模塊引用的時(shí)候荐虐,如果遇到app開頭則需要替換這個(gè)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方法中同時(shí)也應(yīng)該檢測(cè)cfg.path中是否含有這個(gè)屬性丸凭,這時(shí)候會(huì)比較復(fù)雜福扬,因此單獨(dú)抽出為一個(gè)函數(shù)來說是比較好的處理方式,單獨(dú)抽出為一個(gè)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方法中調(diào)用這個(gè)方法就可以了。
我們?cè)賰?yōu)化一下虽界,我們完全可以在define中將name替換為一個(gè)絕對(duì)路徑汽烦,同時(shí)在主模塊加載依賴的時(shí)候,將依賴替換為絕對(duì)路徑即可莉御,因此我們可以在定義模塊的時(shí)候就將這個(gè)這個(gè)路徑替換好撇吞。
不過這個(gè)時(shí)候我們需要明白的是,在定義模塊的時(shí)候是一個(gè)類似單詞礁叔,而聲明依賴的時(shí)候則有可能含有路徑牍颈,如何在模塊聲明的時(shí)候正確解析路徑呢?
很明顯我們可以使用一個(gè)變量來做這個(gè)事情琅关,這個(gè)變量存儲(chǔ)著所有模塊名和依賴這個(gè)模塊時(shí)的聲明煮岁。那么我們就應(yīng)該在use方法加載模塊的時(shí)候?qū)⑦@些變量名添加到這個(gè)變量名之下,之后再define中進(jìn)行轉(zhuǎn)化涣易,那么最后我們的整個(gè)代碼如下:


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

我們進(jìn)行一下測(cè)試:

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

打開瀏覽器我們可以看到正常輸出画机,如下:


說明我們的所做的路徑解析工作是正確的。

系列文章:
動(dòng)手實(shí)現(xiàn)一個(gè)AMD模塊加載器(一)
動(dòng)手實(shí)現(xiàn)一個(gè)AMD模塊加載器(二)
動(dòng)手實(shí)現(xiàn)一個(gè)AMD模塊加載器(三)

最后是一個(gè)廣告貼新症,最近新開了一個(gè)分享技術(shù)的公眾號(hào)步氏,歡迎大家關(guān)注??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市账劲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌金抡,老刑警劉巖瀑焦,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異梗肝,居然都是意外死亡榛瓮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門巫击,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禀晓,“玉大人精续,你說我怎么就攤上這事〈饫粒” “怎么了重付?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凫乖。 經(jīng)常有香客問我确垫,道長(zhǎng),這世上最難降的妖魔是什么帽芽? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任删掀,我火速辦了婚禮,結(jié)果婚禮上导街,老公的妹妹穿的比我還像新娘披泪。我一直安慰自己,他們只是感情好搬瑰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布款票。 她就那樣靜靜地躺著,像睡著了一般跌捆。 火紅的嫁衣襯著肌膚如雪徽职。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天佩厚,我揣著相機(jī)與錄音姆钉,去河邊找鬼。 笑死抄瓦,一個(gè)胖子當(dāng)著我的面吹牛潮瓶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钙姊,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼毯辅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了煞额?” 一聲冷哼從身側(cè)響起思恐,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膊毁,沒想到半個(gè)月后胀莹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婚温,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年描焰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栅螟。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荆秦,死狀恐怖篱竭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情步绸,我是刑警寧澤掺逼,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站靡努,受9級(jí)特大地震影響坪圾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惑朦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一兽泄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漾月,春花似錦病梢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吩蔑,卻和暖如春钮热,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烛芬。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工隧期, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赘娄。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓仆潮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親遣臼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子性置,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)揍堰,斷路器鹏浅,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 隨著前端業(yè)務(wù)復(fù)雜度的增加西采,模塊化成為一個(gè)大的趨勢(shì)凰萨。而在ES6還未被瀏覽器所支持的情況下继控,commonjs作為ES6...
    吳高亮閱讀 1,052評(píng)論 0 3
  • 背景介紹:廣東某211高校財(cái)務(wù)管理專業(yè)械馆,一名愛寫作愛攝影的會(huì)計(jì)人胖眷,校招投過50多家公司,拿到安永霹崎、華潤(rùn)珊搀、移動(dòng)等of...
    失憶詩(shī)人閱讀 391評(píng)論 1 5
  • 記不清具體哪天我開始頹廢,幾乎不出宿舍門尾菇。大概是上周二交了三方吧境析,感覺工作有著落了,也不去老師那里做畢設(shè)了派诬,...
    狼二蠻閱讀 243評(píng)論 3 0
  • ----------------------昨夜熱醒劳淆,賦閑文記 迷糊之間,終于好像睡著了…………迷糊之間默赂,掙扎著醒...
    有理性思想的浪漫詩(shī)人閱讀 282評(píng)論 4 4