As part of the Google Summer of Code (GSOC) program, I worked on improving ESM output support in Webpack. The main objective was to implement the "ESM Module Output" feature outlined in issue #17121.
With the JavaScript ecosystem transitioning to ES modules, enabling Webpack to output ESM allows for better tree-shaking and more natural dynamic imports. This report summarizes the key contributions I made during the program.
When using Webpack's externals option to exclude external modules from bundling, the previous implementation imported all named exports from ESM external modules, regardless of actual usage.
Implemented selective import of only used exports during module concatenation:
// Before: Import everything
import * as __WEBPACK_EXTERNAL_MODULE__ from "external";
// After: Import only what's used
import { HomeLayout, a } from "external";Key implementation details:
- Leveraged
exportsInfo.getUsedExportsingetSourceForModuleExternalfunction - Properly collected and processed InitFragments in
ModuleConcatenationPlugin - Handled name collision avoidance during external module resolution
This optimization significantly reduces final bundle size by excluding unused exports.
When using ESM modules in Node.js, chunk loading for Web Workers and Worker Threads was limited to CommonJS format.
Added ESM worker support for Node.js targets:
// ESM workers now supported
const worker = new Worker(
new URL("./worker.js", import.meta.url),
{ type: "module" }
);Technical details:
- Confirmed support for Node.js 12.17.0 LTS and later
- Automatic configuration of
dynamicImportInWorker - Set output file extension to
.mjs
This enables the same worker code to run in both browser and Node.js environments without platform-specific branching.
HMR was not implemented when using ESM output with experiments.outputModule: true.
Extended the following modules to enable HMR functionality in ESM chunk format:
- ModuleChunkFormatPlugin: Export
__webpack_modules__,__webpack_runtime__, and__webpack_ids__when rendering HotUpdateChunk - ModuleChunkLoadingPlugin: Register runtime requirements for
RuntimeGlobals.hmrDownloadUpdateHandlersandRuntimeGlobals.hmrDownloadManifest - ModuleChunkLoadingRuntimeModule: Implement dynamic update chunk loading using
import()
This enables fast development iteration while using ESM output with various web frameworks.
Addressing the issue reported in #15947. Webpack bundles output as ESM libraries couldn't load chunks via dynamic imports when used in other Webpack projects.
The previous implementation used string concatenation for URL generation, causing relative path resolution failures and file:// protocol errors when used as ESM modules.
Working towards achieving "chunk loading using new URL()" and "statically analyzable import()" statements.
Completed implementations:
- Basic improvements using
new URL()for ESM library output - Created
LibraryHelpers.jsto consolidate library type determination logic - Added test cases confirming dynamic imports work in ESM libraries
Currently in development:
- Making chunk filename generation fully statically analyzable
Implementing resource hints functionality requested in issues #19377, #19373, and #18524 for new URL() syntax and Worker syntax.
Implemented resource hint configuration using magic comments:
// Prefetch assets for future use
const imageUrl = new URL(
/* webpackPrefetch: true */
'./assets/hero.jpg',
import.meta.url
);Key features implemented:
- Added
prefetchAssetandpreloadAssettoRuntimeGlobalsfor dynamic<link>element generation at runtime - Support for numeric ordering in Worker syntax (similar to
import()behavior) - Automatic
asattribute determination based on file extension (image, font, style, script, etc.)
When modules are output as ESM, they automatically run in strict mode. This feature detects code patterns that may cause issues during this conversion and warns developers.
Implemented compatibility checks based on ECMAScript specification Annex C:
- Syntax errors:
withstatements, duplicate parameters, etc. - Runtime behavior changes:
arguments.callee, top-levelthis, etc. - Assignment errors: assignments to undeclared variables, etc.
module.exports = {
experiments: {
strictModeChecks: true // or "warn" | "error"
}
};This helps detect potential issues early and ensures smooth ESM migration.
- Jest Upgrade – PR #19604: Upgraded test runner to Jest v30
- Circular Dependency Tests – PR #19623: Added test cases for circular dependencies involving ESM external modules
- CI Fix – PR #19813: Fixed GitHub Actions to use Node.js 24.5.0
- Discussion Template Improvements – PR #19693: Improved discussion templates to reduce maintenance burden and streamline community communication
Through this GSOC program, I have addressed 10 out of 15 tasks outlined in issue #17121, with several PRs already merged and others currently in active development. These contributions have advanced Webpack's ESM output support.
I plan to continue working on the remaining tasks to bring Webpack's ESM output capabilities to their full potential through long-term maintenance and contributions.
I would like to thank @alexander-akait, @xiaoxiaojx, and @hai-x for their technical support, and @evenstensberg, @snitin315, and @ovflowd for their overall guidance and support throughout the program.