1. 程式人生 > >(四)seajs.config中vars、alias、paths和map的作用,以及util-path路徑解析原始碼

(四)seajs.config中vars、alias、paths和map的作用,以及util-path路徑解析原始碼

這篇文章主要是學習下seajs.config中vars、alias、paths、map這4個配置引數的作用和使用方式。這4個配置都會影響一個模組最終的url路徑。

alias

seajs.config({
  
  base: 'http://www.main.com/base/',
  
  alias: {
    'jquery': 'jquery/jquery/1.10.1/jquery',
    'app/biz': 'http://path/to/app/biz.js',
  }
});
define(function(require, exports, module) {

   var $ = require('jquery');
   //=> 載入的是 http://www.main.com/base/jquery/jquery/1.10.1/jquery.js

   var biz = require('app/biz');
   //=> 載入的是 http://path/to/app/biz.js

});
當模組標識很長,寫起來不方便、容易出錯的時候,可以使用alias來簡化模組標識。在seajs.config中進行一次配置之後,所有js模組都可以用require("jquery")這種簡單的方式來載入對應的模組了。使用alias,可以讓檔案的真實路徑與模組呼叫標識分開,有利於統一維護。

paths

seajs.config({

  base: 'http://www.main.com/base/',
  
  // 別名配置
  alias: {
    'hostA-jquery': 'hostA/jquery/1.10.1/jquery.js',
  },

  paths: {
    'hostA': 'https://www.shenzhen.com/sz'
	'app': 'path/to/app'
  }
});
define(function(require, exports, module) {

   var underscore = require('hostA/underscore');
     //=> 載入的是 https://www.shenzhen.com/sz/underscore.js
	 
   var jquery = require('hostA-jquery');
	 //=> 載入的是 https://www.shenzhen.com/sz/jquery/1.10.1/jquery.js
	 
   var biz = require('APP/biz');
     //=> 載入的是 http://www.main.com/base/path/to/app/biz.js

});
當目錄層次比較深,或者是跨目錄呼叫模組的時候,可以用path簡化模組標識的書寫。paths 配置可以結合 alias 配置一起使用,讓模組引用非常方便。

vars

seajs.config({
  base: 'http://www.main.com/base/',
  
  vars: {
    'locale': 'zh-cn'
  }
});
define(function(require, exports, module) {

  var lang = require('i18n/{locale}.js');
     //=> 載入的是 http://www.main.com/base/i18n/zh-cn.js
});
有些場景下,模組路徑在執行時才能確定,這時可以使用 vars 變數來配置。vars配置的是模組標識中的變數值,在模組標識中用 {key} 來表示變數。

map

seajs.config({
   base: 'http://www.main.com/base/',
   
  'map': [
    [ /(.*?)(\.js)$/i , '$1-debug.js']
  ]
});
define(function(require, exports, module) {

  var a = require('cs/a');
     //=> 載入的是 http://www.main.com/base/cs/a-debug.js

});
map配置主要用來做除錯用途,用來做路徑轉換、版本號、時間戳等管理。


seajs解析模組的url,就是根據模組標識(到底是相對標識、頂級標識還是普通標識),和上面這4個配置來確定的。相關原始碼在util-path.js中,下面附上util-path.js中最核心部分的原始碼,我加了很多註釋,應該很容易看懂。如果有興趣專研,可以用seajs.resolve()來嘗試一下。

/**
 * util-path.js - The utilities for operating path such as id, uri
 */

 
// '/'字元是正則表示式常量的邊界,所以需要用'\'進行轉義
var DIRNAME_RE = /[^?#]*\//;

// 全域性匹配"/./",字元slash和dot都是特殊字元
var DOT_RE = /\/\.\//g;

// DOUBLE_DOT_RE.test("//../") ==>false
// DOUBLE_DOT_RE.test("/as/../") ==>true
var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//;


// MULTI_SLASH_RE.test("c//") ==> true
// MULTI_SLASH_RE.test("///") ==> false
// MULTI_SLASH_RE.test("://") ==> false
// MULTI_SLASH_RE.test("c://") ==> false
// "http://asb///ss".match(MULTI_SLASH_RE)[0] ==> "b///"
var MULTI_SLASH_RE = /([^:/])\/+\//g;

// Extract the directory portion(一部分) of a path
// dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/"
// ref: http://jsperf.com/regex-vs-split/2
function dirname(path) {
	//如果path不匹配正則,此時方法會報異常
	//沒有使用/g模式,只查詢第一個匹配項,match()返回的是陣列
  return path.match(DIRNAME_RE)[0];
}

// Canonicalize(規範化) a path
// realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c"
function realpath(path) {
  // /a/b/./c/./d ==> /a/b/c/d
  path = path.replace(DOT_RE, "/");

  /*
    @author wh1100717
    a//b/c ==> a/b/c
    a///b/////c ==> a/b/c
    DOUBLE_DOT_RE matches a/b/c//../d path correctly only if replace // with / first
  */
  path = path.replace(MULTI_SLASH_RE, "$1/");//$1代表第一個分組內容

  // a/b/c/../../d  ==>  a/b/../d  ==>  a/d
  // first replace "/c/../" ,then replace "/b/../" with "/"
  while (path.match(DOUBLE_DOT_RE)) {
    path = path.replace(DOUBLE_DOT_RE, "/");
  }

  return path;
}

// Normalize an id
// normalize("path/to/a") ==> "path/to/a.js"
// normalize("a/b/c/") ==> "a/b/c"
// normalize("a/b/c/?d=1") ===> "a/b/c/?d=1"
// NOTICE: substring is faster than negative slice and RegExp
// 看懂這個方法後,理解[http://www.zhangxinxu.com/sp/seajs/docs/zh-cn/module-identifier.html#tips]太容易了吧
function normalize(path) {
  var last = path.length - 1;
  var lastC = path.charCodeAt(last);

  // If the uri ends with `#`, just return it without '#'
  if (lastC === 35 /* "#" */) {
    return path.substring(0, last);
  }

  return (path.substring(last - 2) === ".js" ||
      path.indexOf("?") > 0 ||
      lastC === 47 /* "/" */) ? path : path + ".js";
}

// 不能以"/"和":"開頭, 結尾必須要是 "/"後面跟著任意字元(數量至少1個)
// PATHS_RE.test("dd/")  ==> false
// PATHS_RE.test("dd/ss") ==> true
// PATHS_RE.test("d:d/ss")==> false
// PATHS_RE.test("/aa/ss")==> false
var PATHS_RE = /^([^/:]+)(\/.+)$/;

// 匹配變數
// VARS_RE.test("{}") ==>  false
// VARS_RE.test("{{}") ===> false
// VARS_RE.test("{as}") ===> true
var VARS_RE = /{([^{]+)}/g;

// 解析seajs.config({})中的alias配置
function parseAlias(id) {
  var alias = data.alias;
  return alias && isString(alias[id]) ? alias[id] : id;
}

function parsePaths(id) {
  var paths = data.paths;
  var m;

  // 當id匹配PATHS_RE的時候,m[1]代表第一個分組的內容,m[2]代表第二個分組的內容
  // m[0]代表的是匹配的內容(不含分組資訊,對我這裡來說沒有啥用)
  if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {
    id = paths[m[1]] + m[2];
  }

  return id;
}

function parseVars(id) {
  var vars = data.vars;

  if (vars && id.indexOf("{") > -1) {
    id = id.replace(VARS_RE, function(m, key) {
	 // m like {name}, key like name
      return isString(vars[key]) ? vars[key] : m;
    });
  }

  return id;
}

// 解析config.map支援正則表示式和函式
// map最佳實踐:用來做版本號和時間戳管理的
/*
seajs.config({
  map: [
    ['.js', '-debug.js']
  ]
});
*/
/*
seajs.config({
  'map': [
    [ /^(.*\.(?:css|js))(.*)$/i, '$1?20110801']
  ]
});
*/
function parseMap(uri) {
  var map = data.map;
  var ret = uri;

  if (map) {
    for (var i = 0, len = map.length; i < len; i++) {
      var rule = map[i]

      ret = isFunction(rule) ?
          (rule(uri) || uri) :
          uri.replace(rule[0], rule[1]);

      // Only apply the first matched rule
      if (ret !== uri) break;
    }
  }

  return ret;
}

// 包含":/" 或者以"//{char}"開頭({char}代表任意一個字元)
// ABSOLUTE_RE.test("/a") ==>  false
var ABSOLUTE_RE = /^\/\/.|:\//;

// ROOT_DIR_RE.test("///")  ==> true
// ROOT_DIR_RE.test("sd//dd") ==>false
// ROOT_DIR_RE.test("sd//dd/") ==>true
// ROOT_DIR_RE.test("http://127.0.0.1:8080/s/") ==> true
// ROOT_DIR_RE.test("http://127.0.0.1:8080/s") ==> true
var ROOT_DIR_RE = /^.*?\/\/.*?\//;


function addBase(id, refUri) {
  var ret;
  var first = id.charCodeAt(0);

  // Absolute
  if (ABSOLUTE_RE.test(id)) {
    ret = id;
  }
  // Relative
  else if (first === 46 /* "." */) {
    ret = (refUri ? dirname(refUri) : data.cwd) + id;
  }
  // Root
  else if (first === 47 /* "/" */) {
    var m = data.cwd.match(ROOT_DIR_RE);
    ret = m ? m[0] + id.substring(1) : id;
  }
  // Top-level
  else {
    ret = data.base + id;
  }

  // Add default protocol when uri begins with "//"
  if (ret.indexOf("//") === 0) {
    ret = location.protocol + ret;
  }

  return realpath(ret);
}

function id2Uri(id, refUri) {
  if (!id) return "";

  id = parseAlias(id);
  id = parsePaths(id);
  id = parseAlias(id);
  id = parseVars(id);
  id = parseAlias(id);
  id = normalize(id);
  id = parseAlias(id);

  var uri = addBase(id, refUri);
  uri = parseAlias(uri);
  uri = parseMap(uri);

  return uri;
}

// For Developers
seajs.resolve = id2Uri;

可以看到id2Uri這個方法在不停的處理alias、paths、vars、map,有興趣的可以去專研。util-path.js核心就是一堆正則表示式,上面已經加了很多註釋。