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

Can’t resolve @types when vendoring dependencies for browser ESM imports #50600

Open
pehrlich2 opened this issue Sep 1, 2022 · 8 comments
Open
Labels
Domain: ES Modules The issue relates to import/export style module behavior Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented.

Comments

@pehrlich2
Copy link

Bug Report

🔎 Search Terms

  • native modules alias relative paths
  • Relative references must start with either "/", "./", or "../".

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about declaration files

⏯ Playground Link

N/A (behavior is documented)

💻 Code

Related frustrations have been posted many times before - so I've hesitated to post this:

#16577
#13422
#28288
#42151

My case, and workaround, are documented here: https://stackoverflow.com/questions/67725840/how-can-i-use-threejs-es6-native-modules-with-typescript/73575993#73575993

🙁 Actual behavior

  1. The Declaration File must be included with an alias, e.g. import * as THREE from 'three'
  2. es6 modules require extensions, which the alias does not allow. TypeScript does and will not support URL rewriting.
  3. As a result, I cannot use Declaration Files + TypeScript without importing additional software

🙂 Expected behavior

There could be multiple possible solutions here:

  1. Just document it better, here: https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html If this was described as a known limitation, it would have saved me half a day.

  2. Support aliases with extensions in one of two ways:
    a) Allow the alias itself to be of the final expected form ./js/lib/three.module.js
    b) Allow some sort of a custom mapping, which I can use to tell tsc to look for an .d.ts alias three when it sees ./js/lib/three.module.js

Thanks in advance!

@Jamesernator
Copy link

Related frustrations have been posted many times before - so I've hesitated to post this:

#16577 #13422 #28288 #42151

The initial problem linked to doesn't really seem to have anything to do with these issues. Like the problem here is that this doesn't work right?

// Could not find a declaration file for module './node_modules/three/src/Three.js'. '/home/jamesernator/projects/playground/node_modules/three/src/Three.js' implicitly has an 'any' type.ts(7016)
import * as THREE from './node_modules/three/src/Three.js'

But that this does, but this isn't syntactically valid for browsers:

// @types/three work
import * as THREE from 'three'

In principle, TypeScript probably could support the former by noticing node_modules/lib in any path and mapping it to node_modules/@types/lib although I'm not sure there's a massive demand given most people just use some kind've bundler or such. (In addition it would require @types to be written in such a way that file structures match fully even if "exports" is used).

An alternative solution would be to use import maps, although they're currently Chrome only, but if that limitation doesn't bother you OR you don't mind waiting for other browsers to support them then you could just do:

<script type="importmap">
{
   "three/": "./path/to/node_modules/three/"
}
</script>

In which case import * as THREE from "three"; would ALSO WORK in the browser.

(For libraries more complicated than three.js such an import map is a bit too simple, in which case you'd need an actual node_modules -> importmap generator).

@pehrlich2
Copy link
Author

pehrlich2 commented Sep 2, 2022

Thanks for the quick reply!

Yes, the linked issues are maybe only related but not the same (not that I've exhaustively read all the replies therein).

In principle, TypeScript probably could support the former by noticing node_modules/lib in any path and mapping it to node_modules/@types/lib

This would require that THREE is installed as a node module right? Seems common but for something this fundamental still a limitation. (I believe I ended up making my own copy of THREE to get the modules in es6 style, so it's not on npm 🤷‍♂️).

I will readily admit my use-case is not a common one :). It's just that when I'm working on a hobby project, the page of ~15 possible build tools to research is one I want to avoid for a while, ha!

TIL about import maps! With a slight syntax tweak, your example worked perfectly:

<script type="importmap">
            {
                "imports": {
                    "three": "./js/lib/three.module.js"
                    "src/": "./js/"
                 }
            }    
 </script>

Seems like a reasonable work around - probably by the time I want to support other browsers I'll have to add a JS build pipeline anyway..

@fatcerberus
Copy link

fatcerberus commented Sep 2, 2022

this isn't syntactically valid for browsers

Small nitpick: It is syntactically valid, it's just semantically invalid (unless the browser supports import maps, as you note). This is just a pet peeve of mine so take it with a grain of salt. 😅

An alternative solution would be to use import maps, although they're currently Chrome only

Trying to make myself a bit more useful, I wanted to chime in on this that you can use es-module-shims to get import maps working on non-Chromium browsers. I've been using this in Oozaru, it works quite well. (@pehrlich2 this might be useful for you as well)

@andrewbranch
Copy link
Member

This isn’t a solution, but you will be probably be interested in watching #50153 (part of #50152). I’m definitely open to brainstorming good ways to resolve DT types for vendored dependencies once that lands.

@andrewbranch andrewbranch changed the title Declaration Files not compatible with native es6 modules Can’t resolve @types when vendoring dependencies for browser ESM imports Sep 2, 2022
@andrewbranch andrewbranch added Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Domain: ES Modules The issue relates to import/export style module behavior labels Sep 2, 2022
@andrewbranch
Copy link
Member

My current thoughts are

  1. This should Just Work™ for mapping ./node_modules/* to ./node_modules/@types/* at least in --moduleResolution minimal, but possibly in all modes?
  2. But I’m interested in solving it for relative imports in general. If you have all your browser-compatible dependencies in ./vendor/*, you should be able to add types for them in ./types/* if you want.

@andrewbranch
Copy link
Member

andrewbranch commented Sep 21, 2022

After thinking about it for about 30 seconds, I’m wondering if it needs to be more complicated than

{
  "compilerOptions": {
    "relativePaths": {
      "./node_modules/*": ["./node_modules/*", "./node_modules/@types/*"]
    }
  }
}

where unlike paths, the key side prefix doesn’t need to be an exact string match of the module specifier, but rather a prefix match after both the module specifier and the key are resolved to an absolute directory path. That is, it should match both "./node_modules/foo/index.js" and "../../../node_modules/foo/index.js" as long as both mentions of node_moduels refer to the same directory that the tsconfig mentioned.

@andrewbranch
Copy link
Member

Ok, so the problem with doing this with node_modules / npm packages specifically is that package.json files can contain complex intra-package mappings for types in themselves, so while the scheme I wrote above might work for relative paths in general, types inside packages cannot always be linked up just by relative paths. With this scheme, you’d immediately hit two different kinds of trouble:

  1. Packages that ship their own types but use exports or typesVersions to put them in a different folder from the implementation. import "./node_modules/rxjs/dist/esm/index.js" doesn’t know to look up types from ./node_modules/rxjs/dist/types/index.d.ts without looking at the package.json.
  2. Any @types package that uses typesVersions to ship multiple copies of the types for different TypeScript versions wouldn’t work without looking at that package.json.

There’s no way around stuff in node_modules being special; even if your module specifier into it doesn’t use any special Node resolution magic, we won’t be able to find types without using some of those things.

So the question is, for a mode like --moduleResolution minimal, if you have a way like relativePaths above to set up types for your relative dependencies, do we also need to include any special behavior that lets relative imports into node_modules resolve types? Unfortunately I just don’t know if anyone is doing this, or has any desire to.

@thw0rted
Copy link

I don't have an example on hand, but can't this be fixed by a .d.ts file in your local project that re-exports the types from DT (@types scope) in a declare module "./lib/threejs.min.js" {...}? (I'm guessing at the syntax, the problem with composing this from my phone...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: ES Modules The issue relates to import/export style module behavior Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented.
Projects
None yet
Development

No branches or pull requests

5 participants