-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
What’s confusing about modules? #51876
Comments
Is there a difference between |
Any chance you could include a dedicated section or two on why it's not practical to have the compiler transform imports ending in I've seen a few of the TypeScript team posting some pretty solid rationale in various GitHub comments over the years, but I can never remember all the reasons, and finding those comments again is quite difficult. It would be amazing to get one clear, objective, well-written explanation/rationale that could be easily linked to by anyone whenever this comes up. |
One thing that tripped me up was how |
What are the exact requirements for publishable packages? And how do they relate to: |
An import path cannot end with a '.mts' extension. Consider importing './XXX.mjs' instead. Why does this error message even exist? It seems insane to direct tsc to import a .mjs when we have a .mts with types and everything. What am I missing here? Is there a way to direct tsc to the real type info? That suggestion certainly doesn't sound like something I want to do. |
I'm working on a demo about many issues with the current It's still a work in progress. I can probably finish it by this weekend or next week. Feel free to check it out or cooperate about this topic. https://github.com/cyberuni/typescript-module-resolutions-demo |
My team is building a non-fancy web-app (not a library/framework) in a monorepo setup. Are there any different configs needed for app vs. library authors? I would love to see some kind of cli check -> "Is my setup ok?" like |
Cross linking the great post made by Ryan: #49083 (comment) There are still many things worth to discuss and figure out, for example:
But in general, that is a good read and should check it out. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Seems like this isn't really the place to argue over ESM-only vs CJS compat... |
This comment was marked as off-topic.
This comment was marked as off-topic.
Let’s keep this an uncluttered place for people to ask questions, please. Because GitHub issues don’t have a reasonable means of threading or replies, that means not answering other people’s questions, even if you have an answer or opinion. Thanks for understanding! |
I wanted to emphasize these two. While being either fully Specifically for dual packaging lib authors, things that would be awesome:
I think the above would be tremendously helpful as those seem to be were many authors on twitter (and myself) trip over all the steps surrounding dual packaging, |
One thing that would be nice to document, is how to correctly setup modules where you're using a bundler that takes in ESM code, but also tooling like Jest that operates on CJS code. As An ideal setup would allow use of package.json exports/imports fields, the app code to ouput ESM to the bundler (webpack, etc), and Jest to recieve CJS output (eg, using a tsconfig.test.json file). From my testing there doesn't seem to be a "perfect" setup here, so documentation around the best way to solve something like this / best practices IMO would be ideal |
I'm taking this to mean module and module resolution. If I had to guess, I think what most people want is:
I'm sure thousands of man-years have been spent (in anger!) debugging JS/TS module and resolution issues. Most people have no clue what's going on, they just muddle through, turning on and off config flags until things break less. If you can make this more seamless you'd literally be saving thousands of lives — and bringing big smiles to all JS/TS devs 😀 A tangent: I have high hopes for the |
Echoing @beorn’s eloquent "path forward" and "future proofing" points in a more "Hey, I'm new" way.
*And maybe in depth guides come later, not now, or maybe just include a collection of resources. Also, I think recommendations differ from "library" vs. "application" authoring, and I still find that odd X years later. If they are different, maybe this clarifies that too. Awesome to see this issue! I was looking for Edit: I just re-read #50152 in depth, and it has a lot of great context. Unsure how I missed some of that info before. Pulling some of that into permanent documentation (or linking to it for context) would be great! |
this part from
How does it run in "CJS mode" and uses an |
“CJS mode” just refers to the flavor of module resolution and doesn’t actually indicate anything about the kind of the importing or imported modules here. It means index files and extensionless shenanigans are supported. Do you have a suggestion for an alternative nomenclature? |
What I find more confusing about this is not the "CJS mode" but the mention of the |
You wrote an import statement, so the |
At least I misunderstood what "resolving in CJS mode" meant — I thought it meant modules that were resolved would be assumed to be CJS. Is there another name that could be used to refer to the module resolution without involving ESM/CJS names (which I think will make most people think about the module format, not just resolution algorithm)? |
One comment in a documentation example says that package.json Basically https://www.typescriptlang.org/docs/handbook/esm-node.html doesn't list all of the resolutions that |
If that wouldn't be the case then we probably wouldn't be able to use those new |
That comment is specific to the example it’s contained in. It means to say that because there are |
Yes that's fair enough, I have been fighting with this issue many times before and surely there was some repressed frustration deep down, also I am not a native so sorry if I wrote something horribly when I didn't mean bad towards you, but in the end it was genuine questions on the
Yes that makes sense, thank you.
No my question is: why does the TypeScript code I write need to be heavily coupled to the compiler I will use in the future? Importing a simple In my mind this isn't how compiler should work, I should write |
Got it, I misunderstood your question with the |
I am really confused about why you need both Edit: opened #54876 |
off topic but there's a difference betweenexport default foo and export { foo as default } The latter creates a live binding, while the former does not. Solid playground without live binding Solid playground with live binding Type-wise no difference in TypeScript. |
@Mahi The problem is there are various different ES Module and non-ESM systems, and TypeScript needs to be configurable for all of them:
TypeScript cannot simply build code a single way, because then it would work in only one, maybe two, of those environments. When you write TypeScript code, what is going to be importing your TypeScript code? Is it gonna be a browser ES module that will natively How will TypeScript know the output format that your code needs to be in, for it to work properly in some target environment, maybe even a non-ESM environment? The answer is that in reality, there are currently too many ESM and non-ESM consumers (build tools or runtimes or both) with varying rules. Bun recently threw a wrench into the mix by mixing CommonJS with ESM in the same file (❔, don't do that unless you want to write very non-portable code).
TLDR: because the compiler or environment you choose may have certain rules that others don't. My advice:Avoid If you do this, then it'll be the simplest to get up and running with any alternative tools in the future with the least amount of config fiddling or code changes.
Basically: when you write code as close as possible to what a browser can consume, only having to set up an import map for bare specifiers referring to library names, then your code will be as portable as possible. The |
The new theory document is referenced from the tsconfig reference for paths, but the "There is a larger coverage of paths in the handbook" statement references a |
Thank you. You got linked there via a redirect from the old content. I need to update the link to point here: https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths |
|
|
That's not completely true. You are using output paths in An example test case for this can be found here. Notice how |
@Andarist , wait so you say that it is possible to do the conditional import? If that's true, then I have to try once more myself and cross out the paragraph in my previous message.
Thank you for mentioning this, @GabenGar. Actually, I first saw the idea of using the source files as the type source in a blog post by Turbo. But they also declared And like you said, they rely on applications being the "leaves of the topological tree", being the projects that actually do the building. This means that the compiler options in apps cannot be "stricter" than the compiler options for the libraries, because the library is ultimately compiled with the dependent applications' compiler options. This might seem like a tiny detail, but it matters when we want to gradually introduce some stricter compiler options to a monorepo, project by project, where each project/lib can be developed by a separate team. That was one of the pitfalls I discovered when using .ts files as
@GabenGar , that was the only way before it was announced that the |
Sure thing. Conditions are supported both in |
Supported since version 4.7? It's only the auto-completion that will be added in 5.4, right? |
Yes, the module resolution part of things was released in 4.7 (release note here). 5.4 just offers new auto-completions in this area |
The key word in the underlying PR is "roughly". Since
While technically true, it's very unlikely you'll end up in a monorepo situation without all js code lathed in typescript (which might or might not be a bundler) and without a build step. |
It seems we happen to work on projects with entirely different DX -- hence the disagreement. We usually seem to be stuck between choosing solutions that either require lots of configuration or those that are super magical and in case of a non-standard issue require reading their source code to understand what they are doing. I'm searching for a minimal, but a non-magical approach, where we are as compliant with various tools as we can get. And I'd like the TS docs to help other people get there as well. And I believe that #imports are a "native" alternative to the path aliases, at least for Node, because they just provide the "type layer" on the stuff that is already supported by the runtime. So no bundler magic, no unnecessary configuration -- it's the sweet spot. Let me go back to the original issue, which is about the docs themselves. The aforementioned path aliases, that 5 years ago were not explained well enough and made people (ab)use them as import syntax-sugar, today they have a single essential paragraph explaining they should not be used to reference other packages in a monorepo. It's great that this particular piece of information is now in the docs, and it's great that Yarn/pnpm workspaces are mentioned as the alternative. What I don't get though, is that the self-referencing path aliases seem to be endorsed in the next subsection of the same document. The docs could be more specific that this is probably a good idea only if you work with an additional build step other than just tsc. And here we could be mentioning #imports as the alternative, in the same way we are mentioning Yarn workspaces as the alternative for aliases to internal libs. Moreover, I know that the information about paths not being emitted is 2 sections above, but since the documentation is not separated into "vanilla tsc + Node/browser" and "things that make sense if your bundler allows it", reading the "wildcard pattern" section alone can still mislead developers into using the path aliases without understanding their consequences. EDIT: what's also confusing is the part where baseUrl stopped being necessary for the path aliases to work. As a user of path aliases I obviously thought it's a good idea to reduce unnecessary "baseUrl: '.'" entry in my config. But today, knowing about the original idea behind the feature, I'm wondering if it was a good idea that we simplified the usage of the feature that at the same time we try to warn people about (did it make sense to drop baseUrl in the context of usage with RequireJS? I assume not) |
I don’t know how anything in that document can be read as an endorsement. It’s a reference page that has to spell out how features work. I prefixed the section with a big disclaimer about what not to do, but eventually I had to put some examples on the page.
When I removed the need for |
You are right, I went too far with endorsement. What I was trying to say is that I thought that the usage of paths for imports convenience was not meant to be officially recognized while the docs show it. But reading your reply I understand that my assumptions were wrong and the de facto usage of paths was embraced by the maintaining team (hence the decoupling of baseUrl from paths). Thank you, @andrewbranch, for sharing your thoughts on this matter.
Do you mean some specific issue that can happen when using baseUrl? What is this way you're mentioning if I may ask? |
{
"compilerOptions": {
"baseUrl": "./src"
}
} This means that a file import foo from "foo"; from any file in the program, no matter its location. All “bare specifiers” like this are looked up in the Basically, you’re saying that an AMD loader is going to make an HTTP request with URL fragment |
@Andarist , coming back to the "imports" thing -- I can't make them work in my project.
But I also don't understand how TS is supposed to work here. Could I ask for your help here? Please, take a look at the following example. Having these compiler options in "module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": ".",
"outDir": "dist", and these entries in "type": "commonjs",
"imports": {
"#lib/*": {
"default": "./dist/src/*.js"
}
} I'm trying to import anything from the top-level of the package with" import { something } from '#lib/something' but it does not work. So here are the questions that could help me understand the resolution algorithm better (and at the same time fix my project configuration 😅):
EDIT: 7 [META]: Is there a specific place where I could read all about this without using your help and time and/or referring to comments under GH issues 😅 |
@akwodkiewicz i'll try to respond to questions sometime later - but when it comes to the given example, could you wrap it up in a repository that I could clone? that would save me some time |
Btw. you can use |
@Andarist, I was preparing the reproduction repository and was able to reproduce the issue, but I started messing with the |
It all boiled down to me not understanding how Node works. Pointing to folders will not work for subpath imports. Your imports have to point to particular files. Or more exactly, the sum of the import mapping and the thing in your import statement has to result in a particular file. I'll explain in the examples below in a while. "Folders as modules" seem not to work for subpath imports. It's not mentioned explicitly that it won't work, there's just a following line in the docs:
which suggests that "subpath imports" replace "folders as modules". And the "folders as modules" feature itself is marked as legacy anyway. So if you have a tree like this:
and you want to import from "imports" {
"#lib/foo": "./dist/foo/index.js"
} If you don't want to specify a separate subpath for each directory, you can try using wildcards, like this: "imports" {
"#lib/*": "./dist/*/index.js"
} The above will work even for nested directories, because the asterisk is not a proper glob pattern, it's just string substitution. Now, if you make the mistake of specifying the path mapping like this: "imports" {
"#lib/*": "./dist/*"
} assuming that Node will resolve the folder as a module, then it won't work for Or if you decide to use: "imports" {
"#lib/*": "./dist/*.js"
} then the correct import statement will be even weirder: ———- When I wrote the import statement including the word “index” in my project, the type acquisition worked out of the box, even without adding any conditional “types” entries to “imports”. Now the answers to my questions are not that important, I guess, but it still would be useful to know how TS manages to find type information for the subpath imports. I believe it relies on the information from |
Yeah, that is a tricky subtlety. Note that it applies to {
"name": "foo",
"exports": {
"./*": "./dist/*"
}
} // main.mjs
import "foo/bar.js"; // main.cjs
require("foo/bar") Given a file This is also one way the |
I have just said jokingly to a colleague that “Node forgot to backport the folders as modules feature to imports”. I understand the decision, and seeing the feature marked as legacy helped me tie the pieces together. That was the similarity of exports and imports I failed to recognise. I thought the similarity is the support of the conditional “types” entry. I got too fixated on trying to point the type sources to TypeScript with the conditional entry, that I just did not realize that I’m writing improper code, resulting in Node runtime errors after transpilation.
And the reason it will still “work in runtime” (when translated with the help of bundlers or tools like tsconfig-paths) is that these alias paths end up being relative paths. And “folders as modules” is a feature working exactly just with the relative paths. |
After #51669 is merged, I plan to write documentation for it, and try to update/rewrite a bunch of our existing module-related documentation. While there are a lot of good examples in this issue tracker of specific questions and misconceptions about modules and TypeScript’s module-related options, I thought I’d ask explicitly what questions you have and what aspects of the module landscape or our configuration specifically are the most confusing.
The text was updated successfully, but these errors were encountered: