DEV Community

Sol Lee
Sol Lee

Posted on

Making Shared ESLint, Prettier Config Files

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

(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)
  • 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
Enter fullscreen mode Exit fullscreen mode

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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/*'
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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: {

  },
};
Enter fullscreen mode Exit fullscreen mode

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",
  },
};
Enter fullscreen mode Exit fullscreen mode

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",
],
Enter fullscreen mode Exit fullscreen mode

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
    },
  },
Enter fullscreen mode Exit fullscreen mode

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',
      },
    },
  ],
Enter fullscreen mode Exit fullscreen mode

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 }]
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

// βœ… 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,
  },
};
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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
],
Enter fullscreen mode Exit fullscreen mode

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",
  },
},
Enter fullscreen mode Exit fullscreen mode

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,
},
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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

"devDependencies": {
  "@org/eslint-config": "workspace:*",
  "@org/prettier-config": "workspace:*",
},
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Testing

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

$ pnpm install
Enter fullscreen mode Exit fullscreen mode

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);  
  }, []);
// ...
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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": "@" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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"; 
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

{
  plugins: ['prettier-plugin-tailwindcss']
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)