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

Viewed 194

Environment: webpack 5.44 + vue.js 3.0 + node 12.21

I'm trying to generate a module at compile-time, in order to avoid a costly computation at run-time (as well as 10Mb of dependencies that will never be used except during said computation). Basically run this at compile-time:

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

which will be imported at run-time as:

export default [ /* static info */ ];

I tried using val-loader (latest 4.0) which seems designed exactly for this use case.

Problem: big-module is an ESM, but val-loader apparently only supports CJS. So I can neither import ("Cannot use import statement outside a module" error) nor require ("Unexpected token 'export'" error).

Is there any way to make val-loader somehow load the ESM module? Note that I'm not bent on using val-loader, any other technique that achieves the same goal is just as welcome.

1 Answers

After learning way more than I wanted about this issue and node/webpack internals, there seems to be two possible approaches to import ESM from CJS:

  1. Use dynamic import(). But it is asynchronous which makes it unfit here, as val-loader requires a synchronous result.

  2. Transpile the ESM into CJS, which is the approach I took.

In my case, full transpiling is overkill and rewriting imports/exports is sufficient, so I'm using ascjs to rewrite the ESM files, along with eval to safely evaluate the resulting string.

All in all:

// 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);

Note that:

  • ascjs is safe to use on CJS modules, since it only rewrites ESM imports/exports. So it's OK for big-module or its dependencies to require CJS files.
  • the third argument to _eval enables recursive rewriting, otherwise only the top-level file (the one passed to requireESM) is translated.
Related