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

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

Open
rdsedmundo opened this issue Feb 14, 2023 · 127 comments
Open

Comments

@rdsedmundo
Copy link

rdsedmundo commented Feb 14, 2023

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.
@ljharb
Copy link
Member

ljharb commented Feb 14, 2023

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

@rdsedmundo
Copy link
Author

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

@ljharb
Copy link
Member

ljharb commented Feb 14, 2023

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
Copy link
Author

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
Copy link
Member

ljharb commented Feb 14, 2023

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
Copy link

what1s1ove commented Feb 16, 2023

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
Copy link
Member

ljharb commented Feb 16, 2023

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

@what1s1ove
Copy link

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
Copy link
Member

ljharb commented Feb 17, 2023

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
Copy link

meyfa commented Feb 17, 2023

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
Copy link
Member

ljharb commented Feb 17, 2023

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
Copy link

what1s1ove commented Feb 18, 2023

Additional points:
nodejs/node#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
Copy link

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
Copy link

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

@ljharb
Copy link
Member

ljharb commented Mar 3, 2023

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

lachrist added a commit to getappmap/appmap-agent-js that referenced this issue Mar 14, 2023
This eslint rule is being discussed but not yet implemented -- import-js/eslint-plugin-import#2717. For the moment, we can just forbid the entire node standard library without prefix.
@lencioni
Copy link
Contributor

lencioni commented May 8, 2023

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
Copy link
Member

ljharb commented May 8, 2023

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
Copy link

zettca commented May 8, 2023

@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

This comment was marked as spam.

@ljharb
Copy link
Member

ljharb commented May 22, 2023

@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
Copy link

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
Copy link
Member

ljharb commented Jun 8, 2023

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

@so1ve
Copy link

so1ve commented Jun 30, 2023

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

@Sivli-Embir
Copy link

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
Copy link
Member

ljharb commented Jul 7, 2023

@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
Copy link

Sivli-Embir commented Jul 12, 2023

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

@Dmitry-White
Copy link

@ljharb
What's your take on this node: prefix providing additional semantic load to the imports and making them more explicit in what they do?
This elevates the whole argument from "host hallucinations" and "platform-enforced standards" to a normal "explicit > implicit" debate in any language, not just JS.

@ljharb
Copy link
Member

ljharb commented May 22, 2024

@Dmitry-White While in general I'm solidly on the side of explicit > implicit, I don't think the prefix actually provides that kind of clarity, especially since things that aren't node support node:. You have to just know what specifiers might potentially mean.

@Dmitry-White
Copy link

Dmitry-White commented May 22, 2024

@ljharb
I'm having a similar debate internally in my team and so far it seems that imports marked with node: prefix give a very clear indication that they import something directly from Node.js API without any "what ifs".
And for devs who're new to the ecosystem, it has additional connotation of "something from Node.js standard library", allowing us to easily differentiate between all the imported things in our code and what's actually explicitly mentioned in package.json during code reviews.
So having this node: prefix as part of our automated static analysis tooling would greatly help, especially if that's coming from an already used plugin, like this eslint-plugin-import.
As to the argument about "things that aren't node support node:" - do you have any other examples of any platform/thing that isn't Node that uses node: prefix to indicate something other than Node.js API? Cause everywhere on the web this node: prefix inevitable leads to Node.js official docs (that are almost exclusively use the prefix), making its semantic meaning pretty clear and easy to discover 🤔

@ljharb
Copy link
Member

ljharb commented May 22, 2024

You're right that the node prefix always means "node APIs or node-like APIs", and so discovery isn't that obfuscated.

@Dmitry-White
Copy link

@ljharb
I understand the desire to be as agnostic as possible in this plugin, but since there's a rule that explicitly disallows Node.js API imports for client-side projects (implying that the default for the plugin is in fact Node.js environment), it seems logical to provide a separate rule for server-side projects to enforce explicit use of node: prefix for anything related to Node.js API, for consistency sake 😉

@markandrus
Copy link

Today in my team's codebase, we found that we have a mixture of "node:" and non-"node:" prefixed imports. I don't really care about which form is used, I just came here hoping to find a rule to enforce consistency either way. I was really surprised to see this is not supported in eslint-plugin-import. Please consider implementing this. 🙏

@alumni
Copy link

alumni commented May 23, 2024

we found that we have a mixture of "node:" and non-"node:" prefixed imports

That's because editors (I'm talking about VSCode, but very likely it's the LSP, so very likely all editors) used to add unprefixed imports in the past, however they recently (sometime in the past year) started to add prefixed imports.

Often you just type the function or class you want to use and let the editor add the imports for you. I'm now getting both options in the fix menu (ctrl + dot), but the first one is the prefixed one, and I think if you let it add all missing imports automatically, they will always be prefixed.

@markandrus
Copy link

That's because editors (I'm talking about VSCode, but very likely it's the LSP, so very likely all editors) used to add unprefixed imports in the past, however they recently (sometime in the past year) started to add prefixed imports.

I think that whether imports are generated by an editor, copied from a code sample, or written by hand is another matter. We don't control these details of how our team authors code, only what gets checked-in, through automated jobs.

I thought for sure I could configure my lint job to enforce consistency with eslint-plugin-import, since we're already using that. It's dissatisfying to have to configure another eslint plugin in conjunction with eslint-plugin-import, when the name of this plugin suggests it would be a comprehensive solution to linting imports.

@jozefizso
Copy link

There is a clear concensus in the community in favor of having this rule. And there are two clear real world use cases which must be enforced by this rule.

What's the drawback of having it implemented in this package?
And when the decision will be made?

@benmccann

This comment was marked as spam.

@GoldStrikeArch

This comment was marked as off-topic.

@SukkaW
Copy link

SukkaW commented Jul 6, 2024

What's the drawback of having it implemented in this package?

Actually, there are a few dropbacks:

@JasonMan34
Copy link

JasonMan34 commented Jul 6, 2024

What's the drawback of having it implemented in this package?

Actually, there are a few dropbacks:

* The rule enforcing `node:` prefix is only applicable under Node.js projects while `eslint-plugin-import` is not limited to only Node.js projects, thus the rule may not belong to `eslint-plugin-import`. This rule belongs to an eslint plugin that is designed for Node.js projects.

The suggestion is to make the rule dynamic, with a "never" and "always" option (at least)
So your first point is actually for this rule, as you could set "always" on projects that run on an environment other than node

@SukkaW
Copy link

SukkaW commented Jul 8, 2024

The suggestion is to make the rule dynamic, with a "never" and "always" option (at least)

Unfortunately, making this rule dynamic and configurable is also not that easy. Remember, there are also Deno, Bun (uses bun: prefix, e.g. bun:sqlite), and Cloudflare Workers (uses cloudflare: prefix, e.g. cloudflare:sockets) out there in the field. And bun: and node: can co-exist in the same projects.


Along with my original reply, these are reasons behind I rejected @benmccann's proposal (un-ts#97) of introducing prefer-node-protocol to eslint-plugin-import-x.

@vorant94
Copy link

vorant94 commented Jul 8, 2024

And bun: and node: can co-exist in the same projects

I suppose the sole purpose of this feature is to avoid such cases, no? Like having a single configurable way to import built-in modules for the project

@SukkaW
Copy link

SukkaW commented Jul 8, 2024

And bun: and node: can co-exist in the same projects

I suppose the sole purpose of this feature is to avoid such cases, no? Like having a single configurable way to import built-in modules for the project

In the context of Bun, you can/have to use both Node.js compatible API (node:fs, node:worker_threads) provided Bun and Bun-specific features (bun:jsc, bun:sqlite).

@GoldStrikeArch
Copy link

And bun: and node: can co-exist in the same projects

I suppose the sole purpose of this feature is to avoid such cases, no? Like having a single configurable way to import built-in modules for the project

In the context of Bun, you can/have to use both Node.js compatible API (node:fs, node:worker_threads) provided Bun and Bun-specific features (bun:jsc, bun:sqlite).

But you shouldn't be able to import it as sqlite instead of bun:sqlite right? I am not that familiar with bun

@SukkaW
Copy link

SukkaW commented Jul 8, 2024

But you shouldn't be able to import it as sqlite instead of bun:sqlite right? I am not that familiar with bun

Yeah, you can't (Due to being compatible with Node.js).

@GoldStrikeArch
Copy link

But you shouldn't be able to import it as sqlite instead of bun:sqlite right? I am not that familiar with bun

Yeah, you can't (Due to being compatible with Node.js).

So, this rule shouldn't conflict with bun then. Since the implementation would only check about node builtin modules anyway.

@vorant94
Copy link

vorant94 commented Jul 8, 2024

you can/have to use both Node.js compatible API

i know it, but the sole purpose of ESLint itself is to restrict what dev can do to what he/she should do where it makes sense

what are the cases when you want to specify both node: and bun: prefixes?

if the project should be runtime-agnostic there should be no prefixes to begin with.

if project uses Bun-specific API then what is the motivation to use node: prefix for Node-compatible wrappers of Bun? I suppose such project might want to enforce bun: prefix or to just not enforce anything, but 100% not enforce both simultaneously

thewilkybarkid added a commit to PREreview/stats that referenced this issue Jul 11, 2024
This change disables eslint-plugin-import's import resolution check, which doesn't understand the 'npm:' prefix required by Observable.

Refs #31, c9d0f50, import-js/eslint-plugin-import#2717
@Mouvedia
Copy link

If this rule is added it should have exceptions for sea, test and test/reporters.
see https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix

@kkimdev
Copy link

kkimdev commented Jul 12, 2024

fwiw, this is also a Biome linter's default(recommended) enabled rule https://biomejs.dev/linter/rules/use-nodejs-import-protocol/

@what1s1ove
Copy link

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.

I know the nodejs SQLite module is in active development, but 😬

This module is only available under the node: scheme. The following will not work:
import sqlite from 'sqlite';

https://nodejs.org/api/sqlite.html

@ljharb
Copy link
Member

ljharb commented Aug 20, 2024

indeed, there's now node:sea and node:sqlite (once it's unflagged) along with node:test in this category.

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

No branches or pull requests