How, and why, you should add JavaScript linting to your project. With ESLint and Gulp
Feb 28, 2018 |1,851 views
#Prerequisites
Some people prefer to see the code first, so if you would like to download a working demo, the files can be found here (be sure to run npm i
to install the dependencies).
#What is linting?
Linting is the process of checking your code for potential errors. Linting tools usually allow you to specify a set of rules to check your code against. These rules can vary from general code layout, like maximum line length and code indentation, to how you declare functions.
#Why lint?
Linters help you to write standardised code to ensure quality, minimise errors and increase readability. This is helpful when working in teams to ensure all developers are writing code in the same way. In day-to-day work, if developers are working with various files that have a consistent layout, it makes the code easier to read, which means developers can work faster, saving time. Linters also offer performance benefits in highlighting inefficient or unused code. Naturally we want the best user experience for our customers and keeping the code as efficient and lightweight as possible to reduce page load time is a big part of that.
#ESLint
Some of the most popular JavaScript linting tools are ESLint, JSHint, JSLint and JSCS. We're going to be using ESLint. It’s very flexible, easy to use and has the best ES6 support, which will be helpful if we introduce more modern JavaScript (that will be transpiled for older browsers using https://babeljs.io/). All rules for ESLint can be found here: https://eslint.org/docs/rules/.
#A great starting point – Airbnb JavaScript Style Guide
Instead of writing our own exhaustive list of JavaScript rules, we can use a ruleset defined by the developers at Airbnb, and then adjust it to suit our preferences. The Airbnb JavaScript Style Guide is well known among JavaScript developers and used by some big companies: Lonely Planet, National Geographic and Sainsburys to name a few. It’s a great place to start.
#Using ESLint with Gulp
Note: Over recent years Webpack has become a more popular choice than Gulp due the increase of JavaScript applications which require a more configurable build system. However, if that flexibility isn’t needed Gulp is still great for many projects.
We could install ESLint globally and run it from the command line using the command eslint
. However, in this tutorial we are going to be using ESLint in a gulp project and that means we can install ESLint, and all necessary dependencies, locally. This is beneficial for other people on the project as all dependencies are kept neatly inside the project (listed in the package.json
) and won’t require any extra work or global installations.
#Installing our dependencies
If you don’t yet have gulp installed, in terminal navigate to the root of your project folder and run:
npm install gulp --save-dev
npm install gulp --save-dev
Note the --save-dev
will add the package to your package.json
as development dependencies.
Here are the ESLint dependencies we are going to install:
- eslint – the ESLint tool
- gulp-eslint – the gulp plugin for ESLint
- eslint-config-airbnb – the Airbnb ESLint configuration (rule set)
- eslint-plugin-react – a plugin to add react-specific rules for ESLint to use
- eslint-plugin-import – a plugin to add linting ES2015+ (ES6+) import/export syntax
- eslint-plugin-jsx-a11y – a plugin to check accessibility of JSX elements (react)
To install all of these run (again from the root of your project folder):
npm install gulp gulp-eslint eslint-config-airbnb eslint eslint-plugin-react eslint-plugin-import eslint-plugin-jsx-a11y --save-dev
npm install gulp gulp-eslint eslint-config-airbnb eslint eslint-plugin-react eslint-plugin-import eslint-plugin-jsx-a11y --save-dev
#Adding an ESLint configuration file
We now need to add an ESLint configuration file, which will tell our linting process what standards and rules our code needs to meet. We could have a file named .eslintrc
in the root of our user folder, which would set global default linting rules. Instead, we will have an .eslintrc
file in the root of our project folder to set rules for this individual project.
To create the file, in terminal go to the root of your project and run
touch .eslintrc
touch .eslintrc
This will create the file and we can add our rules to it. Note this file begins with a full stop so might not be visible in mac finder or windows explorer but should be visible in your code editor/IDE.
Open up the file and add the following:
{
"env": {
"es6": true,
"browser": true,
"node": true,
"jquery": true
},
"extends": "airbnb",
"rules": {}
}
{
"env": {
"es6": true,
"browser": true,
"node": true,
"jquery": true
},
"extends": "airbnb",
"rules": {}
}
This will set our linting up to work with ES6, the browser, node, jQuery and use Airbnb’s configuration/ruleset. We can add extra rules that will override those in the Airbnb ruleset in the rules object. For example, if you would rather indent using tabs (you reckless animal), your rules would look like:
"rules" : {
"indent": ["error", "tab"]
}
"rules" : {
"indent": ["error", "tab"]
}
View more rules here: https://eslint.org/docs/rules/ You can specify error, warn, or off to choose how a rule should be flagged or if it's disabled entirely.
#Adding a gulp ESLint task
Now we have ESLint installed and our rules ready, we need to add our gulp task and dependencies to our gulpfile.js
which will run our linting. In your gulp file you will need the following:
var gulp = require('gulp')
var eslint = require('gulp-eslint')
gulp.task('eslint', () => {
return gulp
.src('./exampleCode.js')
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
})
var gulp = require('gulp')
var eslint = require('gulp-eslint')
gulp.task('eslint', () => {
return gulp
.src('./exampleCode.js')
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
})
Be sure to link the src to your JavaScript file, or lint all the JS files in a directory using globbing - return gulp.src(‘./folder-path/**/*.js’)
Now if we run gulp eslint
from the command line it should highlight our issues. For example, if our exampleCode.js
file contained:
function exampleFunction() {
var testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return testObject.name + ', ' + testObject.location
}
exampleFunction()
function exampleFunction() {
var testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return testObject.name + ', ' + testObject.location
}
exampleFunction()
...it would return the following errors:
~/Ash/sites/eslint-tutorial $gulp eslint
[15:37:42] Using gulpfile ~/Ash/sites/eslint-tutorial/gulpfile.js
[15:37:42] Starting 'eslint'...
[15:37:43]
/Users/Ash/Ash/sites/eslint-tutorial/exampleCode.js
2:1 error Expected indentation of 2 spaces but found 4 indent
2:5 error Unexpected var, use let or const instead no-var
3:1 error Expected indentation of 4 spaces but found 8 indent
3:9 error Unnecessarily quoted property 'name' found quote-props
3:16 error Strings must use singlequote quotes
3:16 error Missing space before value for key 'name' key-spacing
4:1 error Expected indentation of 4 spaces but found 8 indent
4:9 error Unnecessarily quoted property 'location' found quote-props
4:20 error Missing space before value for key 'location' key-spacing
4:20 error Strings must use singlequote quotes
4:31 error Missing trailing comma comma-dangle
5:1 error Expected indentation of 2 spaces but found 4 indent
6:1 error Expected indentation of 2 spaces but found 4 indent
6:12 error Unexpected string concatenation prefer-template
✖ 14 problems (14 errors, 0 warnings)
14 errors, 0 warnings potentially fixable with the `--fix` option.
[15:37:43] 'eslint' errored after 1.48 s
[15:37:43] ESLintError in plugin 'gulp-eslint'
Message:
Failed with 14 errors
~/Ash/sites/eslint-tutorial $gulp eslint
[15:37:42] Using gulpfile ~/Ash/sites/eslint-tutorial/gulpfile.js
[15:37:42] Starting 'eslint'...
[15:37:43]
/Users/Ash/Ash/sites/eslint-tutorial/exampleCode.js
2:1 error Expected indentation of 2 spaces but found 4 indent
2:5 error Unexpected var, use let or const instead no-var
3:1 error Expected indentation of 4 spaces but found 8 indent
3:9 error Unnecessarily quoted property 'name' found quote-props
3:16 error Strings must use singlequote quotes
3:16 error Missing space before value for key 'name' key-spacing
4:1 error Expected indentation of 4 spaces but found 8 indent
4:9 error Unnecessarily quoted property 'location' found quote-props
4:20 error Missing space before value for key 'location' key-spacing
4:20 error Strings must use singlequote quotes
4:31 error Missing trailing comma comma-dangle
5:1 error Expected indentation of 2 spaces but found 4 indent
6:1 error Expected indentation of 2 spaces but found 4 indent
6:12 error Unexpected string concatenation prefer-template
✖ 14 problems (14 errors, 0 warnings)
14 errors, 0 warnings potentially fixable with the `--fix` option.
[15:37:43] 'eslint' errored after 1.48 s
[15:37:43] ESLintError in plugin 'gulp-eslint'
Message:
Failed with 14 errors
Now we can see parts of our code that don’t align with the rules.
#⚡️ The magical fix ️️⚡️
One of the great things about ESLint is that it can fix our code for us! To do this we need to create a new task that will both lint and fix the code.
First of all we need to install an npm module called gulp-if
which allows to us to check if ESLint has fixed the file, before we update the file.
npm install gulp-if --save-dev
npm install gulp-if --save-dev
Now in our gulp file we need to:
- Add the new gulp-if dependency
- Add a new function to check if ESLint has fixed the file
- And then add our new task that both lints and fixes the file.
Our entire gulp file will now look like this:
var gulp = require('gulp')
var eslint = require('gulp-eslint')
var gulpIf = require('gulp-if') // new dependency added
gulp.task('eslint', () => {
return gulp
.src('./exampleCode.js')
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
})
// new function added to check if ESLint has run the fix
function isFixed(file) {
return file.eslint != null && file.eslint.fixed
}
// new lint and fix task
gulp.task('eslint-fix', () => {
return (
gulp
.src('./exampleCode.js')
.pipe(
eslint({
fix: true,
})
)
.pipe(eslint.format())
// if running fix - replace existing file with fixed one
.pipe(gulpIf(isFixed, gulp.dest('./')))
.pipe(eslint.failAfterError())
)
})
var gulp = require('gulp')
var eslint = require('gulp-eslint')
var gulpIf = require('gulp-if') // new dependency added
gulp.task('eslint', () => {
return gulp
.src('./exampleCode.js')
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
})
// new function added to check if ESLint has run the fix
function isFixed(file) {
return file.eslint != null && file.eslint.fixed
}
// new lint and fix task
gulp.task('eslint-fix', () => {
return (
gulp
.src('./exampleCode.js')
.pipe(
eslint({
fix: true,
})
)
.pipe(eslint.format())
// if running fix - replace existing file with fixed one
.pipe(gulpIf(isFixed, gulp.dest('./')))
.pipe(eslint.failAfterError())
)
})
Now if we run gulp eslint-fix
we should get the following:
~/Ash/sites/eslint-tutorial $gulp eslint-fix
[15:38:52] Using gulpfile ~/Ash/sites/eslint-tutorial/gulpfile.js
[15:38:52] Starting 'eslint-fix'...
[15:38:54] Finished 'eslint-fix' after 1.51 s
~/Ash/sites/eslint-tutorial $gulp eslint-fix
[15:38:52] Using gulpfile ~/Ash/sites/eslint-tutorial/gulpfile.js
[15:38:52] Starting 'eslint-fix'...
[15:38:54] Finished 'eslint-fix' after 1.51 s
Now we can see that all of our previous problems have been removed and the contents or our exampleCode.js
file has been updated in various ways.
Before:
function exampleFunction() {
var testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return testObject.name + ', ' + testObject.location
}
exampleFunction()
function exampleFunction() {
var testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return testObject.name + ', ' + testObject.location
}
exampleFunction()
After:
function exampleFunction() {
const testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return `${testObject.name}, ${testObject.location}`
}
exampleFunction()
function exampleFunction() {
const testObject = {
name: 'Ash Connolly',
location: 'Sheffield',
}
return `${testObject.name}, ${testObject.location}`
}
exampleFunction()
Changes made:
- All indentation has been updated
- Var changed to ES6 const
- Unnecessary quoting of object property removed
- Double quotes changed to single in object values
- Space before object values added
- Added trailing comma for last object property
- Return string has been changed to an ES6* template literal.
And that's it! All our linting is working and it can fix our code for us! 😀 When running the fix you should be confident that you understand the issues and changes made after running the fix to ensure that your JS behaves as expected.
* Please note the last change involves converting a JavaScript string to an ES6 JavaScript template literal, which as an ES6 feature, is not supported in all browsers. To enable use of ES6 JavaScript in older browsers you will need to transpile it using https://babeljs.io/.
If you would like to download a working demo containing all the code in this tutorial please click here (be sure to run npm i
to install the dependencies).
#Advanced example
If you would like to see a more advanced gulp task where we can pass options via the command line, we can use the advanced example below (note this requires you to install a new dependency by running npm i yargs --save-dev
):
var gulp = require('gulp')
var eslint = require('gulp-eslint')
var gulpIf = require('gulp-if')
var argv = require('yargs').argv
gulp.task('eslint', () => {
var target = argv.file ? argv.file : './exampleCode.js'
var fixCode = argv.fix ? true : false
return gulp
.src(target, { base: './' })
.pipe(
eslint({
fix: fixCode,
})
)
.pipe(eslint.format())
.pipe(gulpIf(isFixed, gulp.dest('./')))
.pipe(eslint.failAfterError())
})
var gulp = require('gulp')
var eslint = require('gulp-eslint')
var gulpIf = require('gulp-if')
var argv = require('yargs').argv
gulp.task('eslint', () => {
var target = argv.file ? argv.file : './exampleCode.js'
var fixCode = argv.fix ? true : false
return gulp
.src(target, { base: './' })
.pipe(
eslint({
fix: fixCode,
})
)
.pipe(eslint.format())
.pipe(gulpIf(isFixed, gulp.dest('./')))
.pipe(eslint.failAfterError())
})
We can now pass ESLint a specific file to lint, if we don’t it will use the default target in the gulp task, currently set to ./exampleCode.js
. We can also set a flag to fix the file using --fix
.
Here is a list of example commands you could pass to the eslint
gulp task:
gulp eslint // lint default target(s)
gulp eslint --fix // lint + fix all default target(s)
gulp eslint --file ./folder/filename.js // lint supplied file
gulp eslint --file ./folder/filename.js --fix // lint + fix supplied file
gulp eslint // lint default target(s)
gulp eslint --fix // lint + fix all default target(s)
gulp eslint --file ./folder/filename.js // lint supplied file
gulp eslint --file ./folder/filename.js --fix // lint + fix supplied file
Enjoy, and happy linting! 😀
If you like React, Next.js or front end development in general, feel free to follow and say hi on Twitter @_AshConnolly! 👋 🙂