When is ESM support planned?
Pre-flight checklist
- [X] I have read the contribution documentation for this project.
- [X] I agree to follow the code of conduct that this project uses.
- [X] I have searched the issue tracker for a feature request that matches the one I want to file, without success.
Problem description
When I wanted to migrate to ESM according to the latest version of electron-store, I found that forge.config.ts did not support ESM.
Proposed solution
Support ESM in forge.config
Alternatives considered
- Wait for official support in future electron-forge versions.
- Use a custom build process outside of electron-forge to support ESM.
- Revert to CommonJS modules for the time being
Additional information
ESM support is crucial for our project as we aim to use modern JavaScript features and improve code maintainability. The ability to use 'import' statements directly would significantly simplify our development process and allow us to better align with current JavaScript best practices. We've already migrated most of our codebase to use ESM syntax, and electron-forge's lack of support is the main blocker for our complete transition
There's already a support for ESM, although you might need to use forge.config.mts for now, since Forge relies on other software in order to load some config formats based on their extension.
As of loading ESM directly in forge, which is mostly useful for extensions that Forge loads directly on its own, I've made a PR that is mostly finished and needs the upstream action in order to proceed with either merge of this or some opinions what I could still change: #3582.
As a dev that actually made a maker for forge with the use of ESM syntax and a requirement of using ESM to load it, I've also made an example how to apply this to Forge at its current stage. Having better implementation on Forge side would still be better suited for more transparent loading of stuff, so users of Forge toolkit don't have to care about whenever Forge loads something as ESM or CJS, so supporting both feels like a way to go unless there will be some movement to decide on dropping CJS entirely at some point in JS…
According to the merge request, this has been incorporated into Electron main two weeks ago but I’m guessing it’s release pending?
This would be a really helpful addition as it would also solve/prevent https://github.com/deepkit/deepkit-framework/issues/615, an issue with the Deepkit type compiler getting understandably thrown off by using a different module setting in tsconfig than what is actually expected to be in the output code.
For Electron apps, this seems particularly strange because — assuming we’re all writing ESM syntax — the chain is ESM → CJS → ESM for everything running on an Electron renderer thread.
As a dev that actually made a maker for forge with the use of ESM syntax and a requirement of using ESM to load it, I've also made an example how to apply this to Forge at its current stage. Having better implementation on Forge side would still be better suited for more transparent loading of stuff, so users of Forge toolkit don't have to care about whenever Forge loads something as ESM or CJS, so supporting both feels like a way to go unless there will be some movement to decide on dropping CJS entirely at some point in JS…
这个例子中,没在package.json中配置相对路径,最终使用的不是forgeConfig.mts,而是{}
the chain is ESM → CJS → ESM for everything running on an Electron renderer thread.
this is really throwing me off on a brand new vite-typescript template build
wondering if that CJS in between is necessary
I've gotten a bit closer using this config override:
Hey everyone,
As an update, we're looking at releasing a new major version of all of our @electron/ packages and Forge itself. This will mean two things:
- We're going to look to publish packages in ESM.
- This is going to require Node 22.12, which supports
require(esm).
For the time being, I believe upgrading to Node 22 LTS will get rid of require of ES Modules is not supported errors.
Tip: if your struggling to get your config to load in ESM with the following error:
Cannot require() ES Module <path>\forge.config.mjs in a cycle
Try putting a breakpoint in line indicated in the callstack. In forge-config.js:140 (7.2 at least, not game to update yet) there is:
try { import(file) }
catch() { require(file); }
Which means any errors in the original import will be swallowed and all you get is the (irrelevant) error from the require statement.
i am using electronforge webpack+typescript i change forge.config.ts to .mts and added "type":"module" in package.json and i get this error App threw an error during load ReferenceError: __dirname is not defined in ES module scope This file is being treated as an ES module because it has a '.js' file extension and 'C:\Users\royal\Desktop\Desk\work\github\Locai\package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension. at file:///C:/Users/royal/Desktop/Desk/work/github/Locai/.webpack/main/index.js:1003:84 at file:///C:/Users/royal/Desktop/Desk/work/github/Locai/.webpack/main/index.js:1060:12 at ModuleJob.run (node:internal/modules/esm/module_job:274:25) at async onImport.tracePromise.proto (node:internal/modules/esm/loader:644:26) at async loadApplicationPackage (file:///C:/Users/royal/Desktop/Desk/work/github/Locai/node_modules/electron/dist/resources/default_app.asar/main.js:127:9) at async file:///C:/Users/royal/Desktop/Desk/work/github/Locai/node_modules/electron/dist/resources/default_app.asar/main.js:240:9
year 2025 and still no native support for esm? It seems impossible to declare an forge.config.mjs as config file. And also key type:"module" in package.json causing strange error message for 'electron-forge make'.
TL;DR: Forge outside its own module resolution also relies upon rechoir and jiti for module resolution.
year 2025 and still no native support for esm? It seems impossible to declare an forge.config.mjs as config file. And also key type:"module" in package.json causing strange error message for 'electron-forge make'.
There was initial implementation done by me and it should work in case of Forge-loaded stuff (static configs, module resolution within Forge):
https://github.com/electron/forge/blob/4dc8ed3e78c9b9ccc0382cdba900fae407363939/packages/api/core/src/util/forge-config.ts#L196-L216
In general, dynamicImportMaybe is what is used when there's need to import an ESM Forge maker/plugin/publisher (when modules are declared as static JSON, when you initialize class by yourself this code isn't really useful), unless I remember something wrong from the last time I worked on the Forge's code.
The config.mjs also worked for me well in the past (even before my changes) when I didn't rely on Forge loading at all, and rechoir was used anyway to handle ESM imports in the config itself. But Forge also uses rechoir for ESM formats and recently they've added jiti for handling TS configurations. In the past as far as I remember, the problem with ts loader (in rechoir) was that it always assumed the config was a CommonJS and internally used require only, plus mts was not implemented at all. This was quite unrelated to Forge directly, as the issue was dependency-wide, and to fix it in Forge there would be a need to fork it or replace it. I'm not sure what's doing jiti tho and whenever it is closer with loading ESM modules in a manner closer to Node itself or still doesn't really care about it.
https://github.com/electron/forge/blob/4dc8ed3e78c9b9ccc0382cdba900fae407363939/packages/api/core/src/util/forge-config.ts#L183-L186
Anyway, I have made myself an example Electron application with ESM config in ReForged project, as I want to have it published in public domain you might give it a try and test whenever it works for you. I might even try it out myself, I don't think I use it in automated tests or have a CI to check if it builds. Might be a good idea to add one to my project.