ECMAScript Modules (ESM)
Starting from version 6.13.0, pkg has improved support for ECMAScript Modules. Most ESM features are automatically transformed to CommonJS during packaging.
Supported ESM features
importandexportstatements — automatically transformed torequire()andmodule.exports- Top-level
await— wrapped in an async IIFE to work in a CommonJS context - Top-level
for await...of— wrapped in an async IIFE to work in a CommonJS context import.meta.url— polyfilled to provide the file URL of the current moduleimport.meta.dirname— polyfilled to provide the directory path (Node.js 20.11+ property)import.meta.filename— polyfilled to provide the file path (Node.js 20.11+ property)
Known limitations
- Modules with both top-level await and exports — modules that use
exportstatements alongside top-levelawaitcannot be wrapped in an async IIFE and will not be transformed to bytecode. They are included as source code instead. import.meta.mainand other custom properties — only the standardimport.metaproperties listed above are polyfilled. Custom properties added by your code or other tools may not work as expected.- Dynamic imports —
import()expressions work but may have limitations depending on the module being imported.
Best practices
- For entry-point scripts (the main file you're packaging), feel free to use top-level await.
- For library modules that will be imported by other code, avoid using both exports and top-level await together.
- Test your packaged executable to ensure all ESM features work as expected in your specific use case.
Enhanced SEA mode
In Enhanced SEA mode, ESM entry points with top-level await work on every supported target (Node >= 22) without the async-IIFE transform. ESM entries are dispatched via vm.Script + USE_MAIN_CONTEXT_DEFAULT_LOADER. If your project relies heavily on modern ESM, SEA mode is the cleaner path.
See also
- SEA mode — first-class ESM support
- Recipes: ship an ESM project
- Troubleshooting:
ERR_REQUIRE_ESM
