Skip to content
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

[WIP] Plugin Ordering #5735

wants to merge 13 commits into
base: master

[WIP] Plugin Ordering #5735

wants to merge 13 commits into from


Copy link

@hzoo hzoo commented May 15, 2017

Fixes #5623


TL;DR - basic topological ordering of plugins.

Currently this is just the minimal change necessary to make class properties and decorators work (not a final approach or anything and not done yet)

This PR adds 2 more properties to the Plugin object:

  • capabilities: an array of values (currently a string, maybe better as a number/semver like in package.json) to represent what a plugin can transform
  • dependencies: an array of values to represent which syntax needs to be transformed after this plugin is run (it depends on that syntax being there beforehand so it should go first)
  • before/after: an array of values to represent which plugin capabilities should be run before/after

This is technically not a breaking change since it's opt-in by the plugins but it would be easier done in 7.x?


  • can defer to separate discussion: how to specify dependencies and standardize on them (a string, package.json, semver)
    • maybe expose some symbols instead of a string? like babel.capabilities.arrowFunctions = Symbol()
  • handle cycles (dep A -> dep B -> dep C -> dep A) - just error with a good error message this probably isn't an issue and/or we will fix it when it's reported
  • do we need to handle presets? seems ok
  • later: handle multiple plugins providing the same capability - error with a good message because the plugin/preset is being included multiple times
  • add dependencies/capabilities for all internal plugins
  • how to handle multiple passes (deprecate passPerPreset and introduce a new option to enable it per preset individually?) - maybe not as important
  • how does this tie into preset-env (native environments provide the same capabilities and we need to use the same interface)

Use case

  • plugin ordering of official plugins (both in spec and proposals)
  • third party plugins ordering
  • fork on outputting syntax based on capabilities
  • better config validation

cc @benjamn, @rwjblue

Copy link

@mention-bot mention-bot commented May 15, 2017

@hzoo, thanks for your PR! By analyzing the history of the files in this pull request, we identified @loganfsmyth, @alxpy and @existentialism to be potential reviewers.

@hzoo hzoo requested a review from loganfsmyth May 15, 2017
@hzoo hzoo changed the title WIP plugin ordering [WIP] Plugin Ordering May 15, 2017
@hzoo hzoo requested a review from danez May 15, 2017
@hzoo hzoo requested a review from gaearon May 15, 2017
@@ -42,6 +42,8 @@ export default function ({ types: t }) {
return {
inherits: syntaxClassProperties,

capabilities: ["classProperties"],

This comment has been minimized.


hzoo May 15, 2017
Author Member

Opt-in so 3rd party plugins can also provide capabilities/dependencies. Right now it's just a string but maybe it should be a versioned number?

@hzoo hzoo removed the request for review from gaearon May 15, 2017
Copy link

@rwjblue rwjblue commented May 16, 2017

I like it, thanks for working on this! As I reviewed the sortPlugins logic I thought you might be able to take advantage of dag-map which is what ember-cli uses for a similar purpose.

Copy link
Member Author

@hzoo hzoo commented May 16, 2017

Thinking about it now, it's probably fine to not have every plugin have to have a capability anyway since it's opt-in and can be incremented added if someone needs it. It's easiest to do for proposals ironically.

Copy link
Member Author

@hzoo hzoo commented May 17, 2017

Ok as discussed on slack, maybe dependencies isn't a good word for this because for this specific case. as @rwjblue describes:

  • plugin-a literally requires that plugin-b exists (currently what inherits already does)
  • plugin-a wants to know that it is ran before plugin-b if it happens to exist (kinda like an optionalDependency and specifying order)
  • ok maybe as a first pass "dependencies" are optional, we keep inherits
sarupbanskota and others added 9 commits Jun 2, 2017
node_modules are required to exists so that flow can inference the types
@MarckK MarckK mentioned this pull request Jun 6, 2017
0 of 4 tasks complete
@babel-bot babel-bot added the Has PR label Jun 6, 2017
hzoo added 2 commits Jun 6, 2017

Use dagMap to improve plugin ordering

// Calculate the capabilities that each plugin provides, map to a single name.
// Multiple plugins with the same capability are not allowed as that can
// become a cyclic dependency.

This comment has been minimized.


hzoo Jun 9, 2017
Author Member

I don't think it will become a cyclic dependency - don't think there are any actual ones? Our primary goal is to fix plugin ordering in Babel itself (only syntax), then minifier, then 3rd party plugins (not as important).

It's just about telling the user if a plugin is included more than once just because it doesn't have to be.

Copy link
Member Author

@hzoo hzoo left a comment

Don't think it's controversial to land this as is:

  • removes passPerPreset (undocumented, buggy, shouldn't be a boolean flag for all presets anyway, not well known/used)
  • fix plugin ordering for class properties/decorators 👍
  • opt-in for other plugins while it's not required yet
Copy link

@codecov codecov bot commented Jun 13, 2017

Codecov Report

Merging #5735 into 7.0 will increase coverage by 0.04%.
The diff coverage is 88.31%.

Impacted file tree graph

@@            Coverage Diff             @@
##              7.0    #5735      +/-   ##
+ Coverage   85.09%   85.14%   +0.04%     
  Files         284      284              
  Lines        9911     9942      +31     
  Branches     2768     2769       +1     
+ Hits         8434     8465      +31     
+ Misses        976      974       -2     
- Partials      501      503       +2
Impacted Files Coverage Δ
...ges/babel-plugin-transform-decorators/src/index.js 94.28% <ø> (+0.95%) ⬆️
packages/babel-core/src/config/index.js 50% <ø> (ø) ⬆️
...bel-plugin-transform-class-properties/src/index.js 94.59% <ø> (ø) ⬆️
packages/babel-core/src/config/plugin.js 36.36% <33.33%> (-1.14%) ⬇️
packages/babel-core/src/config/option-manager.js 78.12% <92.15%> (+1.93%) ⬆️
...ckages/babel-core/src/transformation/file/index.js 87.25% <95%> (+0.05%) ⬆️
packages/babel-traverse/src/visitors.js 86.66% <0%> (+0.95%) ⬆️
packages/babel-traverse/src/path/modification.js 73.07% <0%> (+0.96%) ⬆️
packages/babel-traverse/src/path/context.js 87.06% <0%> (+1.72%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5cc1cbf...3e27d17. Read the comment docs.

@hzoo hzoo mentioned this pull request Jun 13, 2017
3 of 9 tasks complete
plugins.forEach((pluginTuple, index) => {
const plugin = pluginTuple[0];
if (!plugin.key) { plugin.key = `${unnamedPluginPrefix}${index}`; }

This comment has been minimized.


sarupbanskota Jun 30, 2017

we probably don't need this bit anymore, given that we're making name compulsory. i'll remove it in my naming PR

if (plugin.capabilities) {
plugin.capabilities.forEach((capability) => {
const existingPluginName = capabilitiesToPluginIdMap[capability];
if (existingPluginName !== undefined) {

This comment has been minimized.


sarupbanskota Jun 30, 2017

prob safer to just say if (existingPluginName), this way empty strings can also be caught. (although that shouldn't be allowed in the first place).

throw new Error("Plugin .after must be an array");
if (!Array.isArray(plugin.before)) {
throw new Error("Plugin .before must be an array");

This comment has been minimized.


sarupbanskota Jul 3, 2017

Naive thinking perhaps, but judging by the conditionals above this one, it may freak out if plugin.before / plugin.after isn't present.

@@ -31,7 +34,8 @@ export default class Plugin {
this.pre = plugin.pre;
this.visitor = plugin.visitor;
this.capabilities = plugin.capabilities;
this.dependencies = plugin.dependencies;
this.after = plugin.after;
this.before = plugin.before;

This comment has been minimized.


sarupbanskota Jul 3, 2017

based on my prev comment, might wanna do a this.x = plugin.x || []; to account for cases where its not present?

Copy link

@sarupbanskota sarupbanskota commented Jul 6, 2017

hi @hzoo, on #5911, I've synced work on this branch with 7.0. Went a little nuts trying to rebase, so I just did a merge. Would you pls review and merge it since I can't push directly to this branch?

Once that's done, I can get #5842 ready too - it's essentially good to go, but is failing because it depends on this branch which is currently failing.

cc @nathanhammond

@hzoo hzoo requested a review from jridgewell Jul 21, 2017
@danez danez closed this Aug 31, 2017
@danez danez reopened this Aug 31, 2017
@danez danez changed the base branch from 7.0 to master Aug 31, 2017
Copy link

@hulkish hulkish left a comment

I feel like maybe a stronger approach here could be to introduce some kind of lifecycle hooks concept... to start with would be those known plugins which require one in particular to come before the other.

Copy link

@sw-yx sw-yx commented Feb 5, 2019

hello, trying to catch up on this. What's blocking this today? is the TODO at the top up to date?

@JLHwung JLHwung removed the Has PR label Nov 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
Linked issues

Successfully merging this pull request may close these issues.

You can’t perform that action at this time.