Introduce ESLint to the block development

In this article, I would like to explain how to introduce ESLint to improve the quality of JavaScript code and standardize coding style in WordPress block development.

Why ESLint?

No matter how many spaces there are before and after the brackets, where you break lines, or what variable names you use, as long as the code is all correct, the block will work correctly. No warnings or errors will be displayed in the browser console. If you are the only one who will read the code and you have not exposed it anywhere, that may not be a problem.

However, if you expose the plugin on GitHub or somewhere else, someone may become interested in the plugin, read the code, or write code and submit a pull request. Also, if multiple developers are involved in one block, you will see code written by developers other than yourself.

How would you feel if the coding rules were not unified in such a situation? It may be difficult to read, and it may cause bugs to be introduced.

Create a Block Template

Before explaining about ESLint, let’s first create a template for blocks. You can create each file by yourself, but there is a convenient library called @wordpress/create-block, so we will use it to create a template:

npx @wordpress/create-block gutenpride

If you open the directory called gutenpride, it should consist of the following files:

create-block によって作成されるファイル

EditorConfig

Looking at the file structure, we can see that there is a file called .editorconfig. Before introducing ESLint, I would like to explain EditorConfig, which is another important setting for unifying coding style.

As one reason for introducing EditorConfig, I would like to give the following example:

  • Multiple developers are involved in one plugin.
  • Developer A has set the indentation to two half-width spaces in the default settings of his code editor.
  • Developer B has set the indentation to tabs in the default settings of his code editor, with an indentation width of four characters.

Suppose Developer A wrote the following code. Note that all the indentations are spaces.

2スペースインデントを持つコード

Developer B opens the file with this code in his or her code editor, removes the indentation of 34 lines, and then visually rearranges the indentation so that it is correct.

一部にタブインデントを持つコード

However, if Developer A reopens the file, he or she sees that the indentation on line 34 is visually incorrect.

一部にタブインデントを持つコード

Some code editors may automatically detect the indentation of a file, but it is not guaranteed that the indentation will be visually consistent between all developers.

One reason to introduce EditorConfig is to prevent such differences and unify coding style within a project.

EditorConfig is supported by a large number of code editors. For some code editors, you may need to install a plugin. Check the following page to see if your code editor requires an additional plugin:

EditorConfig for create-block

Now let’s take a look at the .editorconfig settings created by create-block.

# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org

# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab

[*.{yml,yaml}]
indent_style = space
indent_size = 2

Looking at the settings in this file, for example, we can see that the indentation rule mentioned above is set to “tab indent.” You can customize these rules to your liking, but the important thing is whether the coding style is consistent across the entire project based on the rules.

I strongly recommend that you define rules using EditorConfig before introducing ESLint.

Check JavaScript Code with ESlint

Returning to the topic of ESLint, let’s say you update the Edit component of a block to store the text the block displays in the my_variable variable.

export default function Edit() {
	const my_variable = __( 'Gutenpride – hello from the editor!', 'gutenpride' );
	return (
		<p { ...useBlockProps() }>
			{ my_variable }
		</p>
	);
}

If you create a template block using create-block, a mechanism for analyzing such code using ESLint is included by default. Let’s try running the lint:js script defined in the scripts field of package.json.

npm run lint:js

When you run this script, you should see an error similar to the following:

lint:js コマンドを実行した後に表示されるエラー

This error tells us that the code does not follow two rules:

  • The variable name my_variable is not in camel case
  • Line breaks and indentation are not correct

Fix Lint Errors Automatically

So, to fix these errors, do we have to fix them all manually and run lint:js over and over again until the errors are fixed?

Like lint:js, create-block has the format script to fix these errors to some extent automatically.

$ npm run format

> gutenpride@0.1.0 format
> wp-scripts format

package-lock.json 129ms
package.json 1ms
src/block.json 5ms
src/edit.js 10ms
src/index.js 3ms
src/save.js 2ms
src/view.js 2ms

When you run this command, one of the two errors that was warned about, “errors related to line breaks and indentation,” will be automatically corrected.

export default function Edit() {
	const my_variable = __(
		'Gutenpride – hello from the editor!',
		'gutenpride'
	);
	return <p { ...useBlockProps() }>{ my_variable }</p>;
}

Fix Lint Errors Manually

However, errors in variable names are not automatically fixed. The format command does not automatically fix all errors, so you must manually fix any remaining errors. Change the variable name to camel case (myVariable) as follows:

export default function Edit() {
	const myVariable = __(
		'Gutenpride – hello from the editor!',
		'gutenpride'
	);
	return <p { ...useBlockProps() }>{ myVariable }</p>;
}

If you run npm run lint:js you should be able to confirm that all errors have been resolved.

Where are These ESLint Rules Defined?

Why is my code being analyzed based on certain rules even though I haven’t defined any ESLint rules myself?

When a file template is created with create-block, a library called @wordpress/scripts is installed by default, and ESLint rules are defined in a library that this library depends on. That library is @wordpress/eslint-plugin.

Using the npm ls command, you can see that @wordpress/scripts depends on @wordpress/eslint-plugin.

$ npm ls @wordpress/eslint-plugin
gutenpride@0.1.0 /home/username/projects/gutenpride
└─┬ @wordpress/scripts@30.3.0
  └── @wordpress/eslint-plugin@21.3.0

When you run lint:js, that is, the wp-scripts lint-js script, your code will be analyzed based on the rules defined in @wordpress/eslint-plugin.

You can also see that ESLint itself exists as a dependency.

$ npm ls eslint
gutenpride@0.1.0 /home/username/projects/gutenpride
└─┬ @wordpress/scripts@30.3.0
  ├─┬ @wordpress/eslint-plugin@21.3.0
  │ ├─┬ @babel/eslint-parser@7.25.7
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ @typescript-eslint/eslint-plugin@6.21.0
  │ │ ├─┬ @typescript-eslint/type-utils@6.21.0
  │ │ │ └── eslint@8.57.1 deduped
  │ │ ├─┬ @typescript-eslint/utils@6.21.0
  │ │ │ └── eslint@8.57.1 deduped
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ @typescript-eslint/parser@6.21.0
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-config-prettier@8.10.0
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-import@2.31.0
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-jest@27.9.0
  │ │ ├─┬ @typescript-eslint/utils@5.62.0
  │ │ │ └── eslint@8.57.1 deduped
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-jsdoc@46.10.1
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-jsx-a11y@6.10.1
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-playwright@0.15.3
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-prettier@5.2.1
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-react-hooks@4.6.2
  │ │ └── eslint@8.57.1 deduped
  │ ├─┬ eslint-plugin-react@7.37.2
  │ │ └── eslint@8.57.1 deduped
  │ └── eslint@8.57.1 deduped
  └─┬ eslint@8.57.1
    └─┬ @eslint-community/eslint-utils@4.4.1
      └── eslint@8.57.1 deduped

Extend ESLint Rules

Of course, you can develop based only on these default rules. However, some developers may want to change some of the rules to suit their preferences or apply stricter rules.

To change or extend these default rules, create a file called .eslintrc in the root directory of your project and write the following code in it:

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ]
}

When you run the lint:js script, your code will be analyzed based on the rules defined here.

Tweak Existing Rules

Try updating this file to change an existing rule.

For example, say you wrote the following code to use an API prefixed with __experimental.

Note: this is just an example; there is no library called @wordpress/foo and no API called __experimentalFeature.

import { __experimentalFeature } from '@wordpress/foo';

export default function Edit() {
	const myVariable = __(
		'Gutenpride – hello from the editor!',
		'gutenpride'
	);
	return <p { ...useBlockProps() }>{ myVariable }</p>;
}

When you run lint:js you will get the following two errors:

lint:js コマンドを実行した後に表示される、実験的な API に関するエラー

Of these two errors, pay attention to the error that says “Usage of __experimentalFeature from @wordpress/foo is not allowed.” This rule is defined by default in @wordpress/eslint-plugin, and you can find more information about the rule in this README.

To disable this rule for the entire project, change the .eslintrc file as follows:

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"rules": {
		"@wordpress/no-unsafe-wp-apis": "off"
	}
}

If you run the lint:js script again, you should see that the errors related to APIs with the __experimental prefix disappear.

This rule is also disabled in the Gutenberg project.

Add New Rules

I would like to introduce some rules that I personally recommend to improve the code quality and security of block development. These rules are not included in the default rules of @wordpress/eslint-plugin, so you need to add them explicitly.

react/jsx-boolean-value

In React, enforces rules when the value of a prop is a boolean and true.

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"rules": {
		"react/jsx-boolean-value": "error"
	}
}

✅ Do

const Hello = <Hello myProp />;

❌ Don’t

const Hello = <Hello myProp={ true } />;

@wordpress/i18n-text-domain

This forces you to add the correct text domain to your translation functions. Change the value of the allowedTextDomain field to suit your project.

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"rules": {
		"@wordpress/i18n-text-domain": [
			"error",
			{
				"allowedTextDomain": "gutenpride"
			}
		]
	}
}

✅ Do

export default function Edit() {
	const myVariable = __(
		'Gutenpride – hello from the editor!',
		'gutenpride'
	);
	return <p { ...useBlockProps() }>{ myVariable }</p>;
}

❌ Don’t

export default function Edit() {
	const myVariable = __(
		'Gutenpride – hello from the editor!'
	);
	return <p { ...useBlockProps() }>{ myVariable }</p>;
}

@wordpress/dependency-group

When you import code from another source, it forces you to group them properly and add comments.

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"rules": {
		"@wordpress/dependency-group": "error"
	}
}

✅ Do

/*
 * External dependencies
 */
import { camelCase } from 'change-case';

/*
 * WordPress dependencies
 */
import { Component } from 'react';

/*
 * Internal dependencies
 */
import edit from './edit';

❌ Don’t

import { camelCase } from 'change-case';
import { Component } from 'react';
import edit from './edit';

.eslintrc with the Rules Applied So Far

With all the changed and new rules applied to your .eslintrc, you should end up with something like this: Use this code as a guide to find the rules that best suit your project and preferences.

{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"rules": {
		"@wordpress/no-unsafe-wp-apis": "off",
		"react/jsx-boolean-value": "error",
		"@wordpress/i18n-text-domain": [
			"error",
			{
				"allowedTextDomain": "gutenpride"
			}
		],
		"@wordpress/dependency-group": "error"
	}
}

Detect Lint Rule Violations in the Code Editor

So far, we have fixed Lint errors and changed/added rules. But does that mean we need to run the lint:js or format script every time we update our code to detect and fix these errors?

Many code editors automatically read the ESLint rules in your project and detect them as errors in the code editor, or automatically fix code that violates the rules when you save the file.

Here, I’ll introduce the method I usually use in VSCode.

First, install the ESLint extension in VSCode and make sure ESLint is enabled in the user or workspace settings.

ユーザー設定で有効化されている ESLint 拡張

If the ESLint extension is enabled and you have code that violates a rule, you should see a warning like this:

ESLint 拡張が発する警告

Documents