-
Notifications
You must be signed in to change notification settings - Fork 104
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
baseUrl has a side effect on the used dependency export #133
Comments
Sharing in case it's helpful: tsconfig-paths hooks into node's resolver here: https://github.com/dividab/tsconfig-paths/blob/master/src/register.ts#L73-L96 And it checks for package.json "main" field here: https://github.com/dividab/tsconfig-paths/blob/master/src/match-path-sync.ts#L85-L106 ...but doesn't check for the new I assume it needs to be updated to understand node's new ESM/CJS resolver behaviors. EDIT: I may be off-track with these observations, though, since I see that @katywings narrowed this down to a specific baseUrl setting. |
I think we want to keep tsconfig-paths logic as close as possible to how
I ran the repro above and it seems this issue is really stuck on (3) above, not (1) or (2)? |
I'm not really sure of the use case for the repro. Seems to do some bundling, but why use tsconfig-paths with it? Removing the |
Regarding resolving cjs/mjs there is an interesting comment from the typescript team here. Seems like there is going to be multiple resolution modes in the future. Not sure how this will affect tsconfig-paths. |
All of those options have one question in common: why does it only fail with the baseUrl "./node_modules" but works without issues with baseUrl: "."?
I am not sure if this question is a troll, but the use case of an src for issue reproduction is to give you a way to reproduce the problem 🙈. microbundle was literally the first project that came to my mind that is having this issue in combination with tsconfig-paths and the node_modules baseUrl... If you are asking because you try to find a workaround for me: I already know a workaround: I can get rid of baseUrl "./node_modules" in my projects with some slight changes ;). |
Yes that is a good question. I guess we would need to debug through it to see the difference?
No trolling, sorry if it came across that way :-) I was not referring to reproductions in general but what the code actually does in this particular repro. From what I understand it does bundling? Why would you want to involve tsconfig-paths when bundling? The bundler should be able to resolve everything by itself better than tsconfig-paths can? For example most bundlers take into account the |
@jonaskello Thanks for the detailled answer :)
I use ts-node / tsconfig-paths during development for the express http server with ssr. Also during dev microbundle is ran by the node server to bundle client assets - the microbundle result/errors are outputted in the frontend by the express server, all of this happens in one node process. |
I thought about it a bit more and I think I know why there is a difference, although I have not debugged it so I'm not sure. When you do If you use If you use I think you have a mix of requirements in your setup. Currently typescript (and Perahaps you can reconfigure the bundler to not use ESM style bundling? Is there any specific reason you need ESM style bundling in development mode? Another option might be to manually bootstrap tsconfig-paths so it does only affect the server side execution and not the bundling. Yet another approach would be to use two separate node processes, one for the server and one for the client assets bundling (this is the way I usually handle it starting them in parallel with npm-run-all). |
This would makes a Iot of sense 😍
Nonono 🙈, the bundler has nothing to do with the source of problem. In the reproduction you literally could replace the microbundle stuff with a require of autoprefixer and you would get the same error. Changing the output format of microbundle doesnt help, because neither does it change the fact that microbundle will require autoprefixer->colorette, nor do I even require any esm output of microbundle in a node process... I know, well I hope, you just try to help with a workaround, thank you for that, but I think we better should just focus on the debugging/validation/follow up of your amazing thought at the top :). |
@cspotcode Did you already start with the exports/esm stuff in some hidden place? How do we coordinate us? Who makes what? - do you even need my help? 😂 |
ts-node added experimental support for node's (also experimental) native ESM support. It's already been published, and feedback is being tracked here. TypeStrong/ts-node#1007 However, ts-node doesn't need to hook into node's CommonJS resolver, so we don't touch that. Unfortunately, to make our ESM loader hooks work, we need to duplicate the functionality of node's built-in ESM resolver. So we've copy-pasted the source code from node, and tweaked as needed. |
@katywings If the problem has nothing to do with the bundling perhaps it is possible to create a simpler repro that does not involve a bundler? For example does it work to directly require colorette without any bundler? (I think I will give that a try as colorette is using .cjs in it's @cspotcode If ts-node supports the new ESM resolution that would mean that |
I did an experiment/repro here that does not involve Looking into |
I'm referring specifically to node's new native ESM mode, where you have to use a special CLI flag to use a custom loader hook which intercepts ESM imports / exports. https://nodejs.org/api/esm.html#esm_hooks I explained exactly how this works here: nodejs/modules#351 (comment) tl;dr is that you must put the |
@cspotcode Yes, I know all of that, no explanation necessary :-). My point was that if ts-node compile and runs stuff by looking at the EDIT: The reason I mentioned it was because then there is a inconsistency how tsc work vs how ts-node works regarding path resolving. I think we should keep tsconfig-paths as close to |
Perhaps, I'm not sure. I don't think that's a use-case we need to prioritize. In other words, if you're writing code that works with Do you have an example of what you're describing, perhaps a sample |
@cspotcode Yes, an example would be good, I'll try to put one together. |
Having investigated further, the problem in this issue seems to be unrelated to the new node resolution of ESM. Instead it is related to tsconfig-paths not respecting the file extensions of the file found in the If we don't strip the extension in this line the repro that I provided above works. As the comment says, I'm not sure why we are stripping the extension there :-). If anyone wants to take a stab at at PR that adds tests for other extensions than |
@jonaskello I gonna try to create that pr now ;) |
@jonaskello Btw. in the original commit message for removeExtension you already added a hint "but why?": 847d314, you don't remember why you added this commit in the first place? :D Edit: it looks like it has something todo with json require handling, still investigating this ;) |
Originally during the file path resolution, file extensions were removed without explicit reason. This commit changes the resolution logic to keep file extensions, with the goal to add support for modern non-js extensions like cjs. The change however keeps /xyz/index resolves as is as they still should resolve to /xyz. Refs 847d314
@jonaskello I'm reviewing #135 and looking at the behavior of the code immediately prior to when #27 was merged. It looks like when you require a directory containing a Specifically I think these 2 places in the code are the bug:
Combining those 2x modifications, tsconfig-paths will read the package.json, make sure it points to something that can be loaded, and then return the path to the module's directory. node will receive the path to the directory and take care of reading the package.json and loading the correct file. |
@cspotcode Yes, I think you are on to something here. The original intent of the stripping of some stuff like extensions were probably that node should be able to re-resolve with it's own logic. However I'm not sure that is a good idea anymore. If tsconfig-paths has found the exact file we want to resolve to, why should it then return less information to node, like removing the extension, or the returning the dir instead of the actual file in the case of package.json? This is probably why I left comments in some places questioning the removal of information. Regarding package.json, there is a case where you can provide other fields to look at instead of (or in addition to) tsconfig-paths/src/match-path-sync.ts Lines 64 to 70 in 7204659
So for example you could add |
@cspotcode Perhaps we could go in the direction of letting tsconfig-paths always decide the exact file and just pass that to node instead of having node re-resolve by removing info? That would probably increase performance by a small bit too since node has less work to do. Do you think there will be issues going in this direction? Specifically I think we can remove this function: tsconfig-paths/src/try-path.ts Lines 63 to 74 in 7204659
For the "index" part, this means tsconfig-paths has found an index file that should be resolved, but it then removes the filename so node can re-resolve from the path only and re-find the index file that tsconfig-paths already found. For the "file" part, it already returns the exact path found. For the "extension" part, it only returns the filename so node needs to re-find the correct extension. For the "package" part it already returns the exact path found (however in previous code it strips the extension of the found file causing the bug in this issue). If we remove this function, tsconfig-paths will be 100% responsible for finding the exact file to use instead of relying on node to re-evalutate. I think removing this function will make things more predictable. The downside may be that any updates to node's algorithm will not be automatically used? |
Thinking more on the extension stripping. When using tsconfig-paths together with ts-node ts-node will add |
If this case exists, shouldnt it be added in the tests then? Since you are the one who knows about this case, it probably makes sense if you can add a test for it, then I can make sure in my pr that your new test still works with my changes ^^. |
Originally during the file path resolution, file extensions were removed without explicit reason. This commit changes the resolution logic to keep file extensions, with the goal to add support for modern non-js extensions like cjs. The change however keeps /xyz/index resolves as is as they still should resolve to /xyz. Refs 847d314
@katywings It is already covered by this test: tsconfig-paths/test/data/match-path-data.ts Lines 128 to 142 in 2461cc9
|
To allow node to resolve the package's entry-point according to its configuration, which
If we return the path to the module's root directory, then tsconfig-paths doesn't need to know about those additional fields because node will resolve from there. For example, node will consult the package.json
@katywings sorry for the back-and-forth. Agreed that there's no way for an external contributor to know how this library is supposed to behave, so it's far too difficult to write a bugfix. |
Yes of course :-) However my point was that currently the API allows you to specify additional fields and if we remove that feature we are making a breaking change. See #45 for some more background. |
Interesting, could you provide an example when it would find the wrong file? I'm thinking only old style resolution here, not the new style for ESM. My thinking is that the unclear rules for resolution are mainly caused by not having tsconfig-paths deterministically find a file. If we resolve to "something" and then let node resolve to the real thing I think it will be very hard to have clear rules on how the resolution should work. The fact that the stripping of paths and extensions is the main pain point right now seems to point in this direction. To be clear, the stripping is done because we wanted node to do part of the resolution. I can see how letting node do the work should in theory be more compatible, however that is actually the way it is already done, hence the stripping and nondeterministic stuff going on that causes this very issue. If we should let node handle some part of the resolution then we need to make some other rule-set than the actual resolution very clear. Some kind of rule-set for pre-resolution that works in tandem with node's internal resolution. I guess we also need decide on the goals. I think my goals are more on the side of having the old style very clear and reliable, while you may be thinking about adding the new style resolution? I could work either way but it helps to have a common goal :-). |
Agreed about common goals. My goal is to make it work seamlessly with ts-node, perhaps even add a ts-node flag to register it automatically. But I also understand the need to support other consumers like webpack. I suppose I'm thinking of the new style of resolution. What happens if "paths" redirects an import/require to a directory that has package.json with Could we add an opt-in flag, something like |
Supposing I have this
To resolve
If so, perhaps tsconfig-paths can expose an API to generate that list of candidates, then we can run them through a loop like above? I think node's resolver does FS caching internally, so this may improve performance. |
What you describe is a bit similar to how it works in the current code. The export interface TryPath {
readonly type: "file" | "extension" | "index" | "package";
readonly path: string;
} The
I'm thinking the call to node's resolve function in the current code is not needed at all since we already know the exact file and can return it directly. At least the API functions (as documented here) could return the exact file and the code calling the API could then decide if it want to strip it and call node etc. The above is just some description of how it currently works and how it could be improved with minimal changes. If we think about the new resolution things may have to be done differently. From what I understand the typescript team is considering adding more |
The new node14 resolution seems rather complex so I can see how avoiding implementing it all in tsconfig-paths could be helpful: |
I haven't really had any time for this, so I hope you'll forgive the terse, delayed response. I thought about what you said with tsconfig-paths returning the full, absolute path, always. And I thought about our needs in ts-node. I think ideally tsconfig-paths should expose 2x ways to do resolution:
tl;dr I agree on (b) but also think that (a) should be exposed as an API. |
Yes, this seems like a nice solution. So there will be one low-level API (a) where you only get the candidates and then a higher level API (b) that calls lower level but also does more work to make the resolution complete. Today it is done a bit like this where the I also like the name candidate path. think the current name |
I have this issue when I try to run webpack with when I remove I already set trace
|
Originally during the file path resolution, file extensions were removed without explicit reason. This commit changes the resolution logic to keep file extensions, with the goal to add support for modern non-js extensions like cjs. The change however keeps /xyz/index resolves as is as they still should resolve to /xyz. Refs 847d314 Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com>
Originally during the file path resolution, file extensions were removed without explicit reason. This commit changes the resolution logic to keep file extensions, with the goal to add support for modern non-js extensions like cjs. The change however keeps /xyz/index resolves as is as they still should resolve to /xyz. Refs 847d314 Co-authored-by: Andrew Bradley <cspotcode@gmail.com> Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com>
Originally during the file path resolution, file extensions were removed without explicit reason. This commit changes the resolution logic to keep file extensions, with the goal to add support for modern non-js extensions like cjs. The change however keeps /xyz/index resolves as is as they still should resolve to /xyz. Refs 847d314 Co-authored-by: Andrew Bradley <cspotcode@gmail.com> Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com> Co-authored-by: Katja Lutz <mail@katjalutz.ch> Co-authored-by: Andrew Bradley <cspotcode@gmail.com>
Is
If I use ts-node together with tsconfig-paths/register and a tsconfig baseUrl of
./node_modules
the runtime seems to "ignore" package.json exports of dependencies and uses the wrong file. As the bottom example shows it usescolorette/index.js
but it should usecolorette/index.cjs
.Should
It should use the correct dependency dist as defined by its package.json exports, so for colorette it should use
colorette/index.cjs
.Reproduction
npm install && npm start
Trace
Background:
ts-node
fails when ES Modules are in the dependency graph in Node.js 13+ TypeStrong/ts-node#935 / ESM support: soliciting feedback TypeStrong/ts-node#1007 but after working out a reproduction case I figured out that the error only happens with tsconfig-paths and the mentioned baseUrl value 🙈.The text was updated successfully, but these errors were encountered: