-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge class features plugins #8130
Merge class features plugins #8130
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/9414/ |
@@ -0,0 +1,19 @@ | |||
# @babel/plugin-proposal-enhanced-classes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not obvious for me how this is supposed to be used (by looking into README), could you add some simple example here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't 😛 (#8108)
I will open a new PR containing the docs when this PR is more ready
path.insertBefore(computedNodes); | ||
path.insertAfter(staticNodes); | ||
}, | ||
inherits(babel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this be used in combination with enhanced classes? or is it using enhanced-classes or class-properties (where latter is ofc just a thin config wrapper over former)? could we validate misusage somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class-properties
is meant to be a "switch" which turns on cass fields in the enhanced-classes
plugin. If enhanced-classes
hasn't already been added by the user, class-properties
will do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But can they be used side by side? Would enhanced-classes
turned on by class-properties
be "merged" with "standalone" enhanced-classes
? In other words - is enhanced-classes
' instance shareable among such invocations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose a user uses both class-properties
and deorators
. Each plugin will
- Register a new
enhanced-classes
plugin - Turn on the coresponding feature in an option set in the shared map in the
File
class.
The first instance enhanced-classes
will run, transforming both decorators and class fields because they are both enabled in the shared map.
The second instance of enhanced-classes
will run, but it will be a no-op because the classes have already been transformed.
We can't currently disable the second plugin (babel-core doesn't offer this ability), but I think that we can live with the first run doing "everything" and the other beeing no-op.
Whats the story now behind enhanced-classes and transform-classes? Have u considered merging them too? Atm it's hard to do some transformations which requires coordination between transform-classes & class-properties - would love to solve this. |
Also - what will happen once i.e. class properties get standardised? Will it stay as part of |
I suspect they have to stay transformed because of decorators. Though it could be possible to add a mode to make the plugin not transform properties if the class isn't decorated. |
I would suggest to merge this with |
I didn't do it yet because classes have been part of JS for a long time, while in this plugin I wanted to merge the proposals about class fields, private methods, static class features and decorators (which are considered like a "family" of proposal, since they depend on each other). We could do it if it simplifies classes transformation.
This plugin would stay as-is, maybe we will remove the |
Either way is fine to me (although I believe it would make certain transformations easier if we would just merge all class-related transforms into a single plugin). I feel like there should be a clear path defined what will happen in the future regarding those. I think we might be pretty certain that all of those (class fields, private methods, decorators) will get to the language - so it's only a matter of time. And knowing that let us define now what should be done in the future in regards of those transforms. We kinda have an opportunity here to avoid users being confused in the future by moved parts etc. |
Im going to discuss further here - because it's imho annoying to reply under "hidden" section of comments. From what I understand - |
We could do something like this: ["@babel/plugin-proposal-enhanced-classes", {
"loose": true, // Applies to all the features, but it can be overwritten
"privateMethods": true, // loose, as specified before
"instanceFields": { "loose": false } // spec compliant
}] I'm not 100% sure that we should, though. Decorators: why would someone want to have, for example, private instance properties in loose mode (without the weakmap), but private methods spec compliant? Also, the decorators don't have their own loose mode, but they need to be loose if, for example, instance fields or static fields are loose. |
ad4c05d
to
1fe676f
Compare
If it complicates things too much, then I guess this is not a blocker of any kind. I was thinking about the idea of merging this with regular class transform and in such situation it's easier to imagine a situation where 1 would like to use spec classes but i.e. loose class fields. What do u think anyway about the idea of merging them? |
1fe676f
to
9bfb632
Compare
👍 IMHO it should be done, it simplifies transformations around classes.
Couldnt it be just another option of the plugin that couldnt be used in combination with other options?
Would be a good feature in places where it makes sense.
It seems sane to me to just go with 1 option per proposal. |
9bfb632
to
8399597
Compare
8399597
to
04de64d
Compare
Object.defineProperty(Foo, _foo, { | ||
writable: true, | ||
value: "foo" | ||
}); | ||
|
||
var _bar = babelHelpers.classPrivateFieldLooseKey("bar"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only change in the output, because I merged the logic for static and instance properties, but it shouldn't be observable.
{ | ||
"name": "@babel/plugin-proposal-enhanced-classes", | ||
"version": "7.0.0", | ||
"author": "The Babel Team (https://babeljs.io/team)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be Babel contributors, because we have contributors that are not in the team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, there is the "contributors"
field for that (https://docs.npmjs.com/files/package.json#people-fields-author-contributors). I think that "author"
shouldn't be whoever wrote some parts of the code, but who is responsible for it.
"author": "The Babel Team (https://babeljs.io/team)", | ||
"license": "MIT", | ||
"description": "Compile class public and private fields, private methods and decorators to ES6", | ||
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-enhanced-classes", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enhanced-classes
-> classes-features
everywhere
@@ -0,0 +1,3 @@ | |||
export function hasDecorators(path) { | |||
return path.node.decorators && path.node.decorators.length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could cast it to boolean here
what's the status of this PR? is this something that rest of the team wants too? or is it still under consideration? |
Yeah, we want this PR but it still needs to be reviewed. |
Is there anything I could help with? |
I think that I will move the loose option to
You could review this PR. (or, if you already did it, leave a ✔️) |
The workaround Any progress about the support of HTMLElements decoration in this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall awesome job!
Also wondering if we should determine if we want to consider making a generic API to get some of this stuff working for other kinds of plugins as a result of this work (having to write all this logic around manipulateOptions
, pre
, loose
etc)
export function verifyUsedFeatures(path, file) { | ||
if (hasFeature(file, FEATURES.decorators)) { | ||
throw new Error( | ||
"@babel/plugin-class-features doesn't suport decorators yet.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"@babel/plugin-class-features doesn't suport decorators yet.", | |
"@babel/plugin-class-features doesn't support decorators yet.", |
and below
c290381
to
9953836
Compare
} | ||
}, | ||
|
||
pre() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit concerned around how some of this will work if there are multiple versions of things. If we eventually add decorators with their own inherits
, we'll have two separate copies of this plugin running. Would we want to allow both to run? We could potentially have one detect that another instance has already loaded or something, but then we still have the question of which one we end up choosing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, only the first plug-in runs (actually both runs, but it is a noop because everything is already transformed).
Probably I should require("package.json"). version
and only run the most recent version? Or is it overkill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, I will merge the decorators transform before the next release, to check that everything works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The big question for me is if we want plugins like proposal-class-properties
and proposal-decorators
at the same time as babel-plugin-class-features
(which I also notice we may want to be babel-plugin-proposal-class-features
)?
For instance, we could also deprecate those two plugins, and instead instruct people to use
plugins: [
["@babel/proposal-class-features", {
decorators: true,
publicFields: true,
privateFields: true,
}]
]
which would more nicely bundle up everything more consistently with our current plugin design.
It's more work for users though, which is why I'm on the fence, but it would mean things are less confusingly side-effectful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial idea was to create a single plugin (I didn't even thought at the current implementation), but different people advocated for keeping the interface as separate plugins.
The big question for me is if we want plugins like proposal-class-properties and proposal-decorators at the same time as babel-plugin-class-features
Unless people rely on exact versions of the class-features plugin (without ^
) and, at the same time, npm/yarn doesn't dedupe this package, I don't think that using both class-features
and class-properties
at the same time would cause confusing effects.
Anyway, we could disallow using them together if you think that it would be better.
which I also notice we may want to be babel-plugin-proposal-class-features
There will be a time when this plugin will transform standard features and proposals (e.g. when class fields become standard). This was one of the reason that the multi-plugins interface was preferred.
I'm going to split up the actual transform into another PR, so we can keep discussing the interface without blocking #8654 and #8248
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
different people advocated for keeping the interface as separate plugins.
Do you have a link to those discussions? I'm concerned that the approach here will make it really hard to maintain and predict how changes might affect users.
Unless people rely on exact versions of the class-features plugin (without ^) and, at the same time, npm/yarn doesn't dedupe this package, I don't think that using both class-features and class-properties at the same time would cause confusing effects.
Sorry, I should clarify, I don't mean whether they should work together, I mean whether we should maintain both. I'd be tempted to have babel-plugin-proposal-class-features
be an entire replacement for proposal-decorators
and proposal-class-properties
, with those being removed from the monorepo.
There will be a time when this plugin will transform standard features and proposals (e.g. when class fields become standard). This was one of the reason that the multi-plugins interface was preferred.
There hard part there is that in many cases it's not possible to have one without the other. If we keep things bundled in one plugin, at least it is clear to users that this plugin of class props + decorators + private would likely take recedence, so if say class fields stabilized first, preset-env could default-include
['transform-class-features', { publicFields: true }]
Which could throw if it encounters classes that have public fields that are decorated, or private fields.
Then if the user adds
['proposal-class-features', { decorators: true }]
this would essentially override the previous config because it would run first, so it could loudly throw if it encounters public/private fields because they haven't been enabled on that instance of the plugin. Potentially we'd only have to technically error here if a class contained both a decorator and a public field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a link to those discussions?
Unfortunately there isn't very much in the meeting notes - the older reference I could find is https://github.com/babel/notes/blob/f58a330d02a2ba7eb29f85dfb88f3c95174d077b/2018/2018-09/sept-10.md#new-class-proposals-decorators-private-fields-etc, which is three months after this PR was opened.
Sorry, I should clarify, I don't mean whether they should work together, I mean whether we should maintain both.
class-properties
is only a switch for the class-features
plugin, it's not something by itself (https://github.com/nicolo-ribaudo/babel/blob/e70d0f03543058ae804bb7814dd7936ae9e0ace7/packages/babel-plugin-proposal-class-properties/src/index.js).
Then if the user adds
['proposal-class-features', { decorators: true }]
this would essentially override the previous config because it would run first, so it could loudly throw if it encounters public/private fields because they haven't been enabled on that instance of the plugin.
I don't think that this is what a user expects: decorators and class fields are different features (even if they need to be implemented together). Why would enabling decorators disable class fields?
Having the separate "switchers" plugin avoids this confusing behavior: if I enable decorators, it doesn't affect class fields being enabled/disabled.
Btw, the fact that you used both transform
and proposal
shows that this package's name shouldn't contain either (or should contain both), unless you meant that they should be two separated plugins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I had to choose, I would disable using the class-features
plugin directly, since I agree that this configuration would be confusing:
plugins: [
["@babel/plugin-class-features", { "classFields": true }],
["@babel/plugin-class-features", { "decorators": true }],
]
would be surprising if none of them are hidden inside a preset.
|
||
```sh | ||
yarn add @babel/plugin-class-features --dev | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is auto generated btw, see the script in scripts
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, please re-generate the README before
|
||
visitor: { | ||
Class(path, state) { | ||
if (this.file.get(versionKey) !== version) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll want this check on the PrivateName
visitor below too.
e70d0f0
to
5114047
Compare
Great work @nicolo-ribaudo! |
Thank you! Merging this PR is so relieving 🙂 |
cc @loganfsmyth @jridgewell I tried to move plugin-proposal-class-properties to the new "big" plugin. All the original tests are still passing 🎉
I split the class properties transformation logic in different functions, so that it can be reused. For example, to transform decorators we will need to reuse
buildPrivateNamesMap
,buildPrivateNamesNodes
,transformPrivateNamesUsage
andinjectInitialization
.Questions: (next meeting?)
@babel/plugin-transform-classes
?@babel/plugin-proposal-enhanced-classes
plugin because every feature is not compatible, so we would probably need to create another plugin which depends on this one with only class transpilation enabled.-proposal-
from this plugin's name now? e.g.@babel/plugin-enhanced-classes
or@babel/plugin-transform-enhanced-classes
.loose
for the different features separately?transform-classes
andproposal-class-properties
can be loose separately (this can be probably worked-around though).instanceFields
,staticFieldsAndMethods
,instancePrivateMethods
?instance
,static
-public
,private
-fields
,methods
,accessors
)publicInstanceFields
,privateStaticMethods
, ...)@babel/plugin-proposal-enhanced-classes
?@babel/plugin-proposal-class-features
?NOTE: This PR only "merges" the class properties plugin, but it creates the whole infrastructure.