Making Shared ESLint, Prettier Config Files
Published: 2024-05-18 23:49:03

* This is a translation from the original article: https://techblog.woowahan.com/15903/

* The final code example may be found here: https://github.com/iicdii/shared-config-example

# Intro

**ESLint** and **Prettier** are tools that you often use to **improve the code quality and maintain a consistent format** for JavaScript or TypeScript. ESLint allows you to quickly **identify potential problems**, and Prettier is convenient because you can focus on writing code without worrying about the **formatting of the code**.

However, setting up ESLint/Pretier is quite cumbersome whenever you create a new project. Creating a configuration file, installing the plug-in, applying recommendation rules are repeated tasks, and sometimes you can import and write completely different storage settings.

There is also a way to unify the rules with old and famous libraries like `eslint-config-airbnb`, but after trying it out, I felt that the rules were more stringent than necessary, disrupting workflow and reducing productivity. ESLint told me to fix it, so I fixed it, but I also thought, why should I fix this? Also, I was confused by different configuration settings for different repositories.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82jfy0gsrz930cqjy6sv.png)

(Escaping from ESLint's repression)

Our development team introduced a shared configuration package that included only the rules we needed to fundamentally address these issues. As a result, each repository provided a consistent development experience, which greatly helped improve the productivity of developers.

While creating a shared configuration, team members naturally discussed the rules. During the discussion, it was necessary to coordinate disagreement rules, which also reduced unnecessary arguments in code reviews.

Implementing the package was not as difficult as I thought, but what was more difficult was the agreement between the team members. The process of coordinating opinions took a long time because each developer had different preferred rules and ideas. When a long discussion was required, opinions were gathered and decided on Slack voting or a channel where front-end developers gathered.

In this article, I would like to share the process of choosing `@rushstack/eslint-config` as an alternative to the `eslint-config-airbnb` package. Also, I will discuss the process of creating a shared ESLint config package, explaining the configuration, and some recommended rules.

# The Alternative to Airbnb convention

Airbnb's `eslint-config-airbnb` package is a representative coding style guide used by many front-end developers. The Airbnb rule, which contains various JavaScript and React-related rules, was first released as an open source in May 2015 and is still used by many projects.

The Airbnb rule has the advantage of reducing ESLint setup time, but it has the disadvantage of having to follow the coding style guide set by Airbnb. Since the rules are applied to the details of the code, there are often **struggles to remove ESLint warnings during the development process**. I wrote down examples of discomfort while using the Airbnb rule in the appendix at the end of the article.

I felt that Airbnb rules were reducing productivity, so I suggested the team that we create our own ESLint configuration. At first, I suggested starting with blank paper and working out the rules whenever necessary, but I accepted the team's opinion that it would be nice to have a **base configuration** and looked into an alternative package.
An alternative to the Airbnb configuration I found is the `@rushstack/eslint-config` package managed by Microsoft. Rushstack is a monorepo management tool, but it also offers a universal ESLint shared config package. Here's **why I chose this package as an alternative.**

- Managed by Microsoft
- Made recently (first committed in December 2021)
- Non-independent rule
- The annotation provides a clear basis for each rule ([example](https://github.com/microsoft/rushstack/blob/main/eslint/eslint-config/profile/_common.js#L165-L170))
- High code quality and detailed documentation

When I looked at the actual code and documents on Github, I felt that the quality was quite good. I liked the details such as the part that considered **preventing conflicts with Prettier**, the part that organized the configuration without a recommended template in consideration of priority issues, and the fact that it provided a clear basis for each rule. Therefore, we decided to set this package as the base configuration and add the necessary rules and plug-ins. In order to use the shared config package in common within the team, I created a presentation material to persuade team members, and as a result, I succeeded in drawing empathy from our team members and began to develop the package.

# A Monorepo for the Package

First, we create a repository to manage the shared config packages. You have configured a monorepo to deploy `ESLint` and `Pretier` shared configurations in their respective npm packages. The reason you configured a monorepo is to increase the efficiency of development by **managing multiple related packages in one repository**. Monorepo makes it easier to manage dependencies between shared configuration packages, and it has the advantage of being able to **test in one place when changes are needed**.

The Core Webfront development team chose `pnpm` through an agreement within the team for consistent package manager use in all projects and repositories, and thus configured a monorepo environment using `pnpm` and `pnpm workspace`.

The following is a simple schematic diagram of the storage structure.

```
📁 example // React Vite example for testing config packages in both local and CI environments
📄 package.json
📁 packages
  📁 eslint-config
    📁 mixins
      📄 react.js // config file for React-based projects
    📄 index.js // common config file
    📄 package.json
  📁 prettier-config
    📄 index.js
    📄 package.json
📄 pnpm-workspace.yaml
```

And the following is the `package.json` at the root of the package:

```
{
  "name": "shared-config-example",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "example": "pnpm --filter example"
  },
  "devDependencies": {
    "prettier": "^3.2.5"
  },
  "pnpm": {
    "overrides": {
      "eslint": "8.57.0",
      "@typescript-eslint/eslint-plugin": "7.5.0",
      "@typescript-eslint/parser": "7.5.0"
    }
  }
}
```

The `scripts` contain sample commands to run the example app quickly. If you want to run a hint in the example app, you can simply run a hint with pnpm sample instead of pnpm --filter sample print. However, the following sections will provide examples including the --filter option for better understanding.

The `pnpm.overrides` field overrides the dependency version. TypeScript version warnings were encountered during the configuration process, so if the version warnings do not occur, it is acceptable to omit them.

The `pnpm-workspace.yaml` file is responsible for informing pnpm of the locations of the packages managed within the monorepo. This is the task of informing pnpm of the sample directory for the configuration test and that all `packages/*` are included in the monorepo. The `pnpm-workspace.yaml` file was created as follows.
```
packages:
  - 'example'
  - 'packages/*'
```
Next, we'll make the `ESLint` common config package.

# Common config package for ESLint

## package.json
First, create `package.json` under `packages/eslint-config`.
```
// packages/eslint-config/package.json
{
  "name": "@org/eslint-config",
  "main": "index.js",
  "version": "1.0.0",
  "dependencies": {
    "@rushstack/eslint-config": "3.6.8",
    "@rushstack/eslint-patch": "1.10.1",
    "@tanstack/eslint-plugin-query": "4.38.0",
    "eslint-plugin-cypress": "2.15.1",
    "eslint-plugin-jsx-a11y": "6.8.0",
    "eslint-plugin-no-relative-import-paths": "1.5.3",
    "eslint-plugin-react": "7.34.1",
    "eslint-plugin-react-hooks": "4.6.0",
    "eslint-plugin-react-refresh": "0.4.6",
    "eslint-plugin-storybook": "0.8.0",
    "eslint-plugin-testing-library": "6.2.0"
  },
  "peerDependencies": {
    "eslint": ">= 8",
    "typescript": ">= 5"
  }
}
```
The `main` field is the file that this package will use as the main, where `index.js` is specified, which is responsible for exporting the configuration of ESLint.

The dependencies field specifies the package to use RushStack's rules and the dependencies on the ESLint plug-ins required for the common rule definition. The `@rushstack/eslint-config` package is required, and the rest of the packages are installed as needed.

- @rushstack/eslint-config : This is a package that must be installed to use the rules of Rushstack.
- @rushstack/eslint-patch: Allows users to use the ESLint plug-in without having to install dependencies.
- @tanstack/eslint-plugin-query: Included because we use react-query as the standard. Cypress, Storybook, and Testing Library plug-ins were also included for the same reason.
- eslint-plugin-jsx-a11y: included to put accessibility rules that are easy for developers to miss.
- eslint-plugin-no-relative-import-paths: The team agreed to use the absolute path when using the import and added it as a rule.

The peerDependencies field stated that the ESLint >= 8 version and TypeScript >= 5 version are required to be installed. Because major changes will cause breaking changes, we specified the minimum installation version based on the major version.

## Config file
The `ESLint` shared configuration package distinguishes between the basic configuration file for general JavaScript projects and the configuration file for React projects. If you don't use React, you don't need React-related rules. For projects that use React, you just need to import both configuration files, and for projects that don't use React, you just need to import JavaScript configuration files.

### Basic config file
Defines the default configuration in the path `eslint-config/index.js`. The extents field contains the configuration of the Rush Stack, and the rest specifies the plug-ins and rules to be used in common.

```
module.exports = {
  // Define necessary configs here
  plugins: ['no-relative-import-paths'],
  extends: [
    // ✅ (required) 
    '@rushstack/eslint-config/profile/web-app',
  ],
  rules: {
    // custom rules here
    '@typescript-eslint/explicit-function-return-type': 'off',
  },
  settings: {

  },
};
```

### React config file
First, define the configuration for the React project in `eslint-config/mixins/react.js`.

```
module.exports = {
  // plugin documentation:
  // https://www.npmjs.com/package/eslint-plugin-react
  // https://github.com/ArnaudBarre/eslint-plugin-react-refresh
  // https://www.npmjs.com/package/eslint-plugin-jsx-a11y
  plugins: ["react", "react-refresh", "jsx-a11y"],
  extends: [
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:react/jsx-runtime",
    "plugin:@tanstack/eslint-plugin-query/recommended",
  ],

  settings: {
    react: {
      // Specify the current react version (other than 'detect')
      // otherwise, it will call the entire react library, leading to slower performance
      version: "detect",
    },
  },

  overrides: [
    {
      files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
      extends: ['plugin:testing-library/react'],
      rules: {
        'react-refresh/only-export-components': 'off',
      },
    },
  ],

  rules: {
    "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
    "jsx-a11y/alt-text": [
      "warn",
      {
        elements: ["img"],
      },
    ],
    "jsx-a11y/aria-props": "warn",
    "jsx-a11y/aria-proptypes": "warn",
    "jsx-a11y/aria-unsupported-elements": "warn",
    "jsx-a11y/role-has-required-aria-props": "warn",
    "jsx-a11y/role-supports-aria-props": "warn", 
    "react/no-unknown-property": "off",
    "react/prop-types": "off",
  },
};
```
The `extents` field was filled with the rules recommended by the plugins.

```
extends: [
  "plugin:react/recommended",
  "plugin:react-hooks/recommended",
  "plugin:react/jsx-runtime",
  "plugin:@tanstack/eslint-plugin-query/recommended",
],
```

The settings field specifies React's version. If you read the `eslint-plugin-react` documentation, there is no problem if you do not specify it, as it automatically detects the React version by default. But if you do not specify the React version, it will bring up the entire React library, which will slow down the lint(a tool that identifies and inspects problems with code in software development). Not all repositories use the latest version of React, so we put the setting as `detect`, and the actual React project should specify the version in use in the project, such as version: '18.2'.

```
  settings: {
    react: {
      version: "detect", // to be replace with the actual react version
    },
  },
```

You can use the `override` field when you want to apply the ESLint settings differently for a particular file or folder. I have set special rules for test files (files in the __tests__ folder and files that include spec or test in their name). Here we extend plugin: `testing-library/react` to apply the test-related recommendation settings and turn off the `react-refresh/only-export-components` rule. This separation of rules to apply to production and test files prevents unnecessary lint warnings from occurring when developing.

```
 overrides: [
    {
      files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
      extends: ['plugin:testing-library/react'],
      rules: {
        'react-refresh/only-export-components': 'off',
      },
    },
  ],
```

The `rules` field defines custom rules by referring to the rules that are enabled by default when creating Vite and Next.js projects. Let's take a look at which rules are defined one by one.

The `react-refresh/only-export-components` rule helps fast refresh work by restricting you to export only the react components from a file. It is also a rule that is applied by default when you create a project with `create-vite`.

```
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }]
```

The `allowConstantExport` option (`{ allowConstantExport: true}`) is an option that determines whether you want to allow other variables or functions to be exported from the component file in addition to the component. For Vite, even if it is set to true it supports the Fast Refresh feature, so we set it to true.

The rules provided by the `jsx-a11y` plug-in refer to the rules in `eslint-config-next`, which are applied by default when creating a project with `create-next-app`. I saw the official documentation and wrote down the explanation of the rules in Korean. This reduces the hassle of checking documents, so I recommend writing down the explanation in the comments if possible.

The `no-unknown-property` rule is a rule included in the recommendation rule of the react plug-in, which prohibits the use of properties that are not defined in the DOM. It was disabled because there were times when the `css` property of the styling library `emotion` was used during the project.

## Creating a Plugin Patch File
ESLint from version 8.5.7 cannot include ESLint plug-ins in packages but the `@rushstack/eslint-patch` package creates a patch file so that you can apply the patch right away without having to install it from the repository. You can simply import the patch files you created here and use them right away. A real-world example can be found in the table of contents: "Verify ESLint/Prettier behavior."

Create `eslint-config/patch.js` and write as below.

```

/*
* @rushstack/eslint-patch adds ESLint plugins to the shared config package.
*
* https://www.npmjs.com/package/@rushstack/eslint-patch
*/
require("@rushstack/eslint-patch/modern-module-resolution");
```

Is there any way to include ESLint plugins without eslint-patch?

There was already a request in August 2015 to include the plug-in as a direct dependency rather than peer dependencies. According to a comment left by ESLint founder Nicholas C. Zakas in August 2022, ESLint's new config mentioned that the plug-in can be designated as a direct dependency.
According to what's coming in ESLint v9.0.0 posted on the ESLint blog on November 7, 2023, flat config will be adopted by default from 9.0.0, and the plug-in can be included in the shared configuration without the patch package in the future.

Next, let's create the `Prettier` config package.

# Prettier Config Package
Add the `pretier-config` folder under packages, and create the `package.json` and `index.js` files under the folder.

```
📁 example
📄 package.json
📁 packages
  📁 eslint-config
    📁 mixins
      📄 react.js
    📄 index.js
    📄 package.json
  📁 prettier-config // package goes here
    📄 index.js
    📄 package.json
📄 pnpm-workspace.yaml
```

Now let's have a look at `package.json`

```
// packages/prettier-config/package.json
{
  "name": "@org/prettier-config",
  "main": "index.js",
  "version": "1.0.0",
  "peerDependencies": {
    "prettier": ">= 3"
  }
}
```

In the same way as the ESLint package, `index.js` was specified in the `main` field for export of the Prettier configuration.

The `peerDependencies` field states that the project that you want to use this shared configuration must be installing Prettier version 3 or later. There is no problem running with versions below 3, but we have strictly set the minimum installation version to get the same execution results with the same version across all repositories.

The `index.js` file defines the Premier settings that you want to use in common.

```
// packages/prettier-config/index.js
module.exports = {  
  printWidth: 100,  
  trailingComma: 'all', // this is the default value  
  tabWidth: 2, // this is the default value  
  semi: true, // adds semi-colon at the beginning of some code
  singleQuote: true,  
  bracketSpacing: true, // Ex. {foo:bar} becomes { foo: bar }  
  arrowParens: 'always', // this is the default value  
  useTabs: false, // this is the default value 
};
```

While mostly we follow the library's default values, some settings have been agreed upon based on preferences and discussions among the team members.

`printWidth` tells Prettier how many characters a line of code will approximately fit. I tried to set it to 80 which is the recommended setting for official documents, but there was an opinion that it would be better to set it to `120` or `100` for easier view. In the case of `120`, there was an opinion that it was too long to see on a 14-inch MacBook, and `80` was too short, so we agreed to a median value of `100`.

`semi` decides whether to automatically put a semicolon on the end of the line. This was an area where there were a lot of likes and dislikes, so we took a vote.

The result was half and half, but I decided to give up my preference and put in a semicolon for a smooth agreement. Since it is a problem with no correct answer, I was able to reach a smooth agreement by choosing to sacrifice(?) my opinion rather than continuing unnecessary discussion.

Next, to see if the shared configuration packages you've made so far work well, we'll create a React example app, set it up, and run the command to verify them.

# Testing
## Example App
In fact, we will create a Vite-based React example app to verify that ESLint and Prettier rules work well. The example app we created here can also be used when changes occur in the shared configuration and to verify errors in the CI.

```
$ pnpm create vite example --template react-swc-ts
```

## Setting `.eslintrc.cjs`

When the project creation is complete, create a `.eslintrc.cjs` file under the folder created and set the ESLint configuration as shown below.

```cjs
// ✅ Recall the previously defined patch file.
// This eliminates the need to install ESLint plug-ins one by one on the project.
require("@org/eslint-config/patch");

module.exports = {
  env: { browser: true, es2020: true },
  extends: [
    "@org/eslint-config", // import common ESLint config
    "@org/eslint-config/mixins/react", // fetching ESLint configuration for React
  ],
  settings: {
    react: {
      // Specifies the current React version.
      // If not specified (default is 'detect'), the entire React library is retrieved
      // It may slow down during the lint process.
      // For example: '16.9', '17.0', '18.0', etc
      version: "18.2",
    },
  },
  // Rush Stack has the @typescript-eslint plug-in built in
  // A setting is required for the type script parser.
  parserOptions: {
    project: true,
    tsconfigRootDir: __dirname,
  },
};
```

The patch file created from the shared configuration package is imported at the top of the configuration file. You need to import the patch file to use the ESLint plug-in without installation.

```
require("@org/eslint-config/patch")
```

The `extends` field calls the shared configuration, which also includes the React configuration.

```
extends: [
  "@org/eslint-config", // basic eslint config
  "@org/eslint-config/mixins/react", // eslint config for react
],
```

In `settings.react.version` field, specify the React version. If you do not specify the React version, the entire React library will be imported, which can slow down ESLint.

```
settings: {
  react: {
    version: "18.2",
  },
},
```

In ESLint settings, `parserOptions` sets the options for parsers that ESLint uses to analyze code. Because RushStack's rules have `typescript-eslint` as a dependency, it is necessary to set the path of `tsconfig.json`.

```
parserOptions: {
  project: true,
  tsconfigRootDir: __dirname,
},
```

We recommend that the `project` be set to `true`. The `true` option is a setting added in version 5.52.0 of typescript-eslint. It sets the source file to be interpreted based on the tsconfig.json closest to that path. This is particularly useful in monorepo structures where multiple `tsconfig.json` files exist within the repository.

We recommend that you set `tsconfigRootDir` as the root directory of the project (most commonly __dirname). This prevents `@typescript-eslint/parser` from finding the parent `tsconfig.json` file in the parent path if you accidentally delete or rename the root's `tsconfig.json` file.

## Editing `package.json`

Let's modify the `package/example/package.json` file to add the shared configuration package dependencies and the `Prettier` configuration settings.

```
{
  "name": "example",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,css,html}\""
  },
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    // ✅ ESLint, Prettier configs
    "@org/eslint-config": "workspace:*",
    "@org/prettier-config": "workspace:*",
    "@types/react": "18.2.74",
    "@types/react-dom": "18.2.24",
    // ✅ @typescript-eslint/* is already included in rushstack, so it is not present here
    "@vitejs/plugin-react-swc": "3.6.0",
    "eslint": "8.57.0",
    "typescript": "5.4.4",
    "vite": "5.2.8"
  },
  "prettier": "@org/prettier-config"
}
```

First, add each shared configuration package to devDependencies as below.

```
"devDependencies": {
  "@org/eslint-config": "workspace:*",
  "@org/prettier-config": "workspace:*",
},
```

Alternatively, you can add the package by executing a command from the root.

```
$ pnpm add -D @org/eslint-config@workspace:* @org/prettier-config@workspace:* --filter example
```

The dependencies that are already included in the shared configuration were removed.

- `eslint-plugin-react-hooks`: excluded because it is included in shared config dependencies.
- `eslint-plugin-react-refresh`: excluded because it is included in shared config dependency.
- `@typescript-eslint/*`: I excluded it because it was included in Rush Stack.

In Prettier field, specify the name of the Prettier config package.

```
"prettier": "@org/prettier-config"
```

## Testing
Before testing, proceed with the package installation on the root path.

```
$ pnpm install
```

I purposely create a situation where lint error occurs in `example/src/App.tsx`. I purposely tried to omit the dependencies that should go into `useEffect`.

```
function App() {  
  const [count, setCount] = useState(0);  

  useEffect(() => {  
    console.log(count);  
  }, []);
// ...
```

At the terminal, run the lint command in the sample workspace to see if ESLint works.

```
$ pnpm --filter example lint

> shared-config-example@0.0.0 example /shared-config-example
> pnpm --filter example "lint"

> example@0.0.0 lint /shared-config-example/example
> eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0

/shared-config-example/example/src/App.tsx
  11:6  warning  React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

✖ 1 problem (0 errors, 1 warning)

ESLint found too many warnings (maximum: 0).
/shared-config-example/example:
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  example@0.0.0 lint: `eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0`
Exit status 1
 ELIFECYCLE  Command failed with exit code 1.
```

By outputting alerts according to the rules you set, you can verify that ESLint is operating normally.

How to troubleshoot TypeScript version alerts

> WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
> SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 > YOUR TYPESCRIPT VERSION: 5.2.2

If the above warning occurs, you can resolve it by overriding the version of the `typecript-eslint` package to the latest version in `package.json` of the root.

```
"pnpm": {
   "overrides": {
     "@typescript-eslint/eslint-plugin": "7.5.0",
     "@typescript-eslint/parser": "7.5.0"
    }
}
```

From the terminal, run the prettier command in the sample workspace to see if the prettier is working as well.

```
$ pnpm example prettier
> example@0.0.0 prettier /shared-config-example/example
> prettier --write "**/*.{js,jsx,ts,tsx,css,html}"

index.html 19ms (unchanged)
src/App.css 18ms (unchanged)
src/App.tsx 114ms
src/index.css 5ms (unchanged)
src/main.tsx 3ms (unchanged)
src/vite-env.d.ts 2ms (unchanged)
vite.config.ts 3ms (unchanged)
```

You can also see that Prettier is working too.

# Recommended Rules and Plugins

## Naming conventions

Disclaimer: you don't need `@typescript-eselint/eslint-plugin` installed if you've already installed `@rushstack/eslint-config`. 

The `@typescript-eslint/naming-convention` rule allows you to define a series of naming conventions for variables, functions, classes, types, etc. If your team has agreed not to use the Hungarian notation in your type names, this rule will warn anyone from accidentally using Hungarian notation.

For your information, the Hungarian notation is a prefix that specifies the data type before the factor name of a variable or function. For example, the typescript `interface` can be expressed using I as a prefix, and Type can be expressed using T as a prefix.

This is how to use it: we first decide on the selector (ex: variable, type..) and specify the format (ex: camelCase, regular expression..) that corresponds to the selector. Below are examples of the rules used by the Core Webfront development team.

```
{
  "rules": {
    "@typescript-eslint/naming-convention": [
      "warn",
      // Allow camelCase variable, PascalCase variable, and UPPER_CASE variable
      {
        "selector": "variable",
        "format": ["camelCase", "PascalCase", "UPPER_CASE"]
      },
      // camelCase function, PascalCase function allowed
      {
        "selector": "function",
        "format": ["camelCase", "PascalCase"]
      },
      // // PascalCase for classes, interfaces, type aliases, enums allowed
      {
        "selector": "typeLike",
        "format": ["PascalCase"]
      },
      // I can't be used in front of interface
      {
        "selector": "interface",
        "format": ["PascalCase"],
        "custom": {
          "regex": "^I[A-Z]",
          "match": false
        }
      },
      // T not available in front of typeAlias
      {
        "selector": "typeAlias",
        "format": ["PascalCase"],
        "custom": {
          "regex": "^T[A-Z]",
          "match": false
        }
      },
      // T not available in front of type parameter
      {
        "selector": "typeParameter",
        "format": ["PascalCase"],
        "custom": {
          "regex": "^T[A-Z]",
          "match": false
        }
      }
    ]
  }
}
```

## Absolute Path Enforcement Rules

For this, we use `eslint-plugin-no-relative-import-paths`.

This is a good rule to use when you agree to use an absolute path for the import path. When I decided to introduce this rule, the most common question I got from my team members was, 'Can we use the relative path when importing from the same directory?' and if the attribute `allowSameFolder` is true, it is possible. Below is an example.

```
{
  "rules": {
    // The import path always uses the absolute path, except in the same folder
    "no-relative-import-paths/no-relative-import-paths": [
      "warn",
      { { "allowSameFolder": true, "rootDir": "src", "prefix": "@" }
    ]
  }
}
```

If you specify a prefix, eslint automatically fixes the import path by adding a prefix when you run the fix.

```
// If you specify the option { "prefix": "@"} 
import Something from "../../components/something";

// Above is converted as follows.
import Something from "@/components/something"; 
```

## Object Destructuring Rule

We took this rule thanks to one of team members who suggested it. You can enforce object/array destructuring through `prefer-destructuring` rule, an ESLint built-in rule. If you align the convention on destructuring, you can maintain consistent code, which is helpful for readability.

No separate plug-in installation was required because the rule is provided by ESLint by default. The Core Webfront development team set the destructuring rule to enforce only on objects in the variable declarations.

```
{
  "prefer-destructuring": [
    "error",
    {
      "VariableDeclarator": {
        "array": false,
        "object": true
      },
      "AssignmentExpression": {
        "array": false,
        "object": false
      }
    }
  ]
}
```

For your information, the `VariableDeclarator.object` option fixes the code that applies the object destructuring when you insert the `--fix` option in ESLint.

Example of this rule:

```
const user = {
  name: 'john',
  age: 25
};

// 🚨 Error: Use object destructuring.
const name = user.name;


// ✅
const { name } = user.name;
```

## Automatic Alignment Plug-in for Tailwind CSS Classes

Officially announced by Tailwind CSS in January 2022, `Prettier-plugin-tailwindcss` is a useful tool that automatically aligns the class names of Tailwind CSS to the recommended order. It is a recommended plug-in because it increases the readability of class names when applied.

To apply, install the `prettier-plugin-tailwindcss` package first.

```
$ pnpm add -D prettier prettier-plugin-tailwindcss
```

Next, specify the package name in the plugins field in the Prettier settings file.

```
{
  plugins: ['prettier-plugin-tailwindcss']
}
```

Example:

```
<!--Before Application -->
<button
  class="text-white px-4 sm:px-8 py-2 sm:py-3 bg-sky-700 hover:bg-sky-800"
>
  ...
</button>

<!--After applying -->
<button
  class="bg-sky-700 px-4 py-2 text-white hover:bg-sky-800 sm:px-8 sm:py-3"
>
  ...
</button>
```

If you want to use the plug-in for public use, you can include it in the Prettier shared configuration package. If you install the plug-in in the Prettier shared configuration package and specify the package name in the plugins field, the plug-in will take effect without setting it up.

# Conclusion

We were able to create and distribute ESLint/Prettier shared configuration packages and apply them to all repositories of the Core Webfront development team to gain a consistent development experience. Through the conversations and agreements we had among the team members, we were able to form a unified development culture and benefit from increasing productivity within the team.

The process of reaching an agreement took some time, but after applying the package, doubtful ESLint errors no longer held us back when working in other repositories. In addition, the code review process also reduced unnecessary controversy and saved communication costs.

As a result, we were able to significantly improve development efficiency by solving problems where Airbnb's ESLint rules impeded productivity and introducing a shared configuration package filled with rules unique to the core web front development team. Through this process, I was able to feel the importance of selecting good tools and communicating with team members once again.

# Appendix: Airbnb rules that impeded productivity

It may seem trivial, but rewriting the code several times to avoid ESLint's warnings breaks the workflow and takes quite a while. I've summarized the Airbnb rules that I felt were hurting productivity.

## `no-restricted-syntax` (`for ... of`)

```
for (const key of obj) {
               // ~~~
               // ESLint: iterators/generators require regenerator-runtime, which is
               // too heavyweight for this guide to allow them. Separately, loops should
               // be avoided in favor of array iterations. (no-restricted-syntax)
}
```

The `for..of` syntax is a useful for writing simple repetitions, regardless of index or key value. The no-restricted-syntax rule defined in Airbnb sets up errors when using certain syntax (ex: for..of). It is said that it was limited in the background of "It is better to use compatible forEach because it is less compatible and the regenerator-runtime polyfill is heavy in older browsers."

In the case of `forEach`, we experienced inconvenience due to restrictions on the use of keywords such as `break`, `continue`, and `await`.

> Why the `for..of` restriction rule is unnecessary
> Coming up on the Github issue in January 2017, there was a lot of discussions on whether the rule was really necessary. The criteria for older browsers we're talking about are environments where the `async` function is not available, namely, Safari version less than 11 and IE. Considering that the percentage of users under iOS version 11 in Korea as of August 2023 is 0.06 percent (Source: StatCounter), the importance of this rule is relatively reduced at this point. This rule is for older browsers that you don't have to consider anymore, so it can be considered unnecessary at this point.

## `arrow-body-style`: Rule of forceful omission of brackets for arrow functions

```
const bar = () => {
               // ~
               // ESLint: Unexpected block statement surrounding arrow body;
               // move the returned value immediately after the '=>'. (arrow-body-style)
  return 0;
}
```

The `arrow-body-style` rule forces the arrow function to omit brackets when they can be omitted. This rule makes the code concise, but it is also a controversial rule. For example, if you want to create an empty function and then add logic later, you need to clear the code and rewrite the brackets.

If you write brackets from scratch, it's convenient to write the code right away if you need additional logic later. A similar example is the history of Prettier 2.0.0 changing its default settings to always include parentheses in the arrow function parameter to gain these advantages. For example, if you write x => x, it converts it to (x) => x.