loaders icon indicating copy to clipboard operation
loaders copied to clipboard

Support typescript with --experimental-strip-types

Open marco-ippolito opened this issue 1 year ago • 60 comments
trafficstars

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 (.js because of transpilation => but at runtime is a TS)
  • [ ] Transpiling .ts files in the node_modules
  • [ ] TypeScript linting

marco-ippolito avatar Jul 02 '24 07:07 marco-ippolito

I have two concerns (none of them blocking):

  1. Is swc on board with following our LTS? This seems a massive dependency we should not take on lightly.
  2. 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"

mcollina avatar Jul 02 '24 08:07 mcollina

To answer to your points:

  1. 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 😄
  2. 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

marco-ippolito avatar Jul 02 '24 11:07 marco-ippolito

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.

kdy1 avatar Jul 02 '24 13:07 kdy1

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

marco-ippolito avatar Jul 02 '24 13:07 marco-ippolito

+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.

benjamingr avatar Jul 02 '24 16:07 benjamingr

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.

GeoffreyBooth avatar Jul 02 '24 17:07 GeoffreyBooth

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.

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

marco-ippolito avatar Jul 02 '24 17:07 marco-ippolito

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.

GeoffreyBooth avatar Jul 02 '24 19:07 GeoffreyBooth

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

kdy1 avatar Jul 03 '24 02:07 kdy1

which barely strips out the type.

How can this be used to just strip types and nothing more?

GeoffreyBooth avatar Jul 03 '24 02:07 GeoffreyBooth

See https://swc.rs/docs/references/wasm-typescript

kdy1 avatar Jul 03 '24 02:07 kdy1

See 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.

GeoffreyBooth avatar Jul 03 '24 03:07 GeoffreyBooth

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.

kdy1 avatar Jul 03 '24 03:07 kdy1

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.

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 avatar Jul 03 '24 04:07 GeoffreyBooth

@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 avatar Jul 03 '24 07:07 JakobJingleheimer

@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.

aduh95 avatar Jul 03 '24 07:07 aduh95

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 😃

marco-ippolito avatar Jul 03 '24 07:07 marco-ippolito

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.

nicolo-ribaudo avatar Jul 03 '24 07:07 nicolo-ribaudo

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.

JakobJingleheimer avatar Jul 03 '24 08:07 JakobJingleheimer

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.

This is exactly what users dont want to do 😆 and we have to acknowledge that node foo.ts is what everybody wants

marco-ippolito avatar Jul 03 '24 09:07 marco-ippolito

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.

JakobJingleheimer avatar Jul 03 '24 10:07 JakobJingleheimer

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

marco-ippolito avatar Jul 03 '24 10:07 marco-ippolito

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.

GeoffreyBooth avatar Jul 03 '24 12:07 GeoffreyBooth

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

marco-ippolito avatar Jul 03 '24 13:07 marco-ippolito

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.

legendecas avatar Jul 03 '24 13:07 legendecas

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.

JakobJingleheimer avatar Jul 03 '24 16:07 JakobJingleheimer

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

marco-ippolito avatar Jul 03 '24 17:07 marco-ippolito

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.)

GeoffreyBooth avatar Jul 03 '24 18:07 GeoffreyBooth

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.

benjamingr avatar Jul 03 '24 18:07 benjamingr

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.

JakobJingleheimer avatar Jul 03 '24 21:07 JakobJingleheimer