babel-plugin-transform-hoist-nested-functions
Babel plugin to hoist nested functions to the outermost scope possible without changing their
contract.
Examples
Example 1 - basic hoisting
In
function renderApp () {
return renderStateContainer(
({value}) => renderValue(value)
);
}
Out
var _hoistedAnonymousFunc = ({ value }) => renderValue(value);
function renderApp () {
return renderStateContainer(_hoistedAnonymousFunc);
}
Example 2 - nested method hoisting
To enable this transformation, pass the methods: true
option to the plugin (see below).
The output code depends on the ES2015 Symbol
feature and the stage 2 class properties proposal.
You will most likely want to run babel-plugin-transform-class-properties
after transform-hoist-nested-function
.
In
class Foo {
bar () {
return () => this;
}
}
Out
const _hoistedMethod = new Symbol("_hoistedMethod"),
class Foo {
[_hoistedMethod] = () => this;
bar() {
return this[_hoistedMethod];
}
}
Motivation
Patterns like React "render callbacks",
that make heavy use of nested functions, incur the nonzero runtime cost of creating those
functions over and over. JavaScript engines don't always optimize this cost away.
To mitigate this cost, this plugin moves functions out of inner scopes wherever possible. A
function can be moved up through any scope that it does not reference explicitly. This is somewhat
analogous to what babel-plugin-transform-react-constant-elements
does (and in fact some of the same Babel machinery is applied).
Caveats
Experimental
This is a new, experimental plugin. Expect changes (adhering religiously to semver), and
please, please, PLEASE test and benchmark your code very thoroughly before using this in
anything important.
Not 100% transparent
While the plugin aims not to change the behavior of hoisted functions, the fact that they are
reused rather than recreated does have some visible consequences.
Consider the following code:
function factory () {
return function foo () {};
}
factory() === factory();
That last expression evaluates to false
in plain JavaScript, but is true
if foo()
has been
hoisted.
More fundamentally, references to hoisted inner functions are allowed to escape their enclosing
scopes. You should determine whether this is appropriate for your code before using this plugin.
Benchmarks
Here are benchmark results from the latest successful build on master
using Node
v4 (make your own with npm run benchmark
). The benchmark code is here -
each file exports a single function that is repeatedly run and timed by [Benchmark.js]
(https://benchmarkjs.com).
From these preliminary results, it appears that hoisting functions this way can in fact improve
performance, at least in principle; but the benefit may not always be significant.
Installation
$ npm install --save-dev babel-plugin-transform-hoist-nested-functions
Usage
Via .babelrc
(Recommended)
.babelrc
{
"plugins": ["transform-hoist-nested-functions"]
}
{
"plugins": [
["transform-hoist-nested-functions", {
"methods": true
}],
"transform-class-properties"
]
}
Via CLI
$ babel --plugins transform-hoist-nested-functions script.js
Via Node API
require("babel-core").transform("code", {
plugins: ["transform-hoist-nested-functions"]
});
Development
Use npm v3: npm install -g npm@3
git clone https://github.com/motiz88/babel-plugin-transform-hoist-nested-functions
cd babel-plugin-transform-hoist-nested-functions
npm install
npm run test:local
See package.json for more dev scripts you can use.
Contributing
PRs are very welcome. Please make sure that test:local
passes on your branch.