Typescript: Implement private fields proposal

Created on 26 Jul 2016  ·  134Comments  ·  Source: microsoft/TypeScript

I think it would be nice to have the stage 1 private fields proposal implemented in TypeScript. It'll mostly supercede the current pseudo-private implementation, and a fully-correct implementation is only transpliable to ES6 using per-class WeakMaps. I'll note that the spec itself uses WeakMaps internally to model the private state, which may help in implementing the proposal.

Currently, the proposal only includes private _properties_, but methods are likely to follow. Also, the most important part of the proposal is that private properties are only available within the class itself.

Committed Fixed Suggestion

Most helpful comment

I don't think people will be happy with a non "private" keyword based solution.

All 134 comments

What does the TypeScript team think about the syntax and the use of a symbol/character prefix?

I personally think that it looks awful, but apparently there are technical limitations (the usages need to identify the visibility, for reasons that I don't fully comprehend).

It would be sad to see TypeScript losing the current private/protected syntax, which is much cleaner and consistent with a number of other languages. Is this likely to happen? Are there any alternatives?

To add to this, there are talks about possibly switching the # and @ symbols around (i.e. using # for decorators), which would obviously have an affect on TypeScript as well.

I think it is "awful" too. I think like the :: bind operator, which didn't make it actually very far, it is worth holding off even trying to implement it until it gets a bit further down the path with T39. I highly suspect it will be a bit of a rough ride. Also, the down-emit to support it will be rather difficult I suspect, because of the need to rewrite every access site.

the usages need to identify the visibility, for reasons that I don't fully comprehend

Mainly because you need to identify at the usage site how to lookup the property, since it needs a different lookup algorithm so that descendent classes can re-use the same private names without needing to "know" about the ancestor private properties.

One advantage of the proposed syntax is that you can omit the this and just use #field directly. Further, the sigil may be swapped with decorators (https://github.com/tc39/proposal-private-fields/issues/32) and instead @ would be used, so you'd use @field, just like Ruby. Do you find this syntax ugly still?

I don't think people will be happy with a non "private" keyword based solution.

I don't see a good way to do that while supporting general eval as JavaScript does--a concrete token allows some differentiation and makes it possible to support private state that's not based on a static type system as in TypeScript. We've discussed a number of syntax alternatives at https://github.com/tc39/proposal-private-fields/issues/14 , and I don't see TypeScript's current syntax as something that can work properly all the time in a fully general JS environment.

Mainly because you need to identify at the usage site how to lookup the property, since it needs a different lookup algorithm so that descendent classes can re-use the same private names without needing to "know" about the ancestor private properties.

I wish I knew more about how JavaScript works internally. I thought that it would work the way that it's described here and here, but apparently not. I still find it difficult to believe that that would have a significant impact on performance, but there are no numbers for comparison.

Do you find this syntax ugly still?

Essentially, yes. More so, I find it both inconsistent with the rest of the JavaScript syntax, as well as inconsistent with many other languages:

| Language | Syntax | Notes |
| --- | --- | --- |
| C# | private int x; | |
| C++ | private:
    int x; | |
| Java | private int x; | |
| PHP | private $x; | |
| Python | __x | "no comment" |
| Ruby | private
    def method
        # ...
    end | It's a method, but fields are private
by default I think. |
| TypeScript | private x; | |
| Visual Basic | Private x As Integer | |

All but _one_ of the above languages use the private keyword, and it seems that Python doesn't really have "proper" private state anyway.

With the exception of Python (again), none of the languages format field declarations or field accesses differently depending on the visibility, and they all allow for new visibility modifiers if that is ever required.

I also feel that both # and @ work better for things like decorators, that are (in Kevin's words) "syntactically 'outside' of the imperative flow".

I don't see a good way to do that while supporting general eval as JavaScript does

Would it be possible to explain this issue in layman's terms? (maybe in https://github.com/tc39/proposal-private-fields/issues/14)

It would be nice to have a list of these issues with examples in one place, so that there is something to refer to.

I still find it difficult to believe that that would have a significant impact on performance, but there are no numbers for comparison.

It would, if you want it to be really private versus just a convention like it is in TypeScript. Whenever you look up a property in JavaScript, the process is essentially like this:

  1. Look at instance for own property.
  2. If no own property, look at prototype for own property.
  3. Ascend prototype chain until no property found.
  4. If found deal with property descriptor (writable, get, set, configurable)
  5. If not found and read, return undefined or write, set value as own property if not sealed or frozen.

Now with privates with no pattern in their name, you wouldn't have own properties, you would have something like own private properties which wouldn't ascend a prototype chain. You would then have to look for them there as well for every operation that could possible access the private properties, because you can't be sure which one is being referred to. Looking it up in the normal way and then only if it is not found looking in privates could be very dangerous, because what if you found something in the prototype chain, but there is also a private version?

The biggest challenge is that JavaScript classes are still a bit of a misnomer. They are essentially syntactic sugar for prototypical inheritance constructor functions. There is one thought that occured to me which I will try to add to tc39/proposal-private-fields#14.

Would it be possible to explain this issue in layman's terms?

If it requires re-writing the call site, then you would never be able to support things like:

class Foo {
    private foobar: string;
    baz() {
        const p = 'foo' + 'bar';
        console.log(this[p]);
    }
}

Or any usage of eval like structures. You could choose not to support things like index access for privates or no eval support, but then "why bother". Pretty much all of this has been pointed out in one way or another in the proposal there but people still are "but I like private" 😁.

you wouldn't have own properties, you would have something like own private properties

If you had a single set of properties with a visibility item in the descriptor, I guess the issue is that, if the property did not exist on the current object, you wouldn't know whether or not to ascend the prototype chain.

If it requires re-writing the call site, then you would never be able to support things like ...

AFAIK, that won't be supported anyway (ref).

I still don't really follow the eval case. I thought that the property checks would happen at runtime, after property names had been calculated.

but people still are "but I like private"

I'm trying not to be one of those people, and to understand the issues, but the proposed syntax just makes me uncomfortable. =)

Okay, I think I understand the eval/rewrite case now. Usage sites within the class would be rewritten to indicate the visibility based on the declared properties, and that wouldn't be possible unless they are simple lookups.

If you had a single set of properties with a visibility item in the descriptor, I guess the issue is that, if the property did not exist on the current object, you wouldn't know whether or not to ascend the prototype chain.

Hmm. But if it doesn't exist in the current class, then isn't it by definition non-private? If so, it would have to move up the prototype chain anyway (as with regular public property lookups). There would be no additional work for private access.

(I'm possibly missing something obvious)

But if it doesn't exist in the current class, then isn't it by definition non-private? If so, it would have to move up the prototype chain anyway (as with regular public property lookups).

No, you don't want to ascend. Private labels aren't inherited and subclasses can re-use privates without worrying about masking/name collisions.

@glen-84 Both of those posts that you mentioned indicate problems with un-sigil'd private state. Do you have ideas for solutions to those problems? I think complicating the lookup chain would be risky compatibility-wise, make JavaScript harder to implement with good performance (possibly making existing programs slower), be basically impossible to square with Proxies, and generally, significantly complicate the mental model of the language (which already has a relatively complex object system).

In the cross-language comparison, you mention Ruby. I think Ruby is a good example of private state _with_ a sigil--@. You can call getter and setter methods without a sigil.

No, you don't want to ascend. Private labels aren't inherited and subclasses can re-use privates without worrying about masking/name collisions.

I meant move up if the property wasn't on the current class, to look for a public or protected property.

Both of those posts that you mentioned indicate problems with un-sigil'd private state. Do you have ideas for solutions to those problems?

It's very difficult for me to do that, as someone without an understanding of the internal workings of JavaScript.

You'd have to somehow encode a "private key" into each lexical environment.

I have no idea what this means. What is it for? Is it impossible to do?

You'd have to change the semantics of each and every property access (because any property access might result in a private field). Engines are highly optimized around the current property lookup semantics.

So highly optimized that a single switch on visibility would significantly affect performance?

Would for-in enumerate over these properties?

I guess it would depend on the context. Within the same class, yes. However, this is not a reason for not using private, it's an implementation detail, and other languages have probably already answered such questions.

Can someone shadow a private property with a normal property lower on the prototype chain? What about the reverse?

Probably yes (visibility is increased) to the first question, and no to the second. Again, this has all been done before, hasn't it?

How do you prevent leaking the names of private fields to clients that shouldn't know that information? This is probably a fatal information leak.

Visibility is just a tool for encapsulation and defining a public interface. 99% of developers probably don't care about "information leaks". That said, I did suggest two separate features here. Perhaps this proposal could be called something different, like "hidden state" or "secure state", and allow for something like private/protected state to be implemented differently.

All of this runtime stuff is going to be terrible for performance

Use "hidden/secure state" if you want perf. :D In all seriousness, what type of performance loss are we talking about? Maybe someone can create a prototype. =)

Also, we couldn't use such a solution to self-host the built-ins

Wouldn't self-hosting built-ins (if I even understand what that means) be really bad for performance? If not ... use "hidden/secure state" and not higher-level private/protected visibility.

I think complicating the lookup chain would be risky compatibility-wise

I'm not the first/only person to think that this is how it could/may work. You access a property, it looks to a descriptor for visibility, and responds accordingly. It doesn't seem complicated if you have no idea how things really work in JS. I really need to read a crash course on JS internals/property lookup algorithms. =P

I think Ruby is a good example of private state with a sigil--@

Ruby isn't even a C-family language, and there are no public fields it seems, but only getters and setters (accessors). The current private state proposal has multiple declarations sitting side-by-side, with the same general purpose (declaring properties), but visibly different and inconsistent syntaxes.

With all that said, I'm way out of my depth here, and I'm most-likely adding more noise than value, so I'll try to keep quiet and let the experts come to a consensus.

I'll discuss this wish with TC39 and we'll see if we can come up with any other ideas about how to avoid a prefix. I don't have any, personally. Note that, if TypeScript does decide to implement private fields, they are still at Stage 1 in TC39 and therefore subject to change.

What does the TypeScript team think about the syntax and the use of a symbol/character prefix?

I personally think that it looks awful...

Note I've made a suggestion to get rid of the aweful # syntax.

@shelby3 Unfortunately, I don't see that as a realistic option. tl;dr, we have to worry about how everything will interact with eval; not including a sigil just makes everything "too dynamic" to work properly.

@littledan I have followed up there now and made my strongest argument against using a sigil, and my argument to maximize compatibility with TypeScript. I do understand now though why we must declare the privacy for untyped arguments of methods.

@isiahmeadows

It'll mostly supercede the current pseudo-private implementation [...] Also, the most important part of the proposal is that private properties are only available within the class itself.

I hope it won't supersede pseudo-private implementation. I think availability only within a class is actually not such a good thing (given that by availability you mean accessibility). I admit there may be special situations where it makes sense to have strictly private and inaccessible properties, e.g. for security purposes. I also admit, that it is obviously confusing to people familiar with other languages, that private is actually not that private in JavaScript. But apart from security reasons, in my opinion, most developers should use private most of the time only to define a contract but not to control accessibility.

E.g. from an architects perspective I think a very good thing about the TS private-keyword and its pseudo-private nature is

  • that it allows to express the contract of a type's interface and how it is meant to be used by clients
  • that the contract is checked at compile time by the TS compiler
  • that I can still consciously "violate" the contract at runtime, particularly for whitebox unit tests.

Accessibility of private properties at runtime very much contributes to testability because I am able to inject private state into a class under test by just setting its private fields (in TypeScript I recommend using bracket notation instead of any casts due to better refactoring support), e.g:

let instance: ClassUnderTest = new ClassUnderTest();
instance["_privateField"] = "My injected state";

No need for complicated test code just to set up a class with a particular (private) state.
Another advantage of pseudo-private properties is that they are essential to monkey patching.

I don't think that the TypeScript community would happily change all of their private variable lines to private #variable.

From the TypeScript perspective most of us are happy with our lovely, sweet illusions of a good language (intellisense, build-time errors, types, various sugars). These illusions give us better development experience, we write better code faster. This is the main reason why we use TS besides ESNext transpilation -- but for the latter there is Babel and that's better (or at least it was better when I last checked).

I'm not talking about those few who actually want anything more from private variables than a syntactic sugar in their source files. I think those guys need something stronger, maybe closer to the native code.

But for the rest us: we don't really need JS private. We need the simple, easy private convenient variables just like as we used it in C++, Java, C#, etc.

Please vote for a solution that won't be a pain for us. Maybe soft private? Because I doubt that we want the sigil private #variable. Or maybe TS private and ES private would be different concepts? Ugly.

@igabesz

I don't think that the TypeScript community would happily change all of their private variable lines to private #variable

Actually, with that proposal, private would be unnecessary. Instead, the # sigil replaces it entirely without conflict.

But for the rest us: we don't really need JS private. We need the simple, easy private convenient variables just like as we used it in C++, Java, C#, etc.

TypeScript's private is soft private, like that of most OO languages. Even Java's private variables are still technically soft-private, because they are still accessible with an escape hatch. JS private is equivalent to using a closure, except that you can still access private state of non-this instances.

Please vote for a solution that won't be a pain for us. Maybe soft private? Because I doubt that we want the sigil private #variable. Or maybe TS private and ES private would be different concepts? Ugly.

They would be different concepts, but in general, escape hatches are rarely useful in practice. The primary exception is with object inspection (e.g. developer tools, Node.js util.inspect), but most are host-defined and already use privileged native APIs now, so there is no pressing need to add it to the spec.

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version. It has always been a strict superset of ES, and it has added (async functions), changed (classes), and/or deprecated (/// <amd-dependency />) features as necessary as ES itself evolves, to maintain itself as a strict superset and not duplicate the current spec.

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version.

😭

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version.

This is not the TS team position. we have no plans of deprecating any thing any time soon.
If and when the private state proposal reaches the correct state, TS will implement it. I do not think this has any implication on privates as TS implement them today.

@mhegazy Okay. I stand corrected. (It was an educated guess, BTW.)

The Class Fields Proposal is at Stage 2 now so I'm glad the TypeScript team thinking through this and ready to follow EcmaScript standards 💯

If and when the private state proposal reaches the correct state, TS will implement it.

@mhegazy Is Stage 3 the correct time to implement?

EDIT: I may have found my answer 😄

@styfle see the discussion from design meeting notes (#16415) that discusses the current considerations around implementation.

Is Stage 3 the correct time to implement?

yes. We are starting to investigate the different designs possible for this feature.

The Class Fields Proposal is now at Stage 3. (As of a month ago)

While the class fields reached Stage 3, the Private Methods and Accessors followed on from that, and was at Stage 2 until yesterday when it was moved to Stage 3. Now everything needed to address private members of classes is at Stage 3.

Any update on the TS team's position/progress on adopting the #[field] syntax now that it's been at stage 3 for a while? Personally I really dislike it, and much prefer TS's existing private keyword, but at the same time want to be as close to standards as possible.

Personally I really dislike it, and much prefer TS's existing private keyword

me too, it's so ugly

but @mhegazy i'd like to (try) do this, could i ?

The parse shouldn't be bad (almost a new modifier that's just a token, almost), but the typecheck will be a pain (since we'll have two different kinds of privates that'll need to be compared somehow), and the downlevel emit is atrocious (because downleveling real runtime privacy is a big transform - @rbuckton has ideas on it, and maybe an implementation (I don't know for sure; he's got a lot of those in his fork)). We technically can't stop you from trying, but I wouldn't recommend it, if you can find something else you'd rather try, and I couldn't guarantee that we'd be ready to accept it once you were done. Plus, while private fields and methods have advanced, it's still slightly incomplete until private statics (and possibly decorators) advances, too (given how they all interact, it may be better to add them all at once).

Suffice to say, there's a lot here.

@weswigham Ok, I give up 😂

Here's a heads-up that I (with @Neuroboy23 and others) will be experimenting with an implementation of private fields. We will post a link here as soon as there are materials for review/discussion.

Update: Our work in progress is here.

yes. We are starting to investigate the different designs possible for this feature.

I don't like # syntax either, but I understand the technical reasons behind this syntax. @mhegazy What I would really like to do by a TypeScript team after implementing a new proposal to think what TypeScript is going to do with obsolete implementations like namespace, private which can be used but generally should not since there is a better es-standard way of doing same or almost the same.

I'll also want to add my few cents about protected. If (now hopefully) we are going to remove/deprecate private keyword someday I think its okay to do the same with protected keyword. I know most of people can disagree, but yeah extra visibility scopes can sometimes be handy, but overall value of it isn't high imho.

@pleerock,

This is what I'd expect to see from TypeScript:

class Example {
    private a = 1;
    #b = 2;
}

Emit (targeting ESNext):

class Example {
    a = 1;
    #b = 2;
}

Emit (targeting ESNext, with new compiler option to emit fields marked as private as ES private fields):

class Example {
    #a = 1;
    #b = 2;
}

That should be backwards compatible, while also allowing you to use ES private fields with a clean syntax.

@glen-84 of course it would be, I was talking about long-term plan on obsolete keywords and syntaxes

We do not have any plans to deprecate any syntax or keywords.

I also don't think we'll have an option of any sort to transpile private into # - they have different expected behavior; # is hard runtime privacy, while private is just design time privacy.

@weswigham I think so as well. @RyanCavanaugh yeah that was expected answer. But still I would apply at least some strategy for a new users to avoid using namespace/private keywords because imho they got obsolete with latest ecmascript proposals

People generally figure this out based on reading documentation, which we of course update. module foo { is still in the language but is basically never seen in the wild anymore except in very old codebases that predate namespace foo {.

@RyanCavanaugh good to hear that. But still I believe that new people coming from c# lets say will definitely try to use namespaces (not all, but some of them). I guess the same might be with users coming from other languages - they will start to use private instead of # and this tends to be more massive than example with namespaces

I also don't think we'll have an option of any sort to transpile private into # - they have different expected behavior; # is hard runtime privacy, while private is just design time privacy.

D'oh. I was hoping that we could optionally use private for hard privacy as well, to avoid the ... unfortunate syntax.

Anyway, soft-private is probably enough for my (current) use cases.

I think it would be great to include this syntax support in upcoming 3 version of TypeScript since this feature is huge and version 3 looks a good occasion to make people to start using new # syntax.

EDIT: damn forgot that version 3 is already released.

Just FYI, there is some recent churn about private fields/slots/whatever in the TC39 proposal's repo, so it's not even certain the proposal in its current form will make it. Here's a couple relevant issues there:

I'm not TC39, but my personal suggestion to everyone is to first wait for the syntax to solidify again (possibly wait for stage 4), and then implement it.

FWIW I would not interpret the discussion in tc39/proposal-class-fields#100 to be indicative of anything in particular. # isn't pretty at first blush, but no preferable syntactic form has been suggested and the only syntax that people seem to want is manifestly not workable. There needs to be a prefix sigil of some kind and all attempts to allow this.someProp to be private are doomed to fail.

I realize that sounds rather dismissive, but frankly that is my intent - none of the complainers in those threads have meaningfully engaged with the core technical problems addressed in extant threads or the FAQ. It's depressing as heck to read through threads on that repo and see long correct explanations of why a sigil is needed get downvoted into the ground.

If the endgame is that no one uses private fields unless they really need hard runtime privacy, that's probably a good outcome for the web in terms of performance and debuggability.

I agree it's probably not an ideal issue to mention, but the other,
tc39/proposal-class-fields#106, is probably much more indicative - there's
serious discussion on using weak maps instead among other things, because
proxy interop plainly sucks.


Isiah Meadows
[email protected]
www.isiahmeadows.com

On Mon, Jul 30, 2018 at 3:38 PM, Ryan Cavanaugh notifications@github.com
wrote:

FWIW I would not interpret the discussion in
tc39/proposal-class-fields#100
https://github.com/tc39/proposal-class-fields/issues/100 to be
indicative of anything in particular. # isn't pretty at first blush, but
no preferable syntactic form has been suggested and the only syntax that
people seem to want is manifestly not workable. There needs to be a prefix
sigil of some kind and all attempts to allow this.someProp to be private
are doomed to fail.

I realize that sounds rather dismissive, but frankly that is my intent -
none of the complainers in those threads have meaningfully engaged with the
core technical problems addressed in extant threads or the FAQ. It's
depressing as heck to read through threads on that repo and see long
correct explanations of why a sigil is needed get downvoted into the
ground.

If the endgame is that no one uses private fields unless they really
need hard runtime privacy, that's probably a good outcome for the web in
terms of performance and debuggability.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/9950#issuecomment-408984395,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBGCphyQUu5lJQW7lgsqALLL3e0_Wks5uL2DLgaJpZM4JVDwV
.

There is some discussion of using WeakMaps, but I don't see how that would improve interaction with Proxy. I'd invite any TC39 member who has another idea about how private class features should work to present it to the committee for feedback; currently, this proposal is at Stage 3 and theoretically represents the consensus position of the committee. Many people made compromises to reach a shared goal of enabling this feature, even as other designs are possible.

I am working to organize a discussion among frameworks about how to use Proxy to observe object mutations, and how that should interact with private fields. Let me know if you would like to join the conversation.

@RyanCavanaugh

FWIW I would not interpret the discussion in tc39/proposal-class-fields#100 to be indicative of anything in particular. # isn't pretty at first blush, but no preferable syntactic form has been suggested and the only syntax that people seem to want is manifestly not workable. There needs to be a prefix sigil of some kind and all attempts to allow this.someProp to be private are doomed to fail.

Late on this, but there actually is a proposal suggesting this->var syntax.

Granted, this proposal is most likely dead ("the committee was not enthused" is the only reason I have been able to find). But alternate syntax has indeed been proposed, and I hope that all interested parties are familiar and aware of these.

I think it would be nice to have a compiler option to make private transpile to some kind of _soft_ private that would be enforced at run-time. I don't think the use case for needing true hard private is especially common, so even aside from any dislike of the # syntax, I think most people would tend toward sticking with private. But compile-time only private is _too_ soft IMO. Also, such an option would help reduce the number of possible variants of private...if developers are left to implement this on their own then you could have all of these:

class Demo {
  private a

  #b

  @reflect
  #c
}

So I propose that with the new option enabled, private a would transpile to @reflect #a, where @reflect is a decorator that enables reflection on the property via a community-supported API. There has been some talk of a standard decorator library, so the @reflect decorator (or whatever we'd want to call it) could be standardized in the future; I think it would be a good candidate for that.

I suppose the main downside of my proposal would be the performance impact. We can hope that one day JS might have some standard decorators that are implemented natively to optimize them, but the reality for the foreseeable future is that @reflect #x would be slower than just #x. But I'm only proposing this as an option, which should probably be disabled by default (for backward compatibility reasons as well).

@MichaelTheriot

Late on this, but there actually is a proposal suggesting this->var syntax.

Granted, this proposal is most likely dead ("the committee was not enthused" is the only reason I have been able to find)

The committee has given plenty of well-considered reasons for endorsing the current proposal instead of the classes 1.1 proposal, for example https://github.com/tc39/proposal-class-fields/issues/100#issuecomment-429460917

@mbrowne

I may have missed it. The issues page of classes 1.1 has been inactive for half a year, and even the example you linked was written a month after my comment.

FYI, I mentioned this in the thread you linked several months ago. My comment here was just to address a misconception that no alternative syntax has been proposed, since I believe all parties involved should be aware that is not the case.

Edit: My comment does not suggest TypeScript do anything different. I believe it is useful to correct misinformation on referenced issues of a proposal.

@MichaelTheriot the parties that need to be informed of alternatives proposals are not the parties involved in this conversation. TypeScript will follow the proposal that is on track to be ratified. The fact that alternative syntaxes have been proposed and not adopted isn't really useful for this conversation in my opinion.

Hey guys, in case it helps, I'd like to share my own implementation of runtime protected/private members:

See lowclass.

Check out the extensive test files showing all sorts of uses of runtime public/protected/private members. The implementation is not long, and some features can be omitted in order to have only the minimal protected/private functionality.

Notes:

  • It uses WeakMaps to store protected/private state.
  • Unlike other implementations I've seen, it works with async code with callbacks/promises/await because it does not rely on synchronous call stack tracing.
  • It works with getters/setters (including super calls) (see tests).
  • Works with native super, or using a Super helper for ES5 (see tests).
  • Supports constructor method (see tests).
  • Ability to extend built-in classes (except for Date, but I wanted to look into that although people say it isn't possible but I want to confirm it. See tests, f.e. working with native Custom Elements).
  • Ability to extends regular native classes (see tests).
  • wrap native classes, giving them protected and private abilities (see tests).
  • Write ES5-style function classes instead of native classes (see tests).
  • lots of room for performance optimizations (f.e. caching super/protected/private helper calls and optimizing the lookup algos)

To run tests:

npm install
npm test

Also see https://github.com/babel/proposals/issues/12 and https://github.com/babel/babel/issues/8421 for details and issues with Babel's implementation.

Hello you all,

please don't implement the not-yet-finalized private class fields spec. It has problems.

Please read this thread.

I would encourage anyone who reads this to please take a look at some other syntax ideas (just peruse around, there's more), and voice your opinions.

f.e. here's another thread (with better syntax ideas than the current # IMHO).


If there's any JavaScript community already using public/private/protected widely, it's the TypeScript community which has a big chance to help shape the future of JS private/protected.

So, private class fields are being shipped in V8 v7.2 and Chrome 72. Are there any plans to begin implementing the private fields proposal?

@ChrisBrownie55 Public fields are shipping. Private are not.

@jhpratt that's my bad, but it would seem that the Chrome team is planning on shipping private in the near future

For reference: https://v8.dev/blog/v8-release-72#public-class-fields

It documents they're shipping public class fields as of V8 7.2, but there's this regarding private fields (emphasis mine):

Support for private class fields is planned for a future V8 release.

@isiahmeadows I was actually referring to a different post on Google's developer blog.

Screenshot of Google Developers page

Conclusion

Public class fields are shipping in V8 v7.2 and Chrome 72. We plan on shipping private class fields soon.

Link to article on Google's developer blog

These articles were written by @mathiasbynens and Andreas Haas, who you can ask for clarification if needed (though I think each of those posts were pretty clear). cc @gsathya and @joyeecheung who are working on the finishing touches of the implementation in V8.

Anyway, the January 2019 TC39 meeting capped off a year and a half of rethinking alternatives to the private fields and methods proposals with yet another reaffirmation of the consensus on the proposals. I think we can consider the current version stable and ready to go for TypeScript.

Chrome just released private class fields 🎉
They're only in Chrome 74 currently released in the _canary_ build.

screenshot of tweet

I just shipped private class fields in Chrome 74! Try it out in Chrome canary today!
— Sathya Gunasekaran via Twtiter

Does anyone know where we're at here on choosing a specific implementation (or building one) for private class fields? I saw that @trusktr recommended lowclass but I don't know if "_Private Inheritance_" is a part of the spec.

It decidedly is not; private fields do not walk the prototype chain (because then other objects would be able to see them, and thus they wouldn't be private)

I would assume that private fields will transpile to WeakMaps (one WeakMap per field, the same way Babel does it). There should also be an option to keep the # syntax in the final output, for those who are exclusively targeting browsers that support it (this will become more relevant over time of course).

For performance reasons, it might also be worth considering an option that would allow developers to use # syntax for future compatibility (when more browsers support it), but actually transpile to public properties with some special prefix, e.g. __ts#. WeakMaps should be benchmarked to see if this is actually necessary, but I suspect that the performance difference between public properties and WeakMaps could be significant. If such an option were added, it should be off by default of course, since developers using # syntax would normally expect it to be both compile-time and run-time private. But for those who want to maximize run-time performance and are satisfied with just compile-time private in the meantime, it could be a nice way to start using the new syntax without potentially causing a performance degradation.

Any updates on implementation progress? Perhaps a PR that we can build to test out and provide feedback?

@jhpratt sure! If you check out the branch in this PR, you'll be able to test out private-named instance fields. Methods and accessors are in progress.

We're hoping to upstream this work. We can P.R. the changes against this repo (Microsoft/TypeScript) as soon as this prerequisite PR is merged: https://github.com/Microsoft/TypeScript/pull/30467.

Feedback is most welcome.

I don't know if "Private Inheritance" is a part of the spec.

Sidenote, I'm planning to drop that feature in lowclass, to enable 100% true privacy. I haven't really needed it in practice, it was more of an experiment.

By the way, I feel like posting here, in case any theory on "private inheritance" may possibly be of any use (maybe just interesting, or perhaps helping to spark any other ideas):


in case you're interested:

"Private inheritance" (as seen in the current version of lowclass) means that a subclass can use private methods inherited from a superclass, but the method applies changes _only_ to private properties of the instance _within the scope of that subclass_, as if the subclass had those methods defined as private methods in its own definition, but the subclass can not modify private properties of the instance in a super- or sibling- class scope.

In comparison, in "protected inheritance" a subclass _can_ modify properties of it's own instance or superclass instance, but still can not modify properties of a sibling class. In protected inheritance a subclass modifies properties that all super classes are able to see (super classes all read/write to those same properties).

In other words, in the "private inheritance" concept each class has a private scope, and any inherited private methods only operate on the local class scope, in contrast to protected where protected methods operate on a single scope for the entire class hierarchy.

Not sure if that made much sense, so here's simple code examples to explain what "private inheritance" would be similar to, if we wrote it using the current of ecma proposal-class-fields private field features:


example code is easier to understand:

There's no "private inheritance" in proposal-class-fields, so the following won't work (and I agree this is desirable, for maintaining true privacy):

class Foo {
    test() {
        this.#privateMethod()
    }

    #foo = 'foo'

    #privateMethod() {
        console.log(this.#foo)
    }
}

class Bar extends Foo {
    test() {
        // This does not work, no private inheritance:
        this.#privateMethod()
    }

    // #foo is private only inside Bar code, it is NOT the same #foo as in Foo
    // scope.
    #foo = 'bar'
}

In lowclass, "private inheritance" would be equivalent to writing the following non-DRY code to emulate the same thing, using some copy/paste in your editor:

class Foo {
    test() {
        this.#privateMethod()
    }

    #foo = 'foo'

    #privateMethod() {
        console.log(this.#foo)
    }
}

class Bar extends Foo {
    test() {
        // This does not work, no private inheritance:
        this.#privateMethod()
    }

    // #foo is private only inside Bar code, it is NOT the same #foo as in Foo
    // scope.
    #foo = 'bar'

    // copy the method over by hand (because there's no private inheritance):
    #privateMethod() {
        console.log(this.#foo)
    }
}

In both examples, the #foo variable is a specific variable to each class. There's two #foo variables, not one; each one readable and writable only by code in the same class definition.

In lowclass, private inheritance allows us to re-use private superclass methods, _but_ the method operates in the scope where the method is being used. So it ends up being as if we copied and pasted the superclass method into the subclass, just like in the above example, but there still are two separate #foo variables for each class.

In lowclass, if #someMethod is used in the super class, it operates on the #foo of the superclass. If #someMethod is used in the scope of the subclass, it operates on the #foo of the subclass, not the #foo of the superclass!

I hope that explains the concept, and even if it won't be used, I hope it may be interesting or useful in sparking ideas of any sort.

It's in Chrome stable channel now, so developers can use it natively. I'm mostly interested in this issue because of VSCode - when writing vanilla .js javascript, VSCode marks it as an error. Is there a way to add support for private class fields in Vanilla JS in VSCode separate from the debate on whether to add them to Typescript?

Personally, I don't feel TS should add support for them before it hits stage 4. TS already has compile-time private that works well enough for now. After it hits stage 4, then I would agree it makes sense. (There's also been occasional bouts of flux in the discussion, so I still wouldn't consider it ready for prime time.)

Update: It's also supported in Node 12 stable. Also, Babel has a plugin to transform this syntax. Developers often use features before they become stage 4 proposals. I would like to see support added for this syntax in Vanilla JS (non-ts) or a way to enable it through jsconfig.json so VSCode doesn't show an error when using them. The VSCode repo says that their JS support is backed by TypeScript and referred to this issue.

I would have to say that it doesn't make sense for TypeScript to not support the feature at all since it already exists in production in a major browser and Node. This isn't to say that TypeScript has to completely solidify everything right now and release it as an on-by-default feature.

I would suggest that TypeScript introduces private fields under a flag that indicates that this feature is experimental and is subject to change between stage 3 and stage 4.

Maybe we could implement the syntax support first without semantics?

Maybe we could implement the syntax support first without semantics?

For type-checking/intellisense, I think there must be some kind of semantics, but can have no downlevel transformation support like BigInt (can only be used with esnext).

fwiw, it’s only subject to change in stage 3 due to implementation feedback. Given that chrome is shipping it, any change whatsoever is highly unlikely.

@ChrisBrownie55 i'd say it doesn't make any sense that this feature is not yet implemented

Looks like this is in progress:
https://github.com/microsoft/TypeScript/pull/30829
(which is apparently waiting on https://github.com/microsoft/TypeScript/pull/30467)

Right, #30829 is about ready for merge after rebase, modulo some feature-gating we may want to do so it blows up on private-named methods.

Guys, just because a major browser implemented the feature (to see how it feels, play with it, etc) doesn't mean everyone should.

There's also been occasional bouts of flux in the discussion, so I still wouldn't consider it ready for prime time

As @isiahmeadows mentioned, the class fields proposal is full of controversy and many people dislike it. Here are a couple issues showing community dislike of the current private fields:

If I were a language implementer, I would be concerned about this, and I would not want to be responsible for a much-disliked feature being released (set in stone) because I decided to allow thousands of (unknowing) developers start using a problematic feature in production code.

Private fields is perhaps the most controversial of all features introduced since ES6. I think it would be wise for a language implementer to take the dislike into consideration before helping set the feature in stone.

@trusktr It may be a controversial feature, but whether developers use it should be a decision left up to the developer, not the tools. A major browser has shipped it not to "play with it" but to ship it for use in production. It has also shipped in Node, the most popular server-side JS runtime. Again, I'm less concerned on whether the syntax makes it to TypeScript or not, but more concerned on whether the syntax is supported by the JavaScript language server on VSCode (which is backed by TypeScript).

It has also shipped in Node

Only as a side-effect of the Chrome release, which maintains Node's JS engine.

It may be a controversial feature, but whether developers use it should be a decision left up to the developer, not the tools

That may be the case for developers like you (maybe you just need to release a product, and you will work with whatever tools you have, which is in fact what you should do in that regard, and what many employed developers do).

There are also many developers that may not know about the foot guns associated with class-fields, while just trying to use tools given to them.

And there are also developers who care about the language itself, and the future of maintainable code, and would like to see it evolve correctly, before certain mistakes are too late to be undone.

The good thing is, because the new private fields use # syntax, there's still room to fix it using the private keyword as an alternative.

Does TS really have to follow the standard 100%? We already know that the committee is going against community with thisGlobal or #privateLol, so maybe it's time to say that Google has no monopoly over JavaScript before it's too late?

@TeoTN Private fields are not a Google thing. It has proceeded through TC39, as has every other recent standard.

A few points to help provide clarity:

  • We're not in a rush to ship ES features that aren't fully baked yet (see also: optional chaining, null coalescing, throw expressions, pipeline operator, nameof, etc)
  • Conversely, implementing ratified and shipping ES features is not optional
  • Lots of people not liking certain things isn't a factor either way
  • Private fields will be in TS when we believe it's safe for us to implement without later design changes (or revocations) causing serious user pain

@RyanCavanaugh Thanks for the clarification and also for all the awesome work! A small suggestion: ES are evolving continuously and there are many features being worked on, I think it's good to have a rule that at what stage - Stage 4? Stage 3? or somewhere inbetween? - ES features are eligible for inclusion. Otherwise, this kind of discussion could repeat again and again in the future for other features.

Private fields can be reasonably said to be fully baked.

@kkimdev Stage 4 is as far as it goes; it means having become part of the standard.

ES features that aren't fully baked

Curious, what are fully baked ES features? What's "fully baked" mean in this example?

implementing ratified and shipping ES features is not optional

That's totally reasonable considering TypeScript's goal to be simply types over vanilla JS (so things like namespace were a bad choice with respect to current goals, and enum is debatable too).

TypeScript has some power to help (or not help) the community in swaying language specs, by waiting (or not waiting) to ship a feature until all major engines have unanimously shipped the given feature.

Seems inefficient, since TS team members sit on TC39, so they’ve already been part of consensus for a feature being stage 3 before anyone has shipped it in the first place - this one included.

I really value TypeScript's design decisions to be conservative in what they ship; in many cases, it makes sense to wait a bit longer in order to give guaranteed stability to developers. I've been very happy with the policy decisions with respect to private fields, and in frequent contact with @DanielRosenwasser about them. The TypeScript team has given a lot of useful feedback to TC39 over the years, which helped shape the design of many features.

I think the best place to discuss these language design decisions would be in the proposal repositories, in TC39 meetings, and in other contexts dedicated to this work. We've discussed the private fields and methods proposals extensively with @trusktr there; discussion here seems off-topic.

Ultimately, I think TC39 is the place where we should continue to do the language design, and the TypeScript repository could be a place to discuss other aspects, such as the priority and implementation details for a feature, and the judgement of when a feature is stable enough to ship.

Since TypeScript Intellisense powers JavaScript syntax highlighting in VS Code and the two syntax highlighing issues I've found (Microsoft/vscode#72867 and Microsoft/vscode#39703) in the VS Code repository redirect here - where implementation of this into TypeScript is discussed - let me contribute by saying that it would be cool for this to not be an error in JavaScript files.

Perfectly valid JavaScript gets marked as erroneous in VS Code because a compiler for a different language is in the process of decising whether the syntax will be supported or not. :-)

I don't have a problem with TypeScript taking its time before making a decision on this (in fact I support it!), but this affects JavaScript syntax highlighting in a way which cannot be easily fixed or worker around even locally (as far as I know) by patching the syntax highlighter grammar file, because there is none to patch (AFAIK).

While I'm not on the TypeScript team, they're actively working on implementing this and I don't think there's much doubt at this point that private fields will be supported. See https://github.com/microsoft/TypeScript/pull/30829.

I can confirm that it's being worked on- we're collaborating with the TS team and are currently wrapping up a rebase. Excited for private-named fields in TS (and, by extension, Language Server).

Big thanks to our collaborators at Bloomberg for implementing this in https://github.com/microsoft/TypeScript/pull/30829!

Hey there. If you'd like me to create a separate issue for this, please say so. I'm interested in learning what the rationale is for emitting the special #private PropertyDeclaration that led to this issue.

For example, the following declarations will be generated:

// index.d.ts
declare class Foo {
    #private;
    // ...
}

...Given the input:

// index.ts
class Foo {
    #name: string;
}

I'm trying to understand first why it needs to be there, but also if it can be left out, given that I'm building tooling around generation of declarations.

I'm trying to understand first why it needs to be there, but also if it can be left out, given that I'm building tooling around generation of declarations.

Presence of a private make the class nominalish in our analysis, so preserving that in a declaration file is important. You could maybe use a post-emit declaration transformer tool like dts-downlevel to downlevel the private declaration for older TSes in some way? (I don't know if it has a downlevel for #privates yet)

To add to what Wesley said: downlevel-dts converts #private to private modifier, for compatibility with older versions of TypeScript.

Presence of a private make the class nominalish in our analysis, so preserving that in a declaration file is important

Can you elaborate on how it makes the class nominal? 🙂 Given multiple private properties, #foo and #bar, still only one special #private ambient declaration will be added to the declarations. There is no way to analyze from the declarations neither what the names of the private properties were nor how many there were, because they are all replaced with a single #private property declaration. I would understand it better if it preserved the names #foo and #bar. It's a bit strange, especially given that the LanguageService complains that the #private property is never being used. It looks like a bug to me. But under all circumstances, private fields are not part of the public properties of the type and not accessible from outside of the class, so theoretically I don't see why they need to be part of the declarations? Shouldn't the same principles apply as for fields and methods annotated with other access Modifiers than public in which they are not a KeyOf the type and not part of the ambient declarations?

To add to what Wesley said: downlevel-dts converts #private to private modifier, for compatibility with older versions of TypeScript.

Sounds like a fine solution. However, by doing that, whatever property you are giving the private modifier won't be part of the .d.ts file given that it isn't part of the public properties of the type. I'm specifically interested in the declarations, which even with the simplest of examples generates diagnostics for an unused property called #private.

@wessberg I reported the bug you noticed re the unwanted language service warnings and can probably implement a solution.

Re your other question:

Can you elaborate on how it makes the class nominal?

Someone opened an issue about that before.

One reason to make something nominal in these cases is so that the implementer of the class can maintain some hidden invariant among the private fields: making the types structural would undermine this goal at least without really fancy types that can express things like "the list is always sorted" or "the diameter is 2 * the radius".

Another reason is that code like the below should fail to type-check, since there is no way for it to "do the right thing" at runtime:

class A {
    #foo = getFoo();
    bar = "bar";
    equals(other: A) {
        return this.#foo === other.#foo && this.bar === other.bar;
    }
}

new A().equals({ bar: "bar" }); // error: missing property '#foo'

hey fellas, i'm so stoked we have #private syntax! it's awesome!

but i must admit i'm really bummed we don't have shorthand syntax (see proposal), like this:

class Counter {
  #count = 0
  increment() {
    return #count++
  }
}

i was wondering — could we develop a typescript plugin or tsconfig option to enable shorthand syntax as an experimental feature?

i'm just dying for the slick ergonomic improvement, to avoid so much unnecessary this. repetition — the downsides to the shorthand syntax, as noted by the proposal, don't seem like significant concerns for me to worry about, so i'd certainly be happy to set "experimentalPrivateShorthand": true or something like this in my own projects

can this be done? maybe there's a good reason it's not possible? what would be the work involved? cheers!

:wave: chase

@chase-moskal TypeScript doesn't implement anything aside from Types beyond the ECMAScript standard. The reason for this is changing semantics, as is demonstrated by private fields and decorators.

@chase-moskal The most practical way to avoid typing this all the time is honestly just to assign a shortcut for it in your IDE (like F3 for example). Many things are possible by forking the TS parser or Babel parser and writing plugins, but they're all a lot of work and would put you out of sync with official tooling. I realize that your question was about official support of course, just thought I would share my thoughts.

@jhpratt — typescript used to help me stay hip. it gave me decorators and arrow functions back in ye olde times! it used to be competitive with babel, at least more than now

honestly, i'm just really jelly-belly that i cannot write gorgeous minimalistic classes like my babel-using counterparts could more than a year ago, and babel even has all the bells and whistles i'm pining for!


@mbrowne — it's not really the "writeability" of code that matters so much as the readability, so let's not worry about counting keypresses. my point is just that javascript this repetition is too often superfluous unworthy clutter

now think of this for readability: when we get shorthand #private syntax, every time you ever see this, you'd instantly know it was in order to access a public member — a huge win for readability!


anyways, i just discovered that typescript doesn't yet support private methods

so this all just needs more work and time, apparently from the bloomberg folks

without private methods to match the fields, we just need to wait (because nobody would mix typescript-privates with hashtag-privates in the same class without going insane!)

so, it's the bloomberg team modernizing typescript here, and they were also involved in the same privacy features for babel a year ago too? they're doing god's work, and i'm curious why! keep it up! cheers!

:wave: chase

Yes, TypeScript gave you decorators years ago. Guess what? Semantics changed, big time. Now TypeScript is stuck supporting the legacy API for the near future, and it'll cause massive breakage whenever it's decided to remove it. Arrow functions are part of the ECMAScript spec, so bringing that up is completely irrelevant.

TypeScript does not currently implement any ECMAScript proposals before reaching stage 3. That is where you'd need to get the shorthand to before TS is interested.

Yes, TypeScript gave you decorators years ago. Guess what? Semantics changed, big time.

We live in not an ideal world, and some features are needed right now. For example, decorators proposal is still in stage 2, but some big projects (Angular) actively use them for a long time. Another example: ECMAScript doesn't specify protected modifiers for class members, which is useful for solving real problems.

Now TypeScript is stuck supporting the legacy API for the near future, and it'll cause massive breakage whenever it's decided to remove it.

Other languages introduce experimental features under compiler flags too.

I don't say that TypeScript should implement each proposal under Stage 1 or 2, but many interesting proposals for TS just stuck, because there are no equivalents in ECMAScript, e.g. https://github.com/microsoft/TypeScript/issues/2000

@ikokostya I was using decorators as an example of something where semantics have changed after TypeScript implemented them. Are you really saying that this shorthand is "needed right now"? Trust me, we can manage the extra five characters. If it's that important to you, you're free to fork the compiler.

TypeScript has been firm on not implementing anything before stage 3, even under a flag. This has been thoroughly discussed in various other issues previously.

I don't see anything but going in circles, so I won't respond further unless something notable comes up.

@jhpratt My point is not about shorthand. Please read my comment through.

Suggestion about fork is absolutely irrelevant.

Not everyone agrees—and more to the point, not everyone on the TC39 committee agrees—that the shorthand would make code more readable or intuitive. There was plenty of discussion about this, and the shorthand syntax was moved to a separate proposal because of those concerns. (I haven't thought through it enough to have formed a definite opinion of my own, just giving a recap.) If you want to push this proposal forward, the way to proceed is to provide feedback to TC39. I'm sure there's already a lot of work involved in maintaining TS and implementing all the proposals that have reached stage 3, so it's hard to envision this being considered for TS unless it advances further in TC39.

but i must admit i'm really bummed we don't have shorthand syntax (see proposal)

That shorthand would be incompatible with pipeline proposal which also might use # and it's already discussed to become stage-2 proposal. It really doesn't make sense to implement proposals this early and then make breaking changes. At such early stage you don't even know which proposal will win.

Even if it turns out to be compatible, I can think of at least a few more use cases for a prefix # operator which are more useful than being a shorthand for typing 5 extra characters.

… especially when a good portion of the use case for private fields isn't on this at all (eg, static isMe(obj) { try { obj.#x; return true; } catch { return false; } }, to name one example).

@phaux — that's really good information about shorthand vs pipeline, thanks!

@jhpratt

TypeScript has been firm on not implementing anything before stage 3, even under a flag.

fair enough, but then it must be pointed out, that private methods are in fact stage 3, but there is no typescript support yet — and look, i'm very fond of the shorthand proposal, but that's just aesthetic — i'm actually now much more bummed about the lack of private methods and accessors right now

i assume (pray) that the bloomberg team are continuing god's work here on typescript, as they did for babel a year ago? if so, then we really just need to be patient and sit tight :)

it does seem to me like typescript used to be much more competitive with babel and browsers for ecmascript features, and now it's actually too conservative — we're in a situation where both babel and chrome had shipped private fields (unflagged) for a year before bloomberg came along to save us

i'm uneasy by the creeping divergence between the typescript and babel featureset — my babel friends have been writing beautifully slick code for the last year, whereas i'm still here paying a typescript penalty — they're laughing at me!

i loved that typescript used to offer experimental flags that allowed me to decide what i'm willing to risk refactoring in the future to buy me some great features — is it now typescript's position, that it's too dangerous to let developers decide those risks, and that typescript is now my nanny?

more and more, it looks like i can choose awesome features, or static types — but i can't now have the best of both worlds, so there is a growing cognitive dissonance within me

i'm really curious about the dynamics here — if bloomberg didn't step in like a knight in shining armor, how long would it have been before typescript took up the challenge themselves? or is this a typescript experiment: "if we just avoid these new features.. how long before the community will just.. do it for us?", hah, that's a pretty cynical even for me to wonder! or maybe this is a really cool benevolent collaboration exploring new avenues for funding and support for open source, and it's just been a little slower than expected? or perhaps typescript's philosophy just a lot more conservative than it used to be? where are we on this spectrum?

for implementing stage 3 features, is it more that typescript is lacking bandwidth, or priority?

sorry ranting on, and hey, i'm really not trying to be rude or level serious accusations here, just tossing thoughts around from my outsider perspective. i'm just a user and i'm genuinely curious about how folks here more in-the-know might react to these pictures i've painted — interesting discussion, i appreciate you all!

:wave: chase

i loved that typescript used to offer experimental flags that allowed me to decide what i'm willing to risk refactoring in the future to buy me some great features — is it now typescript's position, that it's too dangerous to let developers decide those risks, and that typescript is now my nanny?

It's not about nannying you. It's about not wanting to spend time implementing features that will inevitably become a maintenance burden when the semantics change in the proposal. They absolutely don't want to spend time figuring out how to reconcile over the changed semantics when they are in conflict with TS's implementation. They have better things to do. There's still a mountain of type-related problems to solve, so it makes sense to spend their limited resources on that, and not try to do TC39's job on top of solving the type system problems.

As for private methods and accessors: Those are not implemented in V8 either. Class methods and instance fields are different, and they can't just... flip a switch to support those too. It takes actual time to implement them and they have their hands full with work already as it is and they're a small team. Furthermore, the rule isn't just that the proposal needs to be at stage 3: they also explicitly say that they want to have high confidence in the proposal's semantics to be final. Though rare, the semantics _can_ change at Stage 3. The feature is not implemented in V8 for no good reason. Don't get me wrong though; I too would love to see that proposal implemented, and can't wait to use it. However, implementing proposals from earlier stages is a waste of time and the TS team's time is better spent elsewhere. There are countless Stage 2, 1 and 0 proposals that I would _love_ to use _today_, but I understand why I can't have them yet. Patience my friend.

You're also quite able to use babel, exclusively, to transpile your typescript, if you prefer the options babel makes available to you.

@0kku — so you're saying it's just a bandwidth/capacity problem, rather than a philosophical one — the backlog of bugs is now higher priority than the implementation of stage 3 features, however back in typescript's earlier history, perhaps it had fewer bugs, and so it was easier to allocate capacity to experimental implementations? i can buy this theory, and our response should be to wait, or contribute

@ljharb — oh now that is very interesting — is it actually possible to run typescript and babel together in such a way? to achieve the best of both worlds?

but i just can't see how the vscode ts language service could still work without imploding on the unfamiliar syntax (like private methods for example)? lacking intellisense bites too big a chunk out of typescript's value proposition — if intellisense has to be disabled entirely, the strategy is a non-starter

how could this be fixed in the longer term? i suppose if we distinguished specific typescript errors for each experimental feature's syntax, and also, if a typescript project could disable those specific errors at the tsconfig level — then we'd have a shot at opening the door to leverage both babel features and typescript features at the same time — man that would be really cool, and knock out the entire class of gripes.. cheers!

:wave: chase

@ljharb

You're also quite able to use babel, exclusively, to transpile your typescript

I suppose you could set up a type-checking process where you transpile just the private methods using Babel (but leave in the types on everything else) and do typechecking on that code with tsc. (The build script, for after typechecking passes, would just use Babel alone.) But you'd have to ignore lots of syntax errors in your IDE - I think that would be a deal-breaker for most people.

@chase-moskal Although what @0kku said is correct in general, I haven't seen any reason not to implement private methods in TS at this point. I think it's more that that proposal reached stage 3 later than class fields, and the implementers are still working on it.

If you're curious why we're not keen to jump on experimental features, please see the enormous complexity clusterfoxtrot that is --useDefineForClassFields. We implemented class property intializers very, very early under the assumption that no possible TC39 proposal with different semantics could even plausibly exist, and we were quite wrong.

Decorators are trending toward being in the same boat, and you're in for a surprise if you think we can drop support entirely for the old semantics. Multiple large companies have built large well-adopted frameworks around it; it's entirely unrealistic that we could say "Well the flag name started with experimental so you should have known that this feature supported for the last 5 years might just up and disappear one day".

For both of those features we're going to be stuck maintaining two subtly different codepaths here for the rest of our lives, and it sucks, but that's the trade-off -- we're very committed to keeping build-to-build TypeScript updates possible without breaking the runtime behavior of your code; this tradeoff is invisible when it's in your favor (e.g. your program keeps working) but you can see the downside in terms of eager feature adoption more readily.

From our perspective, you cannot disclaim away version-to-version runtime compatibility, the same way you cannot waive away responsibility for negligence.

We also held off on early adoption of optional chaining despite MASSIVE community pushback - if I had a nickel for every time someone said "Well just put it behind a flag", I'd be typing this from a boat. But it was the right thing to do, because we would have had to guess what (null)?.prop produced, and we absolutely would have guessed null (not undefined), and we'd be living with another ongoing complexity burden to figure that one out. There is a strong counterfactual upside here for the fact that no one is stuck sitting on a codebase of 3 million lines filled with ?. usage where they depend on that producing null sometimes, with no way to even figure out how to transition forward to the different undefined behavior.

Bloomberg implemented private fields because they were very excited to have the feature and were eager to contribute to TypeScript. If they hadn't been in this situation, we would have implemented the feature ourselves, just like everything else.

In general I would say that the timeline of JavaScript is very, very long, and that realistically you can write good and ergonomic JavaScript in 2020 without using 2024's syntax, just like you could write good and ergonomic JS in 2016 without using 2020's syntax. No one is blocked by the inability to use syntax that doesn't even have finally-decided semantics yet; it's just imaginary programming at that point.

Not only that, but it's not even 100% certain that the current class fields proposal and all its details will make it to stage 4 in its current form (especially now that there is a TC39 member company agitating to scrap the proposal completely and replace it with a different one). I would guess 95% certainty, but in general even implementing a stage 3 proposal is not completely without risk. However, the likelihood of further changes after stage 3 is very low, so I think the current TS policy is a good one.

now that there is a TC39 member company agitating to scrap the [class fields] proposal completely and replace it with a different one

Interesting, mind point a link to that?


Here's an idea: perhaps the TS team could spend some time to improve the plugin system, to give plugin authors a clear API that hooks into both compile-time and language-server-protocol intellisense-time. This would make it possible for 3rd parties to implement any experimental features they want including new syntax, and still have working intellisense inside IDEs and text editors.

Last I checked, ttypescript is the only way to configure TypeScript transformers non-programmatically in tsconfig.json, and it doesn't hook into intellisense of any IDEs, so the ability for 3rd parties to make meaningful features that integrate well with existing tools like VS Code aren't there yet. We wouldn't want to give up our nice VS Code experience by switching to ttypescript and needing to rely on terminal output while VS Code is unable to understand syntax and throw errors.

Then TypeScript could focus on the stable features, and let 3rd parties make or use risky experimental features (as easy as it is in the Babel ecosystem).

Interesting, mind point a link to that?

https://github.com/microsoft/TypeScript/pull/30829#issuecomment-541338266

There's a very limited set of syntactic things you can do with a Babel plugin; basically the parser has to already support it. The do expression "plugin", for example, still requires logic in the core of Babel itself: https://github.com/babel/babel/blob/master/packages/babel-parser/src/parser/expression.js#L991 It's really not any different from a commandline switch.

Is there an issue that can be followed for private class methods and accessors?

@RyanCavanaugh

For both of those features we're going to be stuck maintaining two subtly different codepaths here for the rest of our lives

I don't think that it's should go this way and I can imagine that it hurts the whole community. If decorators reach stage 3 I think it's reasonable to provide some time to upgrade to the new version of decorators. May be support both for a time, may be make a hard switch and drop legacy decorators in TypeScript 4+. I don't know the difference between old and new decorators, because I don't use them (well, experimental feature). But I think that affected projects should work on the proposal, if the proposal doesn't meet their needs. It's better for everyone. TypeScript is the wrong place to stir up the war about legacy and experimental features.

I regret having landed here :/

@RyanCavanaugh

There's a very limited set of syntactic things you can do with a Babel plugin; basically the parser has to already support it.

True. But in the interest of comparing with TypeScript, in Babel you can fork just the parser and set up your babel.config.js to use the custom parser and any custom syntax plugins you create. Yes it's a lot of work (especially learning how to add new syntax for the first time), but I'm not aware of any equivalent option for TS, and of course in Babel it's much easier to write transformation plugins based on existing syntax. I'm not sure what the current state of customization options is in TypeScript, but when I last looked into it a couple years ago it looked like there was no option to extend it other than forking the entire thing.

The hash for privates IMO is ugly and confusing. It’s hard enforced because there’s no way to implement them otherwise. There’s no build step like there is in TS. The effect is it just clogs up code with symbols for the very little gain of a “hard private” with people thinking it’s the ‘correct’ way to write JS as many people are taught that things should just be private by default.
There should have been more proposal before pushing it into the standard, especially when there’s been obvious disagreement on the matter.

@robot56 things should just be private by default. They're taught that all things should be accessible via reflection, which is not, in fact, a good thing for anyone.

@ljharb Yes, they should. However when the way to declare a private becomes throwing hash symbols in front of variables, the “correct” way to make a class in JS means teaching people to just putting hashes in front of member variables. Not just when declaring them, but also when referencing them. It’s especially confusing if you’re coming from other languages. My first thought when seeing something like this.#x = this.#something(); and declaration in a class was that the hash was apart of the variable itself. I would have never guessed it was a modifier. The underscore for public, that seems backwards too. This isn’t really relevant to TS, but just an annoying rant I guess.

Yes, learning new things when one is cemented in familiarity might be an adjustment. I have faith that the JS community will continue learning!

(the hash is part of the variable itself, the name of this.#x is not "x, but private", but in fact, #x, a unique value in that lexical scope)

Yes, learning new things when one is cemented in familiarity might be an adjustment. I have faith that the JS community will continue learning!

The way you put it, sounds like if that was learning something adding to our programming wisdom, whereas this is just another quirk of JS that one has to memorize :D

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siddjain picture siddjain  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments