Oxc, a New Front-End Linting Setup
TOC
1. What is Oxc?
Oxidation Compiler is a collection of high-performance tools for Javascript and Typescript, written in Rust
2. Oxlint ( an ESLint-compatible linter )
- A high-performance linter for Javascript and Typescript built on the Oxc stack
- 50 to 100 times faster than ESLint
- Usable right away with no separate configuration
- Runs the high-importance correctness checks first
- Includes over 835 rules for compatibility with ESLint
- Turning Oxlint on builds a module graph for the whole project and shares parsing and resolution across rules. This improves checks that depend on cross-file imports while avoiding a performance hit
3. Oxfmt ( a Prettier-compatible formatter )
- A high-performance formatter for the Javascript ecosystem
- Compatible with Prettier
- For specific plugins Oxfmt doesn't support yet, using Prettier is recommended
- Oxfmt follows Prettier's Javascript formatting
- Passes 100% of Prettier's Javascript and Typescript conformance tests
- Supported languages
- JavaScript, JSX, TypeScript, TSX, JSON, JSONC, JSON5, YAML,
- TOML, HTML, Angular, Vue, Svelte, CSS, SCSS, Less, Markdown
- MDX, GraphQL, Ember, Handlebars, and more
- About 30 times faster than Prettier and about 2 times faster than Biome
4. ESLint & Oxlint and Prettier & Oxfmt compared
-
To compare speed I created a project, generated 500 arbitrary components, and wrote unused variables inside them as the test
-
Pnpm time:eslint -> 2.470s
-
Pnpm time:oxlint -> 0.551s
-
Pnpm time:pitter -> 0.969s
-
Pnpm time:oxfmt -> 0.398s
ESLint

Oxlint

4-1. Use cases
4-1-1. A case where Oxlint + eslint-plugin-react-hooks rules caught an actual runtime error up front ( forced infinite rendering )
With the arrival of the React Compiler, new rules were added to eslint-plugin-react-hooks
Notably
set-state-in-render
set-state-in-effect
immutability
purity
static-components
refs
are new.
Rule | Description |
react-hooks-js/set-state-in-render | Forbids calling setState during render |
react-hooks-js/set-state-in-effect | Checks for the pattern of calling an unnecessary setState inside useEffect |
react-hooks-js/immutability | Checks the immutability of Props, State, and so on |
react-hooks-js/purity | Checks the purity of render functions |
.oxlintrc.json
"jsPlugins": [
{
"name": “add-react-hooks-js“,
"specifier": "eslint-plugin-react-hooks"
}
],
"rules": {
"add-react-hooks-js/set-state-in-render": "error"
}
An example of code that errors
function App({ value }: { value: number }) {
const [count, setCount] = useState(0);
if (count !== value) {
setCount(value);
}
return <div>{count}</div>;
}

To distinguish Javascript plugins from built-in rules, Oxlint attaches an alias to each plugin registered under jsPlugins
Oxlint -> npm -> that's how it pulls in eslint-plugin-react-hooks
add-react-hooks-js : an alias the developer picks freely ( changeable )
set-state-in-render : the rule name the plugin provides ( not changeable )
📍 Why support javascript plugins?
-
•There are thousands of plugins in the ESLint ecosystem. Rebuilding all of them in Rust would take a long time.
-
So Oxlint was built to let you use built-in rules ( e.g. react ) together with ESLint plugins (Javascript)
Plugins switches on a set of rules already implemented inside Oxlint,
jsPlugins connects an ESLint plugin installed via Npm and gives it an alias to avoid collisions
4-1-2. A case where it catches performance-risky patterns up front ( oxc/no-accumulating-spread )
It catches the pattern of repeatedly spreading into an accumulator inside a reduce or a loop.
That kind of code copies a new object/array on every iteration, so in the worst case performance degrades to O(n²)
oxlintrc.json
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"oxc/no-accumulating-spread": "error"
}
}
An example of code that errors
type User = {
id: string;
name: string;
};
const users: User[] = [
{ id: "1", name: "Hong Gildong" },
{ id: "2", name: "Kim Youngsoo" },
{ id: "3", name: "Lee Jisoo" },
];
export default function App() {
const userMap = users.reduce<Record<string, User>>((acc, user) => {
return {
...acc,
[user.id]: user,
};
}, {});
return (
<div>
<h1>Oxlint test</h1>
<pre>{JSON.stringify(userMap, null, 2)}</pre>
</div>
);
}
What Oxlint flags here is the following.
return {
...acc,
[user.id]: user,
};
This code copies the whole of acc every time to build a new object, so performance gets worse as the data grows.
The error
oxc/no-accumulating-spread: Avoid spreading accumulator in reduce
The fix
const userMap = users.reduce<Record<string, User>>((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
This isn't code that blows up in the browser right away, but it's a case of Oxlint catching a performance-risky pattern ahead of time.
