babel-plugin-dynamic-import-node
A Babel plugin that rewrites ES `import()` calls into a Promise-wrapped `require()` for Node.js environments.
Repository Health
Technical Analysis
babel-plugin-dynamic-import-node solves a narrow but common compatibility gap: code written with the ES dynamic import() proposal needs to run somewhere that only understands CommonJS require(), such as an older Node runtime, a Jest test file, or any tool in a build pipeline that hasn’t adopted native ESM. The plugin hooks into Babel’s parser to recognize import() syntax, then rewrites each call site into Promise.resolve().then(() => require(source)) (or a template-literal variant when the import source isn’t a static string), optionally interop-wrapping the result so default exports from CommonJS modules behave as expected.
It ships as a single-purpose visitor plugin with no configuration surface beyond a noInterop flag, and it deliberately supports both Babel 6 and Babel 7 side by side so consuming projects can adopt it during a Babel major-version migration. Its main real-world footprint is as the transform used internally by babel-jest and Create React App’s Babel preset to make import() usable inside test files that don’t run through a bundler.
What You Get
- A single Babel visitor that intercepts
ImportAST nodes and swaps them for arequire()-based equivalent, with no other configuration needed beyond adding the plugin to.babelrc - Automatic interop handling so
import()of a CommonJS module still resolves to the expected default-export shape, unless thenoInteropoption is set - Support for both static string import sources and fully dynamic (template-literal or expression) import sources, each compiled to a matching code path
- Dual compatibility with Babel 6 (
babel-core) and Babel 7 (@babel/core), so the same plugin works unmodified across a Babel major-version upgrade - A de-duplication guard (via
WeakSet) that prevents the sameimport()call from being transformed twice when multiple Babel passes visit the same AST
Common Use Cases
- Running Jest test suites that contain
import()calls, since Jest transforms files through Babel rather than a bundler and has no native support for dynamic import on its own - Supporting dynamic
import()syntax in Node.js versions or build pipelines that predate native ESM/dynamic-import support - Migrating a codebase from Babel 6 to Babel 7 while keeping dynamic-import transformation working identically on both versions during the transition
- Providing Node-compatible output for isomorphic code that also targets a bundler (webpack) via a sibling plugin, so the same source can be built for both client and server targets
Under The Hood
Architecture
The plugin is deliberately minimal: src/index.js registers a single Babel visitor for Import AST nodes and delegates the actual rewrite to createDynamicImportTransform in src/utils.js. That factory builds four code templates upfront (static-source/dynamic-source, each with an interop and no-interop variant) using Babel’s template helper, then returns a visitor callback that picks the right template per call site and swaps the compiled expression in via path.parentPath.replaceWith. A WeakSet of already-visited paths guards against double-transformation across multiple compiler passes. With only two small source files and no internal layering beyond “plugin registration” versus “transform factory,” there is little architectural surface to change — a modification to the core Promise/require templates is effectively the only place the transform’s output shape can shift.
Tech Stack
Written in plain ES module JavaScript, compiled to CommonJS via babel-cli (babel src --out-dir lib) for publishing. It targets two Babel major versions simultaneously — babel-core for Babel 6 and @babel/core for Babel 7 — both listed as dev dependencies alongside their respective preset/plugin ecosystems. The only runtime dependency is object.assign, a spec-compliant Object.assign polyfill for older Node targets. Testing runs on tape with airbnb-js-shims and babel-register providing on-the-fly transpilation, and CI runs across a wide Node version matrix (0.12 through 13) via Travis.
Code Quality
The test suite (test/index.js + test/testPlugin.js) drives a fixture-based comparison: for each scenario under test/fixtures/* (basic import, dynamic argument, template argument, chained/nested imports, imports with comments, non-string arguments), it runs the plugin through both Babel 6 and Babel 7 transform pipelines and diffs the output against checked-in expected.*.js files for each interop/es2015 permutation — a thorough matrix given the codebase’s small size. ESLint (Airbnb config) is enforced as a pretest step that fails the test run on lint errors. There is no TypeScript or runtime type checking; the code is plain, untyped JavaScript, and error handling is minimal since the transform has almost no failure modes to guard against.
API Design
The plugin’s entire public surface is a single Babel .babelrc entry ("dynamic-import-node") plus one optional boolean option, noInterop. There is no exported JS API beyond the default Babel-plugin function itself — consumers never import or call anything from this package directly in application code; it only participates through Babel’s plugin-loading mechanism. That minimalism keeps onboarding trivial (one line of Babel config) at the cost of zero flexibility beyond the interop toggle.