Lesson Weekend

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

Installation

We'll install one now, called eslint (a popular JavaScript linter), and something called 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 for those in our nodemodules_ directory. We exclude 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 at last in the array of rules. This is because we need to make sure that our linter is running on our original files, and not the concatted and minified build file. The loaders are applied bottom to top, so by placing it at the bottom, we ensure that it executes first before our other loaders.

Configuring ESLint

To get this working we also need a configuration file called .eslintrc. This should go in the root directory of our project:

.eslintrc
{
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module"
    },
    "extends": "eslint:recommended",
    "env": {
      "es6": true,
      "browser": true,
      "jquery": 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 in ES6 ("ecmaVersion": 6). 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.

  • We use ESLint's recommended set of rules for linting. We could customize these or use other sets as well.

  • 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).

  • We add a few basic rules.

    • First, we are using semi-colons and setting the error level to 1, which means the linter will give us a warning about missing semi-colons. (An error level of 2 means the linter will throw an error instead.)
    • 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.

Console and Debugger Statements

By default, ESLint will throw an error if a call to console is made in our code. It will also throw an error if there is a debugger; statement in our code. According to the ESLint documentation, this is because "it’s considered a best practice to avoid using methods on console" and debugger; statements. However, the console and debugger are helpful tools for understanding our code. So, in order to use the console and debugger, we need to update the rules section of our ESLint configuration:

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

By setting no-console and no-debugger to warn we're telling ESLint to simply issue a warning if it encounters a call to console or debugger;. This is safer than setting the values to off, since we want to make sure that we aren't publishing code that uses console or debugger.

We run into a similar issue if we try to use alert() in our code. The linter throws an error when it runs into alert(), confirm(), and prompt(). The ESLint documentation states:

"JavaScript’s alert, confirm, and prompt functions are widely considered to be obtrusive as UI elements and should be replaced by a more appropriate custom UI implementation."

Sometimes beginner developers use alert() to debug code. But this is clunky and inefficient compared to other options like debugger or console.log(). Always choose to use debugger or console.log() to debug your code.

Note: If you choose to use console.log(), debugger, or alert() in your independent projects, make sure to remove all instances of them before submitting your code. Projects containing console.log(), debugger, or alert are considered to not be in portfolio-ready condition.

More Linter Configurations

Once again, there are many other rules and configurations. Feel free to explore further on your own. Remember, ESLint can be used to find errors and write better code. For instance, we could set ESLint to throw an error if a function has too many statements or even too high a level of complexity. (We determine a function's level of cyclomatic complexity based on how many outcomes a function can have; for instance, a function with 20 conditionals would have high complexity and would be very difficult to read and reason about.) Functions that are too complex or have too many statements are often buggy and hard to read, so we can customize ESLint in lots of unique ways to improve our code! You aren't expected to configure ESLint beyond the settings above for Friday's code review.

Now we can run $ npm run build and ESLint will automatically lint our code. Go ahead and introduce an error and see for yourself.

npm Scripts

Let's also make linting a little more convenient. We might not always want to build every time we lint. So let's add another script to package.json for linting our code:

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.

One thing to note: ESLint can get very mad at your code, sometimes for reasons that seem quite nitpicky. If you find that ESLint is being too nitpicky, you can update the configuration either by turning off the error altogether or setting the error to a warning instead of an error. However, more often than not, you'll want to fix the issue even if the code would work anyway. ESLint can be a stern taskmaster but it's a great way to learn best practices and write better code.