import-js / eslint-plugin-import

ESLint plugin with rules that help validate proper imports.
MIT License
5.45k stars 1.56k forks source link

Suggestion: new rule to enforce usage of `node:` prefix for Node.js built-ins #2717

Open rdsedmundo opened 1 year ago

rdsedmundo commented 1 year ago

I searched a bit in the existing issues and didn't see someone asking for anything similar, also couldn't find in the existing rules. I think this could belong to this package.

Benefits (copied this from 3rd party resources, and added a few personal touches):

ljharb commented 1 year ago

Why? What's the advantage to using the node: prefix?

rdsedmundo commented 1 year ago

I updated the description with a few benefits I found in the web. Let me know if that's sufficient.

ljharb commented 1 year ago

The third point is already true for unprefixed core modules.

You're correct that node:test is only supported via the prefix, but that was a very unfortunate decision that I hope node won't repeat in the future.

rdsedmundo commented 1 year ago

In my opinion, the upside of forcing the prefix is that it allows a more unique name to be chosen, without worrying about naming clashes with a package that is in npm.

ljharb commented 1 year ago

That's certainly a benefit for node core, but i'm not sure how that'd be a benefit for everyone else? node already only adds core modules when they've reserved the package name on npm.

what1s1ove commented 1 year ago

Agreed. Will be looking forward to this rule. Or, if anyone has snippets for no-restricted-import that would be nice too 🙏 (but the new rule would be simpler 😁).

I think another plus for forcing the use of the node: prefix – https://deno.com/blog/v1.30#support-for-built-in-nodejs-modules Other platforms will only support node's API packages with the prefix.

ljharb commented 1 year ago

This is a node project, as is eslint, and I'm not concerned with deno's incomplete support of node modules.

what1s1ove commented 1 year ago

All node documentation uses prefixed imports. Since it becomes the standard why keep two ways of importing? I think this rule would help with that.

ljharb commented 1 year ago

It's not "the standard", it's just that some node collabs want to push usage of the prefix. It's not actually better imo, altho I'm still evaluating the bullet points in the OP.

meyfa commented 1 year ago

I'm very much in favor of this rule, for the reasons stated above. Obviously it's a good idea to subject all rule proposals to some level of scrutiny to avoid feature creep. IMHO in this case there are enough benefits, even if not everybody may want to activate the rule.

More explicit protocol makes it more readable and also makes it clear that a built-in module is being imported.

This is an important one. It reduces cognitive burden for the developers.

The third point is already true for unprefixed core modules.

Can you cite a source for that claim? The Node.js docs state the following:

https://nodejs.org/api/modules.html#core-modules Core modules can be identified using the node: prefix, in which case it bypasses the require cache.

This (to me) implies the cache isn't bypassed when unprefixed.

You're correct that node:test is only supported via the prefix, but that was a very unfortunate decision that I hope node won't repeat in the future.

That's subjective, and even so, having a node: prefix on that one import but not any others is frankly confusing (also subjective, of course). I'd like to achieve consistency in imports, and since node:test forces a prefix, the only way to get there is to prefix everything.

This is a node project, as is eslint, and I'm not concerned with deno's incomplete support of node modules.

This surprises me, since the README really implies this is a project for the whole of JS, not just Node — there's even talk about bundlers; and the import/no-nodejs-modules rule exists. Even if this project targeted just Node.js and nothing else, Node.js developers quite often would like their libraries to work across platforms. eslint-plugin-import isn't "mandated" to add rules specifically for Deno, of course, but the fact that this issue exists is proof some Node.js developers's experience would be improved here, which feels in-scope.

Since it becomes the standard why keep two ways of importing?

While I doubt Node.js will ever phase out the unprefixed imports due to backwards compatibility, I agree that newer code should certainly try to keep up with Node.js best practices.

It's not "the standard", it's just that some node collabs want to push usage of the prefix.

AFAIK stuff like this goes through voting processes and the like, so it's as close to a standard as we're gonna get.

ljharb commented 1 year ago

You can tell by running require.resolve('fs'), eg, in a project where node_modules/fs/index.js exists. A known core module has never hit the filesystem in node afaik.

"The whole of JS" is just node, for the tooling ecosystem. That may change in the future but it's certainly not anywhere close to changing yet.

what1s1ove commented 1 year ago

Additional points: https://github.com/nodejs/node/issues/38343

Btw, the PR has a rule for forcing the use of the node: protocol. But this is part of the unicorn eslint plugin. I think many people use the unicorn plugin much less often than the import plugin. In addition, it seems to me more logical to have this rule in the import plugin.

UPD: just saw your comment there, lol 😁

c-vetter commented 1 year ago

I just learned about the node: prefix and almost immediately came here to look up the rule name to enforce it, or, failing that, to see when this will be supported. To me, eslint-plugin-import is the logical place for that rule.

@ljharb "The whole of JS" is just node, for the tooling ecosystem.

I'm unsure if I understand this point. Please clarify if the following misses the point.

While the tooling lives in node, it works for JS inside node and outside of node. So, for the tooling ecosystem as a whole, "The whole of JS" cannot be node. webpack, for example, would make little sense if it was only concerned with node. To a lesser degree, that also goes for eslint and by extension for eslint plugins and therefore for eslint-plugin-import.

To get back to context: from my perspective, the quote above was prompted by mention of deno's handling of node: as another point in support of the proposed rule. While I have no personal interest in deno, I think the point is valid in showing the scope in which this plugin is being used.

Of course, any tool developer is free to limit the scope of their creation. However, the proposed rule seems to fit perfectly into this plugin, and it could just be opt-in as most rules here – available to the subset of users that want this functionality.

devlzcode commented 1 year ago

I mean, what good reason is there to not prefix node std with node:? How does it make your code worse?

ljharb commented 1 year ago

@agent-ly for one, it doesn't work as cleanly with all tools yet, and it doesn't work on older node versions.

lencioni commented 1 year ago

In my opinion, whether or not the prefix is useful is irrelevant here. What matters is that it exists and people may want to enforce consistency of either always using the prefix or never using the prefix. This plugin seems like a fine place to put such a rule.

ljharb commented 1 year ago

I would agree in a general sense, but I consider using the prefix to be a bad practice, and I'm very hesitant to have this plugin enable a bad practice at scale.

zettca commented 1 year ago

@agent-ly for one, it doesn't work as cleanly with all tools yet, and it doesn't work on older node versions.

I would agree in a general sense, but I consider using the prefix to be a bad practice, and I'm very hesitant to have this plugin enable a bad practice at scale.

Why do you consider it a bad practice? Does it not being 100% supported make it a bad practice? There isn't a single (supported) NodeJS version that doesn't support node:.

If what makes this a bad practice is it not being ~100% supported, then will it stop being a bad practice once it's closer to 100% support? Doesn't make much sense to me... If support is that important, the rule could simply not be under the /recommended configuration, until it is sufficiently supported.

alex-kinokon commented 1 year ago

FYI: If anyone needs it today, here’s my implementation that you can use.

ljharb commented 1 year ago

@zettca no, support level has very little correlation to whether something is a good or bad practice; eval has 100% support for decades but will always be a bad practice for many reasons.

maranomynet commented 1 year ago

The node: prefix exists and it's quite reasonable to wish to keep its use consistent throughout a project – either setting to "never" or "always" (YMMV).

The import plugin seems like the most logical place to add such a rule.

@ljharb it sounds, for example, like you might want to set it to "never"

ljharb commented 1 year ago

That is certainly a viable path forward, but I’m still not excited about even allowing “always” at all.

so1ve commented 1 year ago

There is a existing rule in eslint-plugin-unicorn.

Sivli-Embir commented 1 year ago

Adding a use case to support always. My company has custom external imports (modules not found in the standard package.json dependency list) and has written a number of internal AST scanners to detect them for custom linting and rules enforcement. Using the node:<name> format, we can very easily exclude node packages from those results from our list.

Reading through this, I don't see any argument for not using that format other than @ljharb dislikes it. If there is one, I would love to see an article or explanation as to what issues it introduces. And in that case, I would still like to see a rule for never. Currently, developers are mixing and matching syntax and that is a bad practice that needs a lint rule.

ljharb commented 1 year ago

@Sivli-Embir your tool would need to be able to determine non-prefixed core modules regardless, since they could (and almost certainly do) exist in dependencies. This is trivial based on https://npmjs.com/is-core-module, so I don't think your use case holds up.

Sivli-Embir commented 1 year ago

@ljharb I have to disagree on that point. Adding another npm module increases our maintenance and security burden regardless of size or complexity, where a eslint rule is an elegant fix for us. We only scan our source files, not anything that lives in node_modules.

Regardless, that misses the point I was trying to make. We could easily solve our issues in several ways, but the way we wish to solve it is the one I provided. I was only trying to provide a data point that this is more than for/against preference issue, and there are practical cases where an unopinionated rule would provide value.

If this project shipped a never rule in its recommended list, I would understand. I would also understand if the reluctance to add this rule was due to adding maintenance and security burden for what could be viewed as an unnecessary rule. I am much more concerned that the key argument against it is bias and not data-driven as that undermines the trust in other rules provided by this project. I do recognize that there is apparently a schism in the node community about this, but to me that's even more reason to provide both options and ship with the maintainer's preference.

what1s1ove commented 1 year ago

more and more people will ask for it 🙂

https://github.com/goldbergyoni/nodebestpractices#-627-import-built-in-modules-using-the-node-protocol

ljharb commented 1 year ago

That link points to a rule already, so I’m not sure why it’d make more people ask for it.

meyfa commented 1 year ago

@ljharb It seems that you aren't interested in implementing this rule (fair enough). Would you be open to accepting a contribution from someone else to add it?

ljharb commented 1 year ago

@meyfa it's not about who'll implement it. See https://github.com/import-js/eslint-plugin-import/issues/2717#issuecomment-1538645056

drdaemos commented 1 year ago

@ljharb The stated description of this plugin is "ESLint plugin with rules that help validate proper imports". If certain group of people decided that they need this node: for whatever reasons, they are going to be looking here first, because it is directly related to validating proper imports.

Why having such a rule as an option equals promoting its' usage in your opinion? Why is this against the spirit of eslint-plugin-import plugin?

bennycode commented 1 year ago

I discovered "eslint-plugin-import" this week and was impressed by the amount of configurations it offers. I'm glad to see that people are suggesting the idea of enforcing the node: prefix. I believe this would be beneficial because it makes it much simpler to locate all dependencies related to the Node.js platform throughout your entire application. Instead of searching for individual modules like "fs" or "path", you can search for "node:" to see all the Node.js dependencies in one go. This is important if you have plans to make your library platform-independent.

alex-kinokon commented 1 year ago

support level has very little correlation to whether something is a good or bad practice

for one... it doesn't work on older node versions.

so which one is it?

ljharb commented 1 year ago

@alex-kinokon both.

alumni commented 1 year ago

I came here because there's npm:zlib and node:zlib. I would love to have this :)

ljharb commented 1 year ago

@alumni node doesn't have npm:.

maranomynet commented 1 year ago

As said earlier...

The node: prefix exists and it's quite reasonable to wish to keep its use consistent throughout a project – either setting to "never" or "always".

@ljharb wrote:

That link points to a rule already, so I’m not sure why it’d make more people ask for it.

In fact that rule is highly opinionated and does not offer disallowing the prefix. I bet there are some people out there that share your opinion and would like to use it that way. (After all consistency is the goal.)

I still think this would be a very sensible addition to your awesome plugin, and it's obvious there are a lot of people who like this plugin enough to come here to request this feature.

Please re-consider your stance, and allow someone to offer a PR.

alumni commented 1 year ago

NodeJS is also using the node: prefix in their entire documentation, e.g.: https://nodejs.org/api/timers.html#timers-promises-api

This is now the recommended way to import Node built-in modules, especially since both Deno and Bun have a Node compatibility layer.

Since Deno uses URL imports, they have both node: and npm: prefixes, but they also have package.json compatibility which allows you to omit "npm:".

Bun is a bit more conservative and follows Node conventions.

kkimdev commented 1 year ago

We're planning Node -> Deno migration and this will be immensely useful, especially so if it also provides an auto fix.

GoldStrikeArch commented 1 year ago

Hi! I made a standalone plugin for this . It has 0 dependencies and it is a minimalistic implementation of eslint-plugin-unicorn/prefer-node-protocol rule. All test cases were copied from them as well. I also made a PR to this plugin (I can change it as a rule instead of plugin if needed)

kytta commented 1 year ago

Mikhail beat me to it :)

I have also made a plugin like this, eslint-plugin-node-import (npm). Similar to Mikhail's plugin, it has 0 dependencies and is derived from Sindre's unicorn/prefer-node-protocol. However, I use a different approach, where I reuse a lighterweight visitor just for the Nodes that make sense (ImportDeclaration, etc.) instead of applying it to every Literal

I guess Jordan is against it, so I'll refrain from opening a PR :D

GoldStrikeArch commented 1 year ago

Mikhail beat me to it :)

I have also made a plugin like this, eslint-plugin-node-import (npm). Similar to Mikhail's plugin, it has 0 dependencies and is derived from Sindre's unicorn/prefer-node-protocol. However, I use a different approach, where I reuse a lighterweight visitor just for the Nodes that make sense (ImportDeclaration, etc.) instead of applying it to every Literal

I guess Jordan is against it, so I'll refrain from opening a PR :D

Thanks for suggestion about visiting only "import related" nodes and "CallExpressions" for require. I guess I will optimise my implementation as well :)

kkimdev commented 1 year ago

What a competition. @GoldStrikeArch , you are ahead with 7 Github stars vs @kytta 's 3 Github stars.

kytta commented 1 year ago

What a competition. @GoldStrikeArch , you are ahead with 7 Github stars vs @kytta 's 3 Github stars.

Give me 19 more hours and you'll see 😂

maranomynet commented 1 year ago

Intesting to see that comments linking to newly published stand-alone plugins are now being hidden.

Very enlightening.

ai commented 1 year ago

@ljharb wrote in node: prefix PR

This would break in a bunch of the node versions we support, and philosophically I prefer to never, ever use the node: protocol.

Should we close this issue in this case?

meyfa commented 1 year ago

This project is in the import-js org, not under @ljharb's personal account. IMHO, this means that the plugin should be flexible and support multiple use cases relevant to importing; it shouldn't just represent any single person's style preferences. This is especially true when there are now more than a dozen people actively participating in this discussion with solid arguments why configuring this should be an option, even if it isn't the default. Keep in mind that the number of people watching this issue will be much larger than the number of participants.

The discussion above now contains a rather large list of arguments in support of this rule. I have yet to read any objective (!) reason why all of those arguments are invalid. Of course it's completely fine to not make this rule the default and to never use it personally. But please consider that other people may have different opinions and/or project requirements. Please keep ego out of this.

meyfa commented 12 months ago

Apparently the Fastify team has decided to use the node: prefix across their entire ecosystem for performance reasons: https://github.com/fastify/fastify-static/pull/407

Just adding this as a possible use case, although I couldn't find any benchmarks. Still, I would hazard a guess that the Fastify team would like to stay consistent now that the decision was made. Since Fastify packages already use eslint-plugin-import, validating consistent usage of the prefix could easily be achieved if the rule existed.

ljharb commented 12 months ago

Core module names bypass the require cache whether they’re prefixed or not, so I’m more than skeptical about that performance claim.

lusu007 commented 12 months ago

I'm sorry, @ljharb, but it feels like the conversation is heading more towards less productive (childish) territory.

What about all the other stuff that has been mentioned? So far, your responses have only touched on some aspects (and sound rather like strawman arguments). Questioning a subset of arguments isn't sufficient to invalidate the use case overall.