npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mkrz/oxlint-config

v0.1.1

Published

Opinionated Oxlint ruleset for TypeScript and React

Readme

@mkrz/oxlint-config

@mkrz/oxlint-config is the Oxlint setup I use for TypeScript and React projects. It is a set of guard-rails, not a starting point: every rule runs at error severity, and the switches turn whole concerns off, never individual rules.

Read this first

The ruleset rejects code that most projects accept. Expect the first run on an existing codebase to report a lot.

  • let is an error, including for (let i = 0; …). Use const and derive a new value; use for…of.
  • ../ imports are an error everywhere, inside packages too. Set up path aliases or package imports first.
  • as T is an error except as const. Parse the value with a runtime schema library or narrow it with a type guard.
  • unknown parameters and return types are errors.
  • With react on: useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useReducer, and useSyncExternalStore are errors.
  • In test files, vi.mock and jest.mock are errors. The test override relaxes assertions, not mocking.
  • Disable comments are policed: none at all in applications, one named oxlint-disable-next-line <rule> -- reason in libraries and packages.
  • Test files are assumed to use vitest.
  • Blank lines between multi-line statements, blocks, and before return are enforced. This is a style rule with an autofix (oxlint --fix), not a correctness check; it does not conflict with oxfmt or Prettier.

Install

pnpm add -D oxlint @oxlint/plugins oxlint-tsgolint @mkrz/oxlint-config

@oxlint/plugins is a peer dependency so its version always matches the oxlint you run. oxlint-tsgolint powers the type-aware rules; without it the default config warns and runs without them.

Oxlint cannot resolve npm package names from the extends array in .oxlintrc.json. Use an oxlint.config.ts file and import from this package instead. Node 22.18 or newer is required because Oxlint loads that file at runtime. The package has no ESLint dependency; every custom rule, including the blank-line rule, runs as an Oxlint JS plugin.

Use

Add an oxlint.config.ts file at the root of your project:

import { mkrz } from '@mkrz/oxlint-config';

export default mkrz();

mkrz(options?, config?) returns one flat Oxlint root config. The plugin is declared once, the correctness and suspicious categories are on at error severity once, and there is no subset that fails to load or runs at warning severity.

Options

export default mkrz({
  react: false,
  typeAware: true,
  repositoryType: 'monorepo',
  appsPath: ['products/**'],
  packagesPath: ['modules/**'],
  appPackages: ['@acme/shop', '@acme/admin-*'],
});
  • react (default true): accessibility, React Compiler, hooks, query, and store rules. Turn off for Node services and libraries without JSX.
  • typeAware (default true): type-checked TypeScript rules. Needs a tsconfig and oxlint-tsgolint, and costs more lint time. When oxlint-tsgolint is not installed, the default prints a warning and runs without these rules; an explicit typeAware: true throws.
  • repositoryType (default 'auto'): where the disable-directive policy applies. See below. appsPath, packagesPath, and appPackages are only accepted together with repositoryType: 'monorepo'.

Your own settings

Pass your root config as the second argument. Its rules replace this package's root rules, its overrides run after this package's, and its ignorePatterns, env, globals, settings, and plugins are merged with the ruleset's:

import { mkrz } from '@mkrz/oxlint-config';

export default mkrz(
  { react: false },
  {
    ignorePatterns: ['**/generated/**'],
    rules: {
      'mkrz/no-let': 'off',
    },
    overrides: [
      {
        files: ['scripts/**'],
        rules: { 'no-alert': 'off' },
      },
    ],
  }
);

File overrides take precedence over root rules. To change a rule supplied by one of this package's overrides, add a matching override of your own. For example, to relax the application directive policy:

export default mkrz(
  { repositoryType: 'app' },
  {
    overrides: [
      { files: ['**/*'], rules: { 'mkrz/no-oxlint-disable': 'off' } },
    ],
  }
);

If you compose with Oxlint's extends instead, use the defineConfig export of this package. Oxlint only honors ignorePatterns, env, globals, and settings on the root config and drops them from extended configs; the wrapper folds them back in. The ignore list is also exported as ignorePatterns.

Type imports

Always use separate top-level import type declarations for types. Inline type specifiers are forbidden, including in imports that also contain values. With verbatimModuleSyntax, an all-inline type import leaves a runtime import {} behind and can fail for packages that contain only declarations. The config also checks for these unintended runtime dependencies.

Repository types

  • app: no Oxlint or ESLint disable comment anywhere.
  • library: a single named oxlint-disable-next-line <rule> -- reason comment is allowed.
  • monorepo: the app policy under appsPath (default apps/**), the library policy under packagesPath (default packages/**). Files outside both, such as root configs, get neither. Packages may not import the workspace package names listed in appPackages (gitignore-style globs). Relative paths that climb out of a package are already rejected by import/no-relative-parent-imports.
  • auto (the default) reads the directory Oxlint runs in: a pnpm-workspace.yaml with packages, a workspaces field, or an apps/ or packages/ directory means monorepo. Otherwise "private": true in package.json means app and anything else library. Without a package.json it falls back to app and says so. A workspace without apps/ or packages/ is still monorepo, but the policy overrides would then match nothing, so it warns and asks for explicit appsPath and packagesPath. It is the working directory, not the directory of oxlint.config.ts, because a config file cannot learn its own location from Oxlint; run Oxlint from the repository root or set the type explicitly.

Unused disable directives are always reported. One bypass the directive rules cannot close themselves: a file-level /* oxlint-disable mkrz/no-oxlint-disable */ comment suppresses the very rule that reports it and counts as a used directive. The bare /* oxlint-disable */ form is still caught by unicorn/no-abusive-eslint-disable. Catching the named form needs an out-of-band check, such as a CI grep for oxlint-disable mkrz/.

React policy

react rejects useEffect, useLayoutEffect, useInsertionEffect, useReducer, useSyncExternalStore, useMemo, and useCallback. There is deliberately no sanctioned way to subscribe to something outside React by hand; that is what the query and store libraries are for. Oxlint's native ports of the React Compiler and rules-of-hooks checks run as errors, so no ESLint plugin is loaded for React. The rule sees hooks imported from react and called as React.useEffect; a local re-export of React is not followed.

With react on, both the browser and node environments are enabled for every file. Universal code (server components, loaders) needs both, and file location cannot tell them apart. Use separate tsconfigs with appropriate lib and types settings to check environment-specific globals.

Side-effect imports are rejected by import/no-unassigned-import, except stylesheets (.css, .scss, .sass, .less). Add other side-effect modules such as server-only through your root config's rules.

Query and store hooks are matched by name, on direct calls only: anything of the form use*Query, use*Queries, or use*Mutation counts as a query hook, and a bound zustand hook named after its store, use<Name>Store, counts as a store hook, so wrapper hooks such as useUserQuery follow the same policy. Aliases such as const query = useQuery are not followed. Query results cannot be object-destructured, and a cast or parentheses do not hide the call. Keeping the result together is a project convention: it makes the source of each field visible and keeps checks and reads on the same object. TypeScript can preserve narrowing for const { data, isSuccess } = useQuery() when both bindings come from the same destructuring declaration. This rule deliberately rejects that valid form too. For mutations, write const mutation = useMutation(...) and call mutation.mutate. Store hooks must receive a selector, and the selector may not return the whole state. The store rule is written for zustand: the bare useStore(store, selector) is checked when it is imported from zustand or zustand/react, and a useStore from any other module (react-redux, MobX) is left alone. Name bound stores by domain; a local const useStore = create(...) is not recognised. useSyncExternalStore is not treated as a store hook (it is rejected by the hook policy above instead).

Base policy

base is always on. It turns the correctness and suspicious categories on at error severity for every enabled plugin and lists only the rules outside those categories. Three of the listed rules (no-unreachable-loop, typescript/prefer-optional-chain, react/require-render-return) are in Oxlint's nursery and may be renamed between Oxlint minors.

Test files (**/*.{test,spec}.*, **/*.test-d.*, **/__tests__/**) get the vitest plugin and may use type assertions and non-null assertions.

Custom rules

The custom rules ship from the same package at @mkrz/oxlint-config/plugin. mkrz() loads that subpath itself, so consumers do not configure it separately.

Rules written for this package:

  • mkrz/no-app-requires: the CommonJS counterpart to monorepo import restrictions.
  • mkrz/no-let
  • mkrz/no-oxlint-disable and mkrz/package-disable-policy
  • mkrz/no-query-result-destructuring
  • mkrz/no-restricted-react-hooks
  • mkrz/require-store-selector
  • mkrz/no-type-assertion
  • mkrz/padding-line-between-statements: a port of the @stylistic rule of the same name, with the same options and messages. Oxlint has no esquery, so the selector option accepts * or an exact AST node type instead of a full selector.

Rules vendored from Dillon Mulroy's anti-slop project, enabled as errors by default. The assertion and widening checks are relaxed in test files:

  • mkrz/no-chained-type-assertions
  • mkrz/no-known-value-widening
  • mkrz/no-module-mocking
  • mkrz/no-reflect-apply
  • mkrz/no-reflect-get
  • mkrz/no-unknown-parameters
  • mkrz/no-unknown-returns
  • mkrz/no-unknown-type-aliases
  • mkrz/no-unsafe-dictionary-type
  • mkrz/no-widen-then-assert

Examples and migration guidance for every custom rule are in the rule reference. Each diagnostic links to its rule there. The CommonJS boundary rule uses ignore to match the same gitignore-style patterns as the native ESM restriction. Dynamic CommonJS module names cannot be checked statically; literal require and module.require calls are checked.

The local changes made to the vendored rules are listed in src/plugin/rules/vendor/anti-slop/README.md.

Versions

The peer ranges on oxlint, @oxlint/plugins, and oxlint-tsgolint use ~ because Oxlint's JS plugin API is not covered by semver yet; expect a release of this package per Oxlint minor. This has a consequence for npm users: npm treats an unmet peer range as an install error, so you cannot move to a new Oxlint minor until this package has followed (pnpm only warns). Renovate opens one grouped PR per Oxlint release, and the tarball is installed and linted in CI before every release.

Maintaining the policy

pnpm test compares the effective native rules, severities, options, and custom overrides against src/__snapshots__/policy.json. After an intentional policy or Oxlint update, run pnpm run test:update-policy and review the diff.

pnpm run benchmark measures cold-process linting of a generated TypeScript consumer with type awareness on and off. See the benchmark notes for the fixture, measured results, and limits.

License

MIT. The vendored anti-slop code keeps Dillon Mulroy's MIT license and the blank-line rule keeps the ESLint Stylistic license. The package includes copies at LICENSE.anti-slop and LICENSE.stylistic.