-
-
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
[RFC] Pass per preset #3281
[RFC] Pass per preset #3281
Conversation
This introduces "pass per preset" feature, spawting a new traversal for each preset in case if the `passPerPreset` is `true` (default is `false`). This gives opportunity to define "before" and "after" presets, mimicking a similar feature from Babel 5. A rationally for this is to make plugins as short as possible, and handled only needed nodes, not afrading potential collisions in case if presets are merged.
Current coverage is
|
passPerPresset: { | ||
description: "Whether to spawn a traversal pass per a preset. By default all presets are merged.", | ||
type: "boolean", | ||
default: false |
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.
We could probably get away with enabling this by default in Babel 6. Otherwise we could just leave this disabled and enable it in Babel 7.
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 can add hidden: true
so it doesn't show up on the website I think. cc @thejameskyle
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 don't know what you mean by showing up on the website?
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.
@thejameskyle, I think these options are meant: http://babeljs.io/docs/usage/options/
This looks awesome @DmitrySoshnikov! Code looks good although I'm not sure how this impacts the other non-plugin options. A preset could specify an option like I'll leave this open for a day or two to get feedback from @amasad, @hzoo, @loganfsmyth, @jmm etc if they have any thoughts. |
Having an external flag that controls this seems really weird to me. I could see flagging a preset itself as wanting its own pass. What is the motivation for making this now something that users need to know about? Babel's config is already extremely hard for general users to figure out, adding more flags is something I find scary. If our goal is to control ordering for presets, I think we'd be better off by making that an explicit preset-level config flag. |
Yep, totally agree that it might be an unnecessary option to be controlled by users, and more is an implementation detail. The only reason I made it yet via the flag, is to preserve the existing state, not causing potential breakages, and once we'll have fully tested in production that things work fine with "pass per preset", we can delete the flag. But yeah, I don't wanna make it a user option (maybe we can make it an "internal flag" somehow for now?). Or, we can go without the flag right away, I'm fine with this either. Let me guys know. Re: preset options. If preset options are just nested sub-options, and practically may have anything that may appear at top-level, then we should handle all of them of course, not only plugins (the same what
This arbitrary recursive nested sub-structures seems a bit harder to maintain, and to deal with for users. Probably we can stick with come practical use-cases for now, e.g. allow in presets only limited sub-set of possible options, and don't allows presets within presets, within presets. Unless again there is a practical implication to support it, in which case we can handle it. |
Re: Preset options/more global options could be useful but I think @kittens didn't want to make presets that complicated? |
Sounds like @loganfsmyth's suggestion could be better in the long term? If we want to be safe we should leave it off by default? |
Let's make sure it's not a public option. We can land this and start dogfooding this our self and then enable it by default in Babel 7. |
} | ||
}, | ||
|
||
passPerPresset: { |
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 you add an _
to the name so that we make sure it's a private option?
Other than that, code looks good to me. |
if (opts.passPerPresset) { | ||
opts.presets = this.resolvePresets(opts.presets, dirname, (preset, presetLoc) => { | ||
if (preset.plugins) { | ||
preset.plugins = OptionManager.normalisePlugins(presetLoc, dirname, preset.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.
@kittens: if we want to handle other options in presets (except plugins
), then we could call mergeOptions
here recursively, passing options "to merge into" as a parameter. So it'll be preset options, but not just top-level this.options
, when it calls merge(this.options, opts);
below.
It may look a bit weird though as mergeOptions(preset, preset, ...);
. Or we can refactor mergeOptions
since currently it does much more than actual merging. It does also normalization, e.g. preset.plugins = OptionManager.normalisePlugins(...)
-- that exactly what we need. We don't wanna merge preset into itself, but we want to merge e.g. nested stuff in the presets like envOpts
if they appear in the preset.
Or, better, to limit presets only to one nested level, and handle only small sub-set of options 😄 Let me know.
Ok, I did some manual testing of the API with var code = babel.transform('type Foo = number; let x = (y): Foo => y;', {
passPerPreset: true,
presets: [
// First preset with our plugin, "before"
{
plugins: [
new Plugin({
visitor: {
Function(path) {
var node = path.node;
var scope = path.scope;
var alias = scope
.getProgramParent()
.getBinding(node.returnType.typeAnnotation.id.name)
.path
.node;
console.log(alias.right.type); // NumberTypeAnnotation
}
}
})
]
},
// ES2015 preset
require('babel-preset-es2015'),
// Third preset for Flow.
{
plugins: [
require('babel-plugin-syntax-flow'),
require('babel-plugin-transform-flow-strip-types')
]
}
]
}).code;
console.log(code);
// "use strict";
//
// var x = function x(y) {
// return y;
// }; |
Awesome stuff @DmitrySoshnikov! Would be cool if we can turn your example into a test though |
I wasn't following this too closely, but I had the same thought as @loganfsmyth:
And I thought people agreed with that. E.g.:
But now we're documenting it? https://github.com/babel/babel.github.io/pull/710/files https://github.com/babel/babel.github.io/pull/711/files EDIT: oh...and the release notes https://github.com/babel/babel/releases/tag/v6.5.0 And people are already talking about it in other projects: |
I put in the notes
If we don't want it there, we can remove it from the docs (just revert those PRs)? Do you think we should clarify it further this is a experimental feature that may change to the above? |
I was under the impression it wasn't going to be documented as public API at all for the time being.
At this point we could make an additive change, but couldn't change it in a way that breaks BC except in a major. |
I thought it would be hard to know how it would be used (unless we are just testing it ourselves). I mean either way it's released as an option so people would be using it right? |
I could have misunderstood, but that's basically what I thought the plan was -- make it available for people involved in these discussions to try it out without committing to it as public API yet. E.g.:
And if the intent was for it to be public, what was the purpose of this change?: --- a/packages/babel-core/src/transformation/file/options/config.js
+++ b/packages/babel-core/src/transformation/file/options/config.js
@@ -185,9 +185,10 @@ module.exports = {
type: "string"
},
- passPerPresset: {
+ passPerPreset: {
description: "Whether to spawn a traversal pass per a preset. By default all presets are merged.",
type: "boolean",
- default: false
+ default: false,
+ hidden: true,
},
}; |
Yeah I messed up here then - I just was documenting stuff in the changelog so I figured we could put it in and I forgot we were planning on doing this. No one noticed so I guess it's public now 😅 anyway. |
In case there are situations like this in the future maybe we should come up with a better way to indicate this kind of thing. Would a label work for that do you think? I know doing the excellent changelongs is a lot of work (thank you! 😃) and it must be easy to miss / forget about this kind of thing that's just indicated in the comments, from weeks ago at that. |
Yeah for sure - I think part of it was that we waited a while for this. Yeah a label for this would be good idea - what would be a good label? just like |
Yeah, I'd say just something straightforward that would be likely to get the attention of someone merging / adding docs / doing a release and tip them off that it shouldn't be documented / mentioned in the release notes, etc. If necessary we can also start adding descriptions of the labels somewhere -- in |
Ok https://github.com/babel/babel/wiki/PR-Labels. I cleaned up all the old issue labels - and maybe we don't need some of the other ones either. |
We should just backtrack and hide it and discourage it's usage. It's just going to add noise to the conversation about how we make plugins work well together in the same traversal, I don't think this is a good solution but it's a temporary one to unblock specific use-cases that we don't cater to very well. |
Without this option the plugins work is unpredictable. I'd say it should be documented (just for now), and then make a default behavior: "each preset in its own pass". The issue was raised by several use-cases already -- isn't it enough to make it clear that current design is kinda broken? @kittens, it can be a temporary solution for sure, as long as there is a guarantee that the needed information is still preserved (the "lossy" thingy #3335 (comment) doesn't seems solves the issue fully, and there is a chance to end up with even more complicated and frustrating documentation). That's said, I think we can just make "pass per preset" a default behavior, don't document it to avoid "noise to the conversation", and things "will just work". |
It could be just a note: "NOTE: at top-level or within one preset, all plugins are merged and ran in one traversal pass. However, each preset runs in its own pass.". Something like this, and that's it. (I'm sure you guys can phrase it even more clearly). This will avoid mentioning the Babel's issue, and mentioning this flag. |
@DmitrySoshnikov Just wanted to say thanks for this PR. I've been experimenting with doing a sequential pass with babel so that I can convert stateless, functional React components into
(I'm ignorant to the rationale, but I originally assumed the orders would be defined by nested arrays, similar to Cerebral Signals, but that format is already widely used for passing options to plugins.) I know this feature & syntax will be changing, but I'm glad this PR is out in the wild for testing :) |
Suggestion if this situation comes up in the future: As for this being temporary, the What I'm not sure is whether preset should be the only form of separation, or whether plugins should be able to somehow indicate that they belong to some set of plugins that require strict ordering |
Thanks, that's probably a good idea. |
I have two thoughts about this:
I'm in the situation that I want to publish 2 babel-plugins, where the A must run after B, if B is used as well. I'm planning on doing this by setting custom values in |
@fab1an It would also easily solve the problem most users have with |
Although Babel appears to support an undocumented options.passPerPreset option (see babel/babel#3281), I have not been able to convince myself that it properly separates plugins into a pass that runs before presets. This commit enforces that separation, while still merging all presets together. In other words, there will still be just one pass in the default case, unless the developer provides some additional plugins.
Rationale
See discussion and the issue description in #6730: Plugins order is not respected.
This introduces "pass per preset" feature, spawting a new traversal for each preset in case if the
passPerPreset
istrue
(default isfalse
).This gives opportunity to define "before" and "after" presets, mimicking a similar feature from Babel 5. A rationally for this is to make plugins as short as possible, and handle only needed nodes, not dealing with "unrelated" node in order to avoid potential collisions in case if presets are merged.
RFC: design notes
Currently I handle only
plugins
in presets. Are there actual practical use case when a preset may define its own options, other thanplugins
? The options per preset are inherited from main options, overriding needed fields, such asplugins
.Test plan
make test
. Will add more tests "per preset" after main review.