eslint-plugin-import icon indicating copy to clipboard operation
eslint-plugin-import copied to clipboard

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

Open rdsedmundo opened this issue 1 year ago • 113 comments

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):

  • More explicit protocol makes it more readable and also makes it clear that a built-in module is being imported. Since there are a lot of built-in ones, this extra information is useful for beginners or for someone who might not know about one of them. I once had a senior engineer with 10 years of experience install assert without knowing that Node provided it.
  • It reduces the risk of a module present in node_modules overriding the newer imports.
  • If the prefix is used, the built-in module is always returned, completely bypassing the require cache. For instance, require('node:http') will always return the built in HTTP module, even if there is require.cache entry by that name.
  • New core modules in the future may only be available if the prefix is used, so enforcing it for everything will keep everything consistent. For instance, the new built-in test module introduced in Node.js 18 can only be used via node:test.

rdsedmundo avatar Feb 14 '23 17:02 rdsedmundo

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

ljharb avatar Feb 14 '23 18:02 ljharb

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

rdsedmundo avatar Feb 14 '23 18:02 rdsedmundo

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.

ljharb avatar Feb 14 '23 18:02 ljharb

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.

rdsedmundo avatar Feb 14 '23 18:02 rdsedmundo

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.

ljharb avatar Feb 14 '23 19:02 ljharb

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.

what1s1ove avatar Feb 16 '23 10:02 what1s1ove

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

ljharb avatar Feb 16 '23 18:02 ljharb

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.

what1s1ove avatar Feb 17 '23 09:02 what1s1ove

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.

ljharb avatar Feb 17 '23 17:02 ljharb

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.

meyfa avatar Feb 17 '23 19:02 meyfa

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.

ljharb avatar Feb 17 '23 21:02 ljharb

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 😁

what1s1ove avatar Feb 18 '23 11:02 what1s1ove

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.

c-vetter avatar Feb 28 '23 13:02 c-vetter

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

devlzcode avatar Mar 03 '23 01:03 devlzcode

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

ljharb avatar Mar 03 '23 05:03 ljharb

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.

lencioni avatar May 08 '23 15:05 lencioni

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.

ljharb avatar May 08 '23 15:05 ljharb

@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.

zettca avatar May 08 '23 17:05 zettca

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

alex-kinokon avatar May 22 '23 06:05 alex-kinokon

@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.

ljharb avatar May 22 '23 15:05 ljharb

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"

maranomynet avatar Jun 08 '23 10:06 maranomynet

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

ljharb avatar Jun 08 '23 15:06 ljharb

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

so1ve avatar Jun 30 '23 16:06 so1ve

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.

Sivli-Embir avatar Jul 07 '23 16:07 Sivli-Embir

@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.

ljharb avatar Jul 07 '23 22:07 ljharb

@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.

Sivli-Embir avatar Jul 12 '23 17:07 Sivli-Embir

more and more people will ask for it 🙂

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

what1s1ove avatar Jul 26 '23 14:07 what1s1ove

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

ljharb avatar Jul 26 '23 16:07 ljharb

@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?

meyfa avatar Jul 26 '23 16:07 meyfa

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

ljharb avatar Jul 26 '23 16:07 ljharb