Skip to content

module: support 'module.exports' interop export name in require(esm)#54563

Closed
guybedford wants to merge 1 commit into
nodejs:mainfrom
guybedford:cjs-unwrap
Closed

module: support 'module.exports' interop export name in require(esm)#54563
guybedford wants to merge 1 commit into
nodejs:mainfrom
guybedford:cjs-unwrap

Conversation

@guybedford
Copy link
Copy Markdown
Contributor

@guybedford guybedford commented Aug 26, 2024

This adds support for export { foo as 'module.exports' } in a CJS module to imply that this export value should always be used as the module value when requiring that ESM module under require(esm), allowing other types than just module namespaces in this interop.

This effectively splits off the non-major change from #53848 as discussed in the Node.js loaders meeting, under a rename of this flag to use the 'module.exports' pattern, where that PR will be updated to use the name that lands here on the ESM wrapper as a major change further.

The naming bikeshed took place in nodejs/loaders#221 and was determined by TSC vote.

//cc @nodejs/loaders

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/loaders

@nodejs-github-bot nodejs-github-bot added module Issues and PRs related to the module subsystem. needs-ci PRs that need a full CI run. labels Aug 26, 2024
@avivkeller avivkeller added the loaders Issues and PRs related to ES module loaders label Aug 26, 2024
@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.40%. Comparing base (366e573) to head (78d41a2).
Report is 874 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #54563      +/-   ##
==========================================
- Coverage   88.40%   88.40%   -0.01%     
==========================================
  Files         652      652              
  Lines      186565   186568       +3     
  Branches    36038    36043       +5     
==========================================
- Hits       164935   164929       -6     
+ Misses      14914    14911       -3     
- Partials     6716     6728      +12     
Files with missing lines Coverage Δ
lib/internal/modules/cjs/loader.js 97.42% <100.00%> (+<0.01%) ⬆️

... and 20 files with indirect coverage changes

@guybedford guybedford changed the title module: support __cjsUnwrapDefault interop flag in require(esm) module: support cjsUnwrapDefault interop flag in require(esm) Sep 1, 2024
Comment thread doc/api/modules.md Outdated
@guybedford
Copy link
Copy Markdown
Contributor Author

//cc @nodejs/loaders this PR is ready for final review.

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Sep 2, 2024
Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

I really wish we used __cjsUnwrapDefault instead, but I'm ok with this as well.

@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 2, 2024
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Comment thread test/es-module/test-require-as-esm-interop.mjs Outdated
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Copy link
Copy Markdown
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

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

There is another caveat of the default live-binding being broken (compared to ESM import semantics). Though I guess that's rather an edge case that most won't care about - this feature is intended for library authors who migrate from CommonJS, which doesn't support re-assigning module.exports once it's cached anyway.

Comment thread doc/api/modules.md
Comment thread doc/api/modules.md Outdated
Comment thread doc/api/modules.md Outdated
@ljharb
Copy link
Copy Markdown
Member

ljharb commented Sep 3, 2024

default exports in ESM do not have live bindings, only named exports do.

@joyeecheung
Copy link
Copy Markdown
Member

joyeecheung commented Sep 4, 2024

default exports in ESM do not have live bindings, only named exports do.

I am talking about this (which can't be mimicked with native CommonJS, but then it has never been, so for CommonJS -> ESM module migration, it doesn't matter as the original CommonJS module is unlikely to expect re-assigning module.exports to be effective in consumer code. It's only a problem for existing ESM that re-assigns default exports and tries to extend support to CommonJS consumers, which should be rare):

// a.mjs
let a = 1;
export function increment() {
  a++;
}
export { a as default };

// b.mjs
import a, { increment } from './a.mjs';
increment();
console.log(a);  // 2

@ljharb
Copy link
Copy Markdown
Member

ljharb commented Sep 4, 2024

Oh interesting, you’re saying that when exported as a name, it does have live binding behavior - TIL.

@guybedford guybedford changed the title module: support cjsUnwrapDefault interop flag in require(esm) module: support __cjsUnwrapDefault interop flag in require(esm) Sep 6, 2024
Comment thread lib/internal/modules/cjs/loader.js Outdated
Copy link
Copy Markdown
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

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

We have an alternative proposal in nodejs/loaders#221 which people seem to like over there:

function foo() {}

export { foo as "module.exports" };
export default foo;

I think we should do a poll of this v.s. what this PR currently proposes:

function foo() {}

export const __cjsUnwrapDefault = true;
export default foo;

and decide which one we should go with. I am not sure what the poll audience should be though, should it be:

  • TSC, as usual
  • collaborators, because this is a naming choice and seems fun to vote on
  • Broader user base (social media poll?)

placing a block until we have a decision.

@joyeecheung joyeecheung added the tsc-agenda Issues and PRs to discuss during the meetings of the TSC. label Sep 7, 2024
@guybedford
Copy link
Copy Markdown
Contributor Author

@joyeecheung given there's quite a bit of nuance here, I'd suggest we discuss this within the TSC. I'll make myself available for the next meeting on the 11th. I'd like us to come to a conclusion as swiftly as possible though and encourage a vote / decision in the meeting, as this has discussion has now dragged on since July 14. I'll commit to whatever decision is made there for this PR and align that result with #53848.

@jasnell
Copy link
Copy Markdown
Member

jasnell commented Sep 8, 2024

I prefer the more explicit use of __cjsUnwrapDefault = true personally as it seems less magical and more explicit about the intent.

@joyeecheung
Copy link
Copy Markdown
Member

Backport in #56927

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backported-to-v22.x PRs backported to the v22.x-staging branch. loaders Issues and PRs related to ES module loaders module Issues and PRs related to the module subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants