天天看点

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将会更加的便利。

继续阅读