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

Cache plugins and presets based on their identity #6350

Merged
merged 2 commits into from Oct 2, 2017

Conversation

loganfsmyth
Copy link
Member

Q                       A
Fixed Issues
Patch: Bug Fix? N
Major: Breaking Change? Y
Minor: New Feature?
Tests Added/Pass?
Spec Compliancy?
License MIT
Doc PR
Any Dependency Changes?

This changes how Babel approaches caching and execution of plugins and presets.

In Babel 6

Plugins

The plugin functions in Babel 6 where only ever executed a single time. This was done for performance reasons because plugins do a bunch of work up front, like calling template and other stuff. The downside of this is that plugin options when used like:

plugins: [
  ['transform-thing', {someFlag: true}],
]

where only available during execution of the plugin itself in its handler functions. e.g.

visitor: {
   Identifier() {
     console.log(this.opts);
   },
}

Presets

The preset functions in Babel 6 run every time a file is compiled by Babel. This slows down the process of loading Babel's configuration.

With this PR

With this, up-front API of both plugins and presets is standardized as

function(api, options, { dirname }) { ... }

so plugins may access their options up front during initialization time.

Another big benefit of this is that now plugin options can toggle plugin behavior more aggressively. If the options don't require a feature, you could for instance entirely omit the visitor functions for those nodes to potentially avoid extra work.

Plugins can also now toggle inherits blocks based on options, so if you had a syntax plugin that you only wanted to enable some of the time, it can now be easily toggled.

What has changed to allow this

To achieve this, Babel needs to know how to cache a plugin or preset's execution result, and most importantly, when to consider a plugin or preset invalidated. With this, Babel's config loading has several layers of caching.

A give plugin/preset is tied to its location in the configuration. If you have a config like

{
  plugins: [
    ['my-plugin', {option: true}],
    ['my-plugin', {option: false}],
  ],
}

the plugin construction function will be called twice, with each set of options.

If the .babelrc containing this config changes its mtime, each plugin will be re-executed. For .babelrc.js files, this invalidation behavior will depend on the programmatic API defined for those in https://github.com/babel/babel/pull/5608/files#diff-2f4602ba055eb6ec98d6bac3161d43ffR67.

Importantly, plugins and presets also have access to this programmatic API, the same way .babelrc.js files do, do if a preset depends on some kind of global state like an environmental variable, it can do

api.cache.using(() => process.env.SOME_VAR === "value")

to essentially say that the preset has two possible states, one with and one without a global value flag.

Finally, in #6326 I talked briefly about caching for programmatic options, so that also comes into play here. Take a call like

babel.transform("", { presets: ['es2015'] })
babel.transform("", { presets: ['es2015'] })

the question is, how often does the initialization function for es2015 get executed? In this case, the function will be executed twice, and because the plugins used in this preset depend on it as a parent configuration, all of those plugins have to be re-executed too.

To address this in a way that should be workable for the 99% case, this PR makes it so that doing this will only execute the preset once.

const presets = ['es2015'];
babel.transform("", { preset })
babel.transform("", { preset })

because Babel can see that it was passed exactly the same object for presets both times, it will re-use all of the plugin and preset initialization that happened in the previous call to Babel.

The only downside to this approach, I've called out in #6326 (comment), but I think realistically, it is an edge case that, if it is hit, is easily fixable by cloning the array instead of mutating it.

@loganfsmyth loganfsmyth added the PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release label Sep 30, 2017
@babel-bot
Copy link
Collaborator

babel-bot commented Sep 30, 2017

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/5144/

@loganfsmyth loganfsmyth force-pushed the plugin-preset-caching-updated branch 3 times, most recently from 753e900 to eac4eff Compare October 2, 2017 07:00
@loganfsmyth loganfsmyth merged commit 3d43a6e into babel:master Oct 2, 2017
@loganfsmyth loganfsmyth deleted the plugin-preset-caching-updated branch October 2, 2017 21:40
@hzoo hzoo mentioned this pull request Apr 19, 2018
5 tasks
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 5, 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 PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants