Pythonic JavaScript with ES2015¶
We’ve seen a lot so far on the frontend toolchain, Pythonic development with modules, TDD, etc. But what about basic language features? JavaScript is notoriously more janky than Python. But what if you could code in a more Pythonic JavaScript, while still supporting most browsers?
In this section we introduce the concept of transpiling pleasant JavaScript using Babel.
Overview¶
- Explain the concept of transpiling
- Introduce Babel as the most popular transpiler
- Show Pythonic idioms in latest JavaScript
Background¶
JavaScript code is ugly and missing many features we take for granted in Python. However, it is a platform that is available everywhere. Even though it is unpleasant, people still target it for web development.
In the past few years, a quiet revolution has taken place. What if we
could treat the browser as a deployment target, and write our code in
something nicer which “transpiles” into executable JavaScript? Python
has a slightly similar concept: it reads pretty .py
files and
byte-compiles them into .pyc
files.
For the JavaScript compilers, the output is a flavor of JavaScript that is supported by many browsers, but using crazy techniques to cover various holes. This lets you write in a more pleasant input, transpile into “ES5” (an ECMAScript version supported by many browsers), and gain the productivity win you wanted.
This revolution has freed the ECMAScript (aka JavaScript) standards committee to dramatically speed up the improvements to the language. It matters less whether browsers keep up, as we’re just going to transpile these syntax improvements into compliant deployment output.
Frontend developers now talk about “ES2015”, the set of standards approved by the committee in the year 2015, as a group of improvements they can adopt. ES2016 and beyond are already in multiple stages of adoption.
Fortunately PyCharm (and particularly WebStorm) have done a fantastic job of keeping up with this advanced pace of development.
Note
These improvements were originally version-numbered, so you will frequently see “ES6”. However, the committee adopted time-based releases, so the jargon is now “ES2015” etc.
Starting Point¶
Let’s use, as a starting point, a combination of results from earlier sections:
package.json
for dependencies and automation.eslintrc
for controlling our linter, and thus PyCharm- Webpack for bundling our app file and our lib file
We are going to do a variation on the incrementer
pattern. In this
case, when you click on a button, it adds a random number to a <li>
list in the browser.
The Incrementer¶
Let’s jump into the results. We start with an index.html
file:
<!DOCTYPE html>
<html>
<head>
<title>Incrementer</title>
</head>
<body>
<h1>Incrementer</h1>
<div>
<button id="add">Add Another</button>
</div>
<ul id="incremented"></ul>
<script src="bundle.js"></script>
</body>
</html>
This HTML file has a few responsibilities:
- Provide a button which, when clicked, will add something to the
<ul>
- A
<ul>
that is the holder of the generated numbers - A
<script>
which loads the bundle resulting from Webpack, driven by thewebpack.config.js
and thewebpack-dev-server
We have two JavaScript files. Our “application” interacts with the browser and drives the library:
import Incrementer from './lib';
document.addEventListener('DOMContentLoaded', () => {
let incremented = document.querySelector('#incremented');
let incrementer = new Incrementer();
// Attach a click handler
document
.querySelector('#add')
.addEventListener('click', () => {
// Add a random number to the list, re-render
incrementer.add(Math.floor((Math.random() * 100) + 1));
incremented.innerHTML = incrementer.toHtml();
});
});
Our “library” knows nothing about a browser, which makes it very test-able:
export default class Incrementer {
constructor () {
this.items = [];
}
add (number) {
this.items.push(number);
}
toHtml () {
// Return a string of <li> nodes
return this.items
.map(
(item) => `
<li>
Number: ${item}
</li>
`
)
.join('\n');
}
}
Let’s take a look at the Pythonic JavaScript changes that we get from the switch to ES2015, transpiled by Babel into old-style JavaScript.
Pythonic JavaScript¶
app.js
starts with an “ES6” import, which we saw in
ES6 Imports with Babel. We are loading a “default export”,
so the name we choose in app.js
is de-coupled from the name
used in the exporting library.
We define a function to execute when the document is loaded. But
there is no function
declaration. What’s up with that? We’re
using a new feature called “arrow functions”, which look like
() => {}
. These are anonymous, inline functions that have a
benefit of lexical scoping. They are terse and useful.
Next, we define two variables, but instead of using var
, we
use let
, the new ES2015 way to define variables that gets out
of the this
hell. Essentially there is no reason to ever use
var
again, though you might want to use the new const
.
app.js
then attaches a click handler using an arrow function.
Now, on to the more exciting improvements in lib.js
. On the
very first line we see class
. ES2015 has a mostly-Pythonic
syntax for define classes that are, in most respects, syntactic
sugar atop the existing JavaScript prototype-based hierarchy.
Classes are seeing huge adoption in ES2015-oriented frameworks
and patterns.
Our Incrementer
class has a constructor and two methods.
The add
method shows that this
is bound to the instance of
the class.
The toHtml
method shows two very useful additions. First,
ES2015 has a number of very useful array methods, such as
map
. Also, we finally get multiline strings, similar to
Python’s triple quoted strings. These “template strings” are
string literals which can contain expressions, similar to Python’s
printf
and now .format()
style string templating.
Wrapup¶
The use of Webpack for bundling was a big step. It made us run code through something before we could use it in a browser. Still, we could do TDD natively, directly on our test code and our application code.
With the move to ES2015, none of our code will run without the
transpiling step. We faced that in ES6 Imports with Babel but
we could always chicken-out and go back to require
for
CommonJS modules and imports.
Is the complexity price worth it? As frontend applications get more sophisticated, the answer is yes. Having code that doesn’t hurt your eyeballs means you can write better code the first time.
- Previous topic: ES6 Imports with Babel
- Next topic: DOM TDD with JSDOM