Lesson Weekend

Let's finish up by adding a linter to our project. Linters will check our code for errors. Even better, linters can even tell us when we're writing code that's not very good!

Installation

We'll install eslint, a popular JavaScript linter, along with eslint-loader, which allows us to use the linter with webpack.

npm install [email protected] --save-dev
npm install [email protected] --save-dev

Let's update our webpack configuration so our code is automatically linted whenever we build:

webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "eslint-loader"
      }
    ]
  }
};

Here we specify that ESLint should lint all JavaScript files except the files in our node_modules directory. We don't want to lint those because they are external JavaScript libraries that have (hopefully) been tested by other developers. This is common practice.

Notice that we've added the eslint-loader to the bottom of the array of rules. The rule for the eslint-loader must be placed last in the array of rules. This is because we need to make sure that our linter is running on our original files, not the concatenated and minified build file. The loaders are applied from last to first (in reverse order), so by placing it at the bottom, we ensure that it executes before our other loaders.

Note that ESLint won't work yet - and we won't be able to build our project until we add an .eslintrc configuration file in just a moment. If you try to build now, you'll get an error. That's because our webpack configuration is trying to run ESLint but can't do so without a configuration file.

Configuring ESLint

To use ESLint, we'll need to add a configuration file called .eslintrc in the root directory of our project:

.eslintrc
{
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "extends": "eslint:recommended",
    "env": {
      "es6": true,
      "browser": true,
      "jquery": true,
      "node": true
    },
    "rules": {
        "semi": 1,
        "indent": ["warn", 2]
    }
}

Our configuration file is written using JSON (JavaScript Object Notation), but it can also be written in JavaScript or YAML (Yet Another Markup Language). ESLint is very configurable and we just have a few basics here.

  • We specify the parser should look for "modules" ("sourceType": "module") that are written using up through ES2018 ("ecmaVersion": 2018). So far we are just using a few of these newer JavaScript features (such as import and export statements), but we'll use more soon. Note that ES2018 is provides even more functionality than ES6 (which was released in 2015). Why use a more advanced version here? Using an older version caused bugs in our configuration of ESLint and this was the hot fix. A hot fix is when a bug causes problems and needs to be fixed immediately. Also, ESLint will recognize some newer JS features we'll be using in the next section as well.

  • We use ESLint's recommended set of rules for linting. We could customize these or use other sets as well. Once again, ESLint's recommended rules are very strict - so we can always customize them further as needed. To see exactly which rules are included in the recommended set, go to ESLint Rules. All rules that include a checkmark are part of the recommended set. As you'll see, there are a lot of them!

  • We let ESLint know a few things about our global environment. Specifically we are using ES6 and jQuery and we are working in the browser. If we didn't include these rules, our linter will throw incorrect errors (such as $ is undefined). The example above occurs when ESLint doesn't know that jQuery is supposed to be part of the project.

  • We add a few basic rules.

    • First, we are using semicolons and setting the error level to 1, which means the linter will give us a warning about missing semicolons. (An error level of 2 means the linter will throw an error instead - stopping our code in its tracks.)
    • We also add a rule for indentation. We pass "warn" instead of 1. The second argument in the array is the number of spaces our code should be indented, so this is a little confusing. In this case, 2 means indented spaces, but it often means the error level. We'll cover the error level more shortly.

Now that we have an .eslintrc configuration in place, we can now build our project again - and it will automatically be linted for us! Try introducing an error (for instance, add a typo to a variable name) and see for yourself!

npm Scripts

It would be nice to have the option to lint our code without building it as well. We can add a script to our package.json file to handle this:

package.json
...
  "scripts": {
    "build": "webpack --mode development",
    "start": "npm run build; webpack-dev-server --open --mode development",
    "lint": "eslint src/*.js"
  },
...

Now $ npm run lint will run ESLint for all JavaScript files in our src folder. It's a simple configuration since we're storing all of our JS in one place: just look at all (the * is a wildcard that means all characters) files with a .js extension in the src directory.

Customizing ESLint

ESLint can be a real headache to configure. The default configuration is very strict and it will throw an error at many different things, even when our code is working otherwise. Sometimes these errors are helpful because they'll lead to better code. At other times, though, ESLint can bog us down. In these cases, we can update our ESLint configuration file to be a little less strict.

There are three basic levels for how ESLint can handle an error:

  • 0 or "off" means that ESLint will ignore a rule entirely. In general, we don't want to do this. It's nice to at least get a warning (even if we plan to ignore it).
  • 1 or "warn" means that ESLint will provide a warning but will not throw an error. If we don't want a rule to throw an error, we'll generally change it to be a warning instead.
  • 2 or "error" means that ESLint will throw an error and stop our code in its tracks. That means we can't build until we fix the error (or change the rule to a warning). In general, we should focus on fixing the error instead of changing the rule to a warning, but there will be times when it makes sense to update the rule.

Because we are using the recommended set of rules, many are already automatically set to level 2 - the highest level. We can override the recommended settings by adding custom configurations to the "rules" object of .eslintrc.

Let's go over an example of updating a rule to be a warning instead of an error. The recommended set of rules does not allow debugger; statements. This makes sense - in general, we should add breakpoints via the Sources tab of Chrome DevTools instead.

However, let's say we don't know about this rule yet (after all, there are so many ESLint rules) and we decide we want to add a debugger; statement directly to our code:

src/main.js
import $ from 'jquery';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/styles.css';
import Triangle from './triangle.js';

$(document).ready(function() {
  $('#triangle-checker-form').submit(function(event) {
    event.preventDefault();
    debugger;
    const length1 = $('#length1').val();
    const length2 = $('#length2').val();
    const length3 = $('#length3').val();
    const triangle = new Triangle(length1, length2, length3);
    const response = triangle.checkType();
    $('#response').append("<p>" + response + "</p>");
  });
});

Now if we run $ npm run build, we'll get the following error:

ERROR in ./src/main.js
Module Error (from ./node_modules/eslint-loader/dist/cjs.js):

/Users/staff/Desktop/shape-tracker/src/main.js
  10:5  error  Unexpected 'debugger' statement  no-debugger

✖ 1 problem (1 error, 0 warnings)

This error causes our build to fail entirely. As we can see, this is pretty annoying if we want to be able to have debugger; statements. There are actually several ways we can address this issue.

Let's say we just want to have a debugger; statement briefly - and we don't actually want to update the rule. We can do the following in our code:

/* eslint-disable */
debugger;
/* eslint-enable */

This disables ESLint before the specified line and then enables ESLint again after the specified line (or lines). We can even make it more specific:

/* eslint-disable no-debugger */
debugger;
/* eslint-enable no-debugger */

Here, we specify that just the no-debugger rule should be disabled for the line. We should be more specific if possible, especially if there's a significant amount of code in the section we are temporarily disabling.

In general, we don't want to leave these comments in our code for long - but they can be helpful if we are trying to debug something and we really need to build our code and take a look in the browser to see what's going on. For instance, ESLint may be doing an excellent job pointing at an error in our code - but we might need to look at the code in the browser to understand what's going on.

By the way, these comments should never be included in a submitted independent project. They aren't portfolio-ready code!

The other option is to change an error to a warning in our .eslintrc file. If we do this, we don't have to add comments directly to our code - and these rules will apply everywhere for ESLint.

Here's how we can update the rule in our .eslintrc file:

.eslintrc
"rules": {
      "semi": 1,
      "indent": ["warn", 2],
      "no-debugger": "warn" // new line
  }

We simply add a line to the "rules" object with a key of "no-debugger" (the name of the rule) and a value of "warn". This will override the default setting.

Now if we build our code, we'll get a warning instead:

10:5  warning  Unexpected 'debugger' statement  no-debugger

Our code will build and we'll be able to use our debugger; statement in our built project.

Here's the general process we should follow if ESLint throws an error:

  • Find and fix the error if possible, then build our code as usual.
  • If we can't find and fix the error without looking at our built project in the browser, use comments to disable the section of code that is causing problems. Once the error is fixed, remove those comments.
  • As a last resort, if we really find that ESLint is being too strict, we can update the rule to give us a warning instead of throwing an error. The name of the rule is always included in the error that ESLint throws - so we can just put that rule directly in our .eslintrc file and change its setting to "warn".

We've found that students will have issues with a wide variety of rules - and we can't troubleshoot or list them all. Use this section to guide you through any rules that cause you issues. You are welcome to customize your .eslintrc file to suit you and your coding needs - after all, the purpose of ESLint is to help you write better code, not cause you headaches. You will not be penalized on your independent project for any custom configurations you make in .eslintrc.

We recommend looking over at the ESLint Configuration page. There's a lot of information here - so you don't need to absorb it all. Ultimately, it's fine to just use the recommended rule set - and to update rules as needed (but hopefully sparingly).

Now that we've added a linter, ESLint will help us write better code and avoid bugs along the way.

Lesson 17 of 48
Last updated April 6, 2021