Tạo cấu hình ESLint: chọn khung ứng dụng, chọn quy tắc, nhận luôn lệnh cài gói
Công cụ sinh tệp eslint.config.js theo định dạng flat hoặc tệp .eslintrc.json theo định dạng cũ, cho bốn nhóm dự án là React, Next.js, Vue và Node thuần, với cả TypeScript lẫn JavaScript. Bạn chọn plugin cần dùng, đặt mức off, warn hay error cho từng quy tắc hay gây tranh cãi, chọn cách sống chung với Prettier, rồi nhận đúng danh sách gói phải cài kèm lệnh cài cho npm, pnpm, yarn hoặc bun.
Tính năng nổi bật
- Sinh được cả hai định dạng: eslint.config.js kiểu flat cho ESLint 9 và .eslintrc.json kiểu cũ cho dự án chưa nâng cấp
- Bốn khung ứng dụng React, Next.js, Vue, Node thuần, mỗi khung kéo theo bộ quy tắc nền và biến toàn cục đúng của nó
- Chuyển qua lại giữa TypeScript và JavaScript, bộ phân tích cú pháp cùng tên quy tắc tự đổi theo
- Bật tắt năm plugin thường dùng gồm react-hooks, react, jsx-a11y, import và unused-imports
- Mỗi quy tắc hay gây tranh cãi có ba nút off, warn, error kèm một dòng nói rõ vì sao đội này bật và đội kia tắt
- Ba mức tích hợp Prettier, kèm giải thích vì sao eslint-config-prettier là lựa chọn được khuyến nghị
- Danh sách gói cần cài tự cập nhật theo lựa chọn, kèm lệnh cài viết sẵn cho bốn trình quản lý gói
- Khối lưu ý chỉ ra các tổ hợp dễ gây rắc rối, ví dụ bật quy tắc cần đọc kiểu mà chưa bật chế độ đọc kiểu
Vì sao cấu hình ESLint hay bị bỏ mặc sau lần dựng đầu tiên
Đa số dự án có một tệp cấu hình ESLint được dựng vội trong ngày đầu, chép từ một hướng dẫn nào đó, rồi không ai đụng vào nữa. Hệ quả thường thấy là danh sách cảnh báo dài tới mức không ai đọc, nên cả những lỗi thật cũng trôi qua. Ba nguyên nhân lặp lại: một là cấu hình bật quá nhiều quy tắc về hình thức trong khi phần hình thức đã có Prettier lo, khiến mỗi lần lưu tệp hai công cụ sửa ngược nhau; hai là thiếu đúng những plugin đáng giá nhất, điển hình là react-hooks, nên nhóm lỗi khó lần nhất lại không được chặn; ba là quy tắc bị đặt sai mức, chẳng hạn biến chưa dùng để mức lỗi làm chặn cả bản dựng lúc đang viết dở. Công cụ này bày sẵn các quyết định đó dưới dạng ba nút chọn kèm lời giải thích, thay vì để bạn phải đọc tài liệu của từng plugin. Điểm khác biệt so với việc chép cấu hình mẫu là bạn biết mình đang chọn gì và tại sao, nên vài tháng sau khi có người hỏi thì bạn còn trả lời được.
Lợi ích khi sử dụng
- Không phải tra tên gói và tên quy tắc trong tài liệu của từng plugin
- Biết trước quy tắc nào sẽ gây phiền cho đội để đặt mức phù hợp ngay từ đầu
- Tránh vòng lặp ESLint sửa một kiểu, Prettier sửa kiểu khác ở mỗi lần lưu tệp
- Có sẵn lệnh cài đúng cho trình quản lý gói mà dự án đang dùng
- Chuyển được cấu hình cũ sang định dạng flat mà không phải dò từng trường
Cách tạo cấu hình ESLint bằng công cụ này
- 1Chọn định dạng tệp: flat config nếu dự án dùng ESLint 9 trở lên, định dạng .eslintrc nếu còn đang ở ESLint 8.
- 2Chọn khung ứng dụng và ngôn ngữ, hai lựa chọn này quyết định bộ quy tắc nền cùng bộ phân tích cú pháp.
- 3Bật những plugin bạn thật sự cần. Với dự án React thì react-hooks gần như bắt buộc, còn import và jsx-a11y tùy nhu cầu.
- 4Duyệt danh sách quy tắc hay gây tranh cãi, đặt mức off, warn hay error cho từng quy tắc theo thói quen của đội.
- 5Chọn cách tích hợp Prettier, đọc khối lưu ý, rồi chép cấu hình cùng lệnh cài gói ở khối bên phải về dự án.
Flat config khác định dạng .eslintrc ở chỗ nào
Định dạng cũ dựa trên tệp .eslintrc với trường extends, và ESLint phải đi tìm cấu hình theo cây thư mục, gộp nhiều tầng lại rồi giải quyết xung đột theo một bộ luật ưu tiên mà rất ít người nhớ nổi. Chính vì vậy mà câu hỏi quy tắc này đến từ đâu thường phải trả lời bằng cách chạy lệnh in ra cấu hình đã gộp. Flat config bỏ hẳn cơ chế đó: tệp eslint.config.js xuất ra một mảng JavaScript, mỗi phần tử là một khối cấu hình, và luật duy nhất là khối đứng sau ghi đè khối đứng trước cho những tệp mà nó khớp. Không còn kế thừa ngầm theo thư mục, không còn trường extends, plugin được nhập bằng câu lệnh import bình thường thay vì bằng chuỗi tên. Hệ quả thực tế rất dễ chịu: bạn đọc từ trên xuống là biết quy tắc cuối cùng có giá trị gì, và vì đây là JavaScript nên bạn viết được logic, ví dụ chỉ bật một nhóm quy tắc cho thư mục kiểm thử. Điểm cần lưu ý khi chuyển đổi là trường ignorePatterns đổi thành khối ignores đặt riêng, env đổi thành languageOptions với globals, còn những gói cấu hình chưa kịp cập nhật thì phải bọc qua FlatCompat. Từ ESLint 9, flat config là mặc định, còn định dạng cũ chỉ dùng được khi đặt biến môi trường tương ứng.
Cách để ESLint và Prettier không giẫm chân nhau
Đây là nguồn khó chịu kinh điển trong dự án frontend: bạn lưu tệp, Prettier viết lại cách xuống dòng, rồi ESLint gạch đỏ ngay dòng vừa viết lại, và ngược lại. Nguyên nhân là cả hai đều có ý kiến về hình thức mã, nhưng ý kiến của chúng khác nhau. Nguyên tắc phân vai rất đơn giản: Prettier là công cụ duy nhất được quyền quyết định mã trông thế nào, còn ESLint chỉ giữ phần lỗi logic và quy ước. Để thực thi nguyên tắc đó, cách được khuyến nghị là cài eslint-config-prettier và đặt nó ở vị trí cuối cùng trong mảng flat config hoặc cuối trường extends của định dạng cũ. Gói này không thêm quy tắc nào, nó chỉ tắt toàn bộ quy tắc định dạng của ESLint và của các plugin phổ biến, tức là dọn đường cho Prettier. Vị trí cuối là bắt buộc, vì cấu hình đứng sau mới ghi đè được cấu hình đứng trước; đặt nó ở giữa thì những khối phía sau sẽ bật lại các quy tắc vừa bị tắt. Cách thứ hai là dùng eslint-plugin-prettier để chạy Prettier như một quy tắc của ESLint, khi đó mọi khác biệt định dạng hiện thành lỗi đỏ. Cách này gọn ở chỗ chỉ cần một lệnh duy nhất, nhưng phải trả giá bằng tốc độ và bằng việc danh sách lỗi bị lấp đầy những thứ không phải lỗi. Đa số đội chọn cách thứ nhất và chạy Prettier riêng, gắn vào bước lưu tệp của trình soạn thảo cùng một bước kiểm tra định dạng trong quy trình tích hợp.
Quy tắc cần đọc kiểu: mạnh hơn nhiều nhưng chậm hơn nhiều
Mặc định, ESLint chỉ nhìn cây cú pháp của từng tệp, nên nó biết bạn gọi một hàm nhưng không biết hàm đó trả về gì. Bộ typescript-eslint có một nhóm quy tắc mạnh hơn hẳn, hoạt động bằng cách yêu cầu TypeScript dựng lại toàn bộ chương trình để tra kiểu thật của từng biểu thức. Nhóm này bắt được những lỗi mà cách kiểm tra thường không thấy: gọi hàm trả về promise mà quên await nên lỗi biến mất trong im lặng, so sánh hai giá trị không bao giờ bằng nhau về mặt kiểu, hay dùng toán tử cộng chuỗi với một giá trị có thể là undefined. Trong ba quy tắc đó, no-floating-promises là quy tắc đáng bật nhất với mã có gọi mạng hoặc thao tác cơ sở dữ liệu. Cái giá phải trả rõ ràng: thời gian lint tăng lên nhiều lần vì mỗi lần chạy đều phải dựng lại chương trình, và trên dự án lớn thì trình soạn thảo có thể khựng khi bạn gõ. Cách dung hòa mà nhiều đội áp dụng là bật chế độ đọc kiểu ở bước kiểm tra trước khi gộp mã, còn cấu hình dùng khi soạn thảo thì để chế độ thường. Cần lưu ý một lỗi hay gặp: bật quy tắc thuộc nhóm này mà chưa khai đường dẫn tới tsconfig thì ESLint dừng ngay với thông báo lỗi cấu hình, chứ không âm thầm bỏ qua.
Những quy tắc nên đặt mức nào và vì sao đội hay cãi nhau về chúng
Quy tắc no-console thường được để mức cảnh báo chứ không phải lỗi, vì việc in ra bảng điều khiển khi đang gỡ rối là bình thường, cái cần chặn là để sót lên môi trường thật, mà việc đó nên chặn ở bước kiểm tra trước khi gộp mã. Quy tắc no-unused-vars nếu đặt mức lỗi sẽ chặn cả bản dựng lúc bạn đang viết dở một hàm, nên phần lớn đội để mức cảnh báo, và bổ sung eslint-plugin-unused-imports để tự xóa dòng nhập thừa khi chạy chế độ sửa. Quy tắc no-explicit-any gây tranh cãi nhiều nhất trong nhóm TypeScript: bật thì mã sạch hơn nhưng chặn cả những chỗ any là lối thoát hợp lý khi bọc thư viện cũ, nên cách dung hòa là để mức cảnh báo và yêu cầu ghi chú lý do khi buộc phải dùng. Quy tắc react-hooks/exhaustive-deps thì phải nói riêng: nó rất hay báo về những trường hợp bạn cố tình bỏ bớt phụ thuộc, gây phiền, nhưng tắt hẳn là sai lầm vì nhóm lỗi nó chặn thuộc loại khó lần nhất. Để mức cảnh báo và bỏ qua từng dòng cụ thể bằng chú thích kèm lý do là cách làm đúng. Cuối cùng, react/react-in-jsx-scope phải tắt với mọi dự án dùng cách chuyển đổi JSX mới từ React 17 trở đi, nếu không mọi tệp giao diện đều báo lỗi giả.
Ba tệp cấu hình trong một dự án và ranh giới giữa chúng
Một dự án frontend điển hình có ba tệp cấu hình nằm cạnh nhau và rất dễ bị nhầm vai. Tệp tsconfig.json quyết định trình biên dịch hiểu mã của bạn thế nào: cú pháp nào hợp lệ, biểu thức nào mang kiểu gì, đường dẫn nhập được phân giải tới tệp nào. Cấu hình ESLint quyết định lỗi logic và quy ước nào bị báo, tức những chuyện mà trình biên dịch vẫn cho qua nhưng con người thì không nên chấp nhận. Cấu hình Prettier quyết định mã trông thế nào và không có ý kiến gì về đúng sai. Có hai chỗ giao nhau đáng chú ý. Chỗ thứ nhất là cờ noUnusedLocals của TypeScript trùng vai với quy tắc no-unused-vars của ESLint; đa số đội tắt cờ bên TypeScript và giao việc cho ESLint vì bên đó xử lý mềm hơn, bỏ qua được tham số bắt đầu bằng gạch dưới và tự sửa được. Chỗ thứ hai là cờ verbatimModuleSyntax bên TypeScript đòi mọi thứ chỉ dùng làm kiểu phải viết bằng import type; ESLint có quy tắc consistent-type-imports làm đúng việc đó và tự sửa được hàng loạt, nên hai bên nên bật cùng nhau. Ngoài hai chỗ đó, hãy giữ ranh giới cho sạch: đừng dùng ESLint để ép cách xuống dòng, và đừng mong tsconfig bắt được lỗi quên phụ thuộc trong hook.
Câu hỏi thường gặp (FAQ)
Nên chọn flat config hay .eslintrc cho dự án mới?
Flat config. Từ ESLint 9 đây là định dạng mặc định, còn .eslintrc chỉ chạy được khi bạn giữ ESLint 8 hoặc đặt biến môi trường ESLINT_USE_FLAT_CONFIG thành false. Flat config cũng dễ đọc hơn vì chỉ có một luật: khối đứng sau ghi đè khối đứng trước.
Vì sao eslint-config-prettier bắt buộc phải đặt cuối cùng?
Vì nó hoạt động bằng cách tắt các quy tắc định dạng, mà trong ESLint thì cấu hình đứng sau ghi đè cấu hình đứng trước. Đặt nó ở giữa thì những khối phía sau sẽ bật lại đúng các quy tắc vừa bị tắt, và bạn quay về đúng tình trạng hai công cụ sửa ngược nhau.
Có nên dùng eslint-plugin-prettier để chạy Prettier như một quy tắc không?
Chỉ nên khi bạn thật sự cần một lệnh duy nhất kiểm tra cả hai. Cách này khiến mỗi khác biệt về dấu cách cũng hiện thành lỗi đỏ, làm nhiễu danh sách lỗi thật và làm lint chậm hơn rõ rệt. Đa số đội chỉ dùng eslint-config-prettier rồi chạy Prettier bằng lệnh riêng.
Cấu hình cho Next.js có cần gì đặc biệt không?
Cần gói eslint-config-next, gói này đã gộp sẵn quy tắc của react, react-hooks và một số quy tắc riêng của Next về ảnh và liên kết. Vì gói đó viết theo định dạng cũ nên khi dùng flat config phải bọc qua FlatCompat, và bạn cần cài thêm @eslint/eslintrc.
Chạy ESLint báo lỗi không tìm thấy tệp cấu hình, phải làm sao?
Kiểm tra tên tệp trước. Flat config phải đặt đúng tên eslint.config.js ở thư mục gốc, và nếu package.json không khai type là module thì phải đổi đuôi thành mjs. Ngoài ra ESLint 9 không tự đọc .eslintrc nữa, nên tệp cũ nằm đó cũng bị bỏ qua.
Vì sao bật no-floating-promises lại báo lỗi cấu hình?
Vì quy tắc này thuộc nhóm cần đọc kiểu, nó đòi ESLint dựng lại chương trình TypeScript. Bạn phải bật chế độ đọc kiểu, tức khai projectService trong flat config hoặc trường project trỏ tới tsconfig.json trong định dạng cũ. Thiếu khai báo đó, ESLint dừng ngay.
Lint chạy quá chậm, có cách nào tăng tốc không?
Thủ phạm phổ biến nhất là chế độ đọc kiểu và quy tắc import/no-cycle. Cách thường dùng là tách hai cấu hình: bản nhẹ dùng khi soạn thảo, bản đầy đủ chạy ở bước kiểm tra trước khi gộp mã. Ngoài ra hãy đảm bảo khối ignores đã loại node_modules, thư mục build và thư mục kết quả kiểm thử.
unused-imports và no-unused-vars có trùng nhau không?
Có phần trùng. unused-imports/no-unused-imports chỉ lo dòng nhập thừa và tự xóa được khi chạy chế độ sửa, còn no-unused-vars lo mọi biến chưa dùng. Khi bật cả hai, nên hạ no-unused-vars xuống mức cảnh báo để một dòng nhập thừa không bị báo hai lần.
Dự án TypeScript có cần giữ quy tắc react/prop-types không?
Không. Kiểu của thuộc tính component đã được khai bằng interface hoặc type, nên quy tắc này chỉ tạo cảnh báo thừa. Tắt nó đi, đây là một trong những quy tắc bị tắt nhiều nhất ở dự án React viết bằng TypeScript.
Làm sao để ESLint hiểu alias kiểu @/components?
Bản thân ESLint không cần biết, nhưng eslint-plugin-import thì cần. Cài eslint-import-resolver-typescript rồi thêm khối settings khai import/resolver là typescript, khi đó plugin sẽ đọc trường paths trong tsconfig.json để phân giải đúng đường dẫn.
Có nên bật import/order để sắp thứ tự các dòng nhập không?
Tùy đội. Quy tắc này tự sửa được nên chi phí thấp, nhưng nó dễ đá nhau với lệnh sắp xếp nhập sẵn có của trình soạn thảo, khiến mỗi người lưu tệp lại cho ra một thứ tự khác. Nếu bật thì phải bật cho cả đội và tắt lệnh sắp xếp của trình soạn thảo.
Cấu hình sinh ra ở đây có chạy được với monorepo không?
Chạy được, nhưng nên đặt một tệp cấu hình dùng chung ở thư mục gốc rồi mỗi gói bổ sung phần riêng. Với flat config việc này đơn giản vì bạn chỉ cần nhập mảng cấu hình gốc rồi nối thêm khối của gói, không phải dựa vào cơ chế kế thừa theo thư mục như định dạng cũ.
Từ khóa liên quan
- tạo cấu hình eslint
- eslint config generator
- eslint flat config là gì
- eslint config js hay eslintrc
- cấu hình eslint cho next js
- eslint cho react typescript
- eslint cho vue 3
- typescript eslint recommended
- eslint config prettier
- eslint plugin prettier khác gì
- eslint và prettier xung đột
- react hooks exhaustive deps
- no explicit any eslint
- unused imports eslint
- import order eslint
- eslint type aware linting
- no floating promises
- lệnh cài eslint plugin
- eslint 9 nâng cấp
- eslint chạy chậm cách khắc phục
