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

eslint-module-utils: add peer dependency on eslint #2523

Merged
merged 1 commit into from
Aug 11, 2022

Conversation

wmertens
Copy link
Contributor

This allows it to find eslint from module-require.js when the package manager is strict.

This allows it to find eslint from module-require.js when the package manager is strict.

Maintainer note: this should not be necessary, but using `peerDependenciesMeta` here seems to have no downsides for real npm users, and allows tools like pnpm or yarn PnP to statically "know" that this package wants to access `eslint` if it is present.
@JounQin
Copy link
Collaborator

JounQin commented Aug 11, 2022

Duplicate of #2283

@JounQin JounQin marked this as a duplicate of #2283 Aug 11, 2022
@JounQin JounQin closed this Aug 11, 2022
@wmertens
Copy link
Contributor Author

Not quite a duplicate - that PR uses optional deps and does not include eslint, which is very much a peer dependency, no?

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

I agree it’s not a duplicate, but i don’t think we should be adding an unnecessary peer dep requirement due to behavior that npm doesn’t have.

@JounQin
Copy link
Collaborator

JounQin commented Aug 11, 2022

@wmertens Or can you point out what issue are you facing without this change? A reproduction or screenshot would be appreciated.

@ljharb ljharb added the package: utils eslint-module-utils package label Aug 11, 2022
@wmertens
Copy link
Contributor Author

I'm making a pure, lazy builder that can generate node_modules directories on the fly as a tree of symlinks to read-only packages, based on Nix (see nix-community/dream2nix#195).

This allows full reuse of packages across projects and also instant updates when you check out a different commit with a different package-lock.json file.

The result looks like this, very pure and fast:

lrwxrwxrwx 1 wmertens  72 Aug 11 18:07 node_modules -> /nix/store/379jjmh6k02d344d9lzk54a5ijlky1y9-node_modules-yaska-stratokit/
lrwxrwxrwx 3 root     109 Jan  1  1970 node_modules/eslint-plugin-import -> /nix/store/a9ndcg0hxgxii8y3mly3ndklay98p6i9-eslint-plugin-import-2.26.0/lib/node_modules/eslint-plugin-import/
lrwxrwxrwx 2 root      77 Jan  1  1970 node_modules/eslint-plugin-import/node_modules -> /nix/store/rn61kfj1ay92djl8rzf4nr2fqpzb5pyr-node_modules-eslint-plugin-import/
lrwxrwxrwx 3 root     106 Jan  1  1970 node_modules/eslint-plugin-import/node_modules/eslint-module-utils -> /nix/store/00ijs8j2vsc1ay4mwsf9ji8nz56dqn94-eslint-module-utils-2.7.3/lib/node_modules/eslint-module-utils/

node_modules/eslint-plugin-import/node_modules/eslint-module-utils/node_modules/:
total 8
lrwxrwxrwx 61 root 78 Jan  1  1970 debug -> /nix/store/xnk73y46n9nyfjvk5qdkk9bf459dc4a9-debug-4.3.4/lib/node_modules/debug/
lrwxrwxrwx  7 root 82 Jan  1  1970 find-up -> /nix/store/01d5gp9c2raj2dh5fcv681vrq6rpm38g-find-up-2.1.0/lib/node_modules/find-up/

If I have 2 projects with the same dependencies, the builder will give them the exact same node_modules symlink.

Node.js allows symlinks to modules, but will resolve packages based on the actual location of a package. So if a package doesn't have a dependency in its own node_modules or a parent directory, it won't find it.

This is working, but some (very few) packages don't specify all their dependencies and they don't work. For eslint-import-utils, this means that it can't find espree.

By adding this peer dependency, package managers know that eslint-import-utils should have access to eslint and thus espree. Since all users of the plugin will import eslint, this is the simplest peer dep to fix the build.

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

That's using peer deps as a hack.

If your module resolution algorithm isn't able to replicate what npm does by default, then it's broken (this applies to pnpm and yarn PnP as well).

@wmertens
Copy link
Contributor Author

@ljharb I understand your position, but it's akin to saying "You shouldn't ban smoking, we've been smoking for centuries" in the 80s, no?

On one hand, your setup obviously works with npm, but on the other hand, there are nice optimizations possible if you add this harmless short string to your package.json.

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

No, it's saying you shouldn't allow smoking because it covers up the smell of something else :-)

@wmertens
Copy link
Contributor Author

:-)

How about a security perspective? Tools should only get access to what they request, and npm leaks access to packages that haven't been granted to the tools?

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

That's not a security property that node applications have, and I'm unconvinced it's one they should have. Literally anything that has static config (including eslint config) needs to be able to dynamically access packages even if they're not declared explicitly at the top level.

Every package is supposed to have access to every other package. There's no security vulnerability here, just a perceived ideological flaw, and adding complexity to both tools and many packages in the ecosystem is a high cost for a purely ideological concern.

@wmertens
Copy link
Contributor Author

Hmm. Indeed "perceived ideological flaw" is a good way to put it. But this ideology does result in simpler and more efficient build tools, at the tiny cost of slightly more metadata.

My eslint config works perfectly fine because I use require.resolve to specify the plugins.

How about this:

  • If you merge this PR (or copy it manually without attribution, that's fine too), you will incur no cost, and users of my tool and others like it, will be happier.
  • If you don't merge this PR, tools will have to work around this ideological flaw, incurring a great cost to mankind, including discussions like these :-)

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

I said static eslint config (JSON, primarily), which is still supported and something that - for maintainability reasons - is much more reliable than dynamic config.

If the first bullet point were true, I'd be happy to merge it. However, declaring a peer dep forces downstream users to have eslint installed, which can cause it to be automatically installed by tools, which can prevent them from realizing they need to depend on an explicit version range of eslint in their own package.json.

The only tools that would have to work around it are ones trying to use an incorrect node_modules layout. Sacrificing correctness for performance is always a losing proposition in the long run.

@wmertens
Copy link
Contributor Author

wmertens commented Aug 11, 2022

Well, by using my tool I know I can't use the static config, and the default way of doing things is still working.

The alternative, which is definitely supported, is adding "peerDependenciesMeta": { "eslint": { "optional": true } }. This is a no-op on old npm versions and makes things just work on newer tools.

For you this might be a hack, but for me it's making the metadata "more correct". IMHO any hardcoded dependency should be mentioned, optional or no.

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

I agree that using peerDependenciesMeta avoids the downside I mentioned for the first bullet point.

However, in no way are any of the dep categories supposed to be a way to "grant access", so it's dangerous to overload them for that.

@wmertens
Copy link
Contributor Author

wmertens commented Aug 11, 2022

ES Modules work along the same principle. You can only use import/export statements at package top-levels, for efficiency.

All I'm asking is that, for efficiency, the package manifest includes mentions of all dependencies that it will definitely ask for at some point (but might not get).

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

That's not for efficiency at all, and there was a proposal at one time to allow them nested - and import() can be used anywhere.

@wmertens
Copy link
Contributor Author

Oh, I thought it was to allow scanning without executing. So I guess that argument doesn't convince you :-)

@wmertens
Copy link
Contributor Author

I suppose I'm out of arguments. I can only plead for a small mention of a harmless string, which encodes a truth, which you prefer to leave covered.

@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

Yes, but that's not for efficiency, that's for being able to statically know the dep graph before evaluating code :-) (for correctness - it actually is part of the reason why ESM is so much slower than CJS)

I do appreciate the persistence with trying multiple arguments. peerDependenciesMeta is the closest thing to a viable approach, fwiw.

@wmertens
Copy link
Contributor Author

If you reopen the PR, you'll find an extra commit that makes it super simple for you to push the squash merge button to add peerDependenciesMeta, so that build tools like mine can know the dep graph before manually intervening :-)

@ljharb ljharb reopened this Aug 11, 2022
@ljharb
Copy link
Member

ljharb commented Aug 11, 2022

And how are you planning to handle utils/resolve.js, which has its tryRequire function doing a bunch of dynamic lookups?

@wmertens
Copy link
Contributor Author

those are dynamic and therefore will just work for npm-adherents and purists like me will have to provide full paths to resolve plugins.

Actually, by editing the package-lock file to add the dep, the build was working for me, so I assume that I already did everything I needed in my case.

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

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

Given that this package is named "eslint-module-utils", it theoretically won't ever be present in a tree that lacks eslint, and given that marking it as an optional peer dep without marking it also as a peer dep should have no negative consequences for users of proper npm, i'll accept this

@codecov
Copy link

codecov bot commented Aug 11, 2022

Codecov Report

Merging #2523 (8fee22d) into main (ab8b6d8) will not change coverage.
The diff coverage is n/a.

❗ Current head 8fee22d differs from pull request most recent head e4c90e5. Consider uploading reports for the commit e4c90e5 to get more accurate results

@@           Coverage Diff           @@
##             main    #2523   +/-   ##
=======================================
  Coverage   95.17%   95.17%           
=======================================
  Files          66       66           
  Lines        2798     2798           
  Branches      940      940           
=======================================
  Hits         2663     2663           
  Misses        135      135           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@ljharb ljharb merged commit e4c90e5 into import-js:main Aug 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: utils eslint-module-utils package
Development

Successfully merging this pull request may close these issues.

3 participants