1. 背景
在使用webpack的時候,Can't resolve ... in ...這樣的錯誤真是屢見不鮮了。
這種錯誤源于webpack沒有找到相關(guān)的資源,
那么webpack到底是去什么地方找了呢?尋找方式是什么呢?
webpack默認(rèn)的出錯信息并沒有寫清楚,
ERROR in ./src/index.js
Module not found: Error: Can't resolve 'xyz' in '/Users/thzt/projj/github.com/thzt/debug-webpack/src'
@ ./src/index.js 1:0-22
以上錯誤提示,只是說明webpack找不到xyz這個模塊,
并沒有寫明,webpack的尋找方式。
2. Resolver
webpack并沒有使用node默認(rèn)的查找資源的方式,
而是自己寫了一個Resolver。
在 webpack/lib/ResolverFactory.js 第60行,webpack 調(diào)用了 enhanced-resolve 模塊創(chuàng)建了一個 resolver,
const Factory = require("enhanced-resolve").ResolverFactory;
...
module.exports = class ResolverFactory extends Tapable {
...
_create(type, resolveOptions) {
...
const resolver = Factory.createResolver(resolveOptions);
...
this.hooks.resolver.for(type).call(resolver, resolveOptions);
return resolver;
}
}
enhanced-resolve/lib/Resolver.js 第49行 給 resolver 添加了 noResolve hooks,
this.hooks = {
resolveStep: withName("resolveStep", new SyncHook(["hook", "request"])),
noResolve: withName("noResolve", new SyncHook(["request", "error"])),
resolve: withName("resolve", new AsyncSeriesBailHook(["request", "resolveContext"])),
result: new AsyncSeriesHook(["result", "resolveContext"])
};
因此,我們就可以通過 noResolve 這個hooks 來獲取模塊加載失敗的信息了。
3. compiler.resolverFactory.hooks.resolver
class MyPlugin {
apply(compiler) {
compiler.resolverFactory.hooks.resolver.for('normal').tap('MyPlugin: normal resolver', resolver => {
resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
console.log('resolver.hooks.noResolve.for(normal)', request, error);
});
});
compiler.resolverFactory.hooks.resolver.for('context').tap('MyPlugin: context resolver', resolver => {
resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
console.log('resolver.hooks.noResolve.for(context)', request, error);
});
});
compiler.resolverFactory.hooks.resolver.for('loader').tap('MyPlugin: loader resolver', resolver => {
resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
console.log('resolver.hooks.noResolve.for(loader)', request, error);
});
});
}
}
其中,compiler.resolverFactory.hooks.resolver是一個 HookMap,
源碼位于,webpack/lib/ResolverFactory.js 第19行,
this.hooks = {
resolveOptions: new HookMap(
() => new SyncWaterfallHook(["resolveOptions"])
),
resolver: new HookMap(() => new SyncHook(["resolver", "resolveOptions"]))
};
查看 HookMap Interface,可知 HookMap 中的某一個 hook 可以通過 .for(name) 來獲取,
因此,這里寫了
compiler.resolverFactory.hooks.resolver.for('normal')
compiler.resolverFactory.hooks.resolver.for('context')
compiler.resolverFactory.hooks.resolver.for('loader')
對應(yīng)了三種 resolver,詳見Resolvers - Types。
'normal': Resolves a module via an absolute or relative path.
'context': Resolves a module within a given context.
'loader': Resolves a webpack loader.
(1)normal對應(yīng)了通過絕對或相對路徑加載模塊的方式,例如,
require('xyz');
(2)context對應(yīng)了 require.context 的模塊加載方式,例如,
require.context('./lib');
(3)loader是當(dāng)webpack加載指定資源對應(yīng)loader時觸發(fā)的。
4. resolver.hooks.noResolve
resolver 的 noResolve hook的寫法如下,
resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
console.log('resolver.hooks.noResolve.for(normal)', request, error);
});
下面我們用 normal resolver 的 加載失敗,來說明 request 和 error 的數(shù)據(jù)結(jié)構(gòu),

參數(shù)說明:
request: {
context: {
issuer, // 父模塊絕對路徑
},
path, // 項(xiàng)目根目錄
request, // 模塊名
}
error: {
details, // 路徑解析堆棧
message, // 錯誤消息
missing, // 查找過程
stack, // 錯誤堆棧
}
其中,
(1)request.context.issuer
"/Users/thzt/projj/github.com/thzt/debug-webpack/src/index.js"
(2)request.path
"/Users/thzt/projj/github.com/thzt/debug-webpack/src"
(3)request.request
"xyz"
(4)error.missing
[
"/Users/thzt/projj/github.com/thzt/debug-webpack/src/node_modules",
"/Users/thzt/projj/github.com/thzt/node_modules",
"/Users/thzt/projj/github.com/node_modules",
"/Users/thzt/projj/node_modules",
"/Users/thzt/node_modules",
"/Users/node_modules",
"/node_modules",
"/Users/thzt/projj/github.com/thzt/debug-webpack/node_modules/xyz",
"/Users/thzt/projj/github.com/thzt/debug-webpack/node_modules/xyz.wasm",
"/Users/thzt/projj/github.com/thzt/debug-webpack/node_modules/xyz.mjs",
"/Users/thzt/projj/github.com/thzt/debug-webpack/node_modules/xyz.js",
"/Users/thzt/projj/github.com/thzt/debug-webpack/node_modules/xyz.json"
]
(5)error.message
"Can't resolve 'xyz' in '/Users/thzt/projj/github.com/thzt/debug-webpack/src'"
5. 總結(jié)
我們通過compiler.resolverFactory找到了針對于不同類型模塊的 resolver,
分為三種normal,context和loader。
然后使用特定 resolver 的 noResolve hook,攔截到了錯誤對象。
其中,request.context.issuer表示當(dāng)前被查找模塊的父模塊的文件路徑,
request.request,表示了當(dāng)前被查找的模塊名,
而error.missing,展示了模塊的查找順序。
compiler.resolverFactory.hooks.resolver.for('normal').tap('my', resolver => {
resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
request.context.issuer // 父模塊
request.request // 當(dāng)前被查找的模塊
error.missing // 當(dāng)前模塊的查找順序
});
});
參考
webpack: Resolvers
webpack: require.context
tapable: HookMap
github: enhanced-resolve
github: webpack