Turbopack: Add importModule() support to webpack loaders (#89630)
## What?
Adds support for `this.importModule()` in Turbopack's webpack loader
compatibility layer. This API allows webpack loaders to dynamically
import and execute modules (CJS, ESM, TypeScript, JSON, WebAssembly,
etc.) during the build process, matching webpack's native
`this.importModule()` behavior.
## Why?
Webpack loaders like `@vanilla-extract/webpack-plugin`, `val-loader`,
and custom loaders use `this.importModule()` to:
- Load configuration files that are themselves modules with dependencies
- Execute code at build time to generate derived source
- Process dependency chains (e.g., a TS config that imports a CJS module
that requires JSON)
Without this, loaders relying on `importModule()` cannot work with
Turbopack.
## How?
### Architecture
The implementation follows a request/response pattern between the
Node.js loader runner and the Rust-side Turbopack compiler:
1. **Loader calls `this.importModule(request)`** in the Node.js loader
runner
2. **IPC message sent to Rust** with the module request and lookup path
3. **Rust resolves the module** using Turbopack's full resolver (with
aliases, loader rules, etc.)
4. **Rust builds a Node.js bundle** containing the module and all its
dependencies using Turbopack's code generation pipeline
5. **Bundle chunks sent back to Node.js** via IPC
6. **Node.js evaluates the bundle in-memory** using `vm.compileFunction`
with a minimal CJS module system
This approach leverages Turbopack's full module graph, so imported
modules benefit from the same resolution (aliases, custom loaders,
TypeScript support, etc.) as regular imports.
### Key Changes
**Rust side (`turbopack-node/src/transforms/webpack.rs`):**
- New `ImportModule` IPC response message type
- Resolves modules through Turbopack's `esm_resolve` with full
`AssetContext`
- Generates a complete Node.js bundle (runtime + chunks + entry) for the
imported module
- Tracks file dependencies for proper cache invalidation
**`SourceTransform` trait (`turbopack-core/src/source_transform.rs`):**
- Added `asset_context` parameter to `SourceTransform::transform()` so
transforms can access the full asset context for module resolution and
bundling
- Updated all implementors: `JsonSourceTransform`,
`TextSourceTransform`, `BytesSourceTransform`, `MdxTransform`,
`PostCssTransform`, `WebpackLoaderItems`
**Node.js runtime (`turbopack-node/js/src/transforms/`):**
- `webpack-loaders.ts`: Implements `this.importModule()` on the loader
context, sends IPC request and evaluates the returned bundle
- `webpack-loaders-runtime.ts`: In-memory CJS module evaluator that
loads bundle chunks using `vm.compileFunction`, with support for:
- Relative and absolute path resolution within the bundle
- External package delegation to real Node.js `require()`
- Patched `fs.createReadStream` for in-memory WebAssembly binary assets
**New `ImportModule` reference subtype
(`turbopack-core/src/reference_type.rs`):**
- Added `EcmaScriptModulesReferenceSubType::ImportModule` to mark
references created by importModule for proper handling in the module
graph
### Test Coverage
Comprehensive e2e test
(`test/e2e/app-dir/webpack-loader-import-module/`) covering:
- **TypeScript modules** with CJS and ESM dependencies
- **CJS dependency chains** (TS → CJS → JSON)
- **ESM `.mjs` modules** with shared dependencies
- **Resolve aliases** (`alias-data` → `alias-data.mjs` via
`resolveAlias` config)
- **Custom loader rules** (`.custom-data` files processed by
`text-to-export-loader`)
- **Transitive dependencies** through aliases and custom loaders
- **Turbopack-only features**: `new URL()` asset references, WebAssembly
imports (`add.wasm`), and dynamic `import()` within importModule targets
All tests pass for both webpack and Turbopack modes.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>