天天看點

seajs 路徑解析過程

簡要說明

本文對Seajs 3.0.1 的部分源碼(util-path.js) 進行學習,習得的體會。

重點是對Sea.js中路徑解析的過程進行源碼級的了解和探索,包括seajs.resolve的定義;id解析到檔案路徑的過程;seajs.config 中alias,paths,vars,map等的具體使用。

檔案出處:util-paths.js

resolve 的定義

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
           

可以看到resolve解析id的主要過程:

alias, 别名解析

path, path解析

vars, 變量解析

normalize, 字尾标準化js

addBase, 添加基礎路徑

map 路徑映射

至此 resolve 讓id 變成 了檔案的路徑,可以進行後續的檔案通路。

alias 别名

seajs.data.alias 存放的是seajs.config中設定的别名映射。

function parseAlias(id) {
      var alias = data.alias
      return alias && isString(alias[id]) ? alias[id] : id
    }
           

alias 解析的函數中可以看到:id作為一個整體 作為alias映射的key

paths 路徑

seajs.data.paths 存放的是seajs.config 中設定的paths的路徑映射。

//比對路徑正規表達式 
    var PATHS_RE = /^([^/:]+)(\/.+)$/;

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

      if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[]])) {
        id = paths[m[]] + m[]
      }

      return id
    }
           

這裡主要就是路徑的正規表達式:id.match(PATHS_RE)

例如:

id  = "abc/def/ghi ";
id.match(PATHS_RE) // 結果是["abc/def/ghi", "abc","def/ghi"]  
           

這裡取值 m[1] ,擷取的是 第一個 / 之前的字元串作為 paths 的 key ,來擷取 paths[‘abc’]的值

最後的結果是: paths[‘abc’] + ‘def/ghi’

vars 變量

seajs.data.vars 存放的是seajs.config 中設定的vars對象。

// 比對vars 的格式 {..}
    var VARS_RE = /{([^{]+)}/g

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

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

      return id
    }
           

如果 id 中存在{ , 将id中的{key} 替換為 vars中定義的vars[key]

主要在正規表達式比對id中的{..} 格式的字元:

vars: {
        "bbb" : "ccc"
    }
    id = "abc/{bbb}/def"
           

VARS_RE 比對結果: [{bbb},bbb] , 其中子模式 bbb作為key,取得vars變量的值 vars[bbb],字元串替換,結果是 abc/ccc/def

normalize 标準化字尾

  1. 以 #結尾的path,不處理
  2. 以.js 結尾,或者 包含有? 的path, 不處理
  3. 以 / 結尾,不處理

給path 添加字尾.js

function normalize(path) {
          var last = path.length - 
          var lastC = path.charCodeAt(last)

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

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

addBase 添加base 基礎路徑

給解析後的path添加上基礎路徑,形成可以用給的檔案位址

可以在seajs.config 中配置base來說設定基礎 路徑

1. 絕對路徑   http://  或者 // 開頭
2. 相對路徑    ./  ../ 
3. 根路徑     /
4. 頂級路徑   a/b/c
           

上面四種路徑的形式,解析成一個有效的檔案位址

// "//"開頭 或者 包含":/" 的絕對路徑
    var ABSOLUTE_RE = /^\/\/.|:\//

    // 比對 ..//../ 結構的字元
    var ROOT_DIR_RE = /^.*?\/\/.*?\//

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

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

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

      return realpath(ret)
    }
           

絕對路徑: ABSOLUTE_RE 比對的,直接傳回

相對路徑: 相對于參考路徑或者目前工作目錄

根路徑: 目前工作目錄如果是一個根路徑,拼接一個位址

頂級路徑: 前面加上 base

// 開頭的path,添加上協定

realpath 真實路徑

轉換path為一個真實路徑;其中包含對相對路徑,多餘// 的處理;從過程中可以看到正規表達式的強大。

var DOT_RE = /\/\.\//g
var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//
var MULTI_SLASH_RE = /([^:/])\/+\//g    
           

其中DOT_RE 比對 /./ 模式的相對路徑,目前目錄下的檔案,直接使用 /替換

a/./b => a/b
           

MULTI_SLASH_RE 将多個/ 轉化 隻有一個 /

a///b//c => a/b/c
           

DOUBLE_DOT_RE 在除去多餘/ 後, 進行切換到上層目錄,将上層目錄替換為 /

a/b/../c  => a/c  其中/b/../ 是比對的字元串,用/ 替換  
           

源碼:

// 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/")

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

      return path
    }
           

map 映射

在對id解析後,得到一個檔案位址,最後進行一次map映射。

seajs.config 可以設定map的映射規則,如果uri應用了一條映射規則後,便傳回,隻映射一次。

function parseMap(uri) {
      var map = data.map
      var ret = uri

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

          ret = isFunction(rule) ?
              (rule(uri) || uri) :
              uri.replace(rule], rule])

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

      return ret
    }
           

總結

以上貼上了seajs的相關源碼,最其中的思想是很佩服的。通過了解這些實作過程,對于我們使用seajs将會更加的便利。

繼續閱讀