Skip to content
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

Tsconfig option to disallow features requiring transformations which are not supported by Node.js' --strip-types #59601

Open
6 tasks done
marcomuser opened this issue Aug 12, 2024 · 20 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@marcomuser
Copy link

πŸ” Search Terms

--strip-types

βœ… Viability Checklist

⭐ Suggestion

Node.js has introduced an experimental flag that allows type annotations to be stripped. However, since Node.js only erases inline types, all TypeScript features that involve replacing TypeScript syntax with new JavaScript syntax will fail as described in the Node.js docs. The following features are listed in the docs as the most important features not supported:

  • Enum
  • experimentalDecorators
  • namespaces
  • parameter properties

Would it be possible to introduce a single flag in tsconfig that tells the compiler in one fell swoop that all these features should not be enabled to ensure compatibility with Node.js --strip-types?

πŸ“ƒ Motivating Example

If there was such a configuration option, you could easily ensure that the code you write always contains only standard JavaScript + type annotations that can be executed by Node.js without installing any additional packages.

πŸ’» Use Cases

Finding the correct configuration of tsconfig for different Node.js projects is already relatively complicated. A simplified configuration that allows you to author compliant Typescript code that works smoothly with Node.js new out-of-the-box ts support via the --strip-types flag would be a great help.

@RyanCavanaugh
Copy link
Member

#54283 is relevant here

@Bnaya
Copy link

Bnaya commented Aug 13, 2024

Feels like a lint rule and not a ts flag

I have a strong feeling that node will eventually support everything under isolatedModules, as it's the baseline for transformers these day and there you got your flag :)

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Aug 13, 2024

Discussed a bit with some folks internally and there was possible appetite for this. This has been a longstanding request for other reasons (ideologically purity [complimentary], future-proofing, de facto tool support, etc). A sticking point is what the heck to name it and some suggestions to get the ball rolling would be useful.

@marcomuser
Copy link
Author

marcomuser commented Aug 13, 2024

Wohooo! πŸ₯³ So if you feel like poking a little fun at the ideological purity section, call it --ecmaStrict. 😁

@robpalme
Copy link

Just to add to @RyanCavanaugh's list of reasons to add this mode: a further benefit not yet stated is that it will permit TypeScript (if the team wishes) to introduce a new JS emit mode that preserves JS syntax coordinates, meaning no sourcemap is required.

SWC have already shipped such an emitter written in Rust and compiled to Wasm. @acutmore will soon be open-sourcing another example of such an emitter - this time written in TypeScript. ts-blank-space is a type-stripper built on top of the TypeScript parser. It is ~700 lines of code. With large files it achieves a speed up of 4.7x relative to TS 5.5 ts.transpileModule with noCheck. With small files it goes even faster due to less GC.

I agree the hardest problem is what to name it.

@RyanCavanaugh
Copy link
Member

I'll just start throwing ideas in:

  • --noTranspiledFeatures
  • --typeSyntaxOnly
  • --disallowRuntimeSyntax

Note that we almost always prefer a flag like this to be false by default, so the name should reflect that

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Aug 13, 2024
@marcomuser
Copy link
Author

marcomuser commented Aug 13, 2024

I like the direction that --typeSyntaxOnly is taking, but given the waves that the stage 1 proposal has made already, I would change it slightly to match its title: --typeAnnotationsOnly.

@allisonkarlitskaya
Copy link

Highly relevant:

No, not all of today's TypeScript syntax would be supported by this proposal. This is similar to how Babel support for TypeScript does not support all of the existing TypeScript syntax.

For example enums, namespaces and class parameter properties are unlikely to be supported. In addition, specifying type arguments at function call-sites will require a slightly different syntax.

This is early stuff, but it seems like this would probably be the thing to target with such an option.

@RyanCavanaugh
Copy link
Member

--typeSyntaxOnly πŸ‘

Rough notes

Who is this for?

  • Ideological purists
  • Space-only transpilation users
  • Today's version of node (but not tomorrow's?)
  • Forward-versioning safety if committee changes its mind about enum
  • People who don't like adding a linter
  • Keep syntax in the space that's likely to be support by type annotations in JS, if that ever happens

Downsides: nothing allowed in .ts exactly replicates what enum does today

Must use verbatimModuleSyntax and isolatedModules to turn this on

Applies only to .ts, not .d.ts

Recommended to combine with verbatimModuleSyntax and isolatedModules, but not required

Exact definition of what's disallowed and what's not

import x = require('fs'); // no (CJS+VMS will not have a good workaround at this time)
import A = e.p; // no
class X {
  public x; // OK (just erase `public`)
  constructor(public y) { } // not OK - runtime-observable
}
enum X { } // All forms (including `const`) not OK
namespace T { } // OK (type-only)
namespace X { // Not OK (instantiated)
  const x = 1;
}

@robpalme
Copy link

Thanks for the clear concise comprehensive update, @RyanCavanaugh.

namespace T { } // OK (type-only)

This was the only surprise to me. I appreciate it does not emit anything so can be considered erasable. I'm curious why anyone would use this form and whether its used in real-life.

@RyanCavanaugh
Copy link
Member

Non-instantiated namespaces are need to do certain kinds of declaration merging, e.g. adding a no-emit static member to a class

@Conaclos
Copy link

Conaclos commented Aug 18, 2024

namespace T { } // OK (type-only)

I still find confusing that declare is not required here to make the namespace ambient/non-instantiated.
This requires extra work for a compiler/linter to check whether the namespace is ambient.

const foo = {...} as enum

Wouldn't be a full substitute.

const x = {
  a: 1,
  b: a, // can't do this
} as enum;

I really like this proposal from this design note.
Yes it is not a substitute, however it allows most of the cases users encounter and this provides a concise syntax for users that want to avoid runtime TS features.

@jakebailey
Copy link
Member

jakebailey commented Aug 19, 2024

declare namespace is not a replacement for type-only namespaces; you can declare any value-space thing and it will "just work". Code like:

declare namespace T {
    export function doSomething(): void;
}

console.log(T.doSomething())

Will typecheck, but then crash at runtime.

This syntax is designed for declaring things that already exist in the runtime environment (think declare var __webpack_require__: any), which is a generally useful thing, but isn't a safe replacement.

Compare that to a rule which bans "instantiated namespaces", complaining about the value declaration.

@khaosdoctor
Copy link

I'll just start throwing ideas in:

  • --noTranspiledFeatures
  • --typeSyntaxOnly
  • --disallowRuntimeSyntax

Note that we almost always prefer a flag like this to be false by default, so the name should reflect that

What about --disableTransform?

@robpalme
Copy link

The ts-blank-space compiler I mentioned earlier in this issue is now publicly available.

One correction to the earlier post is that the performance multiplier relative to classic ts.transpileModule with noCheck was previously 4.7x but after further optimization is now 5.6x. The full performance results including benchmarks are here.

@robpalme
Copy link

robpalme commented Dec 9, 2024

The next step appears to be naming the flag. So here are some options.

(Personally I mildly prefer noTypeDrivenEmit)

Node-centric

  • stripTypes: true
  • stripTypesOnly: true
  • onlyStripTypes: true
  • justStripTypes: true
  • enforceStripTypes: true
  • (enforce/match)Node: true
  • (enforce/match)NodeRestrictions: true

Opt-in Positives

  • erasableTypes: true
  • erasableTypesOnly: true
  • onlyErasableTypes: true
  • eraseTypes: true
  • typesOnly: true
  • onlyTypes: true
  • typeAnnotationsOnly: true
  • onlyTypeAnnotations: true
  • jsPlusTypes: true
  • verbatimCode: true
  • verbatimEmit: true
  • verbatimJavaScript: true

Opt-out Negatives

All these could either be inverted with a no or prevent prefix, or could simply default to true without a prefix.

  • (TypeScript)RuntimeFeatures: false
  • (TypeScript)CodeGen: false
  • TypeDrivenEmit: false
  • EnumsNamespacesOrParameterProperties: false
  • Regrats: false

@khaosdoctor
Copy link

The next step appears to be naming the flag. So here are some options.

(Personally I mildly prefer noTypeDrivenEmit)

Node-centric

  • stripTypes: true
  • stripTypesOnly: true
  • onlyStripTypes: true
  • justStripTypes: true
  • enforceStripTypes: true
  • (enforce/match)Node: true
  • (enforce/match)NodeRestrictions: true

Opt-in Positives

  • erasableTypes: true
  • erasableTypesOnly: true
  • onlyErasableTypes: true
  • eraseTypes: true
  • typesOnly: true
  • onlyTypes: true
  • typeAnnotationsOnly: true
  • onlyTypeAnnotations: true
  • jsPlusTypes: true
  • verbatimCode: true
  • verbatimEmit: true
  • verbatimJavaScript: true

Opt-out Negatives

All these could either be inverted with a no or prevent prefix, or could simply default to true without a prefix.

  • (TypeScript)RuntimeFeatures: false
  • (TypeScript)CodeGen: false
  • TypeDrivenEmit: false
  • EnumsNamespacesOrParameterProperties: false
  • Regrats: false

I'm personally more into an option that's more descriptive. So from these options I'd go for:

While these two makes me think of "typescript will only strip the types" and not "I can't use namespaces" I think it's a good name:

  • stripTypesOnly: true
  • onlyStripTypes: true

Those I think are more descriptive to what it's actually going to do:

  • erasableTypesOnly: true
  • onlyTypeAnnotations: true
  • (TypeScript)CodeGen: false
  • EnumsNamespacesOrParameterProperties: false

@jakebailey
Copy link
Member

Anders had previously suggested --erasableSyntaxOnly (which I like), similar to --erasableTypesOnly ish.

I do not think that naming this such that it's related to what Node is doing is a good idea, since it's entirely possible Node decides to instead enable all features supported with isolatedModules (e.g. enums, namespaces), so if we had to change our behavior to match, that would give people who actually want to disable these features for different reasons from having a flag at all.

@allisonkarlitskaya
Copy link

I do not think that naming this such that it's related to what Node is doing is a good idea, since it's entirely possible Node decides to instead enable all features...

Indeed, in addition to --experimental-strip-types they now also have --experimental-transform-types.

@khaosdoctor
Copy link

I do not think that naming this such that it's related to what Node is doing is a good idea, since it's entirely possible Node decides to instead enable all features...

Indeed, in addition to --experimental-strip-types they now also have --experimental-transform-types.

And one implies the other too, so if you use --experimental-transform-types the --experimental-strip-types will be implied because it's also needed. I agree with @jakebailey on this, maybe using something akin to erasableTypesOnly is better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants