Skip to content

fix: provide module/exports in VM sandbox for Node.js 25+ compatibility#1889

Merged
alexander-akait merged 1 commit intojantimon:mainfrom
Marukome0743:fix/node25-vm-context-module
Apr 17, 2026
Merged

fix: provide module/exports in VM sandbox for Node.js 25+ compatibility#1889
alexander-akait merged 1 commit intojantimon:mainfrom
Marukome0743:fix/node25-vm-context-module

Conversation

@Marukome0743
Copy link
Copy Markdown
Contributor

Summary

Templates whose child compilation output is a CommonJS module — for example,
HtmlWebpackPlugin's child compilation under Rspack — fail at evaluation time
with ReferenceError: module is not defined under Node.js 25+.

The VM sandbox built in evaluateCompilationResult (reshaped in #1880 to work
around a localStorage regression in Node.js 25.2) no longer exposes
module/exports. Any source that assigns to module.exports = ... now throws.

This PR adds a throwaway module = { exports: {} } pair (plus exports) to the
sandbox so CommonJS-wrapped sources evaluate without relying on Node populating
them.

Reproduction

With a Docusaurus 3.10 site using future.faster: true (Rspack) under Node 25.9.0:

Html Webpack Plugin:
Error: module is not defined
  - dev.html.template.ejs:1 eval
  - dev.html.template.ejs:5 [email protected]/.../lib/loader.js!.../dev.html.template.ejs
  - node:vm:149 Script.runInContext
  - index.js:663 HtmlWebpackPlugin.evaluateCompilationResult

Both docusaurus build and docusaurus start fail.

Root cause

The child compilation emitted for the template under Rspack is a regular
CommonJS module that looks roughly like:

module.exports = "<!DOCTYPE html>…";

runInContext executes it against the sandbox built in
evaluateCompilationResult, which since #1880 only exposes the
cloned globals plus require, HTML_WEBPACK_PLUGIN,
htmlWebpackPluginPublicPath, __filename, and __dirname. With neither
module nor exports present, the assignment throws before the string can be
returned.

Under Webpack 5 the child compilation output tends not to reference
module.exports directly, which is why this did not surface there.

Fix

Expose a local { exports: {} } container as module (and a mirror as
exports) in the sandbox. Nothing in the plugin reads from them — they only
exist so a CommonJS-wrapped result can evaluate without throwing:

+ const sandboxModule = { exports: {} };
  const vmContext = vm.createContext(
    Object.assign(globalClone, {
      HTML_WEBPACK_PLUGIN: true,
      require: require,
+     module: sandboxModule,
+     exports: sandboxModule.exports,
      ...
    }),
  );

This mirrors what a normal Node module boundary would provide, without pulling
anything else into the sandbox.

Tests

Added a focused unit test in spec/basic.spec.js that calls
evaluateCompilationResult with a module.exports = "…" source and asserts
the string result.

Verified locally on Node 25.9.0 (macOS):

  • npm run test:only161/161 passed (includes new test)
  • npm run lint — clean (added Rspack to .cspell.json for the new code comment)

End-to-end: patching node_modules/html-webpack-plugin inside a Docusaurus
3.10 project with future.faster: true: docusaurus start and docusaurus build
both succeed.

Related

Templates whose child compilation output is a CommonJS module (for
example Rspack's HtmlWebpackPlugin child compilation, which wraps the
evaluated template in `module.exports = ...`) were failing at evaluation
time with `ReferenceError: module is not defined` under Node.js 25+.

In 5.6.5 the VM context was switched away from a `...global` spread
because spreading `global` on Node 25 throws once `localStorage` is
touched without `--localstorage-file`. The replacement clone only
exposes the standard globals plus `require`, so any CommonJS-wrapped
source that assigned to `module.exports` hit the ReferenceError.

Provide a throwaway `module = { exports: {} }` pair (plus `exports`
aliasing it) in the sandbox so CommonJS-style outputs can evaluate
without assuming the host Node version populates them.

Refs: PR jantimon#1880, facebook/docusaurus#11545
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 17, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@0d1ff98). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1889   +/-   ##
=======================================
  Coverage        ?   92.40%           
=======================================
  Files           ?       18           
  Lines           ?      750           
  Branches        ?      208           
=======================================
  Hits            ?      693           
  Misses          ?       54           
  Partials        ?        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alexander-akait alexander-akait merged commit c862830 into jantimon:main Apr 17, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants