Code for this chapter available here.
We're now going to use some ES6 syntax, which is a great improvement over the "old" ES5 syntax. All browsers and JS environments understand ES5 well, but not ES6. That's where a tool called Babel comes to the rescue!
💡 Babel is a compiler that transforms ES6 code (and other things like React's JSX syntax) into ES5 code. It is very modular and can be used in tons of different environments. It is by far the preferred ES5 compiler of the React community.
- Move your
index.js
into a newsrc
folder. This is where you will write your ES6 code. Remove the previouscolor
-related code inindex.js
, and replace it with a simple:
const str = 'ES6'
console.log(`Hello ${str}`)
We're using a template string here, which is an ES6 feature that lets us inject variables directly inside the string without concatenation using ${}
. Note that template strings are created using backquotes.
- Run
yarn add --dev babel-cli
to install the CLI interface for Babel.
Babel CLI comes with two executables: babel
, which compiles ES6 files into new ES5 files, and babel-node
, which you can use to replace your call to the node
binary and execute ES6 files directly on the fly. babel-node
is great for development but it is heavy and not meant for production. In this chapter we are going to use babel-node
to set up the development environment, and in the next one we'll use babel
to build ES5 files for production.
- In
package.json
, in yourstart
script, replacenode .
withbabel-node src
(index.js
is the default file Node looks for, which is why we can omitindex.js
).
If you try to run yarn start
now, it should print the correct output, but Babel is not actually doing anything. That's because we didn't give it any information about which transformations we want to apply. The only reason it prints the right output is because Node natively understands ES6 without Babel's help. Some browsers or older versions of Node would not be so successful though!
-
Run
yarn add --dev babel-preset-env
to install a Babel preset package calledenv
, which contains configurations for the most recent ECMAScript features supported by Babel. -
Create a
.babelrc
file at the root of your project, which is a JSON file for your Babel configuration. Write the following to it to make Babel use theenv
preset:
{
"presets": [
"env"
]
}
🏁 yarn start
should still work, but it's actually doing something now. We can't really tell if it is though, since we're using babel-node
to interpret ES6 code on the fly. You'll soon have a proof that your ES6 code is actually transformed when you reach the ES6 modules syntax section of this chapter.
💡 ES6: The most significant improvement of the JavaScript language. There are too many ES6 features to list them here but typical ES6 code uses classes with
class
,const
andlet
, template strings, and arrow functions ((text) => { console.log(text) }
).
- Create a new file,
src/dog.js
, containing the following ES6 class:
class Dog {
constructor(name) {
this.name = name
}
bark() {
return `Wah wah, I am ${this.name}`
}
}
module.exports = Dog
It should not look surprising to you if you've done OOP in the past in any language. It's relatively recent for JavaScript though. The class is exposed to the outside world via the module.exports
assignment.
In src/index.js
, write the following:
const Dog = require('./dog')
const toby = new Dog('Toby')
console.log(toby.bark())
As you can see, unlike the community-made package color
that we used before, when we require one of our files, we use ./
in the require()
.
🏁 Run yarn start
and it should print "Wah wah, I am Toby".
Here we simply replace const Dog = require('./dog')
by import Dog from './dog'
, which is the newer ES6 modules syntax (as opposed to "CommonJS" modules syntax). It is currently not natively supported by NodeJS, so this is your proof that Babel processes those ES6 files correctly.
In dog.js
, we also replace module.exports = Dog
by export default Dog
🏁 yarn start
should still print "Wah wah, I am Toby".
💡 ESLint is the linter of choice for ES6 code. A linter gives you recommendations about code formatting, which enforces style consistency in your code, and code you share with your team. It's also a great way to learn about JavaScript by making mistakes that ESLint will catch.
ESLint works with rules, and there are many of them. Instead of configuring the rules we want for our code ourselves, we will use the config created by Airbnb. This config uses a few plugins, so we need to install those as well.
Check out Airbnb's most recent instructions to install the config package and all its dependencies correctly. As of 2017-02-03, they recommend using the following command in your terminal:
npm info eslint-config-airbnb@latest peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs yarn add --dev eslint-config-airbnb@latest
It should install everything you need and add eslint-config-airbnb
, eslint-plugin-import
, eslint-plugin-jsx-a11y
, and eslint-plugin-react
to your package.json
file automatically.
Note: I've replaced npm install
by yarn add
in this command. Also, this won't work on Windows, so take a look at the package.json
file of this repository and just install all the ESLint-related dependencies manually using yarn add --dev packagename@^#.#.#
with #.#.#
being the versions given in package.json
for each package.
- Create an
.eslintrc.json
file at the root of your project, just like we did for Babel, and write the following to it:
{
"extends": "airbnb"
}
We'll create an NPM/Yarn script to run ESLint. Let's install the eslint
package to be able to use the eslint
CLI:
- Run
yarn add --dev eslint
Update the scripts
of your package.json
to include a new test
task:
"scripts": {
"start": "babel-node src",
"test": "eslint src"
},
Here we just tell ESLint that we want it to lint all JavaScript files under the src
folder.
We will use this standard test
task to run a chain of all the commands that validate our code, whether it's linting, type checking, or unit testing.
- Run
yarn test
, and you should see a whole bunch of errors for missing semicolons, and a warning for usingconsole.log()
inindex.js
. Add/* eslint-disable no-console */
at the top of ourindex.js
file to allow the use ofconsole
in this file.
Note: If you're on Windows, make sure you configure your editor and Git to use Unix LF line endings and not Windows CRLF. If your project is only used in Windows environments, you can add "linebreak-style": [2, "windows"]
in ESLint's rules
array (see the example below) to enforce CRLF instead.
Alright, this is probably the most heated debate in the JavaScript community, let's talk about it for a minute. JavaScript has this thing called Automatic Semicolon Insertion, which allows you to write your code with or without semicolons. It really comes down to personal preference and there is no right and wrong on this topic. If you like the syntax of Python, Ruby, or Scala, you will probably enjoy omitting semicolons. If you prefer the syntax of Java, C#, or PHP, you will probably prefer using semicolons.
Most people write JavaScript with semicolons, out of habit. That was my case until I tried going semicolon-less after seeing code samples from the Redux documentation. At first it felt a bit weird, simply because I was not used to it. After just one day of writing code this way I could not see myself going back to using semicolons at all. They felt so cumbersome and unnecessary. A semicolon-less code is easier on the eyes in my opinion, and is faster to type.
I recommend reading the ESLint documentation about semicolons. As mentioned in this page, if you're going semicolon-less, there are some rather rare cases where semicolons are required. ESLint can protect you from such cases with the no-unexpected-multiline
rule. Let's set up ESLint to safely go semicolon-less in .eslintrc.json
:
{
"extends": "airbnb",
"rules": {
"semi": [2, "never"],
"no-unexpected-multiline": 2
}
}
🏁 Run yarn test
, and it should now pass successfully. Try adding an unnecessary semicolon somewhere to make sure the rule is set up correctly.
I am aware that some of you will want to keep using semicolons, which will make the code provided in this tutorial inconvenient. If you are using this tutorial just for learning, I'm sure it will remain bearable to learn without semicolons, until going back to using them on your real projects. If you want to use the code provided in this tutorial as a boilerplate though, it will require a bit of rewriting, which should be pretty quick with ESLint set to enforce semicolons to guide you through the process. I apologize if you're in such case.
Compat is a neat ESLint plugin that warns you if you use some JavaScript APIs that are not available in the browsers you need to support. It uses Browserslist, which relies on Can I Use.
-
Run
yarn add --dev eslint-plugin-compat
-
Add the following to your
package.json
, to indicate that we want to support browsers that have more than 1% market share:
"browserslist": ["> 1%"],
- Edit your
.eslintrc.json
file like so:
{
"extends": "airbnb",
"plugins": [
"compat"
],
"rules": {
"semi": [2, "never"],
"no-unexpected-multiline": 2,
"compat/compat": 2
}
}
You can try the plugin by using navigator.serviceWorker
or fetch
in your code for instance, which should raise an ESLint warning.
This chapter set you up with ESLint in the terminal, which is great for catching errors at build time / before pushing, but you also probably want it integrated to your IDE for immediate feedback. Do NOT use your IDE's native ES6 linting. Configure it so the binary it uses for linting is the one in your node_modules
folder instead. This way it can use all of your project's config, the Airbnb preset, etc. Otherwise you will just get some generic ES6 linting.
💡 Flow: A static type checker by Facebook. It detects inconsistent types in your code. For instance, it will give you an error if you try to use a string where should be using a number.
Right now, our JavaScript code is valid ES6 code. Flow can analyze plain JavaScript to give us some insights, but in order to use its full power, we need to add type annotations in our code, which will make it non-standard. We need to teach Babel and ESLint what those type annotations are in order for these tools to not freak out when parsing our files.
- Run
yarn add --dev flow-bin babel-preset-flow babel-eslint eslint-plugin-flowtype
flow-bin
is the binary to run Flow in our scripts
tasks, babel-preset-flow
is the preset for Babel to understand Flow annotations, babel-eslint
is a package to enable ESLint to rely on Babel's parser instead of its own, and eslint-plugin-flowtype
is an ESLint plugin to lint Flow annotations. Phew.
- Update your
.babelrc
file like so:
{
"presets": [
"env",
"flow"
]
}
- And update
.eslintrc.json
as well:
{
"extends": [
"airbnb",
"plugin:flowtype/recommended"
],
"plugins": [
"flowtype",
"compat"
],
"rules": {
"semi": [2, "never"],
"no-unexpected-multiline": 2,
"compat/compat": 2
}
}
Note: The plugin:flowtype/recommended
contains the instruction for ESLint to use Babel's parser. If you want to be more explicit, feel free to add "parser": "babel-eslint"
in .eslintrc.json
.
I know this is a lot to take in, so take a minute to think about it. I'm still amazed that it is even possible for ESLint to use Babel's parser to understand Flow annotations. These 2 tools are really incredible for being so modular.
- Chain
flow
to yourtest
task:
"scripts": {
"start": "babel-node src",
"test": "eslint src && flow"
},
- Create a
.flowconfig
file at the root of your project containing:
[options]
suppress_comment= \\(.\\|\n\\)*\\flow-disable-next-line
This is a little utility that we set up to make Flow ignore any warning detected on the next line. You would use it like this, similarly to eslint-disable
:
// flow-disable-next-line
something.flow(doesnt.like).for.instance()
Alright, we should be all set for the configuration part.
- Add Flow annotations to
src/dog.js
like so:
// @flow
class Dog {
name: string
constructor(name: string) {
this.name = name
}
bark() {
return `Wah wah, I am ${this.name}`
}
}
export default Dog
The // @flow
comment tells Flow that we want this file to be type-checked. For the rest, Flow annotations are typically a colon after a function parameter or a function name. Check out the documentation for more details.
- Add
// @flow
at the top ofindex.js
as well.
yarn test
should now both lint and type-check your code fine.
There are 2 things that I want you to try:
-
In
dog.js
, replaceconstructor(name: string)
byconstructor(name: number)
, and runyarn test
. You should get a Flow error telling you that those types are incompatible. That means Flow is set up correctly. -
Now replace
constructor(name: string)
byconstructor(name:string)
, and runyarn test
. You should get an ESLint error telling you that Flow annotations should have a space after the colon. That means the Flow plugin for ESLint is set up correctly.
🏁 If you got the 2 different errors working, you are all set with Flow and ESLint! Remember to put the missing space back in the Flow annotation.
Just like with ESLint, you should spend some time configuring your editor / IDE to give you immediate feedback when Flow detects issues in your code.
💡 Jest: A JavaScript testing library by Facebook. It is very simple to set up and provides everything you would need from a testing library right out of the box. It can also test React components.
-
Run
yarn add --dev jest babel-jest
to install Jest and the package to make it use Babel. -
Add the following to your
.eslintrc.json
at the root of the object to allow the use of Jest's functions without having to import them in every test file:
"env": {
"jest": true
}
- Create a
src/dog.test.js
file containing:
import Dog from './dog'
test('Dog.bark', () => {
const testDog = new Dog('Test')
expect(testDog.bark()).toBe('Wah wah, I am Test')
})
- Add
jest
to yourtest
script:
"scripts": {
"start": "babel-node src",
"test": "eslint src && flow && jest --coverage"
},
The --coverage
flag makes Jest generate coverage data for your tests automatically. This is useful to see which parts of your codebase lack testing. It writes this data into a coverage
folder.
- Add
/coverage/
to your.gitignore
🏁 Run yarn test
. After linting and type checking, it should run Jest tests and show a coverage table. Everything should be green!
💡 Git Hooks: Scripts that are run when certain actions like a commit or a push occur.
Okay so we now have this neat test
task that tells us if our code looks good or not. We're going to set up Git Hooks to automatically run this task before every git commit
and git push
, which will prevent us from pushing bad code to the repository if it doesn't pass the test
task.
Husky is a package that makes this very easy to set up Git Hooks.
- Run
yarn add --dev husky
All we have to do is to create two new tasks in scripts
, precommit
and prepush
:
"scripts": {
"start": "babel-node src",
"test": "eslint src && flow && jest --coverage",
"precommit": "yarn test",
"prepush": "yarn test"
},
🏁 If you now try to commit or push your code, it should automatically run the test
task.
If it does not work, it is possible that yarn add --dev husky
did not install the Git Hooks properly. I have never encountered this issue but it happens for some people. If that's your case, run yarn add --dev husky --force
, and maybe post a note describing your situation in this issue.
Note: If you are pushing right after a commit, you can use git push --no-verify
to avoid running all the tests again.
Next section: 03 - Express, Nodemon, PM2
Back to the previous section or the table of contents.