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

@razorness/prettier-config

v0.2.1

Published

Shareable Prettier config plus a printer plugin for array bracket spacing, declaration bundling, vertical alignment and blank-line padding

Readme

@razorness/prettier-config

Shareable Prettier config plus the printer plugin it needs, in one package.

pnpm add -D @razorness/prettier-config prettier
// package.json
{
    "prettier": "@razorness/prettier-config"
}

@ianvs/prettier-plugin-sort-imports, prettier-plugin-tailwindcss (when the project uses Tailwind) and this package's own plugin come along, resolved by absolute path — nothing else to install.

What it does

Prettier's own options, set to semi, singleQuote, printWidth: 160, endOfLine: 'lf', trailingComma: 'all', useTabs with tabWidth: 4, bracketSpacing, htmlWhitespaceSensitivity: 'ignore' and importOrder: ['<THIRD_PARTY_MODULES>', '^[.]'].

On top of that, six rules Prettier has no option for — all six at once, which also shows how they interact:

// before
const ids = [0];
const retries = 3;

const config = {
    name: 'widget',
    enabled: true,
};

class Widget {
    private cache = new Map();
    render(id: string) {
        const entry = this.cache.get(id);
        const label = entry ?? 'unknown';
        log(label);
        emit(label);
        return label;
    }
}
// after
const ids   = [ 0 ],
    retries = 3;

const config = {
    name   : 'widget',
    enabled: true,
};

class Widget {

    private cache = new Map();

    render(id: string) {

        const entry = this.cache.get(id),
            label   = entry ?? 'unknown';
        log(label);
        emit(label);
        return label;

    }

}
  1. [0] became [ 0 ].
  2. The adjacent consts became one declaration. config stayed out — its value spans lines.
  3. Their = line up, and so do render's — note that ids got two spaces, not four: the padding is measured from the column each name starts in, and retries sits at an indent while ids sits behind const .
  4. config's keys line up on the colon.
  5. The class body gained a blank line at each end.
  6. render runs past four lines, so it gained blank lines around it and inside its braces.

bracketSpacing already covers {foo:true} → { foo: true }, .json included.

What the rules refuse to do

  • Array spacing only when the array fits on one line; a broken array is left as Prettier laid it out and [] stays []. Covers array literals, destructuring patterns and TS tuple types.
  • Bundling skips mixed kinds, a blank line between, an attached comment, a multi-line initialiser, export/declare/using — and var, unless razMergeDeclarations: "all".
  • = alignment only for declarators that are a plain identifier with an initialiser. Type annotations and destructuring make the left-hand width depend on layout, so those are skipped — without disqualifying the declaration and without counting towards the column, the same way key alignment skips shorthand and spread members. It pads the names and never touches indentation: continuation declarators stay at the plain indent Prettier gives them, and the padding is measured from the column each name starts in — keyword.length + 1 for the first, tabWidth for the rest — so the separators still land in one column. Aligning the indentation to the keyword width instead is what an earlier version did, and it cannot be done cleanly under useTabs: the keyword width is no multiple of tabWidth, so it needs spaces, and Prettier emits an align string ahead of the tabs of any nested indent. A declarator whose value spanned lines came out indented ' \t' — spaces before a tab.
  • Key alignment only for objects already multi-line in the source — the same signal Prettier itself uses, which is what keeps it stable across runs. Also covers interface bodies and type literals. Shorthand properties, methods and spreads are skipped without splitting a group; computed keys are skipped entirely, since a computed key can contain its own :.
  • Class body padding applies regardless of length. Empty bodies stay {}.

Block padding

Two halves, two options, one shared razBlockPaddingMinLines threshold — but they weigh different things, which is visible in the output:

| | measures | at the default of 5 | | ------------------------------ | ------------------------------------ | -------------------------------- | | razBlockPadding (outside) | the whole statement, braces included | a four-line body still qualifies | | razBlockBodyPadding (inside) | only what sits between the braces | needs more than four lines |

// four lines between the braces: outside padding only
function four() {
    one();
    two();
    three();
    four();
}

// five lines: outside and inside
function five() {

    one();
    two();
    three();
    four();
    five();

}

// too short for either half
function small() {
    return 1;
}
if (x) tiny();

Line counts decide it, never nesting depth. Here the describe body spans six lines and is padded; the for body inside it spans four and stays tight:

describe('fixtures', () => {

    for (const name of names) {
        test(name, () => {
            expect(format(name)).toBe(name);
        });
    }

});

Both are measured on the formatted output, not the source, which is what makes the rule idempotent: a three-line function that formats to eight is padded on the first run, so format-on-save never rewrites the same file twice.

Options

| Option | Type | Default | | ------------------------- | ------------------------------- | ------------- | | razArrayBracketSpacing | boolean | true | | razMergeDeclarations | "off" \| "const-let" \| "all" | "const-let" | | razAlignDeclarators | boolean | true | | razAlignObjectKeys | "off" \| "all" \| "groups" | "groups" | | razClassBodyPadding | boolean | true | | razBlockPadding | boolean | true | | razBlockBodyPadding | boolean | true | | razBlockPaddingMinLines | int | 5 |

razAlignObjectKeys: "groups" starts a fresh group at every blank line and after any property whose value is forced onto several lines; "all" treats the object as one group.

// prettier.config.mjs
import config from '@razorness/prettier-config';

export default {
    ...config,
    razBlockPaddingMinLines: 8,
    razAlignObjectKeys     : 'all',
};

Tailwind

The default entry point loads prettier-plugin-tailwindcss only when the project can resolve tailwindcss, so that projects without Tailwind keep their class attributes untouched — the plugin ships its own copy of Tailwind v4 and would otherwise happily reorder them. Three things override the detection: the @razorness/prettier-config/base entry point (never loads it), /tailwind (always does), or RAZORNESS_PRETTIER_TAILWIND=0|1.

Detection resolves tailwindcss from process.cwd(), and it fails silently — class sorting just stops happening. Prefer an explicit entry point wherever the cwd is not the project root.

Tailwind v4 derives class order from your stylesheet, so sorting needs tailwindStylesheet pointing at the CSS file with @import "tailwindcss". Without it the plugin falls back to the bundled default theme and treats your own utilities as unknown classes, sorting them to the front of the list.

Monorepos

Both failure modes bite at once here. Prettier runs from the workspace root, where tailwindcss is not resolvable because it lives in the individual packages — so pick /tailwind explicitly. Relative tailwindStylesheet paths resolve against the directory of the Prettier config that applies to the file being formatted, which means one root config can point each package at its own stylesheet:

// prettier.config.mjs, at the workspace root
import config from '@razorness/prettier-config/tailwind';

export default {
    ...config,
    overrides: [
        { files: 'packages/ui/**', options: { tailwindStylesheet: 'packages/ui/src/app.css' } },
        { files: 'apps/web/**', options: { tailwindStylesheet: 'apps/web/src/app.css' } },
    ],
};

Per-package config files work too, but Prettier does not merge configs, so each one has to repeat the import.

Using the plugin on its own

// prettier.config.mjs
export default {
    plugins           : [ '@razorness/prettier-config/plugin' ],
    razAlignObjectKeys: 'groups',
};

How it works

The plugin claims only printers.estree and delegates every node to Prettier's built-in printer before patching the doc it returns. Wrapping parsers instead — the usual approach — would collide with prettier-plugin-tailwindcss, which claims the JS/TS parsers and only special-cases a hardcoded list of plugins: depending on plugin order, either Tailwind's class sorting or these rules would silently stop working.

That this is possible at all is narrower than it looks. From Prettier's src/main/normalize-format-options.js:

const printerPlugin = parserPlugin.printers?.[parser.astFormat]
    ? parserPlugin                                             // the parser's own plugin wins outright
    : getPrinterPluginByAstFormat(rawOptions.plugins, ...);     // only then is findLast consulted

If the plugin owning the parser also ships a printer for that astFormat, findLast is never reached. Prettier's babel, typescript, acorn and flow plugins ship parsers only — the estree printer sits in a separate plugin — so the lookup falls through and a later printers.estree wins. Its html, postcss, markdown and yaml plugins bundle both, making those printers unreachable this way. JS/TS is the only family where a printer-only plugin is possible, which is also why SFC block sorting is out of reach.

The risk condition, then: a plugin owning parsers.babel that added its own printers.estree would shadow this one. Tailwind currently registers only printers['svelte-ast']. test/integration.test.ts guards the combination; test/noop.test.ts asserts that with every rule disabled the output is byte-identical to vanilla Prettier across three parsers.

Known limitations

  • package.json is the one file the rules do not reach. Prettier formats it with the json-stringify parser, the only JSON parser whose astFormat is estree-json rather than estree, and this plugin claims estree. So its arrays stay one entry per line and its keys stay unaligned. Every other .json and .jsonc file — tsconfigs, lint configs, i18n message catalogues — does get key alignment and [ … ] spacing, which is worth knowing before you point this config at a repo full of them.
  • Class bodies are not colon-aligned — a class mixes foo: string = 'a' with foobar = 1, so there is no single separator to align on. They get blank-line padding instead.
  • enum members are not aligned; they use =, not :.
  • Block padding does not apply between statements inside a switch case, only around the switch.

Development

TypeScript 6, no bundler: tsc emits JavaScript, declarations and maps in one pass, and dist/ mirrors src/ file for file. Vitest runs against src/, so coverage measures the real sources — and its thresholds are pinned at 100% for lines, branches, functions and statements, with no v8 ignore escape hatches. .prettierrc.mjs re-exports the built config, so the repo is formatted by the plugin it ships and pnpm format:check needs a build first.

node scripts/dump-doc.mjs 'const a = [1];' ArrayExpression   # inspect Prettier's Doc IR
node scripts/try.mjs 'const a = [1];' --twice                # format a snippet, check stability
node scripts/generate-expected.mjs                           # regenerate test fixtures

Every doc patch is matched against structures read off the real printer with dump-doc, and returns the doc untouched when the shape is not what it expects. After changing a rule, regenerate the fixtures and read the diff — the generator will happily record a regression as the new expectation.

License

MIT. Includes code adapted from @huggingface/prettier-plugin-vertical-align (ISC) and from Prettier's internals (MIT) — see LICENSE.