一、前文回顾
本文主要接上文介绍了 enhance-resolve 中实现 resolve 功能的内部插件实现细节,本小结主要讨论了以下插件:
- SelfReferencePlugin:其作用是结合 exportsFields 配置项和 package.json.exports 字段改写默认的主入口以及子路径的解析规则;
- ModulesInHierarchicalDirectoriesPlugin:其作用是模拟 require.resolve 行为,从当前目录查找 node_moduels 一直到 / 下的这一行为;
- PnpPlugin:咱也不知道,也不想学(一言不合就开摆,就问你气不气😂)
- JoinRequestPartPlugin:将通过命名空间引用其包内部模块的
@ns/sub/module 语法变成相对路径; - DirectoryExistsPlugin:主要是判断截止到当前流程,path 是不是一个真实存在的目录;
- ExportsFieldPlugin:主要实现的是 package.json 中的 exports 字段相关能力的插件;
- UseFilePlugin:实现导入写只写到目录的情况下自动解析到该目录下的 index.js 能力;
- MainFieldPlugin:实现 package.json 中 main 字段相关能力的插件;
二、内部插件
这里主要接上文继续讲解 ehanced-resolve 内部实现 resolve 能力的插件!
2.19 TryNextPlugin
就是单纯的引导到下个钩子。。。看着个 NextPlugin 差不的啊。。。。what????
module.exports = class TryNextPlugin {
constructor(source, message, target) {
this.source = source;
this.message = message;
this.target = target;
}
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("TryNextPlugin", (request, resolveContext, callback) => {
resolver.doResolve(
target,
request,
this.message,
resolveContext,
callback
);
});
}
};
2.20 AppendPlugin
给 path 和 relativePath 追加各个可能得扩展名?加完不再验证一下子是否存在? 后面的流水线中会有这个过程,用来校验这些路径对应的文件是否真实存在!
module.exports = class AppendPlugin {
constructor(source, appending, target) {
this.source = source;
this.appending = appending;
this.target = target;
}
apply(resolver) {
const target = resolver.ensureHook(this.target);
resolver
.getHook(this.source)
.tapAsync("AppendPlugin", (request, resolveContext, callback) => {
const obj = {
...request,
path: request.path + this.appending, // 追加扩展名
relativePath:
request.relativePath && request.relativePath + this.appending // 追加扩展名
};
resolver.doResolve(
target,
obj,
this.appending,
resolveContext,
callback
);
});
}
};
2.21 FileExistsPlugin
获取文件路径,调用 fs.stat 获取文件的 stat 对象:
- stat 显示 path 不是个文件,加入 missingDependencies;
- 正确得 path 对应的文件,加入 fileDependencies 中;
module.exports = class FileExistsPlugin {
constructor(source, target) {
this.source = source;
this.target = target;
}
apply(resolver) {
const target = resolver.ensureHook(this.target);
const fs = resolver.fileSystem;
resolver
.getHook(this.source)
.tapAsync("FileExistsPlugin", (request, resolveContext, callback) => {
// 获取文件路径
const file = request.path;
if (!file) return callback();
// 调用 fs.stat 获取文件的 stat 对象
fs.stat(file, (err, stat) => {
if (err || !stat) {
// 出错,加入 missingDependencies
if (resolveContext.missingDependencies)
resolveContext.missingDependencies.add(file);
if (resolveContext.log) resolveContext.log(file + " doesn't exist");
return callback();
}
if (!stat.isFile()) {
// stat 显示 path 不是个文件,加入 missingDependencies
if (resolveContext.missingDependencies)
resolveContext.missingDependencies.add(file);
if (resolveContext.log) resolveContext.log(file + " is not a file");
return callback();
}
// 正确得 path 对应的文件,加入 fileDependencies 中
if (resolveContext.fileDependencies)
resolveContext.fileDependencies.add(file);
resolver.doResolve(
target,
request,
"existing file: " + file,
resolveContext,
callback
);
});
});
}
};
2.22 SymlinkPlugin
将 path 当做一个 symlink 尝试读取,读到内容就是 symlink,读不到就不是符号链接。
module.exports = class SymlinkPlugin {
constructor(source, target) {
this.source = source;
this.target = target;
}
apply(resolver) {
const target = resolver.ensureHook(this.target);
const fs = resolver.fileSystem;
resolver
.getHook(this.source)
.tapAsync("SymlinkPlugin", (request, resolveContext, callback) => {
if (request.ignoreSymlinks) return callback();
const pathsResult = getPaths(request.path);
const pathSegments = pathsResult.segments;
const paths = pathsResult.paths;
let containsSymlink = false;
let idx = -1;
forEachBail(
paths,
(path, callback) => {
idx++;
if (resolveContext.fileDependencies)
resolveContext.fileDependencies.add(path);
// 将这个 path 当成 symlink 然后读取,读取到内容就认定是符号链接
// 读不到就不是
fs.readlink(path, (err, result) => {
if (!err && result) {
pathSegments[idx] = result;
containsSymlink = true;
// Shortcut when absolute symlink found
// 读取到就短路
const resultType = getType(result.toString());
if (
resultType === PathType.AbsoluteWin ||
resultType === PathType.AbsolutePosix
) {
return callback(null, idx);
}
}
callback();
});
},
(err, idx) => {
if (!containsSymlink) return callback();
const resultSegments =
typeof idx === "number"
? pathSegments.slice(0, idx + 1)
: pathSegments.slice();
const result = resultSegments.reduceRight((a, b) => {
return resolver.join(a, b);
});
const obj = {
...request,
path: result
};
resolver.doResolve(
target,
obj,
"resolved symlink to " + result,
resolveContext,
callback
);
}
);
});
}
};
2.23 ResultPlugin
触发 resolver.hooks.result 钩子,传入 obj 和 resolverContext,obj.path 就是最后的解析路径结果。
module.exports = class ResultPlugin {
constructor(source) {
this.source = source;
}
apply(resolver) {
this.source.tapAsync(
"ResultPlugin",
(request, resolverContext, callback) => {
const obj = { ...request };
if (resolverContext.log)
resolverContext.log("reporting result " + obj.path);
// 触发 resolver.hooks.result 钩子
resolver.hooks.result.callAsync(obj, resolverContext, err => {
if (err) return callback(err);
if (typeof resolverContext.yield === "function") {
resolverContext.yield(obj);
callback(null, null);
} else {
callback(null, obj);
}
});
}
);
}
};
三、总结
本文完成了 ehanced-resolve 的所有插件注册工作,最后总结一下今天学习的各个插件的作用:
- SelfReferencePlugin:作用是结合 exportsFields 配置项和 package.json.exports 改写默认的 main 和子路径的(映射成别的路径);
- ModulesInHierarchicalDirectoriesPlugin:这个插件用作从当前目录一直向上查 node_modules,一直找到 / 目录下的 node_modules 终止,中间如果找到就中断;
- PnpPlugin:这个我也不会,也懒得看 👻👻👻👻👻👻👻👻
- JoinRequestPartPlugin:这个插件简化 @namespace/a/b/c 这种 request 为相对路径;
- DirectoryExistsPlugin:这个插件的作用主要是判断截止到当前流程,path 是不是一个真实存在的目录;
- ExportsFieldPlugin:结合 package.json.exports 字段是用于重新定义模块的导出规则;
- UseFilePlugin:处理导入目录时自动解析到目录下的 index.js;
- MainFieldPlugin:这个插件的作用是把包名和 package.json.main 字段解析拼接;
- TryNextPlugin:就是单纯的引导到下个钩子;
- AppendPlugin:给 path 和 relativePath 追加各个可能得扩展名;
- FileExistsPlugin:获取文件路径,检查其对应的 fs.stat 结果;
- SymlinkPlugin:将 path 当做一个 symlink 尝试读取,读到内容就是 symlink;
- ResultPlugin:触发 resolver.hooks.result 钩子,返回解析结果;
四、enhanced-resolve 工作全流程
这里结合一张流程图了解一下 enhanced-reolve 的工作全貌:

到这里能回答这问题了:为啥有 enhanced-resolve?
这个问题我曾困惑了很久:因为完备且具备扩展性;
我一直很好奇为啥有 require.resolve() 就能解决的问题,为啥还要整这么多的活儿? 起初我以为是效率,后来证明是错误的,使用 enhanced-resolve 非但不快反而慢。后来经大神提示是因为完备可扩展。
enhanced-resolve 可以提升 resolve 效率,并且支持 alias 在内的各种牛逼的特效,不但可以查询,还可以便捷的更改。