Cách tôi nâng cấp ESLint từ 8 => 9 cùng combo TypeScript + StandardJS

Mất 5 phút upgrade để tận hưởng ESLint 9, config đơn giản hơn, tốc độ kiểm tra nhanh hơn

Các dự án frontend viết bằng Vite hoặc Nextjs của tôi bây giờ thường sẽ đòi hỏi combo TypeScript + ESLint + StandardJS. Tôi thích Standard vì nó khiến cho code trở nên sạch hơn tương tự như Python - ngôn ngữ tôi đã làm việc suốt 20 năm qua.

Khi dùng standard nó không cho phép thay đổi rule linh hoạt, có vài người sẽ thấy nó cứng nhắc. Nhưng đó lại là điểm mạnh khi bạn áp dụng nó vào làm việc nhóm. Nó đảm bảo code base của bạn được chuẩn chỉnh và hạn chế các sai sót do lỗi cú pháp cơ bản. Thêm nó vào git pre-commit hook để không ai có thể commit nếu chưa sửa xong lỗi eslint.

Từ 2025, chuyển qua phiên bản ESLint 9.x, cấu hình file eslint.config.mjs trở nên đơn giản và dễ hiểu hơn rất nhiều:

import neostandard, { resolveIgnoresFromGitignore } from 'neostandard'
import stylisticTs from '@stylistic/eslint-plugin-ts'
import tseslint from '@typescript-eslint/eslint-plugin'
import noBlankBeforeClosePlugin from './src/custom-eslint/eslint-plugin-no-blank-before-close/index.mjs'

export default [
  ...neostandard({
    ts: true,
    ignores: resolveIgnoresFromGitignore()
  }),
  {
    plugins: {
      '@stylistic/ts': stylisticTs,
      '@typescript-eslint': tseslint,
      'no-blank-before-close': noBlankBeforeClosePlugin
    },
    rules: {
      'no-blank-before-close/no-blank-before-close': 'error',
      'comma-dangle': ['error', 'never'],
      'no-unused-vars': 'off',
      '@typescript-eslint/no-unused-vars': 'warn',
      '@stylistic/ts/type-annotation-spacing': 'error',
      '@stylistic/ts/object-curly-spacing': ['error', 'always'],
      '@stylistic/ts/member-delimiter-style': ['error', {
        multiline: {
          delimiter: 'none',
          requireLast: true
        },
        singleline: {
          delimiter: 'comma',
          requireLast: false
        }
      }],
      '@stylistic/ts/padding-line-between-statements': [
        'error',
        {
          blankLine: 'always',
          prev: '*',
          next: ['enum', 'interface', 'type', 'function', 'export']
        },
        {
          blankLine: 'always',
          prev: 'import',
          next: 'const'
        }
      ]
    }
  }
]

Bạn phải cài đặt các gói liên quan:

pnpm add -D neostandard typescript @eslint/js @stylistic/eslint-plugin-ts @typescript-eslint/eslint-plugin

Lưu ý: bạn có thể neo phiên bản ESLint và neostandard lại để tránh các cập nhật lớn dẫn đến project mất ổn định.

Bạn để ý tôi có viết thêm 1 custom rule tên là no-blank-before-close, mục đích tránh các dòng trống trước các dấu đóng khối như ], } hay ). Nếu bạn không thích thì cứ xóa đi:

// src/custom-eslint/eslint-plugin-no-blank-before-close/rules/no-blank-before-close.mjs
export default {
  meta: {
    type: 'layout',
    docs: {
      description: 'Disallow blank lines before closing ), ] and }'
    },
    fixable: 'whitespace',
    schema: []
  },

  create (context) {
    const sourceCode = context.getSourceCode()
    const tokens = sourceCode.tokensAndComments

    return {
      Program () {
        for (let i = 1; i < tokens.length; i++) {
          const current = tokens[i]
          const prev = tokens[i - 1]

          if (!current || !prev) continue

          // Chỉ kiểm tra nếu current là dấu đóng
          if (![')', ']', '}'].includes(current.value)) continue

          const linesBetween = current.loc.start.line - prev.loc.end.line

          if (linesBetween > 1) {
            context.report({
              loc: current.loc,
              message: `Unexpected blank line before '${current.value}'`,
              fix (fixer) {
                const range = [prev.range[1], current.range[0]]
                return fixer.replaceTextRange(range, '\n')
              }
            })
          }
        }
      }
    }
  }
}

Cách viết custom rules như thế nào bạn tự tìm hiểu nhé!

Sau đây là giải thích các rule tôi sử dụng trong các dự án của mình:

'comma-dangle': ['error', 'never'],   // dấu phẩy cuối cùng trong object hoặc list sẽ bị loại bỏ 
'no-unused-vars': 'off',   // tôi tắt kiểm tra biến không dùng của eslint
'@typescript-eslint/no-unused-vars': 'warn',    // sau đó chỉ warn khi có biến không dùng của typescript-eslint
'@stylistic/ts/type-annotation-spacing': 'error',   // phải có khoảng trắng khi khai báo kiểu
'@stylistic/ts/object-curly-spacing': ['error', 'always'],  // bên trong object phải có khoảng trắng để dễ nhìn hơn
'@stylistic/ts/member-delimiter-style': ['error', {...}],   // các thành phần trong object khi xuống dòng không cần dấu ở cuối, khi cùng dòng thì thêm dấu phẩy
'@stylistic/ts/padding-line-between-statements': ['error', {...}]   // phải có khoảng cách giữa các khối với nhau giúp dễ nhìn hơn

Cuối cùng, chúng ta thử nghiệm với 1 file mẫu viết rất cẩu thả như sau:

import React from 'react';  
const x1 = 'hello'  


const _EslintTest = () => {  
  return (  
    <div className={"my-4"}>  
  my text  
    </div>  

  );  
};  
export default EslintTest;

Kết quả như ảnh bên dưới:

test eslint 9

Hầu hết các IDE nổi tiếng như VS Code, Webstorm đều hỗ trợ ESLint rất tốt. Hãy nâng cấp lên bản ESLint 9 để cải thiện trải nghiệm coding nhé!

Anh Tran Phantom
© 2016-2025