You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
My preference (and the current TypeScript implementation) is before export. More complex decorators like Angular's Component decorator often span multiple lines and the export keyword would end up oddly orphaned at the top otherwise.
export
@dec1
@dec2({option1: "foo"})classC{}// vs
@dec1
@dec2({option1: "foo"})exportclassC{}
Reacted by Daniel Rosenwasser, Jeremy Gayed, Joshua Wiens, Diego Ferreiro Val, Iván, Martin Oppitz, Damiano, afc163, Trevor McCauley, Joshua C Elliott and 77 more
Reacted by NSShentu, Damiano, chenyang, Jesús Leganés-Combarro, JSupot, csj8520, Иван Вольнов, Pardal, 流浪大法师 and Lee
Reacted by Alireza Mirian, Yves M., Yuriy Grunin, Theodore Brown, Aman Gupta, Hossein, JasonFes, Abhishek Hingnikar, Reidi Qyrku, Abdelhady Muhammad and 22 more
The current proposal only permits decorators afterexport. The DecoratorList is part of the ClassDeclaration, which is the thing that the export keyword. Should we revisit this decision?
This might be a change vs previous versions, which should be documented as part of #50 .
I recall discussions with @wycats over the past two years where we discussed this and I had thought we had settled on "before export" in the past.
One of the other reasons for TypeScript's implementation is that we have other possible keywords that can come before class (i.e. declare and abstract) and it seemed weird to write:
export
@decorator1
@decorator2abstractclassC{}
With multi-line decorators you can end up losing track of the export keyword when looking at the class. It also fairly common to want to refactor your code by adding the export keyword and you might naturally assume you just put the keyword before class (which is the case in other languages like Java (annotations) and C# (attributes)).
Reacted by Jeremy Gayed, Iván, Joshua C Elliott, Pascal Iske, Federico Zivolo, Nick Sweeting, empyrical, Mykola Tunyk, Mathias Lykkegaard Lorenzen, Yves M. and 20 more
The only other part of the conversation I recall was whether we wanted to eventually allow users to decorate an export binding separate from the class itself. Whether we would ever do such a thing and what that would mean is unclear.
I had suggested an extension to the syntax to support this in a way similar to how C# attributes allow you to specify placement, via a prefix:
@decorator1
@decorator2
@export: decorator3// decorates the export bindingexportclassC{
@route("/foo")
@method: permit("administrators")// decorates the method (optional here)
@return: serializeAs("json")// decorates the return valuemethod(){ ... }}
I'm not necessarily saying we have to figure out what those mean, but that there's room in the design space to put together something meaningful that isn't dependent on whether the decorator is before or after the export keyword.
Reacted by Iván, Yves M., FireFly, Samuel Bishop, ExE Boss and 流浪大法师
export is static, so I think decorating it is impossible. I think that's better to have the class's decorators just before the class keyword because it clearly shows that the decorators apply to the class, and not to export.
Given that export isn't the declaration nor the thing being decorated, I think "after export" is the only thing that makes sense.
In other words, you don't need to "keep track" of export when you're looking at a long chain of decorators, because the fact that it's exported isn't directly relevant to what's being decorated and how.
Reacted by C. Scott Ananian, Sean Vieira, Abhishek Hingnikar, Morgan Sliman, Sampson Crowley, 流浪大法师, Bergmaier, Jayden Seric, Lazar Ljubenović, David Granado and 2 more
Reacted by bary12, ExE Boss, Brian Kim, 流浪大法师, Danny McGee, Advaiya Lad, Jarrod Davis, Stronger, cg-amarginson, Björn Zeutzheim and 2 more
What about if a future proposal adds other keywords before class, for example abstract, final, sealed, static, etc.? Those are logically associated with the class and not the ExportDeclaration, so if they are added there will be an odd mix of export before and these after.
When TypeScript adds support for ES decorators it will be an all-or-nothing switch, so we can definitely support export-before. Since TypeScript has export-after currently, I will see if I can get some feedback from our community as to whether they find our current syntax ergonomic or unergonomic. Having used it myself for a few years now, I personally find export-after the most ergonomic, easiest to refactor, and visually appealing.
Reacted by HE Shi-Jun, Joshua Wiens, Jonathan Grimes, Iván, Yves M., Pascal Iske, empyrical, Yuriy Grunin, Minko Gechev, David Fahlander and 7 more
What about if a future proposal adds other keywords before class, for example abstract, final, sealed, static, etc.? Those are logically associated with the class and not the ExportDeclaration, so if they are added there will be an odd mix of export before and these after.
Why would these keywords make sense before export rather than after? Would @ljharb 's logic in #69 (comment) apply differently to decorators vs these keywords?
I'm not talking about those keywords beforeexport, but rather the inconsistency of, say:
export
@decabstractclassC{}// vs
@decexportabstractclassC{}
In discussing this with others on the TypeScript team, so far the consensus is that we haven't yet heard any negative feedback about our current placement of "before export", which generally indicates that our customers have been happy or at least accepting of that behavior after having decorators as a feature for several years.
Also, there are some things that can't currently be done in a decorator (i.e. final or sealed) so we can't necessarily wash our hands of the possibility of other future keywords.
Reacted by Brian Kim, 流浪大法师, Alex, Jarrod Davis, Stronger and Alex
I guess i don’t understand the inconsistency. If you add or delete the export keyword, the declaration should be the same, conceptually - id find it very inconsistent if the ordering wasn’t “export”, “decorators for the thing”, “thing I’m exporting”.
Reacted by Ricky Munroe, Joseph Lloyd, Sampson Crowley, Nick Oliver, 流浪大法师, Jayden Seric, Joe Pea, Lazar Ljubenović and David Granado
Reacted by Brian Kim, 流浪大法师, Dilyorbek, Advaiya Lad, Jarrod Davis, Stronger and Alex
Coming from C#, I find the separation of the export keyword from the class keyword by its decorators confusing. In C#, you would write this:
[attr1][attr2]publicabstractclassC{}
Where public is what makes the class reachable outside of the assembly (i.e. exported). If export needs to be before decorators, it seems like a refactoring pitfall:
classB{}
@dec1
@dec2classC{}
If I want to export B, I add export before the class keyword. If I want to export C its easy to assume that export goes before the class keyword, just like async goes before the function keyword. It all depends on how you view export. The spec treats it as a declaration in its own right, but its fairly easy for someone to assume export is just a modifier for class just like static is a modifier for a method:
classC{
@dec1
@dec2method(){}}
I would refactor method by adding staticafter the decorators, but before the method name.
Reacted by HE Shi-Jun, Lukáš Novotný, Iván, Huseyn Guliyev, Andrey Borzilo, Pascal Iske, empyrical, Daniel Friesen, Mathias Lykkegaard Lorenzen, Steven and 22 more
As the plenary, the consensus is allowing decorators before export XOR after export. (XOR means, @deco export class X {} and export @deco class X {} are both valid, @deco export @deco class X {} is not valid.)
Thank you everyone to contribute to this 5 years long discussion.
Reacted by Santiago Aguilar Hernández, Kristen, Steven, Toni Villena, geekreal, Michel Weststrate, Claudia Meadows, gulbanana, Björn Zeutzheim, Pier-Luc Whissell and 3 more
Reacted by Santiago Aguilar Hernández, Toni Villena, Patricio Ezequiel Hondagneu Roig, Claudia Meadows, Björn Zeutzheim, Mateusz Wojtyna and Dragoteryx
Reacted by Santiago Aguilar Hernández, Alyx, Daniel Rosenwasser, Keith Cirkel, Steven, Toni Villena, Jie Luo, geekreal, Sean Kelley, Björn Zeutzheim and 4 more
Reacted by Lazar Ljubenović, South Drifter, Alyx, Santiago Aguilar Hernández, Daniel Rosenwasser, Lukáš Novotný, pabloalmunia, Toni Villena, Cutsin, Jie Luo and 9 more
Reacted by Santiago Aguilar Hernández, Daniel Rosenwasser, Lukáš Novotný, Daniel Ehrenberg, Steven, Toni Villena, Jie Luo, Darien Maillet Valentine, geekreal, Michel Weststrate and 3 more
Reacted by Santiago Aguilar Hernández, Yves M., Emanuel Hoogeveen, Toni Villena and Dragoteryx
As can be observed by reading this thread, this issue can be considered
from several perspectives but all of them yield the same conclusion. There
is a popular demand for the idea that export should be always used before
any class decorator mark. Let me summarize what different perspectives can
be considered:
A. Syntax Perspective. From the syntax point of view, the correct
grammatical design considers any class decorator as a variant of expression
and any export sentence as a more abstract syntactic construction dealing
with the expression on the right side no matter if it is a decorator or
not. To my knowledge, language designers at typescript have put effort to
allow both alternatives but this is severe trouble at a grammatical level
causing annoying conflicts to be resolved and it is a bad design strategy
at least from an academic narrative. In particular:
Option 1. [Good] This option is for language engineers a happy design
because all occurrences of export sentences found only one reduction path
within the grammar converting any export particle followed by an expression
into a more abstract export sentence.
exp ::= exp PLUS exp |
exp MINUL exp |
exp TIMES exp | ...
@ IDENTIFIER class-declaration
export ::= EXPORT exp
Option 2. [Bad] In language design many grammar solutions are possible. I
am not sure what is the less bad solution. But just for the sake of
illustration, the following approach shows how this demand has less nice
grammar flavor.
exp ::= exp PLUS exp |
exp MINUL exp |
exp TIMES exp | ...
@ IDENTIFIER class-declaration |
export
export ::= @ IDENTIFIER EXPORT class-declaration |
EXPORT class-declaration
B. Semantic Perspective. From a semantic point of view, class decorators
can be explained as declarative marks to transform classes in any variant
of themselves. In the RUP narrative, we can say that UML Stereotypes can be
nowadays materialized in code by means of using decorators. Whilst class CX
{} is just a class, @task class CX {} is a variant of the concept of class
meaning a Task within a given Bounded Context or domain. When we relate the
class decorator with the export sentence to impose the proper order is very
relevant:
Option 1. [Good] This option can be read, from right to left, as follows: a
given class CX is firstly converted in a Task by applying the @task
transformation, and then, the result of that transformation is exposed as
an exported value to be received by an importer module. Here @task is
clearly a man-in-the-middle particle.
export @task class CX {}
Option 2. [Bad] The other option can be properly explained in JS
documentation but is clearly less intuitive. A foreign developer by
applying the same right-to-left read strategy would interpret as follows:
the class is exported to be received by an imported module and then a
@task transformation would be applied causing, apparently no effect,
because the export has been already processed according to the right to
left intuition. It is not necessary to mention that this reading rule is
the natural one taking into account how parsers work ;)
@task export class CX {}
C. Pragmatic Perspective. From a pragmatic point of view to fix both import
& export as prefix particles at the beginning of any line has also some
benefits. I must recognize that this argument is the weaker one in this
enumeration, so I beg not to take it into account. But that being said, in
my personal experience I have found graceful benefits assuming this order
when I have needed to create tooling processing any JS module. Instead of
using heavy (although powerful) solutions such as Babel or Babylon, I could
solve the past simple processing needs by reading each JS module as a text
stream. In those cases, the code was deployed within a service worker, so a
simple & straightforward solution was mandatory.
D. Behavioral Perspective. From the retrospective point of view, it was
said at the beginning of this spec that a class decorator is defined by
declaring a function. This idea is stressed by illustrating that in any
code place where a class decorator can be found a proper substitution is
allowed to use functional syntax. That is, the name of the decorator can be
used as a function invocation receiving the class as a parameter. This
means that the @ mark is just a graceful syntax sugar moving the language
to a more declarative flavor. And for me, it is a great hit because TC39
can be proud by stating that "JavaScript is constructed over JavaScript".
In some (corner) cases this functional/declarative duality has to resolve
relevant problems, especially in testing scenarios. Let's consider the two
options when the export particle is involved:
Opinion 1. [Good]. As explained before the following two lines can be
considered different dialectical ways of expressing the same behavior in
JavaScript. The first one uses a declarative, decorator-based, approach
whilst the second one is based on function invocation.
export @A@b @C class X {}
A(B(C(class X {})))
Opinion 2. [Bad]. In this other approach, the export participle has
interpretative problems from retrospective attention. The equivalent lines
stated above would be written as follows which cause clearly backward
compatibility issues. Maybe all is resoluble by this is a trouble to deal
with:
@A@b @C export class X {}
A(B(C(export class X {}))) // Error!
E. Cultural Perspective. That being said IMHO there is a solid
misleading revolving around the focus of the thread. What people are
demanding is not academic or scientific arguments in favor or against
options 1 o 2, as stated in the aforementioned perspectives. People just
demand a graceful Developer Experience. I can understand that it is the
more subjective one, but the language must be created and evolved attending
foremost to developer preferences. This goes to the benefit of all because
in another case a bad experience will be resolved at framework level, that
from our perspective in this group could be interpreted as an effort
failure.
El jue, 2 feb 2023 a las 20:58, HE Shi-Jun ***@***.***>)
escribió:
As the plenary, the consensus is allowing decorators before export XOR after export. (XOR means, @deco export class X {} and export @deco class X {} are both valid, @deco export @deco class X {} is not valid.)
Thank you everyone to contribute to this 5 years long discussion.
Wow. Surprise twist in the eleventh hour and it’s like ... beautiful. A rainbow bikeshed!
I raise my @ to all who fought on this storied battlefield, whether on the right side or the wrong side. Especially those who gave their lives ... you will not be forgotten. Three cheers for decorators!
Reacted by pabloalmunia, Keith Cirkel, Michel Weststrate, Nicolò Ribaudo, Steven, Claudia Meadows, Björn Zeutzheim, Jarrod Davis and Christian Svensson
Reacted by Santiago Aguilar Hernández, Keith Cirkel, Henry and Claudia Meadows
@hax so if I read this correctly, decorators can now go before export for classes, but the spec in README.md has not been updated yet - is that correct? It still says this:
There were only two options for which the community was divided; either the export keyword comes before of after the decorator. In the end the allowing both options was the best solution TC39 could have made.
It just took too long for this proposal to conclude... way too long.
@pwhissell most proposals are discussed for more years than that, so i'm not sure a postmortem is needed. It's just an expected part of the process for something that by and large we can never reverse (shipping something on the web, i mean)
In this case, I think it would have been better to have removed exports entirely from the spec until this was sorted out. Decorators as a whole don't directly depend on this. Anyone using decorators could have just exported them on a separate line in the interim.
Perhaps future contentious, nonintegral items could be evaluated for piece-wise deferment while the bulk of a proposal goes forward?
@david0178418 this was not the blocker for the majority of the time that decorators were delayed. This came up again recently, but the main issues that delayed this feature were around performance and API complexity. We went through many, many iterations of design before we found one that worked and solved all constraints.
I'm planning on writing a blog post on this when I have some time to outline all the different iterations and changes that were made for this feature over time.
Reacted by Jordan Harband, David Granado, Darien Maillet Valentine, Toni Villena, Daniel Friesen, Pier-Luc Whissell, Claudia Meadows, Jack Henderson, HE Shi-Jun, Alex and 1 more
Reacted by Darien Maillet Valentine and Toni Villena
Activity
rbuckton commentedon Mar 4, 2018
My preference (and the current TypeScript implementation) is before
export. More complex decorators like Angular's Component decorator often span multiple lines and theexportkeyword would end up oddly orphaned at the top otherwise.rbuckton commentedon Mar 4, 2018
I suppose "both" might be an acceptable mix though:
littledan commentedon Mar 4, 2018
The current proposal only permits decorators after
export. The DecoratorList is part of the ClassDeclaration, which is the thing that theexportkeyword. Should we revisit this decision?This might be a change vs previous versions, which should be documented as part of #50 .
rbuckton commentedon Mar 4, 2018
I recall discussions with @wycats over the past two years where we discussed this and I had thought we had settled on "before
export" in the past.One of the other reasons for TypeScript's implementation is that we have other possible keywords that can come before
class(i.e.declareandabstract) and it seemed weird to write:With multi-line decorators you can end up losing track of the
exportkeyword when looking at the class. It also fairly common to want to refactor your code by adding the export keyword and you might naturally assume you just put the keyword beforeclass(which is the case in other languages like Java (annotations) and C# (attributes)).rbuckton commentedon Mar 4, 2018
The only other part of the conversation I recall was whether we wanted to eventually allow users to decorate an
exportbinding separate from the class itself. Whether we would ever do such a thing and what that would mean is unclear.I had suggested an extension to the syntax to support this in a way similar to how C# attributes allow you to specify placement, via a prefix:
We could do the same thing with decorators:
I'm not necessarily saying we have to figure out what those mean, but that there's room in the design space to put together something meaningful that isn't dependent on whether the decorator is before or after the
exportkeyword.doodadjs commentedon Mar 4, 2018
exportis static, so I think decorating it is impossible. I think that's better to have the class's decorators just before theclasskeyword because it clearly shows that the decorators apply to the class, and not toexport.ljharb commentedon Mar 4, 2018
Given that
exportisn't the declaration nor the thing being decorated, I think "after export" is the only thing that makes sense.In other words, you don't need to "keep track" of
exportwhen you're looking at a long chain of decorators, because the fact that it's exported isn't directly relevant to what's being decorated and how.rbuckton commentedon Mar 4, 2018
What about if a future proposal adds other keywords before class, for example
abstract,final,sealed,static, etc.? Those are logically associated with theclassand not the ExportDeclaration, so if they are added there will be an odd mix ofexportbefore and these after.When TypeScript adds support for ES decorators it will be an all-or-nothing switch, so we can definitely support
export-before. Since TypeScript hasexport-after currently, I will see if I can get some feedback from our community as to whether they find our current syntax ergonomic or unergonomic. Having used it myself for a few years now, I personally findexport-after the most ergonomic, easiest to refactor, and visually appealing.doodadjs commentedon Mar 4, 2018
That could be :
But I think
abstract,final,sealed,staticand etc. will probably be decorators, so :littledan commentedon Mar 4, 2018
Why would these keywords make sense before
exportrather than after? Would @ljharb 's logic in #69 (comment) apply differently to decorators vs these keywords?rbuckton commentedon Mar 4, 2018
I'm not talking about those keywords before
export, but rather the inconsistency of, say:In discussing this with others on the TypeScript team, so far the consensus is that we haven't yet heard any negative feedback about our current placement of "before
export", which generally indicates that our customers have been happy or at least accepting of that behavior after having decorators as a feature for several years.Also, there are some things that can't currently be done in a decorator (i.e.
finalorsealed) so we can't necessarily wash our hands of the possibility of other future keywords.ljharb commentedon Mar 4, 2018
I guess i don’t understand the inconsistency. If you add or delete the export keyword, the declaration should be the same, conceptually - id find it very inconsistent if the ordering wasn’t “export”, “decorators for the thing”, “thing I’m exporting”.
rbuckton commentedon Mar 5, 2018
Coming from C#, I find the separation of the
exportkeyword from theclasskeyword by its decorators confusing. In C#, you would write this:Where
publicis what makes the class reachable outside of the assembly (i.e. exported). Ifexportneeds to be before decorators, it seems like a refactoring pitfall:If I want to export
B, I addexportbefore theclasskeyword. If I want to exportCits easy to assume thatexportgoes before theclasskeyword, just likeasyncgoes before thefunctionkeyword. It all depends on how you viewexport. The spec treats it as a declaration in its own right, but its fairly easy for someone to assumeexportis just a modifier forclassjust likestaticis a modifier for a method:I would refactor
methodby addingstaticafter the decorators, but before the method name.rbuckton commentedon Mar 5, 2018
I'd be happy if the spec allowed me to chose one or the other as a personal preference, something like (simplified for brevity's sake):
Something like that would allow you to choose either
export @dec class C {}or@dec export class C {}, but would disallow@dec export @dec class C {}.doodadjs commentedon Mar 5, 2018
@rbuckton The question is : What do you want to decorate with
@decorator(while reading from Left-To-Right) ?364 remaining items
hax commentedon Feb 2, 2023
As the plenary, the consensus is allowing decorators before
exportXOR afterexport. (XOR means,@deco export class X {}andexport @deco class X {}are both valid,@deco export @deco class X {}is not valid.)Thank you everyone to contribute to this 5 years long discussion.
javiervelezreyes commentedon Feb 2, 2023
bathos commentedon Feb 3, 2023
Wow. Surprise twist in the eleventh hour and it’s like ... beautiful. A rainbow bikeshed!
I raise my @ to all who fought on this storied battlefield, whether on the right side or the wrong side. Especially those who gave their lives ... you will not be forgotten. Three cheers for decorators!
olee commentedon Feb 16, 2023
@hax so if I read this correctly, decorators can now go before export for classes, but the spec in
README.mdhas not been updated yet - is that correct? It still says this:pwhissell commentedon Feb 23, 2023
There were only two options for which the community was divided; either the export keyword comes before of after the decorator. In the end the allowing both options was the best solution TC39 could have made.
It just took too long for this proposal to conclude... way too long.
pwhissell commentedon Feb 23, 2023
It feels like there should be a post-mortem focused on how to avoid 5 years of opinionated discussion over a single proposal
ljharb commentedon Feb 23, 2023
@pwhissell most proposals are discussed for more years than that, so i'm not sure a postmortem is needed. It's just an expected part of the process for something that by and large we can never reverse (shipping something on the web, i mean)
david0178418 commentedon Feb 23, 2023
In this case, I think it would have been better to have removed exports entirely from the spec until this was sorted out. Decorators as a whole don't directly depend on this. Anyone using decorators could have just exported them on a separate line in the interim.
Perhaps future contentious, nonintegral items could be evaluated for piece-wise deferment while the bulk of a proposal goes forward?
pzuraq commentedon Feb 23, 2023
@david0178418 this was not the blocker for the majority of the time that decorators were delayed. This came up again recently, but the main issues that delayed this feature were around performance and API complexity. We went through many, many iterations of design before we found one that worked and solved all constraints.
I'm planning on writing a blog post on this when I have some time to outline all the different iterations and changes that were made for this feature over time.
david0178418 commentedon Feb 23, 2023
@pzuraq Ah, I see. Good to know. Thanks for the clarification.
pwhissell commentedon Feb 24, 2023
Ah makes sens. I'm curious to understand more about the performance issue
Update README to allow exports before export
trusktr commentedon Mar 12, 2023
Comment moved to new issue: #503
pzuraq commentedon Mar 12, 2023
@trusktr this issue has been settled, please don't continue commenting on it.