sinon.spy on "import * as Module" 在更新到 Webpack 5 后失败

sinon.spy on "import * as Module" fails after updating to Webpack 5

我正在将应用程序从 Webpack 4 升级到 Webpack 5。它使用 babel-loader。我的生产代码可以正常工作,但在升级到 karma-webpack v5.

后卡在了我们的 Karma/Sinon 集成测试中

此代码以前有效,但现在抛出错误:

// useEditUI.ts
export default function useEditUI() {}

// integration.ts
import sinon from 'sinon';
import * as useEditUI from 'useEditUI';

const spy = sinon.spy(useEditUI, 'default');

我的理解是发生这种情况是因为 import/export 是如何在幕后进行管理的:

以前,间谍逻辑正在查看一个值设置为默认导出的可配置对象。现在,该对象不可配置,没有值 属性,而是有一个映射到默认函数

的 getter

这会破坏 sinon.spy 功能。

研究问题一段时间后,我发现了几个可能有用的库:

None 这些库似乎“正常工作”,其中一些似乎已被废弃。一般来说,他们似乎强制要求使用 commonjs 模块语法。所有其他研究的结果都是将解决方案宣布为 import * as Module 的帖子,这就是原始解决方案最初存在的方式。

在使用 Jest 等更现代的测试工具时,我没有遇到这个问题。这只是我试图减轻的 sinon 的限制。

这里有人有可行的指导吗?谢谢


哎呀呀呀。我花了一些时间在这里找到正确的答案,但我找到了一个有效的答案!它有点脆弱,可能会在未来版本的 webpack 上崩溃,但在撰写本文时适用于最新版本 (5.72.0)

首先,创建一个新的 ad-hoc 插件。使用它来重写 Webpack 生成的源代码。寻找管理导出的代码位并重写源代码,使其包含 configurable: true

AllowMutateEsmExports.prototype.apply = function (compiler) {
  compiler.hooks.compilation.tap(
    'AllowMutateEsmExports', function (compilation) {
      compilation.hooks.processAssets.tapPromise(
        {
          name: 'AllowMutateEsmExports',
          stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
          additionalAssets: true,
        },
        async (assets) => {
          const oldSource = assets['runtime.js'];
          const { ReplaceSource } = compiler.webpack.sources;
          const newSource = new ReplaceSource(oldSource, 'AllowMutateEsmExports');

          const oldCode = 'Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });';
          const newCode = 'Object.defineProperty(exports, key, { configurable: true, enumerable: true, get: definition[key] });';
          const start = oldSource.source().indexOf(oldCode);
          const end = start + oldCode.length;

          newSource.replace(start, end, newCode, 'AllowMutateEsmExports');

          await compilation.updateAsset('runtime.js', newSource);
        }
      );
    }
  );
};

确保插件已加载

plugins: [
  new AllowMutateEsmExports(),
],

使用 * 符号导入受影响的模块。重写默认导出并为其指定一个传统值 self-referential。这仅在 属性 首先可配置时才有效。

import * as useEditUI from 'useEditUI';

Object.defineProperty(useEditUI, 'default', {
  writable: true,
  value: useEditUI.default,
});

Spy 现在可以正常工作了。 :)

const spy = sinon.spy(useEditUI, 'default');