loaders
loaders copied to clipboard
Support typescript with --experimental-strip-types
Add this issue to keep track of the work for supporting typescript out of the box, in the PR you can find documentation etc... The main idea is to use type-stripping and convert typescript to ESM Javascript. This is achieved ideally through the use of an external dependency, in my opinion @swc/wasm-typescript. I would like to have something basic and usable to propose to the public.
Tracking points to discuss:
- [ ] Support commonjs syntax
- [ ] SourceMaps (enabling them with a flag)
- [ ] Type stripping on
.cts - [ ] Add a flag to enable transformation to support TS only features (enums etc...)
- [ ] Extension guessing (
.jsbecause of transpilation => but at runtime is a TS) - [ ] Transpiling
.tsfiles in the node_modules - [ ] TypeScript linting
I have two concerns (none of them blocking):
- Is swc on board with following our LTS? This seems a massive dependency we should not take on lightly.
- TypeScript does not follow semver, and the language evolves relatively quickly. Is this approach stable long term? Everybody seems always to be supporting the latest version of TypeScript.
My proposal in https://github.com/nodejs/node/issues/43818 was to have a strategy for doing this automatically but not vendor anything. Something along the lines of:
$ node script.ts
Typescript support is missing, install it with:
npm i --location=global typescript-node-core
$ npm i --location=global typescript-node-core
...
$ node script.ts
"Hello World"
An alternative (possibly better for long-term support):
$ node script.ts
Typescript support is missing, install it with:
npx ts-node-core
Learn about TypeScript support at: ...
$ npx ts-node-core
Detected Node.js v24.42.0
What typescript support would you like?
[ ] type stripping - swc@22
[ ] type checking - tsc@23
...Installing
$ node script.ts
"Hello World"
To answer to your points:
- SWC seems to be a mature project, it is already used by Deno for our same purpose, has Apache-2 license which is fine. I'm using @swc/wasm, the wasm version to avoid compiling rust. We probably should ask the blessing from the maintainer @kdy1 and maybe release a version that we dont have to patch 😄
- With typestripping we dont really care what version of typescript the user is using because we just remove it (unless there is some syntax not supported by SWC), we dont perform type checking at all. I think SWC moves quickly enough.
Also asking the user to install a dependency is imho a suboptimal DX, users just want to be able to run
node foo.ts
If this proposal is accepted we could also add hooks to customizate the transpilation, and many more things that come out of the box from swc
I can configure a publish pipeline for a separate package/binary, or a Wasm publish pipeline if node.js will use SWC for this task.
I can configure a publish pipeline for a separate package/binary, or a Wasm publish pipeline if node.js will use SWC for this task.
If we are able to ship the initial PR with v1.6.6 for the next updates that would be amazing, thank you
+1 for all the suggestions and also +1 for @kdy1 have nothing but nice things to say about him and swc + there is prior art in other runtimes.
If what we ship is type stripping, then we’re essentially shipping experimental support for the Type Annotations proposal. Which is good! I feel like that’s what does make sense to include in Node core, and I hope that proposal graduates and V8 adds support and then we can drop our support for doing it ourselves.
In that vein, maybe a flag of --experimental-strip-types might make more sense? To clarify to users that our intended scope is not to support all of TypeScript’s syntax or features, such as paths or enums or type checking; we just strip types and that’s it, and if they want more they can register module customization hooks from a userland library. And type stripping should be stable and unlikely to change as TypeScript evolves.
If what we ship is type stripping, then we’re essentially shipping experimental support for the Type Annotations proposal. Which is good! I feel like that’s what does make sense to include in Node core, and I hope that proposal graduates and V8 adds support and then we can drop our support for doing it ourselves.
In that vein, maybe a flag of
--experimental-strip-typesmight make more sense? To clarify to users that our intended scope is not to support all of TypeScript’s syntax or features, such as paths or enums or type checking; we just strip types and that’s it, and if they want more they can register module customization hooks from a userland library. And type stripping should be stable and unlikely to change as TypeScript evolves.
Not against renaming the flag, but for this very limited features that we are adding, --experimental-typescript makes more sense because we are actually only supporting .ts files, (no tsx or other flavors), but once this lands and remove the flag, we will think on how to make it customizable, and increase the number of flavor, that would be a real type-stripping
This was discussed today in the loaders meeting:
- I think there was general agreement that type stripping (but probably not more) is something that we would like to see in core, either soon or eventually.
- @joyeecheung asked that @marco-ippolito’s branch be implemented as a package similar to Undici that can be developed outside of the core release cycle and eventually merged in when it’s mature.
- To exist in core, support would need to both take advantage of being in core, such as being native; and avoid implementing features of TypeScript that may change faster than our release cycle (such as type checking, and any transforms such as enums, decorators, ESM-CJS transpilation and others).
- @legendecas will schedule a meeting with @marco-ippolito and some champions of the Type Annotations proposal to discuss collaboration and next steps toward potentially implementing that proposal in Node core.
I created https://www.npmjs.com/package/@swc/wasm-typescript, which barely strips out the type.
And I wrote documentation for it at https://swc.rs/docs/references/wasm-typescript
which barely strips out the type.
How can this be used to just strip types and nothing more?
See https://swc.rs/docs/references/wasm-typescript
I saw that. What settings would I use to just strip types and nothing more? No support for enums, decorators, transpiling module import/export, etc. As in, if I wanted to use this to implement the Type Annotations proposal and error on any syntax that requires transforms.
It's not transform() of @swc/core. It's a completely different thing.
It
- strips type
- supports enum
- does not support transpiling decorator (parsing/codegen is possible with a config)
- import/exports are not touched, except type-only imports. (Including imports not used as values, per config)
These operations is one typescript set, but I can remove the enum part and/or handling code for type-only imports.
I'll add options for them soon.
These operations is one
typescriptset, but I can remove the enum part and/or handling code for type-only imports. I’ll add options for them soon.
What I would like is for some option or collection of options where I can use SWC to implement the Type Annotations proposal, but nothing more. In particular, see https://github.com/tc39/proposal-type-annotations#intentional-omissions:
Intentional Omissions
We consider the following items explicitly excluded from the scope of this proposal.
Omitted: TypeScript-specific features that generate code
Some constructs in TypeScript are not supported by this proposal because they have runtime semantics, generating JavaScript code rather than simply being stripped out and ignored. These constructs are not in the scope of this proposal, but could be added by separate TC39 proposals.
Omitted: JSX
JSX is an XML-like syntax extension to JavaScript that is designed to be transformed by a pre-processor into valid JavaScript. It is widely used in the React ecosystem, but it has been used for different libraries and frameworks. Because JSX directly interleaves with JavaScript code, compilers and type-checkers for JavaScript typically also support checking and transforming JSX as well. Some users may hope that the JSX transform could also be directly supported by ECMAScript, to expand the set of use-cases that can be handled without a build step.
We do not consider JSX to be in scope of this proposal because:
- JSX is an orthogonal feature that is independent of optional static types. This proposal does not affect the viability of introducing JSX into ECMAScript via an independent proposal.
- JSX syntax expands into meaningful JavaScript code when transformed. This proposal is only concerned with syntax erasure.
This section basically defines the parts of TypeScript that can’t be stripped: these are transforms, where SWC isn’t merely stripping these things out but rather injecting new JavaScript in their place. If our goal is just to implement type stripping, then we should throw exceptions when encountering any TypeScript syntax that requires transformation.
To be even more careful, we could also throw on the various items in https://github.com/tc39/proposal-type-annotations#up-for-debate, as these are questionable as to whether they can be safely stripped: stuff like the class private keyword. I think it’s better to be conservative and error on all of these if possible, and potentially add things back one-by-one if a particular syntax can be shown to be strippable safely.
The vast majority of TypeScript exists outside of these exceptions; most projects should be able to get just about all the benefits of TypeScript even without this handful of exclusions. The result of adding the ability to run Type Annotations Proposal-compliant TypeScript will be to encourage people to write TypeScript code that is compatible with a potential future where Type Annotations graduates to be part of the language itself. That in itself is a win for the entire ecosystem, as we begin the process of folding TypeScript into the spec and bringing the users along with it. And for Node in particular, once Type Annotations hopefully becomes part of the spec, V8 will implement it and then we can rely on that rather than maintaining this ourselves.
@GeoffreyBooth I get why you're suggesting erroring on things that result in code being generated. But as a TypeScript user, I have enums etc. and if this errors when running on my completely valid code, it's not fit for purpose. I would not change my code to support an incomplete feature—I would just use something else. I imagine that will be the case for many if not all effected users. And enums are not rarely used, so I think such a decision here is a square wheel.
@JakobJingleheimer I think the main target is new users / new projects, I don't think it's realistic (or useful) to try to support existing project – if you already have setup your tooling, why would you move to the experimental support in Node.js, when you can keep using the tooling that works? On the other hand, if you're starting, not having to setup tooling is huge, and if the tradeoff is to not use enums, that's really a no-brainer IMO.
This was discussed today in the loaders meeting:
- I think there was general agreement that type stripping (but probably not more) is something that we would like to see in core, either soon or eventually.
- @joyeecheung asked that @marco-ippolito’s branch be implemented as a package similar to Undici that can be developed outside of the core release cycle and eventually merged in when it’s mature.
- To exist in core, support would need to both take advantage of being in core, such as being native; and avoid implementing features of TypeScript that may change faster than our release cycle (such as type checking, and any transforms such as enums, decorators, ESM-CJS transpilation and others).
- @legendecas will schedule a meeting with @marco-ippolito and some champions of the Type Annotations proposal to discuss collaboration and next steps toward potentially implementing that proposal in Node core.
My view on this
- I totally agree, lets just support type stripping
- I don't see this implemented outside of core simply because there are plenty of alternatives, what we want to improve here is the DX. If this lands even as an incomplete feature, I'm very positive community will be supportive and will see a massive improvement. This imho just needs someone to do the heavy lifting of the first implementation.
- I don't agree that this has to land as NATIVE but the goal is to move it to native side.
- I will be happy to sync with Type Annotations proposal but I'm not gonna let this bogged down by a Stage 1 Proposal
I'm happy ok with renaming it --experimental-strip-types, IF we support real type stripping.
Changed my mind, actually lets remove enums, maybe it will drive the community not to use them or be implemented in the language 😃
Note that the type annotations proposal has different syntax from TypeScript, so I recommend not conflating the two even if your goal is to just strip TS types.
not having to setup tooling is huge
Setting up the tooling for this is trivial:
npm i nodejs-loaders
node --loader=nodejs-loaders/dev/tsx
Bam. Done.
not having to setup tooling is huge
Setting up the tooling for this is trivial:
npm i nodejs-loaders node --loader=nodejs-loaders/dev/tsxBam. Done.
This is exactly what users dont want to do 😆 and we have to acknowledge that node foo.ts is what everybody wants
we have to acknowledge that node foo.ts is what everybody wants
Mm, I get that.
What about some middle-ground: I expect users regardless of project pre-existing or not will not accept (read: ridicule) the missing features we're discussing disallowing. SWC can already handle those (if I'm not mistaken).
Proposed compromise: an additional flag to enable them, like --experimental-generative-transpilation that enables enums, decorators, etc already supported.
currently my pr already supports all of that with the version of swc we are using. BUT it's not real type-stripping.
--experimental-generative-transpilation is reasonable
Proposed compromise: an additional flag to enable them, like --experimental-generative-transpilation that enables enums, decorators, etc already supported.
We could potentially do this, or we could push such users into importing a library. I don't see us being able to ever unflag transforms because of the semver problem and TypeScript moving faster than we do and not being specified. For that reason transforms feel like a bad fit for core.
Proposed compromise: an additional flag to enable them, like --experimental-generative-transpilation that enables enums, decorators, etc already supported.
We could potentially do this, or we could push such users into importing a library. I don't see us being able to ever unflag transforms because of the semver problem and TypeScript moving faster than we do and not being specified. For that reason transforms feel like a bad fit for core.
We can give it a try and see how it goes
It would be better to have TypeScript team buy-in to build in TypeScript syntax, particularly the syntax and transformation would be bound to Node.js release cycles. Ultimately, this type-stripping-only support in Node.js still requires TypeScript to perform type-checking to make it a complete DX. /cc @DanielRosenwasser @robpalme
Additionally, I didn't find how ESM/CJS support would work in the draft PR (e.g. it doesn't support transpiling CJS at the moment). I'd like to learn more about the support plan since TypeScript has Node.js targetted options like module: nodenext and there are open issues like https://github.com/nodejs/node/issues/50981.
I don't see us being able to ever unflag transforms
I was not envisioning ever unflagging them: either they make it to spec or they stay experimental.
Additionally, I didn't find how ESM/CJS support would work in the draft PR (e.g. it doesn't support transpiling CJS at the moment).
I think we should not support transpiling between CJS ↔ ESM at all whatsoever—it stays whatever it was authored.
Some of my thoughts:
If we want to use swc we would need:
- cjs package without dynamic loading of the wasm
- (Thinking out loud) maybe keep the source code in the repo and do the compilation to wasm during the dependency update because we want too see the source we are compiling and patch it if we need it. (this might be hard because swc is a big monorepo). If we keep the source code (but dont bake it), it might be easier to actually at some point add the rust to the build and bake it
- Proper type stripping (no transformation)
What we would need from the typescript team:
- Type stripping flag (this is currently afaik not implemented by the typescript compiler, isolatedModules only performs warning)
- Figure out stability etc...
Why type-stripping and no transformation? I guess stability (?) as those are features without a spec (?) I guess its ok to avoid them. Probably offer a flag to instead perform the transformation
Why type-stripping and no transformation? I guess stability (?) as those are features without a spec (?) I guess its ok to avoid them. Probably offer a flag to instead perform the transformation
I think they’re almost two different features: type stripping is simply running TypeScript files (or a subset of TypeScript syntax) whereas “full” TypeScript is getting transforms, getting type checking, getting transpilation, getting paths, getting the ability to customize all the various options via tsconfig.json and so on. There are going to be people who want the full experience and they’re also not going to want to be locked on a particular version of TypeScript from years ago. I’m not sure there’s much value in something in between, like type stripping plus transforms but not type checking.
So we can add SWC and get type stripping now, and there’s a path toward stability for that; and we can separately work on improving the UX around adding the official typescript package as a local dependency for users who want the full experience in a way that’s better than today’s status quo but also doesn’t break our semver guarantees. (See https://github.com/nodejs/node/pull/49704.)
TypeScript is the most requested feature by our users, we have to acknowledge our own surveys. I support transpilation/type stripping through SWC and thing we should land and iterate.
I don't think we should limit ourselves to the type annotations proposal since that is still likely to change and would prevent adoption. I'm happy for Node.js to eventually decide whether or not to further limit the TypeScript syntax based on standardization efforts before the feature is stable.
I think getting TypeScript itself to do what we need is a no-go, not least of which because they do not follow semver. Also, it's super slow. I think SWC is way more viable.
I just noticed something in the TS docs, so I'm gonna play devil's advocate with myself:
https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums
In modern TypeScript, you may not need an enum when an object with as const could suffice
They are more or less recommending not using enums (and the alternative is just about the same). So if enums are getting phased out in favour of objects as const, actually maybe we're in the clear there and we can call enums "legacy" we don't support.
Decorators though 🤔 The proposal is stage 3, so perhaps native soon anyway (and thus not our problem).
Are there any other features that result in transformation? If none, I'm happy to get off my soapbox.