[FE] resolver.hooks.noResolve

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

resolvernoResolve hook的寫法如下,

resolver.hooks.noResolve.tap('MyPlugin', (request, error) => {
  console.log('resolver.hooks.noResolve.for(normal)', request, error);
});

下面我們用 normal resolver 的 加載失敗,來說明 requesterror 的數(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,contextloader。

然后使用特定 resolvernoResolve 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容