Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

动手实现一个AMD模块加载器(三) #17

Open
huruji opened this issue Nov 4, 2017 · 0 comments
Open

动手实现一个AMD模块加载器(三) #17

huruji opened this issue Nov 4, 2017 · 0 comments

Comments

@huruji
Copy link
Owner

huruji commented Nov 4, 2017

v2-c0469a6a95f2058a19151c4157088014_r

在上一篇文章中,我们的AMD模块加载器基本已经能够使用了,但是还不够,因为我们没有允许匿名模块,以及没有依赖等情况。实际上在amd的规范中规定的就是define函数的前两个参数是可选的,当没有id(模块名)的时候也就意味着不会有模块依赖于这个模块。很显然,我们的define函数的每个参数的类型是不同的,因此我们需要一些函数来做类型判断,如下:

  function isFun(f) {
    return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1;
  }

  function isArr(arr) {
    return Array.isArray(arr);
  }

  function isStr(str) {
    return typeof str === 'string';
  }

将这些类型判断函数运用在define函数,判断这个模块是否有依赖,是否为匿名模块,这是一个比较简单的工作,修改define函数如下:

  function define(name, deps, callback) {
    if(!isStr(name)) {
      callback = deps;
      deps = name;
      name = null;
    }
    
    if(!isArr(deps)) {
      callback = deps;
      deps = [];
    }

    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 || [];
  }

进行一次测试,不过在测试之前,我们需要知道的是,我们将匿名模块的name修改为了null,而后面有一个replaceName方法是做name替换的,这里没有判断name是否为null的情况,因此需要在开头做一次判断,增加如下代码:

  function replaceName(name) {
    if(name===null) {
      return name;
    }
    // ......
  }

测试代码如下:

    loadjs.config({
      baseUrl:'./static',
      paths: {
        app: './app'
      }
    });

    loadjs.define('cc',['a'], function(a) {
      console.log(1);
      console.log(a.add(1,2));
    });

    loadjs.define('ab', function() {
      console.log('ab');
    });

    loadjs.define(function() {
      console.log('unknow');
    });

    loadjs.use(['ab','cc'],function() {
      console.log('main');
    });

测试结果如下:

说明正确。此时我们的一个简单的amd模块加载器就这样写完了,删除console增加注释就可以比较好的使用了,最后整理一下代码如下:

(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 isFun(f) {
    return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1;
  }

  function isArr(arr) {
    return Array.isArray(arr);
  }

  function isStr(str) {
    return typeof str === 'string';
  }
  
  function merge(obj1, obj2) {
    if(obj1 && obj2) {
      for(var key in obj2) {
        obj1[key] = obj2[key]
      }
    }
  }
  
  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(name===null) {
      return 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 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';
    
    /*为每个模块添加一个随机id*/
    node.id = 'loadjs-js-' + (Math.random() * 100).toFixed(3);
    doc.body.appendChild(node);
    node.onload = function() {
      callback();
    }
  }

  function define(name, deps, callback) {
    /*匿名模块*/
    if(!isStr(name)) {
      callback = deps;
      deps = name;
      name = null;
    }

    /*没有依赖*/
    if(!isArr(deps)) {
      callback = deps;
      deps = [];
    }

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

系列文章:
动手实现一个AMD模块加载器(一)
动手实现一个AMD模块加载器(二)
动手实现一个AMD模块加载器(三)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant