Webpack / Vue.js:在编译时使用 ESM 依赖项生成模块代码

Webpack / Vue.js: generate module code at compile-time using ESM dependencies

环境: webpack 5.44 + vue.js 3.0 + node 12.21

我正在尝试在编译时生成一个模块,以避免在 运行 时进行昂贵的计算(以及 10Mb 的依赖项,除非在计算期间永远不会使用) .基本上 运行 这在编译时:

import * as BigModule from "big-module";
function extract_info(module) { ... }
export default extract_info(BigModule);

将在 运行 时导入为:

export default [ /* static info */ ];

我尝试使用 val-loader(最新的 4.0),它似乎正是为这个用例设计的。

问题: big-module是一个ESM,但是val-loader显然只支持CJS。所以我既不能import(“不能在模块外使用导入语句”错误)也不能require(“意外令牌'export'”错误)。

有没有办法让 val-loader 以某种方式加载 ESM 模块? 请注意,我不打算使用 val-loader 任何其他技术达到同样的目标同样受欢迎。

在了解了关于这个问题和 node/webpack 内部的更多信息之后,似乎有两种可能的方法可以从 CJS 导入 ESM:

  1. 使用动态 import()。但它是异步的,这使得它不适合这里,因为 val-loader 需要同步结果。

  2. 将ESM转译为CJS,这是我采用的方法。

在我的例子中,完全转译是矫枉过正,重写 imports/exports 就足够了,所以我使用 ascjs to rewrite the ESM files, along with eval 来安全地计算结果字符串。

总而言之:

// require-esm.js

const fs = require('fs');
const ascjs = require('ascjs');
const _eval = require('eval');
function requireESM(file) {
  file = require.resolve(file);
  return _eval(ascjs(fs.readFileSync(file)), file, { require: requireESM }, true);
}
module.exports = requireESM;

// val-loader-target.js

const requireESM = require('./require-esm');
const BigModule = requireESM('big-module');
function extract_info(module) { ... }
module.exports = extract_info(BigModule);

注意:

  • ascjs 在 CJS 模块上使用是安全的,因为它只重写 ESM imports/exports。因此 big-module 或其依赖项需要 CJS 文件是可以的。
  • _eval 的第三个参数启用递归重写,否则只有顶级文件(传递给 requireESM 的文件)被翻译。