-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
A minimal custom transformer plugin proposal #54276
Comments
Preemptive @nonara ping; I suspect you have feedback! 😄 |
Hey @jakebailey, Thank you for this proposal! I personally like the approach (it feels similar to Quick intro where I came fromI'm the author of 2 optimization-related transformers:
They both allow you to minify properties of objects (either private or internal) and it is achieved by adding a prefix to "all properties that should be minified" so you can use these prefixes in tools like Thanks! |
Oops, I should have explicitly defined that; yes, any other options are passed along to export type CustomTransformersModuleFactory = (mod: { typescript: typeof ts }) => CustomTransformersModule;
export interface CustomTransformersModuleWithName {
name: string;
module: CustomTransformersModule;
}
export interface CustomTransformersModule {
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
}
export interface CustomTransformersCreateInfo {
program: Program;
config: any;
} |
Why wouldn’t it support esm? Is it because the initializing code of tsc is synchronous? It may be nice if it could support esm from the start. |
Yes, correct, in its current form. The same currently applies to tsserver plugins. We'd need dynamic import and that doesn't fit nicely into our current architecture. But, I need to go check what was done in the previous attempt as I believe it may have been solved there. |
It seems great. TS 5.2 update is expected. |
This is just my opinion - @jakebailey Support both
|
Those transforms are unlikely to work because they import
This comes down to the security tradeoffs. In the API, you're already running code, so the flag isn't needed. In the language service, we already load plugins automatically and in VS Code require that you "trust" the workspace before starting I should also note that it's possible that this proposal is not implemented at all. There are still strong arguments against doing this sort of thing whatsoever. The use cases may not outweigh the downsides; it may also be the case that this feature would be avoided by most because this means they cannot use any transpilers except TypeScript itself. |
+1 for this, |
helpers?: { ts: typeof ts; addDiagnostic: (diag: ts.Diagnostic) => void } It seems to me that this feature should be in |
Another use case for custom transformer is to provide dependency injection at compile time. This is what is partially done in https://github.com/wessberg/DI-compiler |
I've actually pushed for this many times over the years as I've written a fair few transforms, most of which are for codegen stuff (e.g. replacing decorators with equivalent code). However, I think i'd vote against it these days. If we open the flood gates here, we're opening ourselves up to inconsistent behaviour and syntax across any two typescript projects - which seems high risk to me. The usual culprits that already implement a lot of "magic" (frameworks, codegen projects, gql, etc) i'm sure would make use of it and build their own "flavour" of typescript soon enough. Which would lead to fragmentation of an otherwise pretty concrete ecosystem/community. On top of that, typescript was always meant to be "javascript with types" (i.e. just some annotations we can strip away). That kind of goes away if we open up to custom syntax. I'd love to be able to simplify some of my builds by this being possible, but i would rather put up with needing my own build script to avoid the above. anyone who really wants to transform the AST can still throw a small build script together. but that slightly higher bar is why we still have a consistent syntax overall. |
This comment was marked as resolved.
This comment was marked as resolved.
I don't understand the above; clearly all type information is available within tsc, it's just not as convenient as how it is within the wider services API. If we were to do this, we would of course want to ensure that things are accessible; that is the point after all. I mentioned some of this preciously. I'll also say that I don't understand how what you're saying is accurate when the patching libs still operate within the same bounds as tsc already has. How are they getting the methods, when such methods don't even exist within tsc? |
@jakebailey This is a list how TS 5.2 transform API breaks transformer libraries:
Also, you're saying that all type informations are available in Furthermore, as you're a member of TypeScript compiler developer, you can feel that everything is possible in In such circumstance, you're just going to adjust breaking change that 3rd party libraries's developers never had experienced. I don't know how much performance benefit would be in the
|
This comment was marked as off-topic.
This comment was marked as off-topic.
re: #54276 (comment)
When you operate within the core compiler, you have to play by the core compiler's rules. These are all things that we do on a regular basis. If there is an API missing, then we can add it. But pushing FUD on multiple issue trackers is really not helping, in fact, it's mostly convincing me that I shouldn't have made this proposal at all. |
Okay, I will argue only in here issue. Sorry for multiple articles' comments |
Thank your for your answer and input :) Very contrived I basically want to be able to extend TypeScript in a way that would allow to dynamically look up a type in another file to use as return type of a function, based on the inputs of the function call. Everything from the type checking to the IDE experience should just behave like I would've annotated the function by hand. |
A possible problem here is that TypeScript would not know about that additional file and, under incremental compilation, may not recompile the main file if you change the imported one. Am I correct? |
It would be great if this allowed the use of |
I would like to add an additional use case here. I'm the author of Isograph (https://github.com/isographlabs/isograph). One workflow is:
This is great, and it works, but the types are lost, and users are required to import the generated file for types to work:
In this workflows (and others in Isograph), the ability to have typescript understand the results of the babel transform would substantially improve the DevEx! EDIT: TBH I'm unsure whether @michaelboyles comment indicates that this is currently possible. Looking into it! But feel free to let me know either way ;) |
Hi all! I was looking into this issue because it's common practice with vite to use @/ as an alias to the src folder. However, as far as I can tell, doing this in typescript when building for a library leads to broken paths in the emitted files. It looks like this issue would result in the ability to include a transformer to handle the above file path resolution. If this issue is still active, what can I do to help it continue forward? The thread states the goal was 5.2.0, but we're well past that release and this pr is still WIP. Very interested in resolving this issue, or any solutions that don't involve wrapping Typescript to resolve a path alias :) |
Path aliases are not recommended to be used in that way (in fact, we now explicitly say not to do so in the docs). I would not pursue this issue as a solution to path aliases being emitted as-is. |
Don't want to derail this thread, but I'm building a Vue Component library and then looking to emit typing for non-component entities (composables, utilities, etc). Those utilities reference shared type information from our types folder, and we're using a path alias to reduce the overhead. Runtime works great (Storybook resolves all the aliasing), and the local builds look good. When we build the library, the emitted type files still contain the unresolved paths (@/types, etc). Is the intended workflow then that we just never use a path alias in Typescript for libraries? I'm re-reading this documentation but struggling to understand what's incorrect about what I'm doing in concept. Or is there some documentation I'm missing about how to sync up my tsconfig with the vite bundler so that vite resolves the aliasing before type information is built. Thanks so much for responding, even knowing that I'm on the wrong path is super helpful :) |
If you're using a bundler, or those paths actually do exist at runtime, then that can be fine; see https://www.typescriptlang.org/tsconfig#paths. But, for shortening import specifiers, import maps do the same thing without needing to "lie" to TypeScript: https://nodejs.org/api/packages.html#subpath-imports |
This comment was marked as outdated.
This comment was marked as outdated.
@M-jerez I don't see how that's productive or on-topic for this issue. The TS team have already said they won't do that: #3628 (comment) Transformers have already been implemented. The decision is purely whether to expose them, and how. The decision is not to bin them in favour of a feature that's already been declined. People have already implemented reflection, and arguably the most relevant use-case for reflection, runtime validation... They're both implemented using custom transformers. Resolving this issue would make it easier to use both of those libs. |
@M-jerez transformers are very commonly used, they are all over the place, even without people knowing: React with JSX, Angular with their decorators that emit runtime data for dependency injection, NestJS with legacy decorators, all these down-level transformers to support new JS features, and many more. Transformers are the backbone of modern web development and probably 99% of all TS rely on them, most unknowingly. Making them available in a more straightforward "official" way would only be a natural next step. |
Prisma is a Typescript ORM with 3M monthly downloads. We have long wanted to implement LINQ-style queries, and this feature would enable us to deliver that feature with excellent DX. We'd love to prototype something on a pre-release version if that is helpful. |
https://github.com/samchon/typia/blob/f1688280397fbc1fdda9a52f7056a519ba537ab2/package.json#L117 @sorenbs Above URL address may work. By the way, prisma is planning support safe raw query expression validated by the compiler? |
Thanks @samchon!
Yes - that's what we would like to do. If we can do it in a way that delivers excellent DX. We do not want to push specific bundlers or something like ts-patch on our users. |
It would be great to also have support for transformers that could run before the core TypeScript ones do - enabling preprocessor steps (like macros). Would the LSP consider transformer plugins or would there be an alternate pathway for adding support for custom syntax? |
In the TypeScript compiler, we have the concept of a "transformer". A transformer is a function which "transforms" one AST to another. TypeScript ships many such transforms, including all of the various transformation steps needed to downlevel newer syntax to older syntax, to convert from
import
/export
torequire
/module.exports
, and so on. Even the declaration emitter is a transformer, stripping away function bodies, filing in types, etc.Since TypeScript 2.3, TypeScript has had the following API:
This API allows users to provide a set of "custom transformers" at
emit
time. These transformers run alongside TypeScript's own transformers, either before or after, depending on where they are placed inCustomTransformers
.However, while this API exists and does work, a major gotcha is that custom transformers can only be provided to the API. There is no way to use custom transformers if you want to use
tsc
alone.This has been a long-standing issue; #14419 is the fourth most upvoted issue on the repo.
What do people use custom transformers for?
This list can go on and on, but transformers are commonly used these days for:
typia
,ts-runtime-checks
, which top the benchmark charts, but there are many moreformatjs
ts-graphql-plugin
I also know of many ideas which want to be implemented via plugins, but are not willing to do so without official support, including other runtime type checks, tracing code, and so on.
How do users currently use custom transformers?
Given the current constraints of custom transformers, there are two ways that downstream users have been able to use them:
webpack
,rollup
's TS plugins,nx
, all provide configuration which allows users to specify a set of custom transformers.ttypescript
,ts-patch
wholly replace TypeScript in a user's workflow.The first one is obviously fine. The latter, eek!
What changed?
In TypeScript 5.0, we shipped the biggest architectural / packaging change in TypeScript's history; conversion of the entire project to modules, including the bundling of our code. The relevant side-effects are twofold:
tsc.js
(and other outputs) changed massively. This broke anyone who was patching the contents of the TypeScript package.esbuild
to bundle our code and the objects it creates are not "configurable", correctly emulating how "real" ES modules would behave. Any runtime patches were already extremely fragile, only functioning due to the structure of our oldnamespace
emit.In response to this, I did an analysis of as many TS "patchers" as I could find, and found that there are actually very few reasons to patch TypeScript at all:
ttypescript
/ts-patch
, which seem to be almost exclusively used to enable the use of custom transformers.vue
/volar
,css
, etc).heft
.prettier
).For 5.0, I was able to fix many of these projects (or at least guide / warn maintainers ahead of time), but many of these patchers have no viable alternative.
We have been seriously considering ways that we can approach the problems that remain in hopes that we can eliminate the need for people to patch TypeScript. This includes (no promises):
This issue intends to address the first bullet.
A conservative, targeted proposal
In short, my proposal is to add to TypeScript the ability to define "custom transformer plugins". This new functionality is targeted and minimal. Its only intent is to satisfy those who just want to be able to add custom transformers.
Many details still have yet to be expanded on, but in short:
plugins
array ofcompilerOptions
, with a discriminator like"type": "transformer"
.ttypescript
/ts-patch
, and offers up a future base for additionaltsc
plugins (e.g. file watching plugins, module resolution plugins)."type": "transformer"
, for now) is intended to avoid conflicting with existing plugins defined byttypescript
orts-patch
.ts
object, returning a factory that can be used to create aCustomTransformers
object. The factory would receive theProgram
, which is required for any type-using plugins.ts
object must be a part of the API for similar reasons to the language service plugins; the APIs available within the core compiler are very, very limited, and the bundle is not the same astypescript.js
.tsclibrary.d.ts
file is added, describing the limited API withintsc
. This is similar totsserverlibrary.d.ts
.tsc
, you must pass--allowPlugins
to opt into executing plugins. When using the API or tsserver, plugins are enabled by default.webpack
orrollup
, your config files are executable anyway.--allowPlugins
is also a part of the watch plugin PR.CustomTransformers
are merged.There are almost assuredly other things that people want to be able to do with custom transformers or other plugins, however, I believe that we can serve the vast majority of those who want custom transformers with this API, and I do not want to let perfect be the enemy of the good.
In the future, we can expand
plugins
to do more, allowing the customization of other aspects of TypeScript, or simply add on to what custom transformer plugins can do. Prior research has shown that there are more interested parties, which I believe that we can eventually support.An example
With the above proposal, a
tsconfig.json
would look like this:The transformer would look something like:
Of course, the details may change.
Unsolved problems
d.ts
API used for one thing.import
?async
? (This may already be "solved" in a prior WIP for plugins)typescript
namespace passed into the plugin?typescript.js
's API directly, even though that may not have always worked?typescript.js
API and rely on it, assuming users won't usetsc
?Please give feedback!
If you are currently using transformers, creating transformers, or otherwise, I would appreciate your feedback.
The text was updated successfully, but these errors were encountered: