Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: fix extension searching for exports #32351

Closed
wants to merge 2 commits into from

Conversation

guybedford
Copy link
Contributor

This PR disables extension searching for the CommonJS exports implementation.

The catch with supporting extension searching for exports on CommonJS is having differences between the ESM and CJS implementations so this brings the gap back together on this, as it really should have been to begin with I think.

Please review @addaleax @jkrems when you can. Would be good to get this out soon as it is a breaking change under the experimental status.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@guybedford guybedford requested review from jkrems and addaleax March 18, 2020 23:53
@guybedford
Copy link
Contributor Author

//cc @nodejs/modules-active-members I would like to try and get this landed for the release next week if possible (and possibly before the next meeting). Please take a look if you have time.

doc/api/modules.md Show resolved Hide resolved
@ljharb
Copy link
Member

ljharb commented Mar 20, 2020

I very much disagree with this; CommonJS means extension searching, and every context that involves CJS should always support it.

This, thus, does not have modules group consensus, and I'd prefer we discuss it before landing it.

@ljharb
Copy link
Member

ljharb commented Mar 20, 2020

If we want CJS and ESM to be the same, the path to that is adding extension searching to ESM, not the reverse.

@GeoffreyBooth
Copy link
Member

GeoffreyBooth commented Mar 20, 2020

"exports" is its own definition. It breaks with "main" in that it requires specifiers begin with ./, for example. Also requiring explicit extensions just makes logical sense, as extensions are required for ESM and "exports" applies to both CommonJS and ESM, so therefore it needs to require the more restrictive rule for both systems.

@nodejs-github-bot
Copy link
Collaborator

@guybedford
Copy link
Contributor Author

For an example of the problem this is fixing see Babel at babel/babel#11283, where require('babel-compat-data/corejs-shipped-proposals') would work fine but import('babel-compat-data/corejs-shipped-proposals') wouldn't. This is very clearly a footgun that is important to patch.

@ljharb I would really like to get this out in the coming 13 release before the meeting if you would reconsider your objection.

@ljharb
Copy link
Member

ljharb commented Mar 23, 2020

I feel very strongly that anything CJS should always have extension and index searching.

@guybedford
Copy link
Contributor Author

guybedford commented Mar 23, 2020 via email

@guybedford guybedford added the modules-agenda Issues and PRs to discuss during the meetings of the Modules team. label Mar 23, 2020
@bmeck
Copy link
Member

bmeck commented Mar 23, 2020

I'd agree with @guybedford , having the field vary between CJS and ESM usage would lead to fragmentation. We need to pick a single choice to always, or never do extension searching for the field. I'd prefer we pick the simpler, non-searching behavior as it is easier for tools to coordinate around that form rather than when we might not know which extensions are supported in CJS at the current time of evaluation (people mutating require.extensions).

Copy link
Contributor

@MylesBorins MylesBorins left a comment

Choose a reason for hiding this comment

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

LGTM

@MylesBorins
Copy link
Contributor

FWIW my LGTM is dependent on discussion in the modules team and ensuring there are no large concerns with it. So please do not land yet

@MylesBorins MylesBorins added the blocked PRs that are blocked by other issues or PRs. label Mar 25, 2020
@GeoffreyBooth
Copy link
Member

There's another complication. Here's how CommonJS treats trailing slashes today:

// ./main.js
require('./folder/');

// ./folder/index.js
console.log('hi');
node ./main.js
hi

Yet, of course, per the "exports" spec a trailing slash means “map everything in this folder.”

So the existing behavior doesn't actually match CommonJS searching behavior already.

@jkrems
Copy link
Contributor

jkrems commented Mar 25, 2020

So the existing behavior doesn't actually match CommonJS searching behavior already.

Trying to parse how your example relates to exports. Maybe something like the following:

require('pkg/mapped/path/custom/part/');

Where pkg would have an entry for ./mapped/path/ I assume? And the argument is that it would fail (?) for the trailing slash when using exports (does it?)?

@ljharb
Copy link
Member

ljharb commented Mar 25, 2020

In CJS, you'd use foo/bar/path/ to differentiate between foo/bar/path.js and foo/bar/path/index.js (or wherever foo/bar/path/package.json pointed) - so @GeoffreyBooth is right that this is an unavoidable and preexisting deviation between CJS and exports maps.

@GeoffreyBooth
Copy link
Member

GeoffreyBooth commented Mar 25, 2020

Test A, traditional CommonJS:

// ./main.js
require('foo/'); // Note the trailing slash

// ./node_modules/foo/dist/index.js
console.log('hi');

// ./node_modules/foo/package.json
{ "main": "./dist/index.js" }
node ./main.js
hi

Test B, with "exports":

// ./main.js and ./node_modules/foo/dist/index.js unchanged from A

// ./node_modules/foo/package.json
{ "exports": "./dist/index.js" }
node ./main.js
internal/modules/cjs/loader.js:524
  throw new ERR_PACKAGE_PATH_NOT_EXPORTED(basePath, mappingKey);
  ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './' is not defined by "exports" in /private/tmp/test/node_modules/foo/package.json
    at applyExports (internal/modules/cjs/loader.js:524:9)
    at resolveExports (internal/modules/cjs/loader.js:541:12)
    at Function.Module._findPath (internal/modules/cjs/loader.js:661:22)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:963:27)
    at Function.Module._load (internal/modules/cjs/loader.js:859:27)
    at Module.require (internal/modules/cjs/loader.js:1036:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/private/tmp/test/main.js:1:1)
    at Module._compile (internal/modules/cjs/loader.js:1147:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

@jkrems
Copy link
Contributor

jkrems commented Mar 25, 2020

Right, but that example is just any unexported path which isn't specific to trailing slash afaict?

@GeoffreyBooth
Copy link
Member

Right, but that example is just any unexported path which isn't specific to trailing slash afaict?

Well it just doesn't match the CommonJS behavior. If in test B you change require('foo/') to require('foo') it works; whereas in CommonJS, either would work.

@jkrems
Copy link
Contributor

jkrems commented Mar 25, 2020

If in test B you change require('foo/') to require('foo') it works; whereas in CommonJS, either would work.

Adding a trailing slash changes the behavior, that's not really the same thing. require('foo/bar') and require('foo/bar/') won't behave the same. And if node_modules/foo.js exists, then require('foo') and require('foo/') won't do the same thing either. This feels unrelated to exports..?

@ljharb
Copy link
Member

ljharb commented Mar 25, 2020

What I think they're saying is that foo/ without exports, doesn't map to what foo/ means in exports.

@guybedford
Copy link
Contributor Author

As discussed in the meeting, I've added the new commit to retain extension searching for folder exports in CommonJS exports resolution only, along with tests of the various lookups.

@MylesBorins MylesBorins removed blocked PRs that are blocked by other issues or PRs. modules-agenda Issues and PRs to discuss during the meetings of the Modules team. labels Mar 26, 2020
@nodejs-github-bot

This comment has been minimized.

}
}

const basePath = path.resolve(curPath, request);
Copy link
Member

Choose a reason for hiding this comment

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

will delete require('path').resolve break this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, although there are a lot of calls to path.resolve within this module, so changing that is best done as a separate refactoring.

lib/internal/modules/cjs/loader.js Outdated Show resolved Hide resolved
@nodejs-github-bot
Copy link
Collaborator


const basePath = path.resolve(nmPath, name);
return applyExports(basePath, expansion);
const [, name, expansion = ''] =
Copy link
Member

Choose a reason for hiding this comment

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

using array destructuring means it will break if Symbol.iterator is deleted off of Array.prototype. this one has a lot of places in the codebase to fix, tho, so it’s probably fine to skip for now.

@nodejs-github-bot
Copy link
Collaborator

guybedford added a commit that referenced this pull request Apr 1, 2020
PR-URL: #32351
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
@guybedford
Copy link
Contributor Author

Landed in 534c204.

@guybedford guybedford closed this Apr 1, 2020
BethGriggs pushed a commit that referenced this pull request Apr 7, 2020
PR-URL: #32351
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
targos pushed a commit that referenced this pull request Apr 12, 2020
PR-URL: #32351
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
MylesBorins pushed a commit to MylesBorins/node that referenced this pull request Apr 16, 2020
PR-URL: nodejs#32351
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
MylesBorins pushed a commit that referenced this pull request Apr 17, 2020
Backport-PR-URL: #32883
PR-URL: #32351
Reviewed-By: Geoffrey Booth <webmaster@geoffreybooth.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
michaelsbradleyjr added a commit to embarklabs/embark that referenced this pull request Apr 24, 2020
Node v13.13.0 introduced a change that is incompatible with `@babel/preset-env`
versions `< 7.9.0`.

See:

nodejs/node#32351
nodejs/node#32852
babel/babel#11283

Bump our direct dependencies to `7.9.5` and manually cause additional bumps in
`yarn.lock` that result in all versions of `@babel/preset-env` installed in the
monorepo's `node_modules` trees being `>= 7.9.0`.
michaelsbradleyjr added a commit to embarklabs/embark that referenced this pull request Apr 24, 2020
Node v13.13.0 introduced a change that is incompatible with `@babel/preset-env`
versions `< 7.9.0`.

See:
* nodejs/node#32351
* nodejs/node#32852
* babel/babel#11283

Bump our direct dependencies to `7.9.5` and manually cause additional bumps in
`yarn.lock` that result in all versions of `@babel/preset-env` installed in the
monorepo's `node_modules` trees being `>= 7.9.0`.
michaelsbradleyjr added a commit to embarklabs/embark that referenced this pull request Apr 24, 2020
Node v13.13.0 introduced a change that is incompatible with `@babel/preset-env`
versions `< 7.9.0`.

See:
* nodejs/node#32351
* nodejs/node#32852
* babel/babel#11283

Bump our direct dependencies to `7.9.5` and manually cause additional bumps in
`yarn.lock` that result in all versions of `@babel/preset-env` installed in the
monorepo's `node_modules` trees being `>= 7.9.0`.
ralish added a commit to draftable/compare-api-node-client that referenced this pull request Jun 22, 2020
Due to the following breaking change:
nodejs/node#32351

This fixes compatibility with Node.js:
- v12.17.0+
- v13.13.0+

It probably also makes us compatible with Node.js v14.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants