Uh oh!
There was an error while loading. Please reload this page.
Add path mapping support to ESM and CJS loaders - #1585
Conversation
The ESM loader now resolves import paths using TypeScripts [path mapping][1] feature. [1]: https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping Signed-off-by: Thomas Scholtes <geigerzaehler@axiom.fm>
cspotcode
commented
Dec 29, 2021
Thanks for this. Because of the American holidays, I have somewhat of a backlog of issues and PRs which need attention before I'll be able to properly review this. There may be some delay. I see some refactoring in node-errors; can you explain the motivation for some of those changes? It's good to have an explanation for such changes when reviewing. Your description says this enables path mapping for the ESM loader. We'd like to also extend the same functionality to the CommonJS loader. I see that your Is this feature-flagged? We should decide if it's enabled or disabled by default, and decide how users can opt-in or opt-out. |
geigerzaehler
commented
Dec 29, 2021
No problem.
The new resolving code needs to detect
I don’t think there will be any issues. The path mapping code is does not include any logic that is specific to ESM resolution. The only inputs to it are the TypeScript compiler options.
It’s not feature-flagged at the moment because I couldn’t think of a scenario where you would want the path mapping to be disabled. If you have configured the |
cspotcode
commented
Dec 29, 2021
Good point. And I think someone could disable it like this: |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files
🚀 New features to boost your workflow:
|
cspotcode
left a comment
There was a problem hiding this comment.
I left comments from an initial code review. I plan to implement the requested changes myself, but if you are feeling enthusiastic and would like to do them before me, I will certainly appreciate the assistance. No pressure though.
Overall this looks great to me. I definitely want to add CommonJS support which shouldn't be too difficult. I'm already familiar with the hooking API.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| ): PathMapper { | ||
| if (compilerOptions.paths) { | ||
| if (!compilerOptions.baseUrl) { | ||
| throw new Error(`Compiler option 'baseUrl' required when 'paths' is set`); |
There was a problem hiding this comment.
Is compilerOptions.baseUrl guaranteed to be an absolute path, or do we need to resolve it relative to the tsconfig's location?
There was a problem hiding this comment.
Good point. I assumed that we always get the configuration from ts.readConfigFile() in src/configuration.ts and the function resolves the base URL relative to the config file. But there are probably other ways to set the base URL like through the command line, API, or environment.
To address this I think it make sense to resolve any base URL that is not a URL but a relative path to the current directory.
There was a problem hiding this comment.
I see, we almost always get the configuration from ts.readConfigFile(), so we should be good. I think the only way to set it otherwise is via the API? Since we don't support all of tsc's command-line flags.
Maybe the correct approach is to resolve baseUrl once within create() so that we have a reliable, absolute baseUrl to be used elsewhere.
There was a problem hiding this comment.
Maybe the correct approach is to resolve baseUrl once within create() so that we have a reliable, absolute baseUrl to be used elsewhere.
Alternatively, we could require baseUrl to be absolute and throw an error otherwise. This would allow us to avoid implicit, environment-dependent behavior for users of the API. This might prevent some confusion when path mapping doesn’t work as expected but only fails at the resolution stage instead of the construction stage. (For example a user may think the base URL they set on the API is considered relative to tsconfig.json and their scripts work whenever they are run from the project root. But then they run the script from a subdirectory and it fails.)
There was a problem hiding this comment.
Sounds good, let's do that. In create() we throw an error if baseUrl is not absolute.
There was a problem hiding this comment.
Actually, scratch that, seems we always pass options through TypeScript's API to be normalized, even when provided via our API:
$ node
> require('ts-node').create({compilerOptions: {baseUrl: "foobar"}}).config.options.baseUrl
'/home/ubuntu/dev/ts-node/ts-node/path-mapping/foobar'
So I guess we're all good.
Uh oh!
There was an error while loading. Please reload this page.
Signed-off-by: Thomas Scholtes <geigerzaehler@axiom.fm>
Signed-off-by: Thomas Scholtes <geigerzaehler@axiom.fm>
Signed-off-by: Thomas Scholtes <geigerzaehler@axiom.fm>
geigerzaehler
commented
Jan 24, 2022
I’d prefer to keep scope of this PR small and I’m not familiar enough with the CommonJS side of things so I’ll leave this to you. One thing to consider is that we’d probably want the users to explicitly opt in to path mapping for CommonJS. They’ll be using |
cspotcode
commented
Jan 24, 2022
via email
Fair point. Would it be a great hardship for those users to disable
tsconfig-paths? I hope they would appreciate the simplicity of removing a
component. I would like our default behaviour to be the best
forward-thinking behaviour, but perhaps we add an optional flag which can
be used to selectively disable it for commonjs? Or we somehow detect
tsconfig-paths and throw a warning when both mappers are competing? …On Mon, Jan 24, 2022 at 6:15 AM Thomas Scholtes ***@***.***> wrote:
I definitely want to add CommonJS support which shouldn't be too
difficult. I'm already familiar with the hooking API.
I’d prefer to keep scope of this PR small and I’m not familiar enough with
the CommonJS side of things so I’ll leave this to you.
One thing to consider is that we’d probably want the users to explicitly
opt in to path mapping for CommonJS. They’ll be using tsconfig-paths at
the moment and it is unclear if the ts-node implementation can be used
side-by-side or compatible.
—
Reply to this email directly, view it on GitHub
<#1585 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAC35OGJBEFQKHZNAXQ5F33UXUYERANCNFSM5K6CBQ2Q>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
cspotcode
commented
Jan 25, 2022
I made an attempt at adding the CommonJS resolver hook. Still some outstanding TODOs that I need to address, but this is looking good. |
Looks like we have test failures on Windows due to module specifiers using forward slashes and windows paths using backslashes. I haven't checked the code yet, but we probably have some logic assuming a module specifier and a path will always look the same. Should be an easy fix. https://github.com/TypeStrong/ts-node/runs/4932463748?check_suite_focus=true#step:15:167 |
geigerzaehler
commented
Jan 25, 2022
I don’t think it would. I just wanted to point out, that this is a breaking change. I do believe most users will appreciate this replacement. Considering the release strategy it probably makes sense to have the commonjs mapping as opt-in and ask users to enable it to collect feedback. Once the feature is stable it can be enabled by default (or always) in a new major release.
This sounds complex and error-prone. I think I’d prefer a solution where it is explicitly configured |
I like that plan: opt-in via a flag today, enable by default in the next major version, get feedback in the meantime. How about |
kuksik
commented
Mar 16, 2023
some news about this PR? |
mprinc
commented
Mar 16, 2023
Sad that the
|
0x80
commented
Mar 17, 2023
@mprinc The workaround is not yet clear to me from skimming through this discussion. Could you share the content of your |
suggesting tsx (and also esm/cjs loader in its description) for this feature |
Naddiseo
commented
Mar 18, 2023
This is what I'm using with node 16 /*loader.jsUsage (I'm also using dotenv, but you can omit the dotenv parts if needed):DOTENV_CONFIG_PATH=.env node -r dotenv/config --loader=./loader.js /bin/path/to/cli.ts*/import{pathToFileURL}from"node:url";import{getFormat,load,resolveasresolveTs,transformSource}from"ts-node/esm";import*astsConfigPathsfrom"tsconfig-paths";export{getFormat,transformSource,load};const{ absoluteBaseUrl, paths }=tsConfigPaths.loadConfig();constmatchPath=tsConfigPaths.createMatchPath(absoluteBaseUrl,paths);exportfunctionresolve(specifier,context,defaultResolver){constmappedSpecifier=matchPath(specifier);if(mappedSpecifier){specifier=`${mappedSpecifier}.js`;/* the resolve functionality can only work with file URLs, so we need to convert, this is especially the case on windows, where the path might not be a valid URL. */consturl=specifier.startsWith("file:") ? specifier : pathToFileURL(specifier.toString());returnresolveTs(url.toString(),context,defaultResolver);}else{// If we can't find a mapping, just pass it on to the default resolverreturnresolveTs(specifier,context,defaultResolver);}} |
gudh
commented
Mar 21, 2023
@trim21 thanks, |
troywweber7
commented
Mar 21, 2023
@trim21 I might be missing something... Why |
trim21
commented
Mar 21, 2023
that's a link, click it |
troywweber7
commented
Mar 21, 2023
@trim21 thanks. My client wasn't rendering the link. Boo. Will look in browser. |
Hi Thijs (@0x80 ) here is the code I am using // https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115// "serve": "cross-env TS_NODE_PROJECT=\"tsconfig.build.json\" node --experimental-specifier-resolution=node --loader ./loader.js src/index.ts",// TS_NODE_PROJECT=\"tsconfig.build.json\" node --experimental-specifier-resolution=node --loader ./loader.js src/index.ts"import{resolveasresolveTs}from"ts-node/esm";import*astsConfigPathsfrom"tsconfig-paths";import{pathToFileURL}from"url";const{ absoluteBaseUrl, paths }=tsConfigPaths.loadConfig();constmatchPath=tsConfigPaths.createMatchPath(absoluteBaseUrl,paths);exportfunctionresolve(specifier,ctx,defaultResolve){constmatch=matchPath(specifier);returnmatch ? resolveTs(pathToFileURL(`${match}`).href,ctx,defaultResolve) : resolveTs(specifier,ctx,defaultResolve);}export{load,getFormat,transformSource}from'ts-node/esm'together with the command again: # $COLABO is a path to where the file is tored
node --experimental-specifier-resolution=node --loader $COLABO/ts-esm-loader-with-tsconfig-paths.js index.ts |
mprinc
commented
Mar 22, 2023
NOTE: I am investigating |
@trim21 Coming from privatenumber/tsx#113 😞 Any news about this PR? |
| exports.codes = {}; | ||
| function defineError(code, buildMessage) { | ||
| if (!buildMessage) { | ||
| buildMessage = (...args) => args.join(' '); | ||
| } | ||
| } | ||
| function createErrorCtor(errorMessageCreator) { | ||
| return class CustomError extends Error { | ||
| exports.codes[code] = class CustomError extends Error { | ||
| constructor(...args) { | ||
| super(errorMessageCreator(...args)) | ||
| super(`${code}: ${buildMessage(...args)}`); | ||
| this.code = code; | ||
| } | ||
| } | ||
| } | ||
| defineError("ERR_INPUT_TYPE_NOT_ALLOWED"); | ||
| defineError("ERR_INVALID_ARG_VALUE"); | ||
| defineError("ERR_INVALID_MODULE_SPECIFIER"); | ||
| defineError("ERR_INVALID_PACKAGE_CONFIG"); | ||
| defineError("ERR_INVALID_PACKAGE_TARGET"); | ||
| defineError("ERR_MANIFEST_DEPENDENCY_MISSING"); | ||
| defineError("ERR_MODULE_NOT_FOUND", (path, base, type = 'package') => { | ||
| return `Cannot find ${type} '${path}' imported from ${base}`; | ||
| }); | ||
| defineError("ERR_PACKAGE_IMPORT_NOT_DEFINED"); | ||
| defineError("ERR_PACKAGE_PATH_NOT_EXPORTED"); | ||
| defineError("ERR_UNSUPPORTED_DIR_IMPORT"); | ||
| defineError("ERR_UNSUPPORTED_ESM_URL_SCHEME"); | ||
| defineError("ERR_UNKNOWN_FILE_EXTENSION"); |
There was a problem hiding this comment.
Why is this portion of the file exactly identical to dist-raw/node-errors.js?
Baterka
commented
Jan 4, 2024
Any movement on this? |
sthasam2
commented
Jan 5, 2024
Is this still in the works? |
Dudeprogram
commented
Jan 5, 2024
via email
no dear …On Thu, Jan 4, 2024 at 8:57 PM Baterka ***@***.***> wrote:
Any movement on this?
—
Reply to this email directly, view it on GitHub
<#1585 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AWO46HSOCMJUSOCBYVE7RETYM3GOVAVCNFSM5K6CBQ22U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBXG4ZTGNZSG44Q>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
billomore
commented
Feb 11, 2024
|
genesiscz
commented
Apr 24, 2024
we really need this :( |
cameralis
commented
May 30, 2024
Tried to run ts. After 4 hours of debugging I ended up here. Where even am I??? |
ladal1
commented
Jul 20, 2024
We're two and a half year into this PR, loader hooks for esm are on release condidate stage, can we push this the final mile? |
ethyaan
commented
Oct 12, 2024
Really? 3 years in the making?? is this hard to push few lines of code and test it nowadays? |
phips28
commented
Apr 8, 2025
🍿 4 years |
geigerzaehler
commented
Apr 11, 2025
Hi everybody. A lot has changed since I opened this PR. I’ve since switched to tsx and subpath imports and I have no motivation to continue this effort, so I’m closing the PR. The branch will remain available if anyone wants to pick it up. To those frustrated by the lack of progress: open source runs on volunteer effort. If something matters to you, contribute. Drive-by complaints don’t help — they actively discourage maintainers and contributors from engaging. That doesn’t mean there is nothing to criticize about how this PR was handled. But if you're just pointing out that a problem still exists, without offering help or insight, you're not moving things forward. |
The ESM loader now resolves import paths using TypeScripts path mapping feature.
EDIT:
Closes#1586