Skip to content

feat!: Node 22 + ESM#4059

Merged
erickzhao merged 71 commits intonextfrom
node-22
Mar 5, 2026
Merged

feat!: Node 22 + ESM#4059
erickzhao merged 71 commits intonextfrom
node-22

Conversation

@erickzhao
Copy link
Member

@erickzhao erickzhao commented Nov 26, 2025

BREAKING CHANGE: this package now requires Node.js 22 and ESM. All @electron/ packages are bumped to their latest major versions.

Closes #3623
Closes #3684
Closes #4059

  • Bump engines to Node 22
  • Update @types/node dependencies
  • Upgrade all @electron/ packages to their latest major versions
  • Use type: module in package.json
  • Drop support Rechoir/Interpret configurations
  • Replace ts-node with tsx
  • Remove dependencies on distutils and Python 3.11
  • Replace lodash with lodash-es
  • Upgrade to Vitest 4
  • Get fast tests to pass
  • Get slow tests to pass
  • Re-enable ESLint import plugin with correct ESM parsing
  • Check Forge configuration ESM/CJS compatibility

Split off PRs:

Future improvements:

  • Check if we still need jiti for the configuration loading
  • Check Webpack plugin ESM/CJS compatibility
  • Check Vite plugin ESM/CJS compatibility

@erickzhao erickzhao added the next label Nov 27, 2025
@erickzhao erickzhao changed the title chore: upgrade major versions to Node 22 chore: Node 22 + ESM Dec 2, 2025
@socket-security
Copy link

socket-security bot commented Dec 4, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

@erickzhao erickzhao dismissed georgexu99’s stale review February 27, 2026 21:03

All comments were addressed in subsequent commits.

@erickzhao erickzhao requested a review from a team February 28, 2026 05:58
"node-fetch": "^2.6.7",
"rechoir": "^0.8.0",
"semver": "^7.2.1",
"source-map-support": "^0.5.13",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aside: I don't think we're using this package ✂️

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oop, never resolved/merged #3773

Comment on lines -25 to -26
case 'arm':
return 'armv6hl';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: as noted in electron/packager#1830, the TargetArch type from Packager 18 (which is extended by the ForgeArch type used in rpmArch) used to evaluate to just string, so arm was a legal value even though it wasn't explicitly supported by Packager. Since the RPM maker handled arm differently, though, this is technically a breaking change (you could get Node 22.22.0 for the archv7l arch on Linux and run Forge 8 on something like a 32-bit Raspberry Pi, which will produce process.arch === 'arm' 🤷🏼‍♂️)

Assuming we don't care about armv6hl, I wonder if we should have rpmArch return armv7hl or aarch64 when nodeArch === 'arm' and update the test accordingly, so that this maker at least provides some valid input to electron-installer-redhat.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I was considering just dropping support for arch/platform combos that weren't supported by Packager in this PR 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, as long as we call it out as a breaking change in the commit message. In that case, should we have the default branch throw an unsupported arch/platform combo error and refuse to proceed, or do you think it's ok to just keep using the provided arch even if it's not supported as we do now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erickzhao erickzhao requested a review from erikian March 4, 2026 21:38
Comment on lines +30 to +31
// The entrypoint can be defined under `exports` or `main` in package.json now!
const main = pj.exports ?? pj.main;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: all subpackages have only the exports field set now, so we should be good to ditch the pj.main fallback here:

Suggested change
// The entrypoint can be defined under `exports` or `main` in package.json now!
const main = pj.exports ?? pj.main;
const main = pj.exports;

Comment on lines +40 to +50
export function serialHooks<T extends (...args: any[]) => any>(
hooks: T[] = [],
): [T] {
return [
async function (opts: Parameters<T>[0]): Promise<void> {
for (const hook of hooks) {
await hook(opts);
}
} as T,
];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Packager exports an identical serialHooks function that we can use directly:

Suggested change
export function serialHooks<T extends (...args: any[]) => any>(
hooks: T[] = [],
): [T] {
return [
async function (opts: Parameters<T>[0]): Promise<void> {
for (const hook of hooks) {
await hook(opts);
}
} as T,
];
}
import {
// ...
serialHooks
} from '@electron/packager';

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops sorry. I think I actually implemented this in Forge first before upstreaming the changes to Packager haha. c6a65db

Comment on lines 75 to 77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: now that importSearchRaw always does a dynamic import, we should always return null if the module has no default export so that we don't get an error down the line if the user-provided file exists and can be correctly imported, but has only named exports by mistake.

For instance, the code below would cause this function to return [Module: null prototype] { afterCopyHook: [Function: afterCopyHook] }, and Packager would then attempt to call the entire module object as a function:

// user-provided-after-copy-hook.js
export function afterCopyHook() {
  //   ^ user error: missing `default`
}
Suggested change
return result?.default || null;

Maybe we could add a test case for this scenario?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 60 to 66
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: still on the importSearch issue, we're not filtering out null return values here, so Packager will attempt to call null as a function and throw an error.

This isn't directly related to this PR, though, as it was also possible to get null when using require if the user-provided file had no module.exports.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eye! 896db28

@erickzhao erickzhao merged commit 4229328 into next Mar 5, 2026
11 checks passed
@erickzhao erickzhao deleted the node-22 branch March 5, 2026 22:23
@erickzhao erickzhao mentioned this pull request Mar 6, 2026
20 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants