简要说明
本文对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[1]])) {
id = paths[m[1]] + m[2]
}
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("{") > -1) {
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 标准化后缀
- 以 #结尾的path,不处理
- 以.js 结尾,或者 包含有? 的path, 不处理
- 以 / 结尾,不处理
给path 添加后缀.js
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"
}
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(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)
}
绝对路径: 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 = 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
}
总结
以上贴上了seajs的相关源码,最其中的思想是很佩服的。通过了解这些实现过程,对于我们使用seajs将会更加的便利。