问题描述
环境: 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
(“意外令牌'导出'”错误)。
有什么方法可以让 val-loader
以某种方式加载 ESM 模块?请注意,我不打算使用 val-loader
,任何其他实现相同目标的技术同样受欢迎。
解决方法
在对这个问题和 node/webpack 内部结构进行了比我想要的更多了解之后,似乎有两种可能的方法可以从 CJS 导入 ESM:
-
使用动态
import()
。但它是异步的,因此不适合这里,因为val-loader
需要同步结果。 -
将 ESM 转译为 CJS,这是我采用的方法。
就我而言,完全转译是多余的,重写导入/导出就足够了,所以我使用 ascjs 重写 ESM 文件,并使用 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 导入/导出。因此,big-module
或其依赖项可以要求 CJS 文件。 -
_eval
的第三个参数启用递归重写,否则只会翻译顶级文件(传递给requireESM
的文件)。