oxlint — Linter siêu nhanh cho JS/TS, và so sánh với ESLint, Biome, Prettier
Giới thiệu và hướng dẫn cơ bản về oxlint: cài đặt, file config, categories, plugins, type-aware linting, migrate từ ESLint, kèm so sánh thẳng với ESLint, Biome và Prettier.
Mỗi frontend engineer đều từng ngồi đợi eslint chạy hàng chục giây trên CI, hoặc nhìn node_modules phình lên vì một rừng plugin và shareable config. oxlint ra đời để giải quyết đúng nỗi đau đó: một linter viết bằng Rust, nhanh hơn ESLint 50–100 lần, có sẵn hơn 800 rule, và gần như cắm-là-chạy.
Bài này đi từ “oxlint là gì” đến đủ để bạn tự tin đưa nó vào project thật, rồi so sánh thẳng thắn với ESLint, Biome và Prettier để bạn biết khi nào nên dùng cái nào.
Phạm vi: bài tập trung vào
oxlint(linter). Người anh em formatter của nó làoxfmtđược nói riêng ở cuối bài. Cả hai đều thuộc hệ sinh thái Oxc (The JavaScript Oxidation Compiler).
oxlint là gì và vì sao nó tồn tại
oxlint là một linter cho JavaScript và TypeScript, xây trên Oxc compiler stack viết bằng Rust. Linter là công cụ phân tích tĩnh (static analysis): nó đọc code mà không chạy, rồi cảnh báo các lỗi tiềm ẩn (bug thật sự, biến không dùng, promise bị bỏ quên…) và các pattern không nhất quán.
Bối cảnh kinh điển là ESLint + một đống plugin (@typescript-eslint, eslint-plugin-react, eslint-plugin-import…) + Prettier để format. Setup này mạnh và linh hoạt, nhưng trên codebase thật nó có ba vấn đề:
- Chậm. ESLint chạy trên Node, parse lại cây cú pháp nhiều lần, và mỗi plugin là một lớp JS nữa. Lint một monorepo có thể mất hàng chục giây.
- Config phình to.
.eslintrc,eslint.config.mjs, version của hàng chục plugin phải khớp nhau, rule stylistic của ESLint còn đánh nhau với Prettier. - Dependency nặng. Mỗi plugin kéo theo cây phụ thuộc riêng.
oxlint đánh vào cả ba: tốc độ Rust, rule có sẵn (không cần cài plugin), và một file config nhỏ gần giống ESLint v8 nên đọc rất quen.
Vì sao oxlint nhanh đến vậy
Tốc độ không phải phép màu, mà đến từ kiến trúc:
- Viết bằng Rust, biên dịch ra native binary — không có overhead của Node runtime cho phần lõi.
- Một lần parse, nhiều rule dùng chung. ESLint thường để mỗi rule tự duyệt AST; oxlint chia sẻ kết quả parse và phân tích giữa các rule.
- Chạy song song theo file (multi-threaded), tận dụng nhiều core — thứ kiến trúc single-thread của Node khó làm tốt.
Kết quả theo benchmark chính thức: nhanh hơn ESLint 50–100 lần. Trong thực tế, lint từ “vài giây” rớt xuống “vài chục mili-giây”, đủ nhanh để chạy mượt trong pre-commit hook mà không ai để ý.
Cài đặt và dùng cơ bản
Cài như một dev dependency:
pnpm add -D oxlint
Chạy ngay không cần config — oxlint hữu ích ngay từ đầu vì mặc định bật nhóm rule correctness (chỉ những lỗi gần như chắc chắn sai):
# lint toàn bộ thư mục hiện tại
pnpm exec oxlint
# tự sửa những lỗi fix được an toàn
pnpm exec oxlint --fix
Thêm script vào package.json cho gọn:
// package.json
{
"scripts": {
"lint": "oxlint",
"lint:fix": "oxlint --fix"
}
}
oxlint hỗ trợ .js, .mjs, .cjs, .ts, .mts, .cts, .jsx, .tsx, và các file framework .vue, .svelte, .astro (chỉ lint phần <script> bên trong).
Một lần chạy trông như thế nào
Giả sử bạn viết if (status == 'active') và quên một biến không dùng:
$ pnpm lint
⚠ eslint(eqeqeq): Expected '===' and instead saw '=='
╭─[src/lib/customers.ts:12:9]
· if (status == 'active') {
· ──
╰────
help: Prefer strict equality '==='
✖ eslint(no-unused-vars): Variable 'tmp' is declared but never used
╭─[src/lib/customers.ts:3:7]
· const tmp = computeRows(data);
· ───
╰────
Found 1 warning and 1 error.
Finished in 41ms on 128 files using 8 threads.
Để ý dòng cuối: lint 128 file trong 41ms — cùng cây code này ESLint thường mất vài giây.
Override nhanh từ CLI
Khi muốn thử nhanh không cần sửa config, dùng cờ -A (allow/off), -W (warn), -D (deny/error). Các cờ áp dụng từ trái sang phải:
oxlint -D no-alert -W oxc/approx-constant -A no-plusplus
File config
oxlint chạy được không cần config, nhưng đa số team sẽ commit một file để local, editor và CI dùng chung một bộ rule. Có hai dạng:
.oxlintrc.json— cho phép comment như JSONC, cố ý tương thích định dạng.eslintrc.jsoncủa ESLint v8 nên migrate rất êm.oxlint.config.ts— config bằng TypeScript vớidefineConfig, có autocomplete và type-check (yêu cầu Node v22.18+/v24+).
Tạo file khởi tạo:
oxlint --init # sinh ra .oxlintrc.json
Một config tối thiểu:
// .oxlintrc.json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "error"
},
"rules": {
"eslint/no-unused-vars": "error"
}
}
Hoặc bản TypeScript tương đương:
// oxlint.config.ts
import { defineConfig } from 'oxlint';
export default defineConfig({
categories: { correctness: 'error' },
rules: { 'eslint/no-unused-vars': 'error' },
});
categories — bật cả nhóm rule một lúc
Thay vì bật từng rule, oxlint nhóm rule theo ý đồ. Mặc định chỉ correctness được bật. Các nhóm có sẵn:
| Category | Ý nghĩa |
|---|---|
correctness | Code gần như chắc chắn sai hoặc vô dụng (bật mặc định) |
suspicious | Code nhiều khả năng sai |
pedantic | Rule cực nghiêm, có thể có false positive |
perf | Rule nhắm cải thiện performance runtime |
style | Rule về style nhất quán, idiomatic |
restriction | Cấm một số pattern/feature cụ thể |
nursery | Rule đang phát triển, có thể thay đổi |
{
"categories": {
"correctness": "error",
"suspicious": "warn",
"pedantic": "off"
}
}
rules — chỉnh từng rule kiểu ESLint
Mỗi rule nhận một severity ("off"/"allow", "warn", "error"/"deny") hoặc mảng [severity, options]. Nếu rule thuộc ESLint core và tên là duy nhất, có thể bỏ prefix plugin (no-console = eslint/no-console):
{
"rules": {
"no-alert": "error",
"oxc/approx-constant": "warn",
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"eslint/prefer-const": ["error", { "destructuring": "any" }]
}
}
plugins — rule có sẵn, không cần cài
Đây là điểm “đã” nhất so với ESLint: các plugin phổ biến được viết sẵn bằng Rust ngay trong oxlint, không cần pnpm add thêm gì. Tổng cộng hơn 830 rule, phủ:
- ESLint core rules
- TypeScript rules (kể cả type-aware)
react,react-hooks,jsx-a11yimport,unicorn,oxcjest,vitest,nextjs
Lưu ý: khai báo plugins sẽ ghi đè bộ mặc định, nên hãy liệt kê đủ những gì bạn muốn bật:
{
"plugins": ["react", "typescript", "import", "unicorn", "oxc"]
}
overrides — config theo pattern file
Dùng overrides để áp rule khác nhau cho test, scripts, hay file TS-only:
{
"rules": { "no-console": "error" },
"overrides": [
{
"files": ["scripts/*.js"],
"rules": { "no-console": "off" }
},
{
"files": ["**/*.{ts,tsx}"],
"plugins": ["typescript"],
"rules": { "typescript/no-explicit-any": "error" }
},
{
"files": ["**/test/**"],
"plugins": ["jest"],
"env": { "jest": true },
"rules": { "jest/no-disabled-tests": "off" }
}
]
}
Các field hữu ích khác
extends— kế thừa từ config khác (merge theo thứ tự, sau ghi đè trước). Import config từ package thì phải dùngoxlint.config.ts.env/globals— bật global cho môi trường (browser, node…) và khai báo global riêng (readonly/writable/off).settings— config dùng chung cho nhiều rule của một plugin (ví dụreact.linkComponents,next.rootDir).ignorePatterns— bỏ qua file ngay trong config. Ngoài ra oxlint tôn trọng cả.gitignorevà.eslintignore.options— tùy chọn cấp linter nhưtypeAware,typeCheck,maxWarnings.
Type-aware linting
Một số rule chỉ làm đúng khi biết kiểu. Ví dụ kinh điển là floating promise — gọi một hàm async mà quên await:
async function save() { /* ... */ }
// bug: promise bị "thả nổi", lỗi nuốt mất, thứ tự chạy sai
save(); // ⚠ no-floating-promises
await save(); // ✅
Để bắt được loại lỗi này, linter phải hiểu save() trả về Promise. ESLint làm việc này qua typescript-eslint với type information — chính xác nhưng chậm.
oxlint dùng tsgo (bản port Go của TypeScript compiler, hay gọi là TypeScript 7), nên có đầy đủ ngữ nghĩa type giống hệt TypeScript thật, mà vẫn nhanh. Bật bằng cờ hoặc config:
oxlint --type-aware
// .oxlintrc.json (chỉ ở config gốc)
{
"options": {
"typeAware": true,
"maxWarnings": 10
}
}
Đây là khác biệt then chốt với Biome: Biome tự viết type inference riêng thay vì dựa vào TypeScript compiler, và độ phủ vẫn đang hoàn thiện — nghĩa là một số rule type-aware có thể chưa chính xác bằng.
Multi-file analysis
Có những rule cần nhìn cả project, không chỉ một file — ví dụ import/no-cycle (phát hiện vòng lặp import). Trên ESLint những rule này nổi tiếng là chậm vì phải tự dựng đồ thị module.
oxlint hỗ trợ multi-file analysis như tính năng hạng nhất: nó dựng module graph toàn project và chia sẻ phần parse/resolve giữa các rule, tránh “vách đá performance” thường thấy.
Tích hợp editor và CI
- Editor: cài extension Oxc cho VS Code (hoặc plugin tương đương) để lint hiện ngay khi gõ, dùng đúng config mà CI dùng.
- CI: vì cực nhanh, chỉ cần chạy
oxlintnhư một step. Dùng--max-warnings 0để fail build khi có warning. - Pre-commit: kết hợp
husky+lint-stagedchạyoxlint --fixtrên file đã stage — nhanh tới mức gần như không cảm nhận được:
// package.json
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": ["oxlint --fix"]
}
}
Migrate từ ESLint
oxlint thiết kế để chuyển đổi êm ái, có hai con đường:
1. Thay hẳn ESLint (khuyến nghị cho đa số project). Dùng @oxlint/migrate để tự convert config ESLint hiện có sang .oxlintrc.json:
npx @oxlint/migrate
2. Migrate dần (cho repo lớn/phức tạp). Chạy oxlint trước cho nhanh, rồi vẫn chạy ESLint nhưng tắt các rule đã trùng bằng eslint-plugin-oxlint:
// eslint.config.js
import oxlint from 'eslint-plugin-oxlint';
export default [
// ...config ESLint của bạn
...oxlint.configs['flat/recommended'], // tắt rule mà oxlint đã lo
];
Cách này giữ CI nhanh trong lúc bạn dần chuyển những rule niche mà oxlint chưa có sang oxlint, hoặc giữ lại trên ESLint.
So sánh: oxlint vs ESLint vs Biome
Trước hết phân biệt cho rõ vai trò để khỏi so nhầm:
- Linter (tìm bug, enforce rule): oxlint, ESLint, Biome.
- Formatter (định dạng code): Prettier, oxfmt, Biome.
Biome đặc biệt vì nó vừa lint vừa format trong một binary. Prettier thì chỉ format, không phải linter — nên so Prettier với oxlint là so nhầm phạm trù (xem mục formatter bên dưới).
Bảng so sánh phần linter:
| Tiêu chí | oxlint | ESLint | Biome (lint) |
|---|---|---|---|
| Ngôn ngữ lõi | Rust | JavaScript (Node) | Rust |
| Tốc độ | Nhanh nhất (50–100× ESLint) | Chậm nhất | Rất nhanh |
| Số rule | 830+ có sẵn | Vô hạn qua plugin | Ít hơn, đang tăng |
| Hệ plugin | Native (Rust) + JS plugin (alpha) | Khổng lồ, trưởng thành nhất | Hạn chế, không nhận plugin ESLint |
| Type-aware | Có, qua tsgo (TS thật) | Có, qua typescript-eslint | Type inference tự viết, đang hoàn thiện |
| Config | .oxlintrc.json / oxlint.config.ts, kiểu ESLint v8 | eslint.config.js (flat) | biome.json |
| Format code | Không (dùng oxfmt) | Không | Có (tích hợp) |
| Điểm mạnh | Tốc độ + độ phủ rule + migrate dễ | Hệ sinh thái & rule niche | All-in-one lint + format |
Tóm gọn:
- Chọn oxlint nếu bạn muốn một linter chuyên dụng nhanh nhất, độ phủ rule rộng, và migrate từ ESLint mượt.
- Giữ ESLint nếu bạn phụ thuộc vào plugin/rule niche mà oxlint chưa hỗ trợ (oxlint phủ rule giá trị cao, không phải mọi rule plugin hiếm).
- Chọn Biome nếu bạn ưu tiên trải nghiệm “một công cụ làm cả lint lẫn format”, chấp nhận hệ rule/plugin còn hẹp hơn.
Formatter: oxfmt vs Prettier vs Biome
oxlint không format code — đó là việc của oxfmt, formatter cùng nhà.
pnpm add -D oxfmt
# format
pnpm exec oxfmt
# kiểm tra mà không sửa file
pnpm exec oxfmt --check
Điểm đáng chú ý của oxfmt:
- Nhanh hơn Prettier ~30 lần, ~2 lần so với Biome.
- Tương thích Prettier: đã pass 100% conformance test JS/TS của Prettier — nghĩa là output gần như y hệt, chuyển đổi không gây diff ồn.
- Batteries included: sẵn import sorting, Tailwind CSS class sorting, sắp xếp field
package.json, format embedded (CSS-in-JS, GraphQL) — những thứ Prettier cần plugin riêng. - Hỗ trợ rất rộng: JS/TS/JSX/TSX, JSON(C/5), YAML, TOML, HTML, Vue, Svelte, CSS/SCSS/Less, Markdown, MDX, GraphQL…
So sánh nhanh:
| Tiêu chí | oxfmt | Prettier | Biome (format) |
|---|---|---|---|
| Tốc độ | Nhanh nhất | Chậm nhất | Nhanh |
| Tương thích Prettier | 100% JS/TS conformance | Bản gốc | Gần, có khác biệt nhỏ |
| Tính năng sẵn | Import/Tailwind/package.json sort | Cần plugin | Một phần |
| Vai trò | Chỉ format | Chỉ format | Lint + format |
Lưu ý quan trọng: đừng chạy oxfmt và Prettier cùng lúc — chọn một, nếu không chúng sẽ “đánh nhau” mỗi lần save.
Một .oxlintrc.json mẫu cho dự án React + TS
Gộp lại những gì đã học, đây là config thực dụng để bắt đầu cho một app React + TypeScript:
// .oxlintrc.json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "import", "unicorn", "oxc"],
"categories": {
"correctness": "error",
"suspicious": "warn"
},
"options": {
"typeAware": true
},
"rules": {
"eqeqeq": "error",
"no-console": "warn",
"typescript/no-explicit-any": "error",
"import/no-cycle": "error"
},
"ignorePatterns": ["dist", "coverage", "build"],
"overrides": [
{
"files": ["**/*.test.{ts,tsx}", "**/test/**"],
"plugins": ["vitest"],
"rules": { "no-console": "off" }
}
]
}
Khi nào nên (và chưa nên) dùng oxlint
Nên dùng khi:
- CI/lint đang chậm và bạn muốn cải thiện ngay.
- Project mới, hoặc đang dùng bộ rule ESLint khá tiêu chuẩn (TS, React, import).
- Monorepo lớn cần lint nhanh và config nhẹ.
Cân nhắc kỹ khi:
- Bạn phụ thuộc vào plugin ESLint rất niche chưa được oxlint hỗ trợ (lúc này dùng
eslint-plugin-oxlintđể chạy song song là phương án an toàn). - Bạn cần JS plugin tùy biến — oxlint có hỗ trợ nhưng còn ở giai đoạn alpha.
Bài tập
1. Tại sao oxlint nhanh hơn ESLint tới 50–100 lần dù làm cùng một việc?
Lời giải
Vì kiến trúc khác hẳn: oxlint viết bằng Rust (native binary, không overhead Node runtime cho phần lõi), parse một lần và chia sẻ kết quả giữa các rule thay vì để mỗi rule tự duyệt AST, và chạy song song nhiều thread. ESLint chạy trên Node single-thread và mỗi plugin là một lớp JS bổ sung.
2. Bạn cần rule bắt floating promise (save() quên await). Cấu hình oxlint thế nào, và vì sao Biome có thể kém chính xác hơn ở đây?
Lời giải
Bật type-aware linting: oxlint --type-aware hoặc "options": { "typeAware": true }. Rule này cần type information để biết hàm trả về Promise. oxlint dùng tsgo (TypeScript compiler thật) nên ngữ nghĩa type chính xác như TS; Biome tự viết type inference riêng và độ phủ vẫn đang hoàn thiện, nên một số trường hợp có thể chưa chính xác bằng.
3. Repo của bạn dùng một plugin ESLint niche mà oxlint chưa hỗ trợ. Làm sao vẫn tận dụng tốc độ oxlint mà không mất rule đó?
Lời giải
Chạy song song: dùng oxlint làm linter chính cho tốc độ, đồng thời vẫn chạy ESLint nhưng dùng eslint-plugin-oxlint để tắt các rule đã trùng với oxlint. ESLint khi đó chỉ còn chạy đúng rule niche, nên tổng thời gian vẫn nhanh.
4. Đồng nghiệp hỏi “oxlint thay được Prettier chưa?”. Bạn trả lời sao?
Lời giải
oxlint là linter, không format code — nó không thay Prettier. Thứ thay Prettier là oxfmt (cùng hệ Oxc): tương thích Prettier (pass 100% conformance JS/TS), nhanh hơn ~30×, lại có sẵn import sorting và Tailwind class sorting. So oxlint với Prettier là so nhầm phạm trù.
Tóm tắt
- oxlint là linter Rust trên Oxc, nhanh hơn ESLint 50–100×, hơn 830 rule có sẵn, gần như cắm-là-chạy.
- Config
.oxlintrc.json(kiểu ESLint v8) hoặcoxlint.config.ts; dùngcategories,rules,plugins,overridesđể điều khiển. - Type-aware linting qua tsgo cho ngữ nghĩa TypeScript thật — bắt được floating promise mà vẫn nhanh; đây là lợi thế so với type inference tự viết của Biome.
- Migrate từ ESLint dễ: thay hẳn bằng
@oxlint/migrate, hoặc chạy song song bằngeslint-plugin-oxlint. - Cần format thì dùng oxfmt (tương thích Prettier, có sẵn Tailwind/import sort) — đừng nhầm oxlint với Prettier vì chúng khác phạm trù.
- Quy tắc chọn nhanh: tốc độ + linter chuyên dụng → oxlint; all-in-one lint+format → Biome; rule/plugin niche trưởng thành → ESLint; chỉ format → Prettier/oxfmt.