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

compilecat

v0.0.9

Published

compiler plugin that helps you write high performance javascript.

Downloads

1,472

Readme

cover

Version GitHub Workflow Status (with event) Downloads

npm install compilecat

compilecat

A JavaScript/TypeScript compiler plugin for hot-path optimizations, driven by opt-in annotations. It does function inlining, scalar-replacement of aggregates (SROA), loop unrolling, and a Closure-style simplify tier (constant folding, purity-driven pure-call elimination, dead-store removal).

Built on a Rust/oxc core. Ships as a bundler plugin (via unplugin) that optimizes each source file before bundling while keeping TypeScript. It is cross-module aware: when a file imports an @inline donor, the plugin resolves and reads the donor module, inlines across the module boundary, and drops the now-unused import. addWatchFile is wired in, so editing a donor re-transforms every consumer that inlined it.

Usage

Pick the subpath for your bundler — the options API is identical across all of them:

| Bundler | Import | |---|---| | Rollup | compilecat/rollup | | Vite | compilecat/vite | | Rolldown | compilecat/rolldown | | Webpack | compilecat/webpack | | esbuild | compilecat/esbuild | | Rspack | compilecat/rspack | | Rsbuild | compilecat/rsbuild | | Farm | compilecat/farm | | Bun | compilecat/bun |

// rollup.config.js
import compilecat from 'compilecat/rollup';

export default {
    plugins: [compilecat({ include: [/\/src\//] })],
};
// vite.config.js
import compilecat from 'compilecat/vite';

export default {
    plugins: [compilecat({ include: [/\/src\//] })],
};
// webpack.config.js
const { compilecat } = require('compilecat/webpack');

module.exports = {
    plugins: [compilecat({ include: [/\/src\//] })],
};

include scopes which module ids compilecat transforms and reads as donors (picomatch globs and/or RegExps). It's required, so node_modules is never trawled unless a package is listed explicitly.

A browser/edge wasm backend is available at compilecat/wasm.

Directives

All optimizations are opt-in via /* @* */ block comments.

@inline: inline at the call site

On a function declaration, every call within the chunk is replaced with the function body. In bundle mode the chunk is the whole tree-shaken program, so callers from other source files reach the annotated function naturally, with no cross-file resolver involved.

/* @inline */
function add(out: Vec3, a: Vec3, b: Vec3): Vec3 {
    out[0] = a[0] + b[0];
    out[1] = a[1] + b[1];
    out[2] = a[2] + b[2];
    return out;
}

function step(result: Vec3, v1: Vec3, v2: Vec3) {
    add(result, v1, v2);
}
function step(result: Vec3, v1: Vec3, v2: Vec3) {
    result[0] = v1[0] + v2[0];
    result[1] = v1[1] + v2[1];
    result[2] = v1[2] + v2[2];
}

On a call site, inlines just that call. Useful for forcing a specific call:

import { vec3 } from 'mathcat';

function step(out: Vec3, v: Vec3) {
    /* @inline */ vec3.normalize(out, v);
}

Library code from node_modules is eligible for inlining whenever it ends up in the chunk and carries (or is targeted by) a directive. The resolver isn't doing anything special for libraries because bundling already pulled the body in.

@flatten: inline every call inside this function

A caller-side bulk directive. Every resolvable call inside the annotated function's body is treated as if its call site had /* @inline */.

/* @flatten */
function step(out: Vec3, v: Vec3) {
    vec3.normalize(out, v);
    vec3.scale(out, out, 2);
}

@sroa: break an array-literal local into scalars

Scalar Replacement of Aggregates. Converts const v = [a, b, c] plus constant-index accesses (v[0], v[1], ...) into scalar locals. Useful for tuple-shaped data (vec3, quat, mat4) in hot loops.

function step(dt: number) {
    const v: Vec3 = /* @sroa */ [0, 0, 0];
    v[0] = 1 * dt;
    v[1] = 2 * dt;
    v[2] = 3 * dt;
    use(v[0], v[1], v[2]);
}
function step(dt: number) {
    let v_0 = 1 * dt;
    let v_1 = 2 * dt;
    let v_2 = 3 * dt;
    use(v_0, v_1, v_2);
}

Escape analysis bails silently if the array leaks, whether by being passed to a function, spread, indexed non-constantly, aliased, or accessed via .length.

Can also be placed on an enclosing function to opt in every qualifying declaration inside it.

SROA also scalarizes object/record locals (const v = { x, y, z } with static .x/.y/.z accesses) and typed-tuple locals (const v: Vec3 = mk()), and performs module-scratch localization. A module-level scratch buffer reused as per-call temporary storage (the crashcat/mathcat house style const _scratch = /*@__PURE__*/ [0, 0, 0], written and read inside one @optimize function) is proven safe (single-owner, killed-on-entry, non-re-entrant via a CFG must-reaching-definitions analysis) and scalarized into per-call locals, with the module const deleted. This borrows LLVM GlobalOpt's global-localization idea, fused into SROA so the buffer is never materialized as a per-call allocation.

@unroll: unroll a loop with a static trip count

/* @unroll */
for (let i = 0; i < 3; i++) {
    process(i);
}
process(0);
process(1);
process(2);

Supports for (let i = <lit>; i <(=) <lit>; i(++|+= <lit>)) { ... } and for (const x of <array literal>) { ... }. Warns and leaves the loop untouched if the trip count isn't static or the body has loop-crossing break/continue/return.

@optimize

Applies @flatten, @sroa, and @unroll. Does not apply @inline.

/* @optimize */
function step(out: Vec3, v: Vec3, dt: number) {
    const scaled: Vec3 = [0, 0, 0];
    for (let i = 0; i < 3; i++) scaled[i] = v[i] * dt;
    vec3.add(out, out, scaled);
}

@pure: assert a function is side-effect-free

compilecat runs a Closure-style purity analysis (a port of PureFunctionIdentifier) that proves most immutable-math helpers pure on its own. A call to a proven-pure function can be dropped when its result is unused, reordered, or de-duplicated, and it's emitted with a /*@__PURE__*/ marker for the downstream bundler. @pure is a developer-assertion override for cases the analysis can't see through (e.g. a helper that bottoms out in an unresolved external call you know is pure):

/* @pure */
function lerp(a: number, b: number, t: number): number {
    return externalMathHelper(a, b, t);
}

Use it sparingly. Asserting purity on a function that actually has side effects will let those effects be optimized away.

Pipeline

Each chunk that contains a compilecat directive runs through:

parse
  → normalize               (MakeDeclaredNamesUnique)
  → stamp-pure-calls        (purity analysis marks side-effect-free calls; emits /*@__PURE__*/)
  → inline-functions        (DIRECT + BLOCK inlining, FunctionArgumentInjector)
  → block-flatten           (lift the scaffolding blocks inlining emits)
  → loop-unroller
  → scalar-replace-aggregates  (incl. module-scratch localization — see `@sroa` above)
  → block-flatten           (lift the per-iteration blocks the unroller emits)
  → simplify (per-function fixpoint, ×8):
      peephole-fold-constants
      minimize-exit-points
      peephole-minimize-conditions
      inline-variables
      cleanup-residue
      peephole-remove-dead-code
      block-flatten          (lift bare blocks exposed during simplification)
      flow-sensitive-inline-variables   (CFG-based)
      dead-assignments-elimination      (CFG-based)
  → remove-unused-code      (drop bindings/imports left unused after inlining)
  → strip-directives        (remove the authored /* @* */ markers)
  → regenerate

Everything is gated: only constructs carrying a directive (and their subtrees), plus the directive-free consumers an @inline was inlined into, are ever rewritten. The rest of the file is left byte-identical.

Some of the optimization passes (Rust, under rust/crates/compilecat_core/src/passes/) are functional ports of corresponding jscomp/*.java files from Google Closure Compiler. Others, notably @unroll and @sroa, diverge. See NOTICE.

Plugin options

The same options apply to every adapter:

compilecat({
    include: string | RegExp | (string | RegExp)[],   // REQUIRED. picomatch
                                                       //  globs and/or RegExps
    exclude?: string | RegExp | (string | RegExp)[],  // additional skips
    sourcemap?: boolean,  // emit source maps. default true
    debug?: boolean,      // per-build timing + counter breakdown. default false
})

include is required and has no implicit default. It bounds both the files that get transformed and the donor modules that may be read and inlined, so node_modules is never trawled unless a package is listed explicitly (e.g. ['**/src/**', '**/node_modules/mathcat/**']). Under rolldown the scope test runs in Rust and out-of-scope files never enter the JS plugin handler at all; other bundlers apply the filter in JS. An in-scope file that calls an in-scope @inline function is always processed, even when it carries no directive of its own.

// e.g. only transform engine code; everything else (app code,
// node_modules, etc.) is invisible to compilecat.
compilecat({
    include: ['src/engine/**'],
})

Acknowledgements

Heavily inspired by unplugin-inline-functions.

Attribution

Some of the optimization passes under rust/crates/compilecat_core/src/passes/ are ports of corresponding files from the Google Closure Compiler, licensed under the Apache License, Version 2.0. See NOTICE for required attribution. Compilecat itself is MIT-licensed (see LICENSE).