when using typescript recommended config it should allow for imports of js equivialent
lifeiscontent opened this issue Β· 91 comments
based off the conversation here: microsoft/TypeScript#16577
if a .ts
or .tsx
file imports a .js
file it will resolve .ts
.tsx
or .js
but the plugin:import/typescript
doesn't seem to have support for this functionality.
the js
extension is used to reference files that WILL be compiled to, so they technically don't exist, but this is how the TS team is allowing people to write ESM compatible modules in TypeScript.
It would be nice to have this eslint plugin to work with this out of the box so I can enforce that all of my modules import .js
files to make sure I'm not missing anything when exporting my package to the web.
The "recommended" config of course doesn't support any typescript functionality - the default is (should always be) JavaScript.
https://github.com/benmosher/eslint-plugin-import#typescript may be helpful.
@ljharb Yeah, I'm specifically talking about the typescript additions.
@ljharb - would there be any appetite for import/extensions
rule to support a typescript option which would allow .ts
and .tsx
to be equivalent to a .js
extension which the Typescript compiler allows us to use as mentioned by @lifeiscontent
// foo.ts
export const foo = 'foo';
// bar.ts
import { foo } from './foo.js';
Currently this errors with Missing file extension "ts" for "./foo.js"
. Maybe we could add an additional option? I'm happy to take a stab at it if it's something that was welcomed?
I donβt think that would really make sense. It would be modifying a general rule with typescript-specific logic to paper over a typescript bug/flaw - one that once they fix, weβd be stuck supporting the workaround for forever.
@ljharb - sorry, do you mind explaining what the Typescript bug/flaw is? Currently I'm struggling to be able to enforce .js
extensions in a project using Typescript using this rule (which will go on to be published as a ESM package)
@leepowelldev my understanding is that typescript explicitly forces you to omit extensions, so that it can conceptually point to .ts
at dev time but .js
at runtime. TS and native ESM are effectively not yet compatible, and can't be used together.
@ljharb Typescript doesn't explicitly force you to omit extensions. It won't let you use .ts
ot .tsx
as an extension, but .js
is perfectly valid (microsoft/TypeScript#16577). This allows me to create valid ESM output from a Typescript source.
TS and native ESM are effectively not yet compatible, and can't be used together.
I'm not sure what you mean by this.
I mean that if your goal is a native ESM package (something i'd strongly discourage for many reasons, unrelated to this), then authoring in TypeScript is not yet an ideal way to get there, because of node's unfortunate "required extensions for ESM" choice.
What I'd suggest is to only use tsc as a typechecker, to use babel as your only transpiler, and then to use a babel plugin to transform the proper omitted-extensions source to whatever output you like, which can include "native ESM with extensions".
I'm migrating a large project written in TypeScript to output native ESM modules for Node. This requires all the import
statements in TypeScript to end in .js
so they get output with the .js
extensions and I would like to validate that behavior.
I don't think TypeScript is going to budge on their treatment of extensions, see microsoft/TypeScript#16577 (comment), neither will Node JS back off of required extensions for ESM so it is perfectly valid to write this:
// foo.ts
export const foo = 'foo';
// bar.ts
import { foo } from './foo.js';
Currently this config produces Missing file extension "ts" for "./foo.js"
which is illogical since TypeScript doesn't actually support .ts
on file paths microsoft/TypeScript#37582.
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
sourceType: "module"
},
plugins: ["@typescript-eslint", "import"],
extends: ["plugin:import/recommended", "plugin:import/typescript"],
rules: {
"import/extensions": ["error", "ignorePackages"]
},
settings: {
"import/extensions": [".js", ".jsx"],
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"]
},
"import/resolver": {
typescript: {
project: "./"
}
}
}
};
Would a compromise maybe be to introduce a validExentions
option so we could say that .js
is a valid extension in .ts
files?
There is also this issue ineslint-import-resolver-typescript
import-js/eslint-import-resolver-typescript#80 that mentions this exact use case.
I think the proper place to do that is the TS resolver. It can, for example, resolve otherwise-missing .js
files to their present .ts
equivalent.
Looking at the src for extensions
rule I believe this plugin would still error even if anything was done in the resolver.
I have to disagree that supporting this feature should be done at the resolver level.
That suggests to me that the extensions rule needs refactoring, if itβs hardcoding node resolution.
I'm not sure about that ... I think it resolves just fine (would need to check), but then simply check that the file extensions match. Obviously it fails as expected when it tries to match .js
with .ts
or .tsx
. We would need additional checks to allow this to be valid.
I just opened import-js/eslint-import-resolver-typescript#82 so we can see what might be able to be done in the resolver. Although I do agree with @leepowelldev that it makes more sense to handle this behavior in the rules not the resolver.
This behavior is really specific to TypeScript though so would this be best as a separate rule just for TypeScript so it doesn't conflict?
We donβt have any typescript-specific rules; this is a JavaScript plugin that works for TS as well.
Sure, and I don't think this has to be typescript specific if we can expose a config option to support alternative relationships between extensions.
"import/extensions": [
<severity>,
"never" | "always" | "ignorePackages",
{
ignorePackages: true | false,
pattern: {
<extension>: "never" | "always" | "ignorePackages"
},
mapExtensions: {
"js": "tsx?"
}
}
]
This gives the flexibility without directly supporting Typescript.
This is actually already supported at import-js/eslint-import-resolver-typescript#56.
Just remove "import/extensions" this setting.
This resolver resolves the .ts file correctly, but it's not allowed in your configuration.
@ljharb The problem is there is no way to enforce to use .js
extension currently for .ts
files.
and overrides that only apply to .ts files, that set the extension to be βalwaysβ, presumably would force .ts, and you want .js?
and overrides that only apply to .ts files, that set the extension to be βalwaysβ, presumably would force .ts, and you want .js?
Yep.
Thanks to @JounQin I was able to get this working by excluding eslint-import-resolver-typescript
which seems a but counter initiative but it is working. If anyone wants to take a look at the minimal reproduction I made I can push it up to GitHub.
// src/bar.ts - should pass
import { foo } from "./foo.js";
// src/baz.ts - should fail
import { foo } from "./foo";
// src/foo.ts
export const foo = "foo";
With eslint-import-resolver-typescript
- both bar.ts
and baz.ts
fail
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"rules": {
"import/extensions": ["error", "ignorePackages"]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
}
}
}
Without eslint-import-resolver-typescript
- works as intended
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"rules": {
"import/extensions": ["error", "ignorePackages"]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
}
}
}
@patrickarlt so if we just don't use eslint-import-resolver-typescript
then this works?
@leepowelldev It works unless you also enable the import/no-unresolved
rule. In my quest to make a VERY minimal reproduction I didn't extend plugin:import/recommended
and plugin:import/typescript
. So if you don't use eslint-import-resolver-typescript
and keep import/no-unresolved
disabled it works which I don't really think is intended. I think this is what was discussed in #2111 (comment). The resolver works but the import/extensions
rule doesn't think the extension is correct.
My minimal reproduction of this is at https://github.com/patrickarlt/minimal-typescript-eslint-with-extensions.
Adding import/no-unresolved
without eslint-import-resolver-typescript
breaks resolving (predictable)
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"extends": [
"plugin:import/recommended",
"plugin:import/typescript"
],
"rules": {
"import/extensions": ["error", "ignorePackages"]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
}
}
}
Adding import/no-unresolved
+ eslint-import-resolver-typescript
breaks (incorrect ./foo.js
is valid TypeScript)
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"extends": [
"plugin:import/recommended",
"plugin:import/typescript"
],
"rules": {
"import/extensions": ["error", "ignorePackages"]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
}
}
}
}
Unfortunately no-unresolved
is necessary to detect missing untyped packages, but perhaps adding an option to no-unresolved
might be the solution here?
@ljharb I think the behavior everyone is asking for is this:
Using this config:
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"extends": [
"plugin:import/recommended",
"plugin:import/typescript"
],
"rules": {
"import/extensions": ["error", "ignorePackages"]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
}
}
}
}
And these files
// src/bar.ts - should pass
import { foo } from "./foo.js";
// src/baz.ts - should fail
import { foo } from "./foo";
// src/foo.ts
export const foo = "foo";
The expected output should be:
Currently it is:
This is because import { foo } from "./foo.js";
is valid TypeScript that compiles to import { foo } from "./foo.js";
If you want the extensions to output from the TypeScript compiler you must add them in the source.
It looks like eslint-import-resolver-typescript
resolves to the correct file but imports/extensions
rejects that you can import .js
from a .ts
file.
@patrickarlt Try to add ts: never, tsx: never
in the rule configuration like the PR's example.
@JounQin unfortunately that just seem to make everything pass.
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"import"
],
"extends": [
"plugin:import/recommended",
"plugin:import/typescript"
],
"rules": {
"import/extensions": [
"error",
"always",
{
"ts": "never",
"tsx": "never"
}
]
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
}
}
}
}
I've already said previously.
@ljharb The problem is there is no way to enforce to use
.js
extension currently for.ts
files.
Any movement on this? It would be great if we can get the extension linting working correctly for ESM in TS as well as the no-unresolved
.
I'm experiencing exactly the same issues as described by @patrickarlt. I need the resolver for no-unresolved
to work, but as soon as I enable the resolver, the extensions
check gives me false negatives. Disabling the no-unresolved
is not really a solution and the extensions
check would add a lot of value!
TS recently merged a PR which might address this problem in TS itself.
@ljharb can you elaborate on that please? which PR and how does it solve the problem? The request here is for a linting rule, we already have the ability to import TS files using a .js
extension for ESM build outputs. The linting rule would ensure that the extension isn't forgotten
I mean microsoft/TypeScript#45884 - which I believe will allow TS to do the right thing here, so that your imports in code don't have to have extensions (since source code shouldn't be using them) and they'll still transpile/resolve to the proper extensioned path.
In other words, a solution here would be a hack around a TypeScript flaw; TS seems on track to fix that flaw, obviating the need for any workaround.
From my understanding, the extension is mandatory in both Node and the Browser https://nodejs.org/api/esm.html#esm_mandatory_file_extensions
It's only bundling systems like babel/webpack that have added the feature of omitting the extension.
TS is not automating this extension as it's an assumption of the extension. TS won't modify import statements at compile as it risks changing the behaviour of the code.
Consider the following:
// file.ts
import x from './x';
Given
somedir/
file.ts
x.mjs
x.d.ts
x.ts
x.tsx
So I wouldn't call it a "hack". I don't see them changing this behaviour.
It would make everyone happy to be able to lint against this extension being present in relative imports.
Your understanding is incorrect; the browser has no concept of extensions whatsoever, and the extension in node is only mandatory for relative imports - imports from packages should omit them by setting up extensionless entry points in their βexportsβ field.
Perhaps I misunderstood this statement about mandatory extensions in native modules. I took "native" here to mean native in the browser.
It states clearly that the server would need to support the mime type, but the browser will infer the content type from the extension.
That is explicitly saying that browsers provide no "lookup" - browsers in NO WAY have the concept of extensions, and the MIME type is only and always explicitly defined by the server and/or by the HTML tag that loaded it.
Not quite sure where this conversation is going. Personally I don't believe the upcoming TS release will address this issue. @ljharb obviously advocates using extensionless imports, while a number of others want to use them (for whatever personal reasons they may have).
@ljharb obviously advocates using extensionless imports
Wait, what? Extensionless imports only work for bare module and explicit declarations in package.json exports
field. Browser argument is rather a little beside the point. Yes, browser does not care for extension per-se but if the module is /foo/bar.js
then it cannot be imported as /foo/bar
.
So unless TS has the option to output explicit extensions in imported paths we are forced to make sure all our TS modules are imported as *.js
@tpluscode - I completely agree, but I think @ljharb made the argument that the .js
extension should be added as part of the build process, either by a babel plugin or as subsequent step to the typescript compiler.
That's not so simple. An import from './foo/bar'
can mean both ./foo/bar.js
as well as ./foo/bar/index.js
and both can exist on disk. Unless the import module is explicit, trying to defer "fixing" paths to the build is bound to be error prone. This is exactly where a import linter seems to fit right in
This plugin should certainly disambiguate those possibilities, but that actually happens very rarely in practice.
TypeScript will not modify the import extension at compile time. They have made this clear in multiple (very long) threads.
@beamery-tomht they already do that with .ts
to .js
on the file; they just don't want to modify your source. So, if your source remains extensionless, then microsoft/TypeScript#45774 (merged in microsoft/TypeScript#45884) i believe should work, unless i'm misunderstanding something.
They aren't modifying the extension at compile time. They are supporting the use of extensions in your source code. When the source is extensionless, the dist is also extensionless, which is invalid ESM.
That is explicitly saying that browsers provide no "lookup"
It's explicitly stating that the extension cannot be omitted in browsers for ESM.
No, it is not. It's saying that the browser will ask the webserver for the exact URL in the source code, whether it has an extension or not, and that it's up to the webserver to handle it. It's exceedingly easy - and in fact common - for webservers to do extension lookup, and certainly to do index
lookup.
Browsers do not have any idea of what an extension is - the word is meaningless in a browser context.
they already do that with
.ts
to.js
on the file; they just don't want to modify your source. So, if your source remains extensionless, then microsoft/TypeScript#45774 (merged in microsoft/TypeScript#45884) i believe should work, unless i'm misunderstanding something.
This is part of the problem - TS doesnβt allow the use of .ts and .tsx extensions in source code. So you either have to be extensionless or use .js
@leepowelldev ok so, to be clear, the problem (assuming the latest TS changes I referenced) is:
- you omit extensions in your source. TS produces output files with an extension (
.js
or.mjs
or.cjs
, i presume?), but the import specifiers remain extensionless. This works fine with this plugin, and every ESM use case except "native ESM in node" or "native ESM in a browser, and without an import map that maps the extensionless specifier to the desired URL", the latter of which is trivial to fix and thus I don't consider remotely important. The former, however, is important to fix - but, this plugin does not support native node ESM, untilresolve
supports exports (node itself doesn't even provide a non-experimental way to resolve ESM specifiers, so it's just not possible for most tooling to support native ESM yet) - you use
.js
in your source. TS produces output files with a.js
extension. Everything works fine in every ESM use case - you'd still want an import map in the browser - but this plugin will report errors whenever those output files do not exist on disk.
Is that accurate?
@ljharb yes I think thats accurate. I think the pain point is around point 2, however it's not around the output files - the plugin reports that the input files don't exist (which technically they don't).
β¦ and if you ran your build process, they would exist, and everyone would be happy?
No, it is not. It's saying that the browser will ask the webserver for the exact URL in the source code, whether it has an extension or not, and that it's up to the webserver to handle it. It's exceedingly easy - and in fact common - for webservers to do extension lookup, and certainly to do
index
lookup.Browsers do not have any idea of what an extension is - the word is meaningless in a browser context.
It's quite clearly saying you can't omit the extension. I don't even know why you're so persistent about this? The extension is about valid syntax, not about how the browser is requesting the file. I never once affirmed that the browser had any concept of extension is so I don't know why you keep repeating this.
Regarding import-maps, this seems like an extra burden to push on any consumers of an ESM lib. They're also not supported in all major browsers.
β¦ and if you ran your build process, they would exist, and everyone would be happy?
No, they wouldn't exist. The generated files would have a .js
extension, but not on the import statements within the files would remain extensionless.
I'm sorry the wiki you're quoting is incorrect, but that doesn't change the facts.
In scenario 2, you're using .js
in your source.
Iβll be honest, I donβt even remember what was being asked for anymore π
If itβs that we can use (and enforce) .js extensions via the import/extensions rule without unresolved errors thatβd be great.
Right - it seems like you can already get that by using .js
in your source, and running your build process, at which point the linter will be able to resolve those files.
So we run eslint against the final production output?
it seems like you can already get that by using .js in your source
Regardless, this isn't the case at the moment, import/extensions
will still produce errors as illustrated in the previous comments. I think i'm going to tap out at this point - we're just going around in circles.
No, you run eslint against your source, but the build files being present means they're still able to be resolved.
I'm sorry this feels like it's going in circles; I'm trying to understand why this isn't TypeScript's bug to fix (or the typescript eslint resolver).
They are already been resolved correctly, the problem here is we want to enforce to use .js
extension in .ts
source codes.
This is actually already supported at alexgorbatchev/eslint-import-resolver-typescript#56.
Just remove "import/extensions" this setting.
This resolver resolves the .ts file correctly, but it's not allowed in your configuration.@ljharb The problem is there is no way to enforce to use
.js
extension currently for.ts
files.
No, you run eslint against your source, but the build files being present means they're still able to be resolved.
We build to a seperate output location that is not co-located with the source. So sadly this wouldn't work for us.
I'm not sure how else to explain the issue without seeing it in action. Maybe @patrickarlt repo he put together would help illustrate it better.
EDIT: just to re-iterate, this isn't about resolving files, as @JounQin mentioned, this works fine. However, because the resolved file extension ends up (correctly) as .ts
, this doesn't match the source file import extension .js
and we get the Missing file extension "ts" for "./foo.js"
error.
// Resolved path is `foo.ts` - so extension is `.ts`
const extension = path.extname(resolvedPath || importPath).substring(1);
...
// The importPath is `foo.js` - so this condition will always be true
if (!extension || !importPath.endsWith(`.${extension}`)) {
// error
}
Just looking at the TS 4.5 release notes(https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html), and it looks like (unless I'm interpreting it incorrectly) that the .js
extensions will need to be manually added to support the new node12
and nodenext
targets.
IMO, these two new targets make getting this issue resolved even more important.
@ljharb If I were to add an option say extensionOverride
in a PR to support this functionality, is that a feature that would land in eslint-plugin-import
. Happy to give it a shot just don't want to start the work if you don't see it making sense in this plugin?
Thinking it would work like this:
'import/extensions': [
'error',
{
js: { mode: 'always', extensionOverride: 'ts' },
},
]
What I did in my project (https://github.com/algolia/instantsearch.js) was:
- no extensions inside the project, enforced by this plugin
- add extensions using a custom babel plugin: https://github.com/algolia/react-instantsearch/blob/master/scripts/babel/extension-resolver.js
- test using node (which is the most strict) that all extensions were added: https://github.com/algolia/react-instantsearch/blob/master/test/module/packages-are-es-modules.mjs
This way all tooling works as before, and I get errors if an extension is mistakenly added or if it didn't resolve correctly.
@AndrewLeedham i donβt think that would make sense, no.
I just arrived on scene here yesterday after adding this plugin to our first project using TypeScript and trying to output ESM. It is disheartening to see postering and personal opinion when people are just trying to make it easy to do the right thing in their codebases, at least according to the TypeScript and ESM specs.
Is there any way to refocus and move past opinions and personal preferences toward a meaningful and actionable path forward, again, at least according to the TypeScript and ESM specs?
@manovotny "the right thing" is always a personal opinion/preference. There's no TS spec, and the ESM spec does not in any way mention specifiers, so the only things that matter are the behavior of browsers (exact URLs only, extensions doesn't exist as a concept), node (extensions are required for relative paths; "exports" means they don't have to be required for anything imported from a package), and babel/typescript (ESM uses the CJS algorithm; this is what almost all code authored with ESM is currently using).
I am probably adding noise (apologies in advance), but I may have missed the solution to my problem.
I have a TS for node codebase, which needs to use ESM now. As such, I was following this:
https://www.typescriptlang.org/docs/handbook/esm-node.html
which among other things say:
relative import paths need full extensions (we have to write
import "./foo.js"
instead ofimport "./foo"
)
Currently if I do that I get a linter error with
ESLint: Missing file extension "ts" for "./foo.js"(import/extensions)
now, foo will be a foo.ts
file indeed. And in fact, if I try to say import "./foo.ts"
I get a TS error
TS2691: An import path cannot end with a '.ts' extension. Consider importing './foo.js' instead.
I could turn off import/extensions for all ts files to avoid the warning, but the thing is that I need a linter rule to let me know which extensions are missing, since the ones that are missing will end up not working at run time.
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'Somewhere/foo' imported from /Somewhere/index.js
The solution to allow an optional override of extensions config to say to the linter "it's a .ts extension, we're cool if you import .js instead" seems like a good solution, but I saw in your message #2111 (comment) @ljharb that it doesn't seem right to you.
Am I missing a solution that will get me warnings of missing extensions but still compile right?
I am happy to give coding the overrides a try btw.
- In general, I suggest using babel to transpile TypeScript; it does a better job than
tsc
does (tsc
works great for typechecking TypeScript, ofc) - TS refuses to rewrite specifiers
- rewriting specifiers with a babel plugin is trivial
- source code should not include extensions (node's decision to require it is an ergonomics loss, and doesn't need to damage your codebase if you're using a build process)
- So, if you want to output node-native ESM for some reason, my suggestion is to use a babel plugin that adds in the extensions for you, and since linting only applies to the actual source code, you can omit extensions there.
If it's helpful, here's an example of using babel-plugin-module-resolver
to do the rewriting: https://github.com/apache/incubator-annotator/blob/main/babel.config.js#L59
The heuristic there is that all relative paths from a .ts file are expected to resolve to a .ts file, but you can use your own heuristic or check for the existence of the target paths.
You can use the related import resolver to have ESLint and this plugin resolve using these rewrites: https://github.com/apache/incubator-annotator/blob/main/.eslintrc.js#L62
I think @ljharb has solid recommendations here. Until this all settles down more, it's easier to work around the rough edges with Babel in the toolchain and let tsc
just do typechecking.
I don't agree with point 4, but I won't offer more on my own opinion there. I think this is the kind of opinion that @manovotny was politely suggesting isn't terribly helpful.
@tilgovi sure. but if you want your source to include extensions, it should include .ts
, and your build should transform that to .mjs
.
Itβs reasonable that you want to avoid adding TypeScript-specific logic to this JavaScript-centered plugin. What do you think about making the issue become eslint-import-resolver-typescript
βs problem, as follows?
Extend the resolver plugin API to let it return two paths, an import path and a physical path. Rules like extensions
that examine the filename itself would work against the import path, while rules like no-unresolved
that examine the fileβs contents would read it through the physical path.
By default the import and physical paths would be the same, but eslint-import-resolver-typescript
could be modified to return foo.js
for the import path and foo.ts
for the physical path, to match tsc
βs expectations. (This would be controlled by an eslint-import-resolver-typescript
option of course, so that it could still be used in Babel-like configurations.)
Is this a solution that would make everyone happy?
That definitely seems like an appropriate extension to the API, and would probably solve a lot of issues in TS where the physical path is a d.ts file but the import path is not.
How do you propose extending that in a non-breaking way?
Is an extension to the API needed? The current resolve interface returns the physical path, and it's called with the import path. Both paths are known, then.
What need is not being met by the TypeScript resolver today?
What need is not being met by the TypeScript resolver today?
TS resolver is working as expected, this plugin does handle extensions correctly.
I think it's perhaps only the "missing file extension" warnings when using .js
to import from the TypeScript build output files. I'll take a look and see what can be done. Maybe import/extensions
should be acting on the import path rather than the resolved path.
import path rather than the resolved path.
I totally agree with this, but it could be a BREAKING change. A new setting can be provided.
The extensions rule knows both the import path and the resolved path (if it resolves). It needs to use the resolver to support checking whether the resolved path is a package or not.
Currently, when the path resolves, the extensions rule is getting the expected extension from the resolved path. The error happens because the resolved .ts
file doesn't end in the expected .js
.
The TypeScript team has made it clear that they don't think TypeScript should transform paths during compilation. They are considering making it possible to reference .ts
files in the source, though (microsoft/TypeScript#37582). They're not going to transform .ts
into .js
in this mode, but expect to use it for compilation targets where the build output is not JavaScript and the runtime isn't Node.js such that the .ts
files will actually be present at runtime. Things are even weirder when there are bundlers involved, because there's actually no files imported at runtime.
So we need to be careful not to haphazardly expand the role of the resolver. Let's take a step back and ask what the user expects the extensions error to do. Are they trying to prevent a runtime error? Are they trying to prevent a bundler error? A compilation error? Are they just enforcing a stylistic preference?
So we need to be careful not to haphazardly expand the role of the resolver.
For example, just because the resolver might be able to tell us what file should resolve to at runtime doesn't mean that's what the user wants their source to say. That may be what TypeScript users want today for a Node.js runtime target, but it might not be what someone else wants today or tomorrow.
If we do want to assume that it's the resolver responsibility to error if a source path is incorrect, we could make the change I was suggesting and use the import path's extension should or should not be present given the options of the extensions rule, as I said above.
As @JounQin said, that'd be a breaking change. And as I said, that'd be enshrining a particular role for the resolver that may or may not be quite the same as its original purpose. It would be saying that the resolver's role is not just to help this plugin find files to check, but to say whether or not the import is correct. That's a bit different than a stylistic check, though. That's saying it's the resolvers responsibility to hard error when the user writes .ts
and should have written .js
. Instead of resolving the .ts
file, it should say no, ./foo.ts
does not resolve.
The new behavior can be enabled under a new setting flag, then there will be no breaking change.
If you can contribute to draft a PR, that would be great!
I'll sleep on it first. I'm not sure how to name the flag or communicate what it does yet. Any suggestions are welcome.
I'll sleep on it first. I'm not sure how to name the flag or communicate what it does yet. Any suggestions are welcome.
Good night. How about preferImportExtension
?
@JounQin @tilgovi Was there a conclusion from the discussion above about a "desired file extension" for TS relative imports?
BTW, another thing that could be helpful about this option would be if this plugin were extended in the future to support auto-fix of this problem by adding missing extensions... although for non-TS files I'm not sure how valuable that would be.
FWIW, I wrote an eslint plugin that requires js extensions, and also fixes them: https://github.com/solana-labs/eslint-plugin-require-extensions
FWIW, I wrote an eslint plugin that requires js extensions, and also fixes them: https://github.com/solana-labs/eslint-plugin-require-extensions
Finally, the savior has arrived. Thank you for that
FWIW, I wrote an eslint plugin that requires js extensions, and also fixes them: https://github.com/solana-labs/eslint-plugin-require-extensions
This works but this plugin's ignorePackages
would be needed to be usable. A shame this hasn't been resolved yet.
But recommended rules with 'import/no-unresolved': 'off',
indeed work see @patrickarlt 's commend.
Could not reproduce on up to date versions.