Skip to content

Example of configurable preset. WIP.#3349

Closed
jmm wants to merge 2 commits intobabel:masterfrom
jmm:configurable-preset
Closed

Example of configurable preset. WIP.#3349
jmm wants to merge 2 commits intobabel:masterfrom
jmm:configurable-preset

Conversation

@jmm
Copy link
Copy Markdown
Member

@jmm jmm commented Feb 11, 2016

This isn't meant to be merged. For now it's just a counterpoint to #3331.

How this would work:

my-custom-preset.js

// Optionally pass an options hash like this:
var opts = {
  loose: true,
  modules: false,
};
var plugins = require("babel-preset-es2015/plugins")();

// Do whatever you want with plugins -- add, remove, reorder, add options,
// change options. Depending exactly how the resolution works, maybe require()
// them here.

module.exports = {plugins: plugins};

.babelrc

{
  "presets": ["my-custom-preset"]
}

package.json

{
  "devDependencies": {
    "babel-preset-es2015": "6.x",
    "my-custom-preset": "file:./my-custom-preset"
  }
}

@loganfsmyth
Copy link
Copy Markdown
Member

My concern with this is that it still makes the average user need to have tons of context. Why should someone need to know the names of the plugins inside the preset for instance.

@jmm
Copy link
Copy Markdown
Member Author

jmm commented Feb 11, 2016

@loganfsmyth For what use case? It depends what they want to do. If they want to remove / reorder / change options for a specific plugin why wouldn't they know the name?

@loganfsmyth
Copy link
Copy Markdown
Member

In my mind, presets are opaque, you shouldn't be thinking about specific plugins inside them. Modules like https://github.com/developit/modify-babel-preset are hacky misuses of the API. If a preset is configurable, I believe it should be up to the preset to decide exactly what mutations of plugin ordering/config it wants to guarantee support for.

What if in the future we find out we need to split a plugin into two plugins or something? Now it'll break anyone relying on the plugin name.

If we want to expose passing options to specific plugins, we can totally do that in my PR too, but then we'd be the ones handling passing the config around, not relying on users to do it right.

@jmm
Copy link
Copy Markdown
Member Author

jmm commented Feb 12, 2016

What if in the future we find out we need to split a plugin into two plugins or something? Now it'll break anyone relying on the plugin name.

How would you do that without breaking people depending on that plugin directly? It's not like they're only consumed via a black box of presets. BTW, that question brings up again the sustainability of a single version line.

If we want to expose passing options to specific plugins, we can totally do that in my PR too, but then we'd be the ones handling passing the config around, not relying on users to do it right.

The crux of the issue is whether it's important to introduce indirection of the plugin names. I'm not sure yet that's important. And if presets are going to be made that configurable via core changes I think we should consider consolidating it with the plugin mechanism first.

@loganfsmyth
Copy link
Copy Markdown
Member

How would you do that without breaking people depending on that plugin directly? It's not like they're only consumed via a black box of presets. BTW, that question brings up again the sustainability of a single version line.

Maybe we deprecate a plugin and create two new plugins to replace it? I believe that happened in Babel 5 with the function parameter logic.

The crux of the issue is whether it's important to introduce indirection of the plugin names. I'm not sure yet that's important. And if presets are going to be made that configurable via core changes I think we should consider consolidating it with the plugin mechanism first.

This definitely does seem like the core of it. I've clearly leaned more toward presets being opaque bundles of functionality. It seems like the primary gain in your approach is that we actually pull the plugin list directly from es2015 instead of copy/pasting it, which is definitely a bonus. I don't quite follow what you're getting at with consolidating it with the plugin mechanism first. though. Could you elaborate?

It's still not totally clear how this addresses the use-cases I mention in my PR. The main benefit I see from passing direct params is that users of your presets do not need to know or care about the specific details. Why should an average user need to know which plugins support a loose param when you can tell the preset to do it? In your proposal, is the idea that there would be a specific preset for loose still, as a standalone preset, but which drew its list of plugins from the primary one? What about a preset that is loose and with modules disabled? There's still a combinatorial explosion as more options are available, which is one of the big things I hope to avoid. There's no need to create a preset for every possible combination when you can configure options using preset config params.

@jmm
Copy link
Copy Markdown
Member Author

jmm commented Feb 13, 2016

@loganfsmyth

Maybe we deprecate a plugin and create two new plugins to replace it? I believe that happened in Babel 5 with the function parameter logic.

I don't think it can happen that way in v6 though, can it? The plugins are independent packages. Splitting one would break dependents unless in a semver major. And if you could do that as a semver major you could do it for a preset too.

This definitely does seem like the core of it. I've clearly leaned more toward presets being opaque bundles of functionality. It seems like the primary gain in your approach is that we actually pull the plugin list directly from es2015 instead of copy/pasting it, which is definitely a bonus.

Yeah...I'm not sure they need to be that opaque, and that seems like it'd only enable a select set of use cases without a bunch of overhead (indirection of plugin names or option names). And it requires changes to core.

The primary appeal that I see to my approach is:

  • Have dynamic access to the full configuration of the preset: don't need to copy and paste and don't necessarily need a bunch of slightly different presets.
  • Doesn't require changing core.

I don't quite follow what you're getting at with consolidating it with the plugin mechanism first. though. Could you elaborate?

Sure, here's what I mean: in v5 there wasn't a proper plugin options mechanism. I opened an issue proposing that. Sebstian put it on the roadmap for v6 and implemented it. He also moved forward with extracting transforms from core. I first heard about the preset concept from him on Slack. My immediate reaction was that I didn't like the idea of having two different mechanisms / option names for closely related things. So I said "what about rolling it into the plugin mechanism / option?". Sebastian didn't like that idea and IIRC the main reason was because he didn't want presets to take options.

Fast forward to after release of v6 where we have presets without options. Now people (including maintainers) are getting experience with the preset mechanism and identifying things they don't like about it, like inability to configure (e.g. omit the CJS transform when using es2015). So the idea of letting presets take options comes up again.

I didn't like the idea in the first place of having presets that don't take options as a separate mechanism from plugins. If presets gain options they're going to be converging on the plugin mechanism. If that's going to happen I want to have a discussion about why we need two separate concepts of plugin and preset and why we can't just have one concept of plugin that encompasses both, redefining what a plugin module can export. Unless there are good reasons for having two separate mechanisms (which originally was so that presets don't take options) I suspect it'd be simpler to just have one. I discussed this in T2756 the last time it came up, but that discussion just tapered off.

It's still not totally clear how this addresses the use-cases I mention in my PR.

I didn't address this well when I originally commented on your PR or in the code of this PR. I posted a comment on your PR later to clarify a bit, but I'll do it here.

The main benefit I see from passing direct params is that users of your presets do not need to know or care about the specific details. Why should an average user need to know which plugins support a loose param when you can tell the preset to do it?

If there's a param that's common to many plugins where you want the same setting for all of them, I can see the utility in that. (Obviously that must be the case with loose based on your proposal.) The modules param that would just omit / include a single plugin I'm a bit more meh on since you'd be able to do that anyway with my proposal, and I don't think it's a big deal to know the name of the plugin to do that, but I guess there's no harm in including it.

In your proposal, is the idea that there would be a specific preset for loose still, as a standalone preset, but which drew its list of plugins from the primary one? What about a preset that is loose and with modules disabled? There's still a combinatorial explosion as more options are available, which is one of the big things I hope to avoid. There's no need to create a preset for every possible combination when you can configure options using preset config params.

No, that's not the idea. I'm with you on avoiding a combinatorial explosion of similar presets. We can accomplish both things with what I'm suggesting:

  • Abstract out certain preset-level options as you want.
  • Provide full configurability beyond what your PR would provide.

Both without changing core.

I pushed a new commit here that illustrates this. Now the function I proposed does what your function does -- accepts an options hash and utilizes loose|modules to determine the output. (I copied and pasted some of your code.) So in a custom preset file you can call this function, optionally pass loose|modules, then still do whatever you want with the returned plugin array before exporting from the custom preset.

@loganfsmyth
Copy link
Copy Markdown
Member

I don't think it can happen that way in v6 though, can it? The plugins are independent packages. Splitting one would break dependents unless in a semver major. And if you could do that as a semver major you could do it for a preset too.

I was assuming it would be a rename, so the old plugin would essentially be left as-is, with two new plugins introduced, so semver would be preserved.

I think I still feel like merging them into one concept may just confuse users more. Why do they need to be the same?

I think the main thing for me is that "full configurability" seems unmaintainable. I don't actually want users to be able to jump in and change everything and anything. My PR limits the amount of configurability on purpose. If you need infinite configurability, I think making your own preset is still the right way to do. If we expose things as you say, we are locked in. People will start making assumptions about the data returned. What if we realize we need to reorder two plugins, and someone had set up a config of their own that does var tmp = [plugins[5], plugins[4]]; [plugins[4], plugins[5]] = tmp;, we can now never without it being a major-version breaking change, add any new plugins to es2015 because it would break anything that made assumptions about ordering.

Also, for your PR update, how would users actually use this? We push .babelrc-based configuration a ton because it's clearer and it keeps your config in a location more local relative to the files being converted. With your PR, a .babelrc can't actually call the plugins function, so users would only be able to use it when using the programmatic API, right?

@jmm
Copy link
Copy Markdown
Member Author

jmm commented Feb 17, 2016

I was assuming it would be a rename, so the old plugin would essentially be left as-is, with two new plugins introduced, so semver would be preserved.

So the old package would be abandoned and users would have to switch to the new ones to get updates? I guess that's conceivable -- not sure how likely.

I think I still feel like merging them into one concept may just confuse users more. Why do they need to be the same?

They don't need to be, but the opposite question is just as valid: why do they need to be different? (See below Options.) I argue that the current system is more confusing. They're already very closely related concepts: in general terms they're both something that you plug in to Babel to affect it's behavior. A user may want to transform arrow functions to ES5, or they may want to transform ES2015 to ES5. Why do there need to be 2 different abstractions and option names for plugging in something that accepts options and affects Babel's behavior?

There could just be a single concept, "plugin", that covers both current use cases. That is a higher level abstraction. Presets abstract away the specifics of there being x number of syntax / transformation plugin packages bundled together to accomplish something, but the user still has to be aware of a distinction between plugin and preset. Why is that important? (See below Options.) It could just be "I have to use plugin xyz to accomplish {{whatever}}, and it takes x, y, z options."

By users I of course mean users consuming those packages. Plugin authors obviously would need to understand what their package needs to export, but that's trivial compared to the other aspects of making those packages.

My PR limits the amount of configurability on purpose

I understand, but I'm curious how much of the use cases people have had for wanting configurable presets are actually served by that limited version. And then anytime someone wants to do something else with it it's going to be a feature request to add options to the preset.

If we expose things as you say, we are locked in. People will start making assumptions about the data returned. What if we realize we need to reorder two plugins, and someone had set up a config of their own that does var tmp = [plugins[5], plugins[4]]; [plugins[4], plugins[5]] = tmp;, we can now never without it being a major-version breaking change, add any new plugins to es2015 because it would break anything that made assumptions about ordering.

It all depends what interface you document. I'm suggesting an interface like this (shape of the export):

Exports a configuration object with a plugins array:

  {
    plugins: [
      [pluginName: string, pluginOpts?: object],
    ]
  }

I'm not picturing documenting this interface:

Exports a configuration object that deep equals:

  {
    plugins: [
      ["babel-plugin-transform-es2015-template-literals"],
      // ...
      ["babel-plugin-transform-regenerator", { async: false, asyncGenerators: false }],
    ]
  }

You could go further and explicitly say that the contents (items, order, etc.) are subject to change in any version.

Also, for your PR update, how would users actually use this? We push .babelrc-based configuration a ton because it's clearer and it keeps your config in a location more local relative to the files being converted. With your PR, a .babelrc can't actually call the plugins function, so users would only be able to use it when using the programmatic API, right?

No, they'd be able to use it with the CLI just as well as they would with any other custom preset, which is the alternative that you see to this idea:

If you need infinite configurability, I think making your own preset is still the right way to do.

To use this with the CLI you'd do as illustrated at the top: create a custom preset that depends on the base and exports whatever it wants, and include it in presets in .babelrc.

Also, I'm not opposed to making presets be able to take options in general (I never really liked the idea of them not), but if it's going to happen I hope it'll be informed by how well it'll solve the problems people have with presets now, and with due consideration for whether it should still be a separate system from plugins. See below Options.

Options

My understanding is that the original rationale for presets being a separate system from plugins was the distinction that presets wouldn't take options. If they're going to take options, what reason is there for them to be separate?

@hzoo
Copy link
Copy Markdown
Member

hzoo commented Aug 18, 2016

Closing this since for now we merged #3331

@hzoo hzoo closed this Aug 18, 2016
This was referenced Sep 7, 2016
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 7, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants