Zypsy's shared configuration for JavaScript and TypeScript projects: linting with oxlint, formatting with oxfmt and a TypeScript base for Next.js apps.
| Import | What |
|---|---|
@zypsy/configs/oxlint |
oxlint config, strict and type-aware |
@zypsy/configs/oxlint-plugin |
the zypsy oxlint plugin, loaded by the oxlint config |
@zypsy/configs/oxfmt |
oxfmt config: Tailwind class sorting and import sorting |
@zypsy/configs/tsconfig/next.json |
compilerOptions for a Next.js app |
- Node 22.18 or later.
oxlint1.88 or later andoxlint-tsgolint7 for the oxlint config,oxfmt0.73 or later for the oxfmt config, TypeScript 7 for the tsconfig. They are peer dependencies, so the versions are yours to pin.
pnpm add -D @zypsy/configs oxlint oxlint-tsgolint oxfmtoxlint.config.ts:
import zypsy from "@zypsy/configs/oxlint";
import { defineConfig } from "oxlint";
export default defineConfig({
extends: [zypsy],
});Add what is specific to your project after extends, for example:
export default defineConfig({
extends: [zypsy],
ignorePatterns: ["src/generated/**"],
settings: { react: { version: "19.3.0" } },
overrides: [{ files: ["src/env.js"], env: { node: true } }],
});What the config does:
- Type-aware. The typescript-eslint
strict-type-checkedandstylistic-type-checkedrules run throughoxlint-tsgolint: floating promises, unsafeany, unnecessary assertions, deprecated APIs. - React and the React Compiler rules: rules of hooks, exhaustive dependencies, purity, immutability.
settings.react.versionis set to19.3.0; set yours if it differs. - Imports: no cycles, no duplicate imports, no default exports (except in
src/app, stories and config files), no file extensions. - House style in
src/**:- Arrow functions only, no
functiondeclarations or expressions. - No explicit return types on arrow functions: inference is enough.
.at(0)and.at(-1)instead ofarray[0]andarray[array.length - 1], which pairs withnoUncheckedIndexedAccess.- No variable or parameter named
e: writeeventorerror.
- Arrow functions only, no
- Rules still in oxlint's nursery stay off until they are stable:
no-unnecessary-condition,prefer-optional-chain,import/export,no-undef,react/require-render-return.
oxlint has no native no-restricted-syntax, so the zypsy plugin provides it as zypsy/restricted-syntax, over oxlint's selector engine. The oxlint config already loads the plugin and uses it for the house style above. Rule options replace the shared ones rather than merge, so to ban more syntax, spread the shared selectors, exported as restrictedSyntax, in an override for the same files:
import zypsy, { restrictedSyntax } from "@zypsy/configs/oxlint";
import { defineConfig } from "oxlint";
export default defineConfig({
extends: [zypsy],
overrides: [
{
files: ["src/**/*.{ts,tsx}"],
rules: {
"zypsy/restricted-syntax": [
"error",
...restrictedSyntax,
{
selector: "TSEnumDeclaration",
message: "Use a union of string literals.",
},
],
},
},
],
});A disable comment names the rule like any other: // oxlint-disable-next-line zypsy/restricted-syntax.
oxfmt has no extends, so spread the shared config in oxfmt.config.ts:
import zypsy from "@zypsy/configs/oxfmt";
import { defineConfig } from "oxfmt";
export default defineConfig({
...zypsy,
ignorePatterns: [...(zypsy.ignorePatterns ?? []), "supabase/templates"],
});It formats at 80 columns, sorts Tailwind classes and sorts imports: builtin, external, internal (@/), then relative, with no blank lines between groups.
{
"extends": "@zypsy/configs/tsconfig/next.json",
"compilerOptions": { "paths": { "@/*": ["./src/*"] } },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next"]
}Strict, with noUncheckedIndexedAccess, verbatimModuleSyntax, checkJs and the Next.js plugin. paths, include and exclude stay in your tsconfig.json, because TypeScript resolves them relative to the file that declares them.
{
"lint": "oxlint src",
"lint:fix": "oxlint src --fix",
"format": "oxfmt",
"format:check": "oxfmt --check"
}The sources are TypeScript in src, built by vp pack into dist. pnpm prepush builds, lints, formats, type-checks and runs the smoke test, which lints a fixture through the built config and checks that the house rules fire. A GitHub release tagged v<version> publishes the package to npm with provenance.