In the last article, we basically completed the basic functions of the module loader. Now let’s complete the problem of path resolution.

In previous functions, all of our module can only be in the same directory, by default in the actual project, our js probably located in multiple directories, and even the CDN, so now this path parsing is very unreasonable, so we need to convert each module name into an absolute path, this is a more perfect solution.

Based on the requirejs idea, we can configure a baseUrl through configuration. When the baseUrl is not configured, we think that the baseUrl is the address of the HTML page, so we need to expose a config method as follows:

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;Copy the code

In the above code, we define a basic global configuration object CFG, a merge method to merge object properties, and a Config method to support configuration. But obviously you need to use an absolute path to configure baseUrl at this point. In practice, however, we may prefer to use a relative path, such as.. / or. / or/This requirement is perfectly normal, so we need to support these implementations. We will first write the matching regular expression for these, and for later use we will also write the full path detection (including HTTP, HTTPS, and file protocols).

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

These judgments are also written into an outputPath method.

    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; }}Copy the code

There may be a relative path issue that needs to be addressed here, as it is possible to return the upper level of the directory, such as./.. /.. /, so this case should also be handled. Also, the reason we match the last slash of baseUrl here is because it may or may not be supplied with a slash. In the end, when using the config method, determine the provided path to do the corresponding processing, modify the config method as follows:

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

Finally, we change the absolute path of each module name so that we don’t have to change the loadScript method. We change the name parameter in the loadMod method and add code:

name = outputPath(cfg.baseUrl, name);Copy the code

Let’s optimize it again, after all if we use every module./ or.. So we still refer to requirejs method and allow the config method to configure the path property. After we configure the path of an app, we think we need to replace the path if we encounter the start of app when the module references it. Therefore, the config method is modified as follows:

  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 inobj.path) { obj.path[key] = outputPath(base, obj.path[key]); } } merge(cfg, obj); }}Copy the code

Therefore, loadMod should also check whether cfg.path contains this attribute, which will be complicated, so it is better to extract it as a function, and extract it as a replaceName method, as follows:

  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('/');
          returnoutputPath(cfg.path[prefix], endPath); }}}}Copy the code

Thus, we just need to call this method in the loadMod method. To optimize, we can replace name with an absolute path in define, and dependency with an absolute path when the main module loads dependencies, so we can replace this path with this path when the module is defined. If a module is declared as a dependency, it may contain a path. If a module is declared as a dependency, it may contain a path. The obvious way to do this is to use a variable that holds all module names and declarations that depend on that module. So we should add these variable names under the variable name when the use method loads the module, and then transform them in define. Finally, our entire code is as follows:


(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('/');
          returnoutputPath(cfg.paths[prefix], endPath); }}else {
        returnoutputPath(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 inobj.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);Copy the code

Let’s take a test:

    loadjs.config({
      baseUrl:'./static',
      paths: {
        app: './app'}}); loadjs.use(['app/b'.'a'].function(b) {
      console.log('main'); The console. The log (b.e quil (1, 2)); })Copy the code
define('a'['app/c'].function(c) {
  console.log('a');
  console.log(c.sqrt(4));
  return {
    add: function(a, b) {
      returna + b; }}});Copy the code
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)
    }
  }
});Copy the code
define('b'['c'].function(c) {
  console.log('b');
  console.log(c.sqrt(9));
  return {
    equil: function(a,b) {
      returna===b; }}});Copy the code

Open the browser and we can see normal output like this:

1111

It shows that our path resolution work is correct.

In this article, we will discuss how to implement a module loader for AMD modules.