Bublé

the blazing fast, batteries-included ES2015 compiler

What is Bublé?

Bublé is an ES2015+ compiler. It takes your ES2015/16 JavaScript code and turns it into code that can run in today's environments, including old versions of Node.js and Internet Explorer.

As the name suggests, Bublé is heavily inspired by (and indebted to) Babel – but there are some key differences:

  • Bublé limits itself to ES features that can be compiled to compact, performant ES5 (plus JSX)
  • There are no plugins or presets – less extensibility, but also zero configuration
  • Code is only altered where necessary – your formatting and code style remain intact
  • It's comparatively tiny and much faster

Complete spec compliance is impossible – some things just can't be expressed in ES5. Other things can be, but not efficiently. Recognising this, Bublé will prevent you from writing code that can't be compiled well – for...of loops, the regular expression u flag, and so on. See the Unsupported features section for more information.

Compiling your first file

Before we begin, you'll need to have Node.js installed so that you can use npm. You'll also need to know how to access the command line on your machine.

The easiest way to use Bublé is via the Command Line Interface (or CLI). For now, we'll install it globally (later on we'll learn how to install it locally to your project so that your build process is portable, but don't worry about that yet). Type this into the command line:

npm install buble --global # or `npm i buble -g` for short

You can now run the buble command. Try it!

buble

Because no arguments were passed, Bublé prints usage instructions. This is the same as running buble --help, or buble -h.

Let's create a simple project:

mkdir my-buble-project
cd my-buble-project

Create a file containing some ES2015 code:

echo "const answer = () => 42;" > input.js

Run Bublé on it:

buble input.js

This will print the bundle straight to stdout:

var answer = function () { return 42; };

You can save the output to a file like so:

buble input.js --output output.js # or buble input.js -o output.js

(You could also do buble input.js > output.js, but as we'll see later, this is less flexible if you're generating sourcemaps.)

Congratulations! You've compiled your first ES2015 file with Bublé.

Supported features

More features may be added in future. Currently:

Arrow functions (transforms.arrow)

// in
var squares = numbers.map( n => n * n );

// out
var squares = numbers.map( function ( n ) { return n * n; } );

Classes (transforms.classes)

Note: Bublé assumes you're using the class keyword correctly – it doesn't stop you from instantiating a class without new, for example, and doesn't quite adhere to the spec as regards enumerability of methods, etc. Think of Babel's 'loose' mode, except looser.

// in
class Circle extends Shape {
  constructor ( radius ) {
    super();
    this.radius = radius;
  }

  area () {
    return Math.PI * Math.pow( this.radius, 2 );
  }
}

// out
var Circle = (function (Shape) {
  function Circle ( radius ) {
    Shape.call(this);
    this.radius = radius;
  }

  Circle.prototype = Object.create( Shape && Shape.prototype );
  Circle.prototype.constructor = Circle;

  Circle.prototype.area = function area () {
    return Math.PI * Math.pow( this.radius, 2 );
  };

  return Circle;
}(Shape));

Object shorthand methods and properties (transforms.conciseMethodProperty)

// in
var person = {
  name,
  age,
  sayHello () {
    alert( 'hello! my name is ' + this.name );
  }
};

// out
var person = {
  name: name,
  age: age,
  sayHello: function () {
    alert( 'hello! my name is ' + this.name );
  }
};

Template strings (transforms.templateString)

Note: Tagged template strings are not supported, unless you use the dangerousTaggedTemplateString transform. See Dangerous transforms.

// in
var message = `
  hello ${name}!
  the answer is ${40 + 2}`.toUpperCase();

// out
var message = ("\n  hello " + name + "!\n  the answer is " + (40 + 2)).toUpperCase();

Object and array destructuring (transforms.destructuring and transforms.parameterDestructuring)

// in
var [ first, second, third ] = nodes;
var { top, left } = first.getBoundingClientRect();

function rect ({ x, y, width, height, color }) {
  ctx.fillStyle = color;
  ctx.fillRect( x, y, width, height );
}

// out
var first = nodes[0], second = nodes[1], third = nodes[2];
var ref = first.getBoundingClientRect(), top = ref.top, left = ref.left;

function rect (ref) {
  var x = ref.x;
  var y = ref.y;
  var width = ref.width;
  var height = ref.height;
  var color = ref.color;

  ctx.fillStyle = color;
  ctx.fillRect( x, y, width, height );
}

Default parameters (transforms.defaultParameter)

// in
function foo ( options = {} ) {
  if ( options.bar ) {
    // code goes here
  }
}

// out
function foo ( options ) {
  if ( options === void 0 ) options = {};

  if ( options.bar ) {
    // code goes here
  }
}

Block scoping (transforms.letConst)

// in
var x = 1;

if ( a ) {
  let x = 2;
  console.log( x );
}

console.log( x );

// out
var x = 1;

if ( a ) {
  var x$1 = 2;
  console.log( x$1 );
}

console.log( x );

Binary and octal literals (transforms.numericLiteral)

// in
assert.equal( 0b101010 === 0o52 );

// out
assert.equal( 42 === 42 );

Exponentiation operator (transforms.exponentiation)

// in
var cubed = x ** 3;
a **= b;

// out
var cubed = Math.pow( x, 3 );
a = Math.pow( a, b );

Computed properties (transforms.computedProperty)

// in
var obj = {
  [a]: 1
};

var obj = {};
obj[a] = 1;

Unicode regular expressions (transforms.unicodeRegExp)

// in
var regex = /a💩b/u;

// out
var regex = /a(?:\uD83D\uDCA9)b/;

Object spread and rest

This isn't part of ES2015 or ES2016 – it's currently a stage 3 proposal – but since it's commonly used with JSX, it's supported in Bublé.

You may need to polyfill Object.assign depending on your target environment; alternatively you can use the objectAssign option to specify an alternative.

// in
var obj = { ...x };

// out
var obj = Object.assign({}, x);

Trailing commas in function calls and declarations

// in
function f(
  a,
  b,
) {
}
f(1, 2,);

// out
function f(
  a,
  b
) {
}
f(1, 2);

JSX elements

See below.

Unsupported features

Bublé only transpiles language features – it doesn't attempt to shim or polyfill the environment. If you want to use things like array.findIndex(...) or 'x'.repeat(3) or Map and Set you'll have to bring your own polyfill (check out core-js or es6-shim).

It also refuses to transpile things that result in ES5 code with size or performance hazards (this list isn't set in stone! It might support some of these features in future), or which are altogether impossible to transpile to ES5.

With the exception of modules (if you're using an ES2015 module bundler such as Rollup), you should probably avoid these features until they have widespread native support.

Dangerous transforms

Certain ES2015 features are useful, but costly to convert to ES5 while adhering closely to spec. In these cases, Bublé offers 'dangerous transforms', which generate highly efficient ES5 code but with important caveats.

Enable these by adding them to the transforms option (see Command line options or Using the JavaScript API).

dangerousForOf

// in
for ( let div of document.querySelectorAll( 'div' ) ) {
  div.style.backgroundColor = randomColor();
}

// out
for ( var i = 0, list = document.querySelectorAll( 'div' ); i < list.length; i += 1 ) {
  var div = list[i];

  div.style.backgroundColor = randomColor();
}

The ES6 for-of statement works with iterable values, which includes any Array, String, Map, Set, or NodeList (think document.querySelectorAll('div')), but also any other object with a [Symbol.iterator] method, or the return value of a generator function.

Since collections (Map, Set etc), Symbol, and generators aren't supported in ES5 environments, they are not supported by dangerousForOf. Instead, it will assume the iterable value to be just an array-like object with length property, this includes arrays, strings and nodelists.

This is very similar to the approach taken by TypeScript. Unless you're doing something crazy, code written with this transformation in mind will continue to work when you stop using it in favour of using native for-of support.

dangerousTaggedTemplateString

// in
get`http://example.com/foo?bar=${bar + baz}&quux=${quux}`;

// out
get([ "http://example.com/foo?bar=", "&quux=", "" ], bar + baz, quux );

This differs from spec in that the first argument to get doesn't have a raw property. The vast majority of custom template string interpolators (e.g. get in this case) don't use raw, and so simply passing an array of string literals is fine.

Custom string interpolators that work with dangerousTaggedTemplateString will continue to work when you stop transpiling template strings altogether.

Command line options

If you're targeting more modern environments, you may not need to transform everything – for example, current versions of Node.js and most modern browsers already support shorthand object properties and methods.

You can tell Bublé to skip unnecessary transformations by telling it which environments you're targeting:

echo "let obj = { x, y };" > input.js
buble input.js --target chrome:48,node:5,safari:9,edge:12 -o output.js
cat output.js
# -> "var obj = { x, y };"

In this example, all the target environments support shorthand object properties, but Safari doesn't support let.

You can get more granular with the --yes and --no options:

buble input.js --target chrome:50 --yes arrow,destructuring

Here, we're telling Bublé that we do want to transforms arrow functions and destructuring, even though they're supported in our target environment (Chrome 50).

Preventing errors

By adding --no modules we're also ensuring that Bublé won't throw an error if it encounters import and export statements, which Chrome 50 doesn't support. This is useful if you're going to pass the resulting code to a module bundler, for example.

List of transforms

The following can be used with --yes and --no (or with the transforms option, if you're using the JavaScript API):

  • arrow
  • classes
  • collections
  • computedProperty
  • conciseMethodProperty
  • constLoop
  • dangerousForOf
  • dangerousTaggedTemplateString
  • defaultParameter
  • destructuring
  • forOf
  • generator
  • letConst
  • modules
  • numericLiteral
  • parameterDestructuring
  • reservedProperties
  • spreadRest
  • stickyRegExp
  • templateString
  • unicodeRegExp

Emitting named function expressions

By default, Bublé will create named function expressions from e.g. class methods, which is useful for debugging. Due to an IE8 bug whereby function expression names become part of the parent scope, this can cause bugs. Disable named function expressions with --no-named-function-expr – equivalent to namedFunctionExpressions: false in the JavaScript API.

Compiling multiple files

You can compile a whole directory of files in one go like so:

buble -i src -o dest

The same options apply as for individual files – except that you must include the --output/-o option.

Sourcemaps

To generate separate sourcemap files – i.e. an output.js.map file to go along with output.js – use the --sourcemap or -m flag:

buble -i input.js -m -o output.js

To append the sourcemap as an inline data URI, use -m inline:

buble -i input.js -m inline -o output.js

If you're using the JavaScript API, you can control the sources and file properties of the sourcemap by passing in the following options:

var output = buble.transform( input, {
  file: 'output.js',
  source: 'input.js'
});

var map = output.map;

The returned map object is a version 3 sourcemap. For convenience it has two methods – toString, which generates a JSON encoding, and toUrl, which generates a data URI:

// to create a separate .map file
output.code += '\n//# sourceMappingURL=output.js.map';
fs.writeFileSync( 'output.js', output.code );
fs.writeFileSync( 'output.js.map', output.map.toString() );

// or, to create an inline sourcemap
output.code += '\n//# sourceMappingURL=' + output.map.toUrl();
fs.writeFileSync( 'output.js', output.code );

Adding a prepublish hook

Coming soon...

Using Bublé with ES2015 modules

Bublé doesn't support import and export statements, because they don't have an ES5 equivalent. (CommonJS – i.e. require and exports – are features of the environment rather than features of the language, and conversion between the two forms is less straightforward than you might imagine.)

If you're only targeting a Node.js environment, and don't want to bundle your modules, you should continue to use require and exports.

If you're targeting browsers instead of (or as well as) Node.js, or want to bundle your code for the many advantages it brings, you should use import and export but tell Bublé not to worry about it by disabling the modules transformation (which just throws an error):

# on the command line
buble input.js --no modules > output.js
// in JavaScript
var output = buble.transform( input, {
  transforms: {
    modules: false
  }
});

Then, once you've got your compiled modules, you can bundle them with a bundler that supports ES modules, such as Rollup (with rollup-plugin-buble) or Webpack 2 (with buble-loader).

Using the JavaScript API

As well as the command line interface, Bublé exposes a JavaScript API, which is useful for (for example) creating integrations with other build tools.

var buble = require( 'buble' );

var input = 'const answer = () => 42;';
var output = buble.transform( input );

console.log( output.code ); // 'var answer = function () { return 42; };'
console.log( output.map ); // { version: 3, ... }

See the Sourcemaps section for more information on how to handle output.map.

Options

The second argument to buble.transform is an optional options object:

var output = buble.transform( input, {
  // corresponds to --target – if absent, everything will be
  // compiled to ES5
  target: { chrome: 48, firefox: 44 },

  // corresponds to --yes (true) and --no (false) – overrides
  // the settings derived from `target`
  transforms: {
    arrow: true,
    modules: false,
    dangerousForOf: true
  },

  // used for sourcemaps
  file: 'output.js',
  source: 'input.js',

  // custom JSX pragma (see below)
  jsx: 'NotReact.createElement',

  // custom `Object.assign` (used in object spread)
  objectAssign: 'angular.extend',

  // prevent function expressions generated from class methods
  // from being given names – needed to prevent scope leak in IE8
  namedFunctionExpressions: false
});

JSX

In addition to ES2015/16, Bublé supports JSX, which is used in React and similar libraries:

// input
ReactDOM.render(
  <h1>Hello {name}!</h1>,
  document.querySelector( 'main' )
);

// output
ReactDOM.render(
  React.createElement( 'h1', null, "Hello ", name, "!" ),
  document.querySelector( 'main' )
);

You can specify a pragma other than React.createElement using the jsx option illustrated in the previous section.

If React (or whatever other JSX library you're using) isn't available in the global namespace, you're responsible for importing it into the file:

// ES module
import * as React from 'react';

// CommonJS
const React = require( 'react' );

Note: Bublé does not currently optimise JSX expressions by, for example, hoisting static elements. Coming soon!

Frequently asked questions

Why is it called Bublé?

Blame Keith Cirkel.

Will you support feature x?

It depends. If support for a feature can be added in a way that doesn't bloat out the code that Bublé compiles, then yes, possibly. Raise an issue and we'll discuss it.

Can we have plugins?

No. The whole point of Bublé is that it's quick to install and takes no mental effort to set up. Plugins would ruin that.

How is Bublé so fast?

The traditional approach to code transformation is to generate an abstract syntax tree (or AST), analyse it, manipulate it, and then generate code from it. Bublé only does the first two parts. Rather than manipulating the AST, Bublé manipulates the source code directly, using the AST as a guide – so there's no costly code generation step at the end. As a bonus, your formatting and code style are left untouched.

This is made possible by magic-string.

Forgoing the flexibility of a plugin system also makes it easy for Bublé to be fast.

What does Babel do better than Bublé?

Lots of things. It's mature, battle-tested and has a huge and enthusiastic community around it. It's extremely flexible, allowing you to create your own transformation plugins, and has a large ecosystem of existing plugins.

If you need the additional power and flexibility, or don't share Bublé's opinions about spec compliance, you should continue to use Babel.