Skip to content

feat: loading ESM pnpmfiles#9730

Merged
zkochan merged 10 commits intov11from
esm-pnpmfiles
Oct 10, 2025
Merged

feat: loading ESM pnpmfiles#9730
zkochan merged 10 commits intov11from
esm-pnpmfiles

Conversation

@zkochan
Copy link
Member

@zkochan zkochan commented Jul 8, 2025

Related issue: yao-pkg/pkg#144

@zkochan
Copy link
Member Author

zkochan commented Jul 8, 2025

Looks like it only works with node.js >= 22. And we need some workarounds for pkg. So, maybe we'll add support for this, when we drops support for node.js 18 and 20.

@zkochan zkochan changed the base branch from main to v11 August 31, 2025 23:55
@zkochan zkochan added this to the v11.0 milestone Aug 31, 2025
@zkochan zkochan marked this pull request as ready for review September 1, 2025 00:15
@TrevorBurnham
Copy link
Contributor

Looks like it only works with node.js >= 22.

FWIW if the issue is loading ESM modules with require without --experimental-require-module, that functionality was backported to v20.19.0.

@zkochan zkochan requested a review from Copilot October 10, 2025 01:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for loading ESM pnpmfiles with the .mjs extension alongside existing CommonJS .cjs support. This enhancement allows pnpm users to write their configuration hooks using ES module syntax.

  • Converting synchronous pnpmfile loading to asynchronous to support dynamic ES module imports
  • Adding ESM detection and loading logic based on .mjs file extension
  • Updating all calling code to handle the new async interface

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
hooks/pnpmfile/src/requirePnpmfile.ts Core logic to detect and load ESM vs CommonJS pnpmfiles
hooks/pnpmfile/src/requireHooks.ts Made function async and updated to handle Promise.all for parallel loading
hooks/pnpmfile/test/index.ts Updated all tests to use async/await syntax
pkg-manager/plugin-commands-installation/src/recursive.ts Added await for async requireHooks call
cli/cli-utils/src/getConfig.ts Added await for async requireHooks call
pnpm/test/hooks.ts Added integration test for ESM pnpmfile loading
.changeset/real-papers-cut.md Changelog entry documenting the new feature

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

if (pnpmFilePath.endsWith('.mjs')) {
const url = pathToFileURL(path.resolve(pnpmFilePath)).href
const module = await import(url)
pnpmfile = module.default || module
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The fallback logic module.default || module may be unclear. Consider being more explicit about when to use default (for default exports) vs the module itself (for named exports like export const hooks = {}). The current logic could mask issues if both exist.

Suggested change
pnpmfile = module.default || module
if ('default' in module && Object.keys(module).length > 1) {
throw new PnpmFileFailError(
pnpmFilePath,
new Error(
'The pnpmfile exports both a default export and named exports. This is ambiguous. Please export either a default or named exports, not both.'
)
)
}
pnpmfile = 'default' in module ? module.default : module

Copilot uses AI. Check for mistakes.
Comment on lines +103 to 106
const pnpmFileRealName = pnpmFilePath.endsWith('.cjs') || pnpmFilePath.endsWith('.mjs')
? pnpmFilePath
: `${pnpmFilePath}.cjs`
return fs.existsSync(pnpmFileRealName)
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The function pnpmFileExistsSync only falls back to .cjs extension when neither .cjs nor .mjs extensions are present. This means .mjs files without explicit extension won't be found, which could be inconsistent behavior. Consider adding fallback logic for .mjs files as well.

Suggested change
const pnpmFileRealName = pnpmFilePath.endsWith('.cjs') || pnpmFilePath.endsWith('.mjs')
? pnpmFilePath
: `${pnpmFilePath}.cjs`
return fs.existsSync(pnpmFileRealName)
if (pnpmFilePath.endsWith('.cjs') || pnpmFilePath.endsWith('.mjs')) {
return fs.existsSync(pnpmFilePath)
}
return fs.existsSync(`${pnpmFilePath}.cjs`) || fs.existsSync(`${pnpmFilePath}.mjs`)

Copilot uses AI. Check for mistakes.
@zkochan zkochan merged commit e146e98 into v11 Oct 10, 2025
7 of 9 checks passed
@zkochan zkochan deleted the esm-pnpmfiles branch October 10, 2025 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants