微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Webpack 导入顺序在文件中的代码和文件夹中的代码之间创建阴影 webpack.config.js

如何解决Webpack 导入顺序在文件中的代码和文件夹中的代码之间创建阴影 webpack.config.js

我们有时会遇到以下情况:

import { foo,bar } from '../../services/blaService';

我们同时拥有文件 blaService.ts文件blaService/index.ts

Webpack 首先加载文件并丢弃文件夹中的代码,这是预期行为。

我们是否可以通过例如在发生这种代码阴影场景时抛出错误来防止这种情况发生?

解决方法

TLDR;

这是一种解决方法:

webpack.config.js

const path = require('path');
const fs = require('fs');

const DetectShadowingPlugin = {
  apply (resolver) {
    const beforeResolved = resolver.getHook('before-resolved');

    // `cb`- refers to the next `tap` function in the chain
    beforeResolved.tapAsync('DetectShadowingPlugin',(req,ctx,cb) => {
      // To inspect the hook's chain until this moment,see `ctx.stack`(from top to bottom)
      // console.log(ctx);
      
      // The `path` will give us the full path for the file we're looking for
      const { path: filePath } = req;
      
      const ext = path.extname(filePath);
      if (ext !== '.js') {
        // Continuing the process
        return cb();
      }

      const fileName = path.basename(filePath,path.extname(filePath)); // https://stackoverflow.com/a/19811573/9632621
      const possibleDirectoryPath = path.resolve(filePath,'..',fileName);
      fs.access(possibleDirectoryPath,err => {
        if (!err) {
          const message = `Apart from the file ${filePath},there is also a directory ${possibleDirectoryPath}`;
          cb(new Error(message));
          
          return;
        }

        cb();
      });
    });
  },};

/**
 * @type {import("webpack/types").Configuration}
 */
const config = {
  /* ... */

  resolve: {
    plugins: [DetectShadowingPlugin]
  },};

module.exports = config;

结果:

result

文件结构如下:

├── deps
│   ├── foo
│   │   └── index.js
│   └── foo.js
├── dist
│   └── main.js
├── index.js
└── webpack.config.js

foo 是这样导入的:

import defaultFooFn from './deps/foo';

如果您想尝试上面的示例,可以查看this Github repo。稍后我将在 repo 的自述文件中添加设置详细信息(当然……稍后:D),但在那之前,这里是步骤:

  • git clone --recurse-submodules
  • cd webpack
    • 纱线
    • 纱线链接
  • cd ..
  • yarn 链接 webpack
  • 理解纱线 - 还可以查看 package.json 的脚本以获取更多信息

说明

webpack 使用 resolver 来查找文件的位置。我将这个发现过程视为分支分支的集合。有点像 git 分支。它有一个起点,并根据某些条件选择到达终点的路径。

如果您复制了我在上一节中链接的存储库,您应该会在 webpack 文件夹中看到 webpack 存储库。如果您想更好地可视化这些选择的后果,您可以打开 webpack/node_modules/enhanced-resolve/lib/ResolverFactory.js 文件。您不必了解发生了什么,只需注意步骤之间的联系即可:

ResolverFactory

如您所见,parsed-resolve 在第一个位置和最后一个位置都作为参数出现。你也可以看到它使用了各种插件,但它们有一个共同点:一般来说,第一个字符串是,最后一个字符串是目标。我之前提到过,这个过程可以看作是分支的分支。好吧,这些分支由节点组成(直观地说),其中一个节点在技术上称为钩子。

起点是 resolve 钩子(来自 for 循环)。它之后的下一个节点是 parsed-resolve(它是 resolve 钩子的目标)。 parsed-resolve 钩子的目标是 described-resolve 钩子。等等。

现在,有一件重要的事情要提。您可能已经注意到,described-resolve 钩子被多次用作。每次发生这种情况时,都会添加一个新步骤(技术上称为 tap)。当从一个节点移动到另一个节点时,将使用这些步骤。如果该插件(一个步骤由插件添加)决定这样做(这可能是插件中满足某些条件的结果),您可以从一个步骤走另一条路线。

所以,如果你有这样的事情:

plugins.push(new Plugin1("described-resolve","another-target-1"));
plugins.push(new Plugin2("described-resolve","another-target-1"));
plugins.push(new Plugin3("described-resolve","another-target-2"));

described-resolve 您可以从 2 个步骤前往 another-target-1(因此有 2 种到达方式)。如果插件中不满足一个条件,它会转到下一个条件,直到满足插件的条件。如果根本没有选择 another-target-1,那么可能 Plugin3 的条件会导致 another-target-2

所以,就我的观点而言,这就是这个过程背后的逻辑。在这个过程的某个地方,有一个钩子(或者一个节点,如果我们坚持最初的类比),它会在文件被成功找到之后被调用。这是 resolved 钩子,也代表了流程的最后一部分。
如果我们到达了这一点,我们肯定知道一个文件存在。我们现在可以做的是检查是否存在同名文件夹。这就是这个自定义插件正在做的事情:

const DetectShadowingPlugin = {
  apply (resolver) {
    const beforeResolved = resolver.getHook('before-resolved');

    beforeResolved.tapAsync('DetectShadowingPlugin',cb) => {
      const { path: filePath } = req;
      
      const ext = path.extname(filePath);
      if (ext !== '.js') {
        return cb();
      }

      const possibleDirectoryPath = path.resolve(filePath,};

这里有一个有趣的实现细节,它是 before-resolved。请记住,每个钩子,为了确定它的新目标,它必须经历一些由使用相同源的插件定义的条件。我们在这里做了类似的事情,除了我们告诉 webpack 首先运行我们的自定义条件。我们可以说它增加了一些优先级。如果我们想在最后一个条件中运行它,我们会将 before 替换为 after


为什么它首先选择 requestName.js 路径而不是 requestName/index.js

这是由于添加内置插件的顺序造成的。如果您稍微向下滚动 ResolverFactory,您应该会看到以下几行:

// The `requestName.js` will be chosen first!
plugins.push(
    new ConditionalPlugin(
        "described-relative",{ directory: false },null,true,"raw-file"
    )
);

// If a successful path was found,there is no way of turning back.
// So if the above way is alright,this plugin's condition won't be invoked.
plugins.push(
    new ConditionalPlugin(
        "described-relative",{ fullySpecified: false },"as directory","directory"
    )
);

您可以通过注释掉上面的 raw-file 插件来测试它:

enter image description here

然后,根据 repo,您​​应该会看到类似的内容,表明已被选中:

enter image description here

您还可以在该工作树中的任何位置放置断点,然后按 F5 检查程序的执行情况。一切都在 launch.json 文件中。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。