Table of Contents
1. About this book
ES6 (whose official name is ECMAScript 2015) is the latest version of JavaScript.
Topics covered in this book:
- Configuring Babel.
- Setting up ES6 projects that are compiled to ES5 via Babel:
- Deploying ES6 in browsers via Babel and webpack.
- Deploying ES6 in Node.js, by statically or dynamically compiling it via Babel.
Versions used in this book:
- Babel 6
- webpack 1.x
- Node.js 5
More information on this book:
- Required knowledge: You should already know ES6. If you don’t, consult my book “Exploring ES6” which you can read online for free.
- Feedback, submitting errata: consult the home page of this book.
More information on Babel:
- Babel has an official forum.
-
The new Babel handbook has two parts:
- User Handbook: how to set up/configure Babel and more.
- Plugin Handbook: how to create plugins for Babel.
Acknowledgements. Many thanks go to the Babel team (especially Logan Smyth, James Kyle and Sebastian McKenzie) for answering questions I had while writing this book.
2. Deploying ECMAScript 6
This chapter describes the options you have for deploying ECMAScript 6 in current JavaScript environments. It is selective w.r.t. the amount of tools it covers. If you want a comprehensive list of tools, I suggest you look at Addy Osmani’s “ECMAScript 6 Tools”.
2.1 Using ECMAScript 6 today
What options do you have for using ECMAScript 6 today?
- Current JavaScript engines (browsers, Node.js, …) already support much of ES6. You can look up which features are supported where:
- In Kangax’ “ECMAScript 6 compatibility table”.
- William Kapke’s “Node.js ES2015 Support” lists only the Node-related entries in Kangax’ table.
- ES6 REPLs (interactive command lines) let you try out smaller pieces of code. A section in this chapter lists available options.
- Compiling from ES6 to ES5: Especially if you take support for legacy engines into consideration, compiling ES6 to ES5 will be the only viable option for using ES6 for quite a while. Compiling from source code to source code is also called transpiling. You can transpile ES6 either before deployment (statically) or at runtime (dynamically). This chapter explains how that works and what other ES6 tools and libraries there are.
The nice thing about ES6 is that it is a superset of ES5, which means that all of your ES5 code bases are already valid ES6. This helps with adopting ES6-specific features, because you can do so incrementally.
2.2 ES6 REPLs
There are many REPLs (command lines) out there for interactively playing with ES6. The obvious choices are the interactive online playgrounds of the following projects:
Additionally, Babel brings ES6 support to the Node.js REPL via its babel-node
tool.
2.3 Transpilation tools
There are three essential choices that you have to make for transpilation:
- A transpiler (for your code)
- A package manager (to install existing libraries)
- A module system (for the complete app)
Note that the choices are not completely independent; not every module system works with every package manager etc. The next sections explain each of these choices in more detail.
2.3.1 Choosing a transpiler
A transpiler compiles your ES6 code to ES5. Popular choices are:
- Microsoft TypeScript: Is basically ECMAScript 6 plus optional type annotations.
- Google Traceur: is the first popular ES6 transpiler. Pronounced French, /tʁa.sœʁ/; an English approximation is “truh-SIR” (source, listen to native French speakers pronounce this word).
- Babel: is a newer ES6 transpiler that has become the de-facto standard. Babel supports React’s JSX syntax in addition to ES6. Pronounced “babble” (think Australian accent – Babel’s creator, Sebastian McKenzie is Australian).
-
Closure Compiler: can be used as a static transpiler from, e.g., ECMAScript 6 to ECMAScript 5, if you use the following two command line options:
- Specify the input language:
--language ECMASCRIPT6_STRICT
- Specify the output language:
--language_out ECMASCRIPT5
- Specify the input language:
In principle, transpilation can be done either:
- statically (before deployment) or
- dynamically (at runtime)
2.3.1.1 Static transpilation
As a build step, TypeScript, Traceur, Babel and Closure Compiler let you produce ES5 code in the following module formats. You can either invoke them directly or use a build tool (grunt, gulp, broccoli, etc.).
- AMD
- CommonJS
- ES6 module loader API: The ES6 code is transpiled to ES5 code that uses this API via a polyfill.
In browsers, such ES5 modules are loaded via one of the module systems described later. On Node.js, you can use the built-in module system (other options exist, e.g. webpack and the ES6 Module Loader Polyfill).
2.3.1.2 Dynamic transpilation
In browsers, you transpile dynamically via a library plus a custom <script type="...">
. This option exists for Traceur and Babel.
For Node.js, Babel has tools for on-the-fly compilation. These are described in another section.
2.3.2 Choosing a package manager
You need a package manager for installing third-party libraries. These are three popular ones:
- npm: package manager that was originally created for Node.js, but has grown in popularity for client-side development thanks to module packaging and loading tools such as browserify and webpack. Packages contain CommonJS modules and/or other content such as command line tools.
- Bower: package manager for client-side code. The most popular packages are AMD modules, but CommonJS modules, CSS, HTML and other artifacts can also be managed via it.
- jspm: package manager for SystemJS (see next bullet list). It can install modules from a variety of sources, including GitHub and npm. One key feature of jspm is that external modules can also be written in ES6 (and will be transpiled), not just your own modules.
2.3.3 Choosing a module system
Module systems bring support for modules to ES5 browsers (Node.js has a built-in module system). That way, you can build your app out of modules – your own and library modules. Popular module systems are:
- RequireJS: is a loader for AMD modules, which can be statically created via TypeScript, Traceur, Babel and Closure Compiler. Loader plugins (based on Traceur and Babel) enable it to load ES6 modules.
- Browserify: packages CommonJS modules (including ones installed via npm) so that they can be loaded in browsers. Supports ES6 modules via transforms (plugins) based on Traceur and Babel.
- webpack: a packager and loader for either CommonJS modules (including ones installed via npm) or AMD modules (including ones installed via Bower). Supports ES6 modules via custom loaders (plugins) based on Traceur and Babel.
- SystemJS: A module system based on the ES6 Module Loader Polyfill that supports ES6 modules and the ES5 module formats CommonJS, AMD and “ES6 module loader API”.
2.4 Other useful ES6 tools and libraries
2.4.1 Linters and checkers
Linters and checkers analyze source code statically and report problems related to style, typing, etc.
The following linters all support ES6, but to varying degrees:
- JSLint (focus: enforcing coding practices)
- JSHint (focus: enforcing coding practices)
- ESLint (focus: letting people implement their own style rules)
- JSCS (focus: enforcing code style)
The following type checkers all support ES6:
-
Flow: type-checks code. It receives type information from three sources:
- Type annotation syntax (non-standard)
- Type annotations in comments
- Type inference (which deduces types by statically analyzing source code, making Flow useful even for completely unannotated code)
- TypeScript: In addition to being a transpiler, the TypeScript compiler also works similarly to Flow and warns about type problems.
- Closure Compiler: type-checks code and understands type information stored in JSDoc tags.
2.4.2 Test tools
Many test tools (such as Jasmine and mocha) can mostly be used as they are, because they work with the transpiled code and don’t have to understand the original ES6 code. Babel’s documention has information on how to use it with various test tools.
2.4.3 Shims/polyfills
Shims/polyfills enable you to use much of the ECMAScript 6 standard library in ES5 code:
2.4.4 ES6 parsers
These are a few parsers can handle ES6:
- Esprima
- Acorn
- Babel has also recently become more of a framework for statically analyzing and transforming JavaScript source code. The “Babel Plugin Handbook” (by James Kyle) provides information on how that part of Babel works.
2.4.5 Documentation tools
These are a few documentation tools that understand ES6:
2.5 The future: native ES6
As soon as the first engine fully supports ES6 and until all non-ES6 engines go away, a hybrid approach could be used for client-side apps:
- The server has two versions of each file: the native ES6 version and its transpilation, an ES5 version.
- When the web app starts, feature detection is used to check whether ES6 is fully supported. If it is, the ES6 version of the app is used. Otherwise, the ES5 version is used.
Detecting ECMAScript versions is difficult, because many engines support parts of versions before they support them completely. For example, this is how you’d check whether an engine supports ECMAScript 6’s for-of
loop – but that may well be the only ES6 feature it supports:
Kyle Simpson’s library ES Feature Tests lets you detect whether an engine supports ES6:
npm may eventually support two versions of the same module, which would enable you to deliver libraries as both ES5 and ES6 for Node.js and client-side module setups that are based on npm.
2.6 Are there ES6 features that can’t be transpiled to ES5?
Some ECMAScript 6 features cannot be transpiled (compiled) to ES5. ES6 has three kinds of features:
- Better syntax for existing features
- New functionality in the standard library
- Completely new features
The next sections explain for each kind of feature how difficult it is to transpile to ES5.
2.6.1 Better syntax for existing features
ES6 provides better syntax for features that are already available via libraries. Two examples:
- Classes
- Modules
Both can be relatively easily compiled to ES5.
2.6.2 New functionality in the standard library
ES6 has a more comprehensive standard library than ES5. Additions include:
- New methods for strings, arrays
- Promises
- Maps, Sets
These can be provided via a library. Much of that functionality (such as String.prototype.repeat()
) is even useful for ES5. A later section lists a few such libraries.
2.6.3 Completely new features
Some ES6 features are completely new and unrelated to existing features. Such features can never be transpiled completely faithfully. But some of them have reasonable simulations, for example:
-
let
andconst
: are transpiled tovar
plus renaming of identifiers to avoid name clashes where necessary. That produces fast code and should work well in practice, but you don’t get the immutable bindings thatconst
creates in native ES6. - Symbols: an be transpiled to objects with unique IDs. These objects can be used as property keys, because the bracket operator coerces them to strings. Additionally, some property-enumerating functions (such as
Object.keys()
) have to be patched to ignore property keys coming from transpiled symbols. - Generators: are compiled to state machines, which is a complex transformation, but works remarkably well. For example, this generator function:
is translated to the following ES5 code:
You can see the state machine in the code, the next state is stored in
context$1$0.next
.Check out Facebook’s regenerator library for more information.
- WeakMaps: The entries of a WeakMap are key-value pairs. Keys are always objects. The main feature of WeakMaps is that keys are weakly held: if an object is a key and there is no other (strong) reference to it then it can be garbage-collected. An ES5 simulation of a WeakMap is an object that contains a unique ID (a string such as
'weakmap_072c-4328-af75'
), but is otherwise empty. Each of its key-value entries is managed by storing the value in the key, via a property whose name is the WeakMap ID. That is quite a hack: It only works if the keys are mutable. And, if a simulated WeakMap is garbage-collected, its properties in the keys are not removed, resulting in wasted memory. On the plus side, keys are held weakly by simulated WeakMaps. The simulation works because all of the WeakMap operations (get
,set
,has
,delete
) require a key. There is no way to clear WeakMaps or to enumerate their entries, keys or values.
Other features are impossible to transpile (in a straightforward manner):
- Proxies: intercepting proxy operations is only possible if you make all operations on objects interceptable. And that would cause a tremendous performance penalty.
- Subclassable built-in constructors (e.g.
Error
andArray
). This feature is enabled by a new subclassing protocol (new.target
etc.) that ES5 built-ins do not support. Replacing them with implementations that do is not a simple solution, either, because there are many places that use built-in constructors, but don’t refer to them via global variables. - Tail call optimization: implementing tail call optimization universally in ES5 would require a radical transformation of the ES6 code (e.g. trampolining). The resulting code would be quite slow.
3. Babel setups for browsers and Node.js
This chapter covers example transpilation setups for Babel:
- Client-side ES6 via webpack and Babel (browsers, webpack, static transpilation)
- Dynamically transpiled ES6 on Node.js via Babel (Node.js, Babel, dynamic transpilation)
- Statically transpiled ES6 on Node.js via Babel (Node.js, Babel, gulp, static transpilation)
For these setups it helps to be roughly familiar with how Babel is configured. Consult the following two sources if you aren’t:
- Chap. “Configuring Babel 6”
- The Babel docs
Two technologies are used by the setups, which is why I’ll explain them first:
- Local installs via npm
- Source maps for debugging transpiled code via the original ES6 code
3.1 npm and local installs
npm can help you to install everything you need to manage a project locally. To see how, take a look at the following package.json
file inside an npm-managed package:
Explanations:
-
dependencies
,devDependencies
: The packages used by the current package. Installed intonode_modules
bynpm install
. The unit test tool mocha is included as a development-only dependency. -
bin
: The entries listed here are available as shell commands (if installed globally) and to scripts of packages that have the current package as a dependency. -
scripts
: Every entry in this object has two parts – the key defines the name of a script, the value defines how the script works. A script whose name isfoo
can be executed vianpm run foo
.- Additional arguments provided after
npm run foo
are passed on to the script. - The
bin
executables provided by the dependencies of the package can be used in the definitions of scripts. That’s why the commandmocha
is available here, even though nothing was installed globally. Alas, thebin
entries of the currentpackage.json
are not available here.
- Additional arguments provided after
Several scripts have shortcuts:
-
npm test
andnpm t
is a shorter version ofnpm run test
. The shorter version has the additional benefit of not showing an error message if execution fails (as it does for mocha if a test fails). -
npm start
is (roughly) a shorter version ofnpm run start
.
3.2 Source maps
Source maps help whenever a programming language is compiled to JavaScript. Compiling source code to source code is also called transpiling. Examples of transpilation are:
- Minification (normal JavaScript to minified JavaScript)
- CoffeeScript
- ECMAScript 6 (ES6 to ES5)
A source map is a file that accompanies the transpilation output and maps the lines of the output to lines in the input files. This information can be used for error messages and debugging, to refer to the original instead of the transpiled code. There are two ways to let tools know about a source map: Either the transpilation output refers to the source map file in the last line or it embeds that file’s contents in the last line.
3.3 Browser setup: ES6 via webpack and Babel
The setup described in this section enables client-side ES6 via the following technologies:
- webpack as a client-side module builder and module loader.
- npm as a package manager.
- Babel as a transpiler from ES6 to ES5 (we’ll be using Babel 6).
3.3.1 webpack features
Notable webpack features include:
- Supported module formats: AMD, CommonJS
- Via loader (plug-in): ES6
- Supported package managers: Bower, npm
- Loaders for non-code: CSS, templates, …
- On-demand loading (chunked transfer)
- Built-in development server that supports automatic browser refreshes and hot module reloading (which are both useful during development)
- Dead code elimination (also known as tree shaking)
3.3.2 Installing the demo project
This section examines a demo project. If you want to, you can install it, like this:
- Download or clone the GitHub repository
webpack-babel-demo
. cd webpack-babel-demo/
npm install
Everything is installed locally.
3.3.3 The structure of the demo project
The demo project has the following basic structure:
Configuration files and libraries:
-
package.json
configures npm so that everything can be installed and run properly. For this project, we only need webpack and Babel. -
node_modules/
is the directory where npm installs packages. -
webpack.config.js
configures webpack, which, in this case, serves as both transpiler and build tool.
Input directories:
-
js/
is the directory that contains the ES6 code that webpack will transpile to ES5. -
html/
contains files that will appear in the output unchanged.
Output directory:
-
build/
is the directory where webpack stores its output. You’ll get plain HTML and ES5 code, without any dependencies on webpack, Babel or npm. The contents I’ve listed here are created by a build step:-
bundle.js
is all of the JavaScript, bundled into a single file. -
bundle.js.map
is a source map which enables browsers to run ES5 code, but to debug ES6 code. -
index.html
is simply copied over fromhtml/
-
3.3.4 File package.json
The file package.json
configures npm:
devDependencies
are the npm packages that are needed during development:
-
webpack
leads to webpack being installed locally. -
webpack-dev-server
adds a hot-reloading development web server to webpack. -
copy-webpack-plugin
is a webpack plugin that copies files to the build directory. -
babel-loader
enables webpack to transpile JavaScript via Babel. This package internally imports a few core Babel packages (babel-core
etc.). -
babel-preset-es2015
is a Babel preset for compiling ES6 to plain ES5.
scripts
specifies several ways in which you can run webpack:
- Build once:
npm run build
- Open
build/index.html
in a web browser
- Watch files continuously, rebuild incrementally whenever one of them changes:
npm run watch
- Open
build/index.html
in a web browser, manually reload page whenever there was a change
- Hot reloading via the webpack development server:
-
npm start
(a shortcut fornpm run start
) - Go to
http://localhost:8080/
. The page reloads automatically when there are changes.
-
babel
tells Babel to use the preset es2015
(which we have installed via the npm package babel-preset-es2015
).
3.3.5 Directory js/
Two small files contain all the JavaScript code in this project.
First, js/main.js
Importing babel-polyfill
in the first line adds whatever is missing from the ES6 standard library to global variables (Object.assign()
, the new ES6 string methods, etc.).
Second, js/world.js
During building, webpack bundles these files into the single output file build/bundle.js
and puts a source map for that file into build.js.map
.
3.3.6 File html/index.html
The HTML file loads and executes the bundle via a <script>
element.
3.3.7 File webpack.config.js
This is an excerpt of the configuration file for webpack (I’ve omitted the imports and a few other details):
The file is a native Node.js module that exports an object with the configuration data. It uses the special Node.js variable __dirname
that contains the path of the parent directory of the currently executed module. The configuration data has the following properties:
-
entry
: This is where the execution of JavaScript code starts. webpack starts compiling here and continues by compiling its dependencies (imported modules), then the dependencies of the dependencies, etc. -
output
: webpack bundles the entry file and everything it depends on into the output filebundle.js
. -
module.loaders
: are preprocessors for imported files. Support for ES6 is enabled via a the module loaderbabel-loader
.- Property
test
specifies what files the loader should transpile. You can specify a single test or multiple tests:- Single test: match an absolute path via a regular expression or a string
- Multiple tests: array of single tests (logical “and”)
- Property
-
plugins
extend webpack in various ways. I useCopyWebpackPlugin
to copy everything inhtml/
over tobuild/
. That means that webpack performs a task here that is traditionally handled by a build tool such asgrunt
orgulp
. - The option
devtool
switches on and configures source maps. -
devServer
tells the webpack development server what files to serve.
3.3.8 Installing client-side libraries via npm
You can install packages via npm and use them from your client-side ES6 code, seamlessly. For example: First install lodash and save that dependency as a runtime dependency via --save
:
Then use it anywhere in your ES6 code:
3.4 Node.js setup: Dynamically transpiled ES6 via Babel
In this section, we run ES6 on Node.js. We transpile ES6 code dynamically (at runtime), via Babel (version 6). We run unit tests via mocha, which we configure so that it transpiles the tests dynamically, again via Babel.
3.4.1 Installing the demo project
This section describes a demo project, which you can install like this:
- Download or clone the GitHub repository
node-babel-dynamic-demo
. cd node-babel-dynamic-demo/
npm install
Everything is installed only locally.
3.4.2 The structure of the demo project
The project has the following structure:
Explanations:
-
node_modules/
contains the packages installed via npm. -
package.json
configures npm. -
src/
contains ES6 source code. -
test/
contains tests for the ES6 source code.
3.4.3 Running ES6 code via babel-node
babel-node
is an executable for running code via Babel that otherwise works like the node
executable. It is installed via the npm package babel-cli
.
The important parts for setting up babel-node
in package.json
are:
Explanations:
-
dependencies
: We need the npm packagebabel-cli
for thebabel-node
executable and thebabel-preset-es2015-node5
so that Babel can transpile ES6. The preset configures Babel so that only ES6 constructs are transpiled that are missing from Node.js 5 (which supports quite a bit of ES6; e.g., classes and generators). -
bin
: If you install the demo project globally then you’ll get the shell commandpoint
. -
scripts
: enables us to runsrc/point.js
vianpm run point
. Even though we have installedbabel-node
only locally, we can use it here, because npm adds all executables in the dependencies to the shell path. The scriptb
is explained later. -
babel
: tells Babel to use the aforementioned preset for transpilation.
The file point.js
looks as follows:
Explanations:
- The first line lets us run this file as an executable on Unix (it’s not needed if you use
npm run
to execute it). It starts with a so-called hashbang (#!
). - Next, class
Point
is declared and exported. - At the end, we check whether
point.js
is run as a script (and not just imported as a module). If it is, we create an instance ofPoint
and log it to the console.
There are several ways of running point.js
:
- On Unix, you can run the file as follows (if it is executable and has the first line shown in the previous code):
- You can execute
point.js
vianpm run
(as configured inscripts
): - If you install
babel-node
globally (vianpm install -g babel-cli
) then you can executepoint.js
like this: - The helper script
b
lets us runbabel-node
locally. The local version of the previous invocation is: - If you install the demo project globally, you get a shell command
point
(as specified viabin
). - If you install the demo project as a dependency of another package, you can execute
point
via thescripts
of that package, as if it were a globally installed command.
This is what the output of point.js
looks like if you execute it via npm run point
:
3.4.4 Running ES6 scripts via a require hook
The idea of a require hook is as follows:
- The first script of an app is a native (untranspiled) module, that you can start via the normal
node
binary. - That script hooks Babel into Node’s
require
. - All modules the script requires are than transpiled from ES6 to ES5.
The inital script looks like this:
Again, the first line (starting with #!
) lets us run this file as a script on Unix (but it must be executable).
If you use babel-register
then that package becomes a runtime dependency!
3.4.5 Standard library and source maps
Whenever you transpile code dynamically on Node.js, Babel automatically includes babel-polyfill
(which polyfills what’s missing from the ES6 standard library) and switches on support for source maps.
3.4.6 Getting an ES6 REPL via babel-node
babel-node
also gives you a REPL, via the following shell command:
In the REPL, you can use ES6:
3.4.7 Running mocha unit tests via Babel
You can run your unit tests in ES6 via mocha. The relevant bits of package.json
are:
Explanations:
-
devDependencies
:mocha
is needed for running unit tests,babel-register
is how we enable mocha to use Babel. -
scripts
: defines a shortcut for invoking mocha.- You can use that shortcut via
npm run test
or vianpm test
or vianpm t
. - I prefer a QUnit-style “frame” around the assertions (the mocha docs call this kind of frame an “interface”). This is switched on via
--ui qunit
.
- You can use that shortcut via
-
babel
: sets up Babel so that it transpiles ES6.
The unit test itself in test/point_test.js
looks as follows.
- As previously mentioned, the mocha “UI” is QUnit (
suite()
andtest()
, flat at the top level). - The assertions API is the built-in Node.js module
assert
. - The unit tests profit from ES6’s arrow functions (line A).
3.5 Node.js setup: Statically transpiled ES6 via Babel
This section explains how to use ES6 on Node.js by statically transpiling it to ES5 via Babel (version 6).
The previous section showed how to dynamically transpile ES6 at runtime (also via Babel). That is more convenient and should work for many projects, but occasionally you may want a simpler and faster setup for your runtime environment.
3.5.1 Installing the demo project
You don’t need to do so in order to follow this section, but here is how to install the demo project that we are examining in this section:
- Download or clone the GitHub repository
node-babel-static-demo
. cd node-babel-static-demo/
npm install
Everything is installed locally.
3.5.2 Structure of the demo project
The repo has the following structure:
Explanations:
-
es6/
: contains the source code of the Node.js app, written in ES6. -
es5/
: contains the ES5 that the ES6 code is transpiled to. And a source map so that the ES5 code can be debugged via the ES6 code. The files in this directory don’t exist, initially. They are created by a build step. -
node_modules/
: contains the packages installed via npm. -
package.json
: contains the configuration data for npm.
3.5.3 File package.json
Explanations:
-
devDependencies
: We needbabel-cli
to get the executablebabel
for transpiling on the command line. And we need the presetbabel-preset-es2015-node5
so that Babel can transpile full ES6 to the partial ES6 supported by Node.js version 5. -
dependencies
: If you importbabel-polyfill
, it adds whatever is missing from the ES6 standard library, globally. Packagesource-map-support
provides support for source maps on Node.js. -
scripts
: lets us use npm to build and run the Node.js app:- Build once:
npm run build
- Watch files, build whenever there are changes:
npm run watch
- Run the app (as ES5 code, via the normal Node.js binary):
npm start
(ornpm run start
)
- Build once:
-
babel
: is for configuring Babel.
3.5.4 Transpilation
The file es6/myapp.js
contains the ES6 code of the Node.js application:
Alas, Node.js does not come with built-in support for source maps. But it can be enabled via a library, e.g. the npm package source-map-support
. That library needs to be called at least once in an app. The first two lines in the previous code take care of that. They also demonstrate that you can use any npm-installed package via ES6 syntax.
Afterwards, importing babel-polyfill
ensures that all of the ES6 standard library is present globally.
The following npm invocation transpiles myapp.js
.
Alternatively, you can use npm run watch
to continuously watch the ES6 files and transpile them whenever they change.
The results of the transpilation are in the directory es5/
:
You can see the ES5 version of es6/myapp.js
and the source map file myapp.js.map
. The contents of the former file are:
3.5.5 Running the transpiled code
The transpiled code is a normal ES5 Node.js app. You can run it as usual:
Or via npm:
The latter invocation produces the following output. Note that, thanks to the source map, the stack trace reports that the exception is thrown where it is in the ES6 file (not where it is thrown in the ES5 file). The source code that is quoted is from the ES6 file, too.
4. Configuring Babel 6
Babel 6 is much more configurable than Babel 5, but also more difficult to configure. This chapter gives tips.
4.1 Installing Babel 6
The following are a few important npm packages. All Babel packages reside in a single repository on GitHub. Browsing their source code and their package.json
files is instructive.
-
babel-core
: the core compilation machinery and plugin infrastructure for Babel. You will rarely need to install this package, because other packages such asbabel-cli
have it as a dependency, meaning that it will be automatically installed when they are installed. -
babel-cli
: a command line interface toBabel
. It includes the following commands:-
babel-doctor
detects common problems with your Babel installation. -
babel
transpiles files or stdin via Babel. -
babel-node
a version of the Node.js executablenode
that transpiles everything via Babel. -
babel-external-helpers
prints all of Babel’s helper functions (such asinherits
for subclassing) to the console.
-
-
babel-register
: lets you switch on Babel transpilation from within Node.js. After you do, all modules you require (minus code you want to ignore, e.g. packages installed via npm) are automatically transpiled.
4.2 Configuration data
Babel is often about compiling an input file, e.g. in the following two scenarios:
- Compiling a file via the command line tool
babel
: - Running a Node.js script written in ES6:
The configuration data is an object of JSON data that is assembled from various sources (which are described later). Two configuration options have much influence on how the output is produced: plugins and presets. These are explained next. The remaining configuration options are explained in the Babel documentation.
4.2.1 Plugins
Roughly, plugins are functions that are applied to the input during compilation. Two important categories of plugins are:
- Syntax plugins enable Babel to parse syntactic entities beyond the built-in base syntax. They help with constructing an abstract syntax tree. Examples are:
- Transform plugins modify the abstract syntax tree. Examples are:
If you want to compile something that isn’t part of the base syntax, you need both a syntax plugin and a corresponding transform plugin. However, each transform plugin that depends on a syntax plugin automatically activates that plugin.
Plugins are installed via npm. Their package names are their names plus the prefix babel-plugin-
:
- Plugin
syntax-jsx
:npm install babel-plugin-syntax-jsx
- Plugin
transform-react-jsx
:npm install babel-plugin-transform-react-jsx
4.2.2 Presets
In order to configure Babel’s output to your liking, you need to specify what plugins it should use. You can specify:
- Individual plugins
- Presets, sets of plugins that support various compilation scenarios.
The following are useful presets:
- es2015: compiles ES6 (as described by the ECMAScript spec) to ES5
- stage-3: compiles stage 3 ECMAScript proposals to ES5
- react: compiles JSX to JavaScript and removes Flow type annotations
-
es2015-node5: Contains just those plugins that are needed to upgrade Node.js 5 to full ES6. Therefore, a lot less is transpiled than with the
es2015
preset. Especially generators not being transpiled helps with debugging.
Presets are installed via npm. Their package names are their names plus the prefix babel-preset-
. For example, this is how to install the preset es2015
:
4.3 Sources of configuration data
This section explains where configuration data can come from.
4.3.1 Primary source of configuration data: .babelrc
The main source of configuration data is a file .babelrc
that should be located close to the input file that you want to compile. Babel first looks for .babelrc
in the same directory as the input file, then in that directory’s parent directory, etc. The contents of .babelrc
are interpreted as JSON and used as Babel options. For example:
The file .babelignore
complements .babelrc
:
- It is searched for independently of
.babelrc
, but in the same manner (by going through the ancestor directories of the input file). - It does not replace
.babelrc
; the two sets of data are merged.
.babelignore
only specifies the option ignore
: its lines are turned into an Array and interpreted as that option.
4.3.2 Alternative to .babelrc
: package.json
Each npm package has a file package.json
. Babel allows you to use that file for configuration, as an alternative to .babelrc
. Then it must have a property babel
(otherwise Babel ignores package.json
files). The value of that property is an object with configuration data. For example:
Babel searches for package.json
in the same way that it searches for .babelrc
, by going through the ancestor directories of the input file. If there is both a .babelrc
and a package.json
in the same directory then the former file wins.
4.3.3 Mixing in additional configuration data
Two properties in configuration data specify additional configuration data:
- Property
env
of maps the names of environments ('development'
,'production'
, etc.) to objects with more configuration data. Ifenv
exists, the object corresponding to the current environment is merged with the configuration data that has already been assembled. Consult the Babel documentation for more information on environments. - Property
extends
contains a path pointing to a file with more configuration data.
4.3.4 Secondary sources of configuration data
4.3.4.1 babel-node
If you are using babel-node
, you can specify the following options (and a few others) via the command line:
--presets
--plugins
-
--ignore
: by default, any file that has the segment'node_modules'
in its path is not transpiled.
The following command runs my-script.js
via babel-node
, with the presets es2015-node5
and react
, and the plugin transform-async-to-generator
enabled.
4.3.4.2 webpack
The following is an excerpt of a webpack configuration file webpack.config.js
:
As you can see, babel-loader supports the property query
for specifying Babel options.
4.4 More information
- The Babel docs are excellent. For example, this page explains the Babel options. The bar at the top gets you to other pages.
- Additionally, the following files in Babel’s source code are helpful for figuring out how it handles options:
-
option-manager.js
contains the algorithm for finding and merging Babel options. -
_babel-node.js
contains the parameter handling code for babel-node. - babel-register for Node.js is hosted here.
-
5. Babel: configuring standard library and helpers
This chapter explains how to configure how Babel 6 accesses its own helper functions and the ES6 standard library.
The following GitHub repo lets you play with what’s explained here: babel-config-demo
5.1 Overview
The code produced by Babel usually has two kinds of external dependencies:
- Helper functions (e.g. for subclassing)
- Standard library functionality (e.g.
Map
or ES6 string methods)
There are two ways of fulfilling these dependencies: by installing functionality globally or via modules. In both cases the functionality is delivered as npm packages.
5.1.1 External dependencies via global variables
The following npm packages install their functionality globally and let you access it via global variables:
- (P)
babel-plugin-external-helpers
and (I) generated file: global helpers - (I)
babel-polyfill
: global standard library- ES5, ES6+, Regenerator runtime
- (I)
core-js
: global standard library-
core-js/shim
: ES5, ES6+ -
core-js/es6
: ES6
-
Installation:
- (P) Plugin: install the npm package as a dev dependency and switch on the plugin in the Babel configuration data (see Chap. “Configuring Babel 6”).
- (I) Import: install the npm package as a runtime dependency and import it when the program starts.
5.1.2 External dependencies via module imports
The following npm packages enable dependencies via modules:
- (P)
babel-plugin-transform-runtime
, (M)babel-runtime
: helpers and standard library via imports (plugin generates imports).- Babel helpers (mandatory)
- Standard library (can be switched off)
- Regenerator API (can be switched off)
- (M)
core-js
: standard library via modules.- Single entities:
- Namespace object (ES5, ES6+):
- Namespace object (ES6):
Installation:
- (P) Plugin: install the npm package as a dev dependency and switch on the plugin in the Babel configuration data (see Chap. “Configuring Babel 6”).
- (M) Module: install the npm package as a runtime dependency and import entities at runtime (as needed).
5.2 External dependencies of transpiled code
The code generated by Babel usually has two kinds of external dependencies that need to be fulfilled.
First, most code invokes functionality of the ES6 standard library. By default, you access this functionality via global variables:
Second, Babel has helper functions (e.g. for subclassing) that are called from the transpiled code. By default, the code of helper functions is inlined, inserted into each file. For example (_classCallCheck
is a helper):
Inlined helpers lead to code duplication whenever the same helper is used in several files.
There are two ways in which you can get the standard library and non-inlined helpers: via global variables and via module imports. How is explained in the next sections.
5.3 External dependencies via global variables
5.3.1 Helpers via a global variable: babel-plugin-external-helpers
There are two things you need to install:
- Plugin
babel-plugin-external-helpers
:- Install as development dependency:
- Switch on in Babel configuration data:
- File that sets up global variable
babelHelpers
:- Must be loaded or imported at runtime, as early as possible. E.g.:
- Generated via command line tool
babel-external-helpers
(how is explained in the next section). - Install
babel-external-helpers
as a development dependency:
The plugin ensures that all helpers are invoked via methods of the global object babelHelpers
. In this section, I’ll explain how that works. In the next section, I’ll explain how to set up babelHelpers
.
As an example, consider the following ES6 code, before transpilation:
If you transpile it with the es2015
preset and without external-helpers
, you get:
Note the two helper functions _createClass
and _classCallCheck
.
If you switch on the plugin external-helpers
, you get this output:
5.3.1.1 Installing the global Babel helpers
How do you install the object with the helpers into the global variable babelHelpers
? Via a file generated by the command line tool babel-external-helpers
. The tool is part of the npm package babel-cli
. The file is available in three formats:
-
babel-external-helpers -t global
prints a Node.js module that puts the helpers intoglobal.babelHelpers
. -
babel-external-helpers -t var
prints a script file (browser code) thatvar
-declaresbabelHelpers
in global scope and assigns it an object with the helpers. -
babel-external-helpers -t umd
prints a Universal Module Definition (UMD) that works as CommonJS module, AMD module and as a script (via a global variable).
This invocation prints usage information:
5.3.2 Standard library and more via global variables: babel-polyfill
The package babel-polyfill
contains a module that installs several things into global variables:
-
ES5 polyfills (whatever is missing from the ES5 standard library):
Object.create()
,Array.prototype.forEach()
, etc. -
ES6 polyfills:
Map
,String.prototype.repeat()
, etc. -
A few polyfills of ECMAScript feature proposals:
Object.entries()
,Array.prototype.includes()
, etc. - The runtime for Regenerator (which is used by Babel to transpile ES6 generators to ES5).
The polyfills are provided by core-js, which you can see if you look at the two import statements making up this module:
5.3.2.1 Installation
Install babel-polyfill
via npm as a runtime dependency if you find that any of the aforementioned functionality is missing in your transpiled code. In current Node.js versions, you may be able to get by without using it, because those versions come with much of the ES6 standard library and native generators.
The module is installed via:
You must import it before you use the standard library:
The module will check global variables and only install missing functionality. The downside of the polyfill is that you always deliver and load all of the functionality, independently of how much you use.
5.3.3 Just the standard library via global variables: core-js
If you don’t need the Regenerator runtime, you can use the core-js polyfill directly.
You install it like this:
There are several ways in which you can install the polyfill functionality. Two common ones are:
-
import 'core-js/shim';
Polyfills ES5, ES6 and some post-ES6 functionality.
-
import 'core-js/es6';
Polyfills just ES6 functionality.
The import
statement needs to happen before you access the runtime library. It installs the polyfills globally.
5.4 External dependencies via module imports
5.4.1 Helpers and standard library via imports: babel-plugin-transform-runtime
Two pieces are needed for this approach:
- A module that contains helpers and standard library. Install as a runtime dependency:
- A plugin that changes Babel output so that helpers and standard library are imported from
babel-runtime
.- Install as a development dependency:
- Switch on in Babel configuration data:
The plugin redirects three kinds of operations to imports from babel-runtime
:
- Babel helpers (mandatory)
- Standard library (can be switched off)
- Regenerator API (can be switched off): used by
babel-plugin-transform-regenerator
to transpile ES6 generators to ES5 code.
babel-runtime
becomes a runtime dependency, but the import statements are created by the plugin – you do not need to import anything.
The plugin lets you switch off #2 and #3 as follows:
The next two sections explore how transform-runtime
works for helpers and for the standard library.
5.4.2 transform-runtime
and Babel helpers
transform-runtime
works well for the helpers. Consider the following ES6 code:
With the plugin, this is transpiled to:
The helpers classCallCheck
(line A) and createClass
(line B) are now imported from babel-runtime
. Note that each function sits in its own module, which ensures that only functions you actually use end up in bundled code.
The helper function _interopRequireDefault
ensures that either plain CommonJS modules or transpiled ES6 modules can be used.
5.4.3 transform-runtime
and the standard library
The plugin transform-runtime
handles helpers automatically, but for much of the standard library, extra work is required. Let’s examine the support for the following ways of accessing the standard library:
- Accessing functions (supported)
- Accessing methods normally (not supported)
- Accessing methods via custom utility functions (supported)
5.4.3.1 Accessing functions (supported)
transform-runtime
does properly detect function invocations: namespaced functions (such as Object.assign
and Math.sign
) and constructors (such as Map
and Promise
). Take, for example, the following ES6 code:
This code is transpiled to:
Note the imports in line A and line B.
5.4.3.2 Accessing methods normally (not supported)
However, transform-runtime
does not detect method calls like those in the following ES6 code:
This is transpiled to:
There are no imports – the input code in basically untouched. The first method call is dynamically dispatched, so it’s not surprising that transform-runtime
doesn’t catch it. However, the second method call is direct and ignored, too.
5.4.3.3 Accessing methods via custom utility functions (supported)
transform-runtime
provides a work-around for method calls – utility functions attached to constructors. You can use them to replace method calls such as this one:
With an invocation of a utility function:
Transpiled, the code looks like this (note the import in line A):
5.4.3.4 Babel’s polyfilling is based on core-js
If you look back, all imports generated by transform-runtime
use module IDs that start with babel-runtime/core-js
. That’s because Babel’s polyfilling is based on the library core-js. You can look up how transform-runtime
maps identifiers and chained property accesses to core-js modules in the repository file definitions.js
. This is an excerpt:
That means that transform-runtime
provides Set
, Array.from()
, String.repeat()
and more.
5.4.4 core-js
: standard library via modules
Given that transform-runtime
requires you to access properties of built-in constructors if you want to make a method call, using core-js directly is a useful alternative to that plugin.
Install it as a runtime dependency:
One way of accessing standard library functionality non-globally is via a single library object:
As for the content of the library object, you have a choice between:
-
core-js/library
: polyfills ES5, ES6 and a few proposed features. -
core-js/library/es6
: polyfills just ES6.
Another way of accessing standard library functionality non-globally is to import entities individually:
5.5 What should I use when?
For libraries, you must not touch global variables, which is why everything must come from imports:
- Babel helpers:
babel-plugin-transform-runtime
andbabel-runtime
work well. - Regenerator runtime: The previous combo also provides the regenerator runtime via imports. If you don’t use or transpile generators then you can switch off this part of
babel-plugin-transform-runtime
. - Standard library: I don’t like that
babel-plugin-transform-runtime
enables ES6 methods (e.g.Array.prototype.repeat()
) via non-standard global methods (e.g.Array.repeat()
). It’d switch this part of the plugin off and use core-js directly.
For apps, you can use a mixed approach:
- Use Babel helpers and – if necessary – the Regenerator runtime via module imports, as explained for libraries.
- Install the ES6 standard library globally. Modules have the advantage that a bundler will only deploy the functionality that is actually used by the app. But a global polyfill lets you write code that will eventually run natively without any changes. I find that more important.
Given that Regenerator is already taken care of, you don’t need
babel-polyfill
; core-js is enough.
Non-redundant Babel helpers are always just an optimization, but especially for large code bases, it is worth it because of space savings.
Acknowledgements. Thanks to Denis Pushkarev (@zloirock) and Paul Klimashkin for their feedback on this content.
6. Babel’s loose mode
Babel’s loose mode transpiles ES6 code to ES5 code that is less faithful to ES6 semantics. This chapter explains how that works and what the pros and cons are (spoiler: normally not recommended).
6.1 Two modes
Many plugins in Babel have two modes:
- A normal mode follows the semantics of ECMAScript 6 as closely as possible.
- A loose mode produces simpler ES5 code.
Normally, it is recommended to not use loose mode. The pros and cons are:
- Pros: The generated code is potentially faster and more compatible with older engines. It also tends to be cleaner, more “ES5-style”.
- Con: You risk getting problems later on, when you switch from transpiled ES6 to native ES6. That is rarely a risk worth taking.
6.1.1 Switching on loose mode
The preset es2015-loose
is the loose version of the standard ES6 preset, es2015
. The preset’s code provides a good overview of what plugins have a loose mode and how to switch it on. This is an excerpt:
This is a CommonJS module where you can use all of ECMAScript 5. If you configure Babel via .babelrc
or package.json
(details), you need to use JSON. You can either include the whole preset:
Or you can include plugins individually:
6.2 Example: the output of normal mode and loose mode
Let’s see how the modes affect the transpilation of the following code.
6.2.1 Normal mode
In normal mode, the prototype methods of the class are added via Object.defineProperty
(line A), to ensure that they are non-enumerable, as required by the ES6 spec.
6.2.2 Loose mode
In loose mode, normal assignment is used to add methods (line A). This style is more like you’d hand-write code in ES5.
7. Babel and CommonJS modules
This chapter examines how Babel ensures that code it transpiles interoperates properly with normal CommonJS modules. Consult chapter “Modules” in “Exploring ES6” for more information on ES6 modules.
7.1 ES6 modules vs. CommonJS modules
7.1.1 ECMAScript 6 modules
Default export (single export):
Named exports (multiple exports):
It is possible to combine both styles of exports, they don’t conflict with each other.
7.1.2 CommonJS modules
Single export:
Multiple exports:
Single exports and multiple exports are mutually exclusive. You have to use either one the two styles. Some modules combine both styles as follows:
7.1.3 Comparing the two modules formats
ES6 modules have two advantages over CommonJS modules.
First, their rigid structure makes them statically analyzable. That enables, e.g., tree shaking (dead code elimination) which can significantly reduce the size of bundled modules.
Second, imports are never accessed directly, which means that cyclic dependencies are always supported. In CommonJS, you must code like this, so that the exported entity foo
can be filled in later:
In contrast, this style of importing does not work (neither do single exports via module.exports
):
More information on cyclic dependencies: Section “Support for cyclic dependencies” in “Exploring ES6”.
7.2 How Babel compiles ES6 modules to CommonJS
As an example, consider the following ES6 module.
Babel transpiles this to the following CommonJS code:
The following subsections answer questions you may have about this code:
- Why isn’t the default export done like a CommonJS single export?
- Why mark transpiled ES6 modules with the flag
__esModule
?
7.2.1 Why isn’t the default export done like a CommonJS single export?
Answer: There are three reasons for doing so.
First, it is closer to ES6 semantics.
Second, you prevent scenarios like the following.
This is illegal in native ES6 and Babel shouldn’t let you do that.
Third, you want to support doing a default export and named exports at the same time. You could treat a module with just a default export like a single-export CommonJS module:
However, then the exports would change completely if you add a named export:
7.2.2 Why mark transpiled ES6 modules with the flag __esModule
?
The flag enables Babel to treat non-ES6 CommonJS modules that have single exports as if they were ES6 modules with default exports. How that is done is examined in the next section.
7.3 How Babel imports CommonJS modules
7.3.1 Default imports
This ES6 code:
is compiled to this ES5 code:
Explanations:
-
_interopRequireDefault()
: An ES6 CommonJS module is used as is (if it has a default export then it has a property nameddefault
). A normal CommonJS module becomes the value of the propertydefault
. In other words, in the later case, the module’s exports become the default export. - Note that the default export is always accessed via the exports object
_assert2
(line A), never directly, like this:The reason for that is support for cyclic dependencies.
-
(0, _assert2['default'])
is done so that the invocation in line A is a function call, not a method call (withthis === _assert2
).
7.3.2 Namespace imports
This ES6 code:
is compiled to this ES5 code:
Explanations:
-
_interopRequireWildcard()
: CommonJS exports are translated to an object where the named exports are the properties of the exports objects and the default exports is (yet again) the exports object. The moduleassert
is an example of where a normal CommonJS module mixes a single export with multiple exports and the Babel work-around translates such a module to the world of ES6:assert
accesses a default export,{ok}
accesses a named export. - Babel creates a new object (line A), because it must not modify the original exports object.
7.3.3 Named imports
This ES6 code:
is compiled to this ES5 code:
Again, you can see that ok()
is never accessed directly, always via _assert
, which ensures that cyclic dependencies work.
7.4 Recommendations
You need to look very closely at what a module exports and then choose the appropriate way of importing. For example, conceptually, the Node.js module fs
is clearly a collection of named exports, not a single export (an object). Therefore, while both of the following two ways of importing this module work, the second one is the better choice.
If you want to future-proof your normal CommonJS module, you should opt for either a single export or multiple named exports, but not for mixing styles (attaching named exports as properties of a single export).
8. The future of bundling JavaScript modules
This chapter examines how the bundling of modules is affected by two future developments: HTTP/2 and native modules.
8.1 Why we bundle modules
Bundling modules means combining several files with modules into a single file. That is done for three reasons:
- Fewer files need to be retrieved in order to load all modules.
- Compressing the bundled file is slightly more efficient than compressing separate files.
- During bundling, unused exports can be removed, potentially resulting in significant space savings.
8.2 JavaScript modules
With ECMAScript 6, JavaScript finally got built-in modules (I’m calling them JavaScript modules for the remainder of this chapter). However, that feature is currently in a strange position:
On one hand, ES6 fully standardized their syntax and much of their semantics. They have become a popular format for writing modules and their static structure enables the automatic omission of unused exports (also known as “tree-shaking” in the JavaScript world).
On the other hand, standardizing how to load JavaScript modules is ongoing and no JavaScript engine supports them natively, yet. That means that, at the moment, the only way of using JavaScript modules is by compiling them to a non-native format. Popular solutions are: browserify, webpack, jspm and Rollup.
8.3 Future developments and bundling
Let’s look at two future developments and how they affect the bundling of JavaScript modules.
8.3.1 Future development: HTTP/2
HTTP/2 is slowly being rolled out. It mainly affects reason #1 for bundling: With HTTP/2, the cost per request has decreased considerably compared to HTTP/1, which means that there are practically no performance gains if you download a single file instead of multiple ones. That enables smaller, more incremental updates: With bundling, you always need to download the complete bundle. Without bundling, you only need to download the parts that have changed (while the other parts are often still in the browser cache).
However, reasons #2 and #3 for bundling are not negated by HTTP/2. Therefore, mixed approaches may be adopted in the future, to optimize for both incremental updates and minimal total download size.
8.3.2 Future development: native JavaScript modules
Once engines support native JavaScript modules, will that affect bundling? Even AMD modules – which run natively in browsers – have a custom bundle format (along with a minimal loader). Will native JS modules be different? It looks like they will. Rollup lets you bundle multiple JS modules into a single JS module.
Take, for example, these two JS modules:
Rollup can bundle these two JS modules into the following single JS module (note the eliminated unused export bar
):
Initially, it wasn’t a given that JavaScript modules would work as a bundle format – quoting Rollup’s creator Rich Harris:
When I started writing Rollup, it was an experiment that I wasn’t certain would succeed.
The way imports are handled by JS modules helps with bundling: they are not copies of exports, they are read-only views on them.
Rollup’s site has a nice interactive playground where you can try it out.
8.4 Further reading
- “Building for HTTP/2” by Rebecca Murphey (explains how best practices change – often radically – with this new version of HTTP)
- Chap. “Modules” in “Exploring ES6” (explains how ES6 modules work)
- Chap. “Babel and CommonJS modules” in this book (explains how Babel ensures that transpiled ES6 modules interoperate properly with CommonJS modules)