DEV Community

Ajay Maurya
Ajay Maurya

Posted on

Set up ESLint, Prettier and pre-commit hooks using Husky for WordPress from Scratch.

EsLint in  WordPress

I hope you guys are aware of Eslint and its advantages, so let's start setting up Eslint, Prettier, with Husky on WordPress or any other project.

A one-liner about each of them.

ESLint: - ESLint is a linter for the JavaScript programming language, written in Node.js. That helps you find and fix problems in your JavaScript code.

Prettier: - Prettier is an opinionated code formatter that formats the code with the help of rules we set.

Husky: - Husky is an NPM package that lets you run a set of commands or script before any git action. For eg pre-push, pre-commit, pre-rebase.

1. Adding NPM to the project.

For running ESLint, we need to have npm installed in our project. If you already have a package.json file, You can skip this step and go directly to adding ESLint to the project; if not, then follow these steps to create a package.json file.

Navigate to the root folder of your project. For WordPress, it can be your theme folder or your assets folder within the theme.

Run this command.

npm init
Enter fullscreen mode Exit fullscreen mode

Please add details about your projects, and after that, a package.json and package-lock.json file will be created in your root folder.

2. Setting up ESLint

Run this command to add ESlint to your project.

npm install eslint --save-dev
Enter fullscreen mode Exit fullscreen mode

Reference: https://eslint.org/docs/user-guide/getting-started

The above command will install ESLint within your project. Now we will be setting up a few ESLint rules and config for our project; we will use eslint-config-airbnb rules.

3. Configure ESLint with Airbnb package

Run this command.

npx eslint --init
Enter fullscreen mode Exit fullscreen mode

Add the details regarding your project. And for the following questions, please select the below answers.

  • “What format do you want your config file to be in?” - please select JSON from the chosen options.

  • “Which style guide do you want to follow?“ - please select Airbnb from the chosen options.

Run this command.

npm i eslint-config-airbnb
Enter fullscreen mode Exit fullscreen mode

Reference: https://www.npmjs.com/package/eslint-config-airbnb

4. Creating ESLint config file

Create a file named .eslintrc.json to add all your ESLint rules.

ESLint config file that disables all rules individually.

This is an Eslint config (eslintrc.json) file that has all the rules turned off so that you can change your code on a rule by rule basis rather than changing all the code to fit all the rules?

Copied the rules from here and turned them off - https://eslint.org/docs/rules/

filename - .eslintrc.json

{
  "env": {
    "browser": true
  },
 "extends": ["airbnb-base", "plugin:prettier/recommended"],
  "plugins": ["prettier"],
  "rules": {
    // disable es6 rules -- start
    "arrow-body-style": "off",
    "arrow-parens": "off",
    "arrow-spacing": "off",
    "constructor-super": "off",
    "generator-star-spacing": "off",
    "no-class-assign": "off",
    "no-confusing-arrow": "off",
    "no-const-assign": "off",
    "no-dupe-class-members": "off",
    "no-duplicate-imports": "off",
    "no-new-symbol": "off",
    "no-restricted-imports": "off",
    "no-this-before-super": "off",
    "no-useless-computed-key": "off",
    "no-useless-constructor": "off",
    "no-useless-rename": "off",
    "no-var": "off",
    "object-shorthand": "off",
    "prefer-arrow-callback": "off",
    "prefer-const": "off",
    "prefer-destructuring": "off",
    "prefer-numeric-literals": "off",
    "prefer-reflect": "off",
    "prefer-rest-params": "off",
    "prefer-spread": "off",
    "prefer-template": "off",
    "require-yield": "off",
    "rest-spread-spacing": "off",
    "sort-imports": "off",
    "symbol-description": "off",
    "template-curly-spacing": "off",
    "yield-star-spacing": "off",
    // disable es6 rules -- end

    // Possible Errors -- start
    "for-direction": "off",
    "getter-return": "off",
    "no-async-promise-executor": "off",
    "no-await-in-loop": "off",
    "no-compare-neg-zero": "off",
    "no-cond-assign": "off",
    "no-console": "off",
    "no-constant-condition": "off",
    "no-control-regex": "off",
    "no-debugger": "off",
    "no-dupe-args": "off",
    "no-dupe-else-if": "off",
    "no-dupe-keys": "off",
    "no-duplicate-case": "off",
    "no-empty": "off",
    "no-empty-character-class": "off",
    "no-ex-assign": "off",
    "no-extra-boolean-cast": "off",
    "no-extra-parens": "off",
    "no-extra-semi": "off",
    "no-func-assign": "off",
    "no-import-assign": "off",
    "no-inner-declarations": "off",
    "no-invalid-regexp": "off",
    "no-irregular-whitespace": "off",
    "no-loss-of-precision": "off",
    "no-misleading-character-class": "off",
    "no-obj-calls": "off",
    "no-promise-executor-return": "off",
    "no-prototype-builtins": "off",
    "no-regex-spaces": "off",
    "no-setter-return": "off",
    "no-sparse-arrays": "off",
    "no-template-curly-in-string": "off",
    "no-unexpected-multiline": "off",
    "no-unreachable": "off",
    "no-unreachable-loop": "off",
    "no-unsafe-finally": "off",
    "no-unsafe-negation": "off",
    "no-useless-backreference": "off",
    "require-atomic-updates": "off",
    "use-isnan": "off",
    "valid-typeof": "off",
    // Possible Errors -- end

    // Best Practices -- start
    "accessor-pairs": "off",
    "array-callback-return": "off",
    "block-scoped-var": "off",
    "class-methods-use-this": "off",
    "complexity": "off",
    "consistent-return": "off",
    "curly": "off",
    "default-case": "off",
    "default-case-last": "off",
    "default-param-last": "off",
    "dot-location": "off",
    "dot-notation": "off",
    "eqeqeq": "off",
    "grouped-accessor-pairs": "off",
    "guard-for-in": "off",
    "max-classes-per-file": "off",
    "no-alert": "off",
    "no-caller": "off",
    "no-case-declarations": "off",
    "no-constructor-return": "off",
    "no-div-regex": "off",
    "no-else-return": "off",
    "no-empty-function": "off",
    "no-empty-pattern": "off",
    "no-eq-null": "off",
    "no-eval": "off",
    "no-extend-native": "off",
    "no-extra-bind": "off",
    "no-extra-label": "off",
    "no-fallthrough": "off",
    "no-floating-decimal": "off",
    "no-global-assign": "off",
    "no-implicit-coercion": "off",
    "no-implicit-globals": "off",
    "no-implied-eval": "off",
    "no-invalid-this": "off",
    "no-iterator": "off",
    "no-labels": "off",
    "no-lone-blocks": "off",
    "no-loop-func": "off",
    "no-magic-numbers": "off",
    "no-multi-spaces": "off",
    "no-multi-str": "off",
    "no-new": "off",
    "no-new-func": "off",
    "no-new-wrappers": "off",
    "no-octal": "off",
    "no-octal-escape": "off",
    "no-param-reassign": "off",
    "no-proto": "off",
    "no-redeclare": "off",
    "no-restricted-properties": "off",
    "no-return-assign": "off",
    "no-return-await": "off",
    "no-script-url": "off",
    "no-self-assign": "off",
    "no-self-compare": "off",
    "no-sequences": "off",
    "no-throw-literal": "off",
    "no-unmodified-loop-condition": "off",
    "no-unused-expressions": "off",
    "no-unused-labels": "off",
    "no-useless-call": "off",
    "no-useless-catch": "off",
    "no-useless-concat": "off",
    "no-useless-escape": "off",
    "no-useless-return": "off",
    "no-void": "off",
    "no-warning-comments": "off",
    "no-with": "off",
    "prefer-named-capture-group": "off",
    "prefer-promise-reject-errors": "off",
    "prefer-regex-literals": "off",
    "radix": "off",
    "require-await": "off",
    "require-unicode-regexp": "off",
    "vars-on-top": "off",
    "wrap-iife": "off",
    "yoda": "off",
    // Best Practices -- end

    // Strict Mode -- start
    "strict": "off",
    // Strict Mode -- end

    // Variables -- start
    "init-declarations": "off",
    "no-delete-var": "off",
    "no-label-var": "off",
    "no-restricted-globals": "off",
    "no-shadow": "off",
    "no-shadow-restricted-names": "off",
    "no-undef": "off",
    "no-undef-init": "off",
    "no-undefined": "off",
    "no-unused-vars": "off",
    "no-use-before-define": "off",
    // Variables -- end

    // Stylistic Issues -- start
    "array-bracket-newline": "off",
    "array-bracket-spacing": "off",
    "array-element-newline": "off",
    "block-spacing": "off",
    "brace-style": "off",
    "camelcase": "off",
    "capitalized-comments": "off",
    "comma-dangle": "off",
    "comma-spacing": "off",
    "comma-style": "off",
    "computed-property-spacing": "off",
    "consistent-this": "off",
    "eol-last": "off",
    "func-call-spacing": "off",
    "func-name-matching": "off",
    "func-names": "off",
    "func-style": "off",
    "function-call-argument-newline": "off",
    "function-paren-newline": "off",
    "id-denylist": "off",
    "id-length": "off",
    "id-match": "off",
    "implicit-arrow-linebreak": "off",
    "indent": "off",
    "jsx-quotes": "off",
    "key-spacing": "off",
    "keyword-spacing": "off",
    "line-comment-position": "off",
    "linebreak-style": "off",
    "lines-around-comment": "off",
    "lines-between-class-members": "off",
    "max-depth": "off",
    "max-len": "off",
    "max-lines": "off",
    "max-lines-per-function": "off",
    "max-nested-callbacks": "off",
    "max-params": "off",
    "max-statements": "off",
    "max-statements-per-line": "off",
    "multiline-comment-style": "off",
    "multiline-ternary": "off",
    "new-cap": "off",
    "new-parens": "off",
    "newline-per-chained-call": "off",
    "no-array-constructor": "off",
    "no-bitwise": "off",
    "no-continue": "off",
    "no-inline-comments": "off",
    "no-lonely-if": "off",
    "no-mixed-operators": "off",
    "no-mixed-spaces-and-tabs": "off",
    "no-multi-assign": "off",
    "no-multiple-empty-lines": "off",
    "no-negated-condition": "off",
    "no-nested-ternary": "off",
    "no-new-object": "off",
    "no-plusplus": "off",
    "no-restricted-syntax": "off",
    "no-tabs": "off",
    "no-ternary": "off",
    "no-trailing-spaces": "off",
    "no-underscore-dangle": "off",
    "no-unneeded-ternary": "off",
    "no-whitespace-before-property": "off",
    "nonblock-statement-body-position": "off",
    "object-curly-newline": "off",
    "object-curly-spacing": "off",
    "object-property-newline": "off",
    "one-var": "off",
    "one-var-declaration-per-line": "off",
    "operator-assignment": "off",
    "operator-linebreak": "off",
    "padded-blocks": "off",
    "padding-line-between-statements": "off",
    "prefer-exponentiation-operator": "off",
    "prefer-object-spread": "off",
    "quote-props": "off",
    "quotes": "off",
    "semi": "off",
    "semi-spacing": "off",
    "semi-style": "off",
    "sort-keys": "off",
    "sort-vars": "off",
    "space-before-blocks": "off",
    "space-before-function-paren": "off",
    "space-in-parens": "off",
    "space-infix-ops": "off",
    "space-unary-ops": "off",
    "spaced-comment": "off",
    "switch-colon-spacing": "off",
    "template-tag-spacing": "off",
    "unicode-bom": "off",
    "wrap-regex": "off"
    // Stylistic Issues -- end
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Creating ESLint ignore file

Reference - https://eslint.org/docs/user-guide/configuring#ignorepatterns-in-config-files

Create a file named .eslintignore This file is used to add rules to ignore specific files or folders.

For now, I have just ignored the node_modules folder; you can customize and add new rules based on your requirement.

filename - .eslintignore

node_modules/**
Enter fullscreen mode Exit fullscreen mode

6. Adding Prettier along with ESLint

Adding prettier with ESLint - https://prettier.io/docs/en/install.html

Run these commands to install prettier

npm install --save-dev --save-exact prettier
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
Enter fullscreen mode Exit fullscreen mode

The former turns off all ESLint rules that could conflict with Prettier, the latter integrates the Prettier rules into ESLint rules.

filename - .prettierrc

{
  "semi": true,
  "trailingComma": "es5",
  "printWidth": 120,
  "singleQuote": true,
  "arrowParens": "always",
  "proseWrap": "preserve"
}
Enter fullscreen mode Exit fullscreen mode

7. Adding Prettier ignore file

.prettierignore file contains a list of files that needs to be ignored from the Prettier check.

filename - .prettierignore

node_modules/**

# Ignore all JS files:
**/*.js
Enter fullscreen mode Exit fullscreen mode

For now, I have ignored all the js files and the node_modules folder.

Hurrah!
Hurrah! We have now set up the ESLint + Prettier for our project.

Command to check ESLint errors in files.

For a single js file.

npx eslint js/amplitude.js --format table
Enter fullscreen mode Exit fullscreen mode

For multiple js files at once

npx eslint **/*.js --format table
Enter fullscreen mode Exit fullscreen mode

8. Configure ESlint and Prettier plugins in VSCode.

We have ESLint and Prettier config files in our project, so the VSCode editor plugins will only use our local config.

ESLint Plugin
Prettier Plugin

8. Pre-Commit hooks check using Husky

Setup Husky to use it with a pre-commit hook and check for any linting errors.

Steps to install Husky

npm install husky --save-dev
npm install --save-dev lint-staged
Enter fullscreen mode Exit fullscreen mode

Add Husky pre-commit config in your package.json file

Add husky config in your package.json file

"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "**/*.js": [
      "eslint --fix",
      "git add"
    ]
  },
Enter fullscreen mode Exit fullscreen mode

Whenever you commit any change and contain a js file, Husky will check if those files meet all the linting requirements or not; if not, it won't let you commit unless you fix those issues.

This is how a successful Husky check looks like.

Husky Check

9. Final package.json File

This is how my package.json file looks like after performing all the above steps.

Filename - package.json

{
  "name": "twentyseventeen",
  "version": "1.0.0",
  "description": "=== Twenty Seventeen === Contributors: wordpressdotorg Tested up to: 5.5 Version: 2.4 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Tags: one-column, two-columns, right-sidebar, flexible-header, accessibility-ready, custom-colors, custom-header, custom-menu, custom-logo, editor-style, featured-images, footer-widgets, post-formats, rtl-language-support, sticky-post, theme-options, threaded-comments, translation-ready",
  "main": "index.js",
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "**/*.js": [
      "eslint --fix",
      "git add"
    ]
  },
  "dependencies": {
    "eslint-config-airbnb": "^18.2.0"
  },
  "devDependencies": {
    "eslint": "^7.11.0",
    "eslint-config-airbnb-base": "^14.2.0",
    "eslint-config-prettier": "^6.12.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-prettier": "^3.1.4",
    "husky": "^4.3.0",
    "lint-staged": "^10.4.0",
    "prettier": "2.1.2"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
azoth128 profile image
Tim Wundenberg

The steps described under 8. and 9. didn't work for me.

In the end I used the main guides of lint-stage and husky:
npmjs.com/package/lint-staged/v/7.3.0
github.com/typicode/husky

Husky seemed to be redesigned at some point. This is explained in a blog post:
blog.typicode.com/husky-git-hooks-...

Collapse
 
learn_with_neil profile image
Neil Mosunic

so that you can change your code on a rule by rule basis rather than changing all the code to fit all the rules?

Can you clarify this? If you extend from air bnb then disable everything what is the point of extending from air bnb?