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

gadget-client-trim

v0.1.0

Published

Trim unused models from a generated Gadget API client to shrink your bundle in dev and production.

Readme

gadget-client-trim

Trim unused models, namespaces, and actions from a generated Gadget API client to shrink your bundle — in dev and in production.

A Gadget app's generated client (@gadget-client/<app>) instantiates every model manager, namespace, and global action in its Client constructor. Because the constructor references all of them, nothing tree-shakes: all of that code ships to the browser and is fetched in dev, even the parts your frontend never touches.

gadget-client-trim is a small, framework-agnostic CLI that scans your frontend source for what you actually use, then removes the rest from the generated client on disk. It trims four kinds of members — models, namespaces, global actions, and computed views — by stripping their references from Client.js (ESM and CJS), deleting orphaned model/namespace modules, and pruning the declarations from Client.d.ts so accidental use becomes a compile error. (The generic api.view and session/currentSession are always kept.)

Because it edits the client on disk, it works with any bundler that reads it — Vite, the Shopify CLI (UI extensions), webpack, esbuild — not just one.

What it does / does not do

  • ✅ Removes unused models, namespaces, global actions, and computed views so your bundler drops them.
  • ✅ Works for any consumer of the client (web app, Shopify UI extensions, SSR).
  • ✅ Fails open (keeps anything it's unsure about) and safe (skips anything whose codegen shape it doesn't recognize) — see Safety.
  • ❌ Does not generate your client, run ggt, or touch any secrets/tokens. You generate the client (e.g. ggt dev / ggt pull); this tool only trims a client that already exists on disk.
  • ❌ Does not ship a GitHub Action or bundler plugin — just a CLI you wire into your own scripts/CI (recipes below).

Install

npm i -D gadget-client-trim
# or: yarn add -D gadget-client-trim / pnpm add -D gadget-client-trim

Usage

# Apply the trim (default command):
gadget-client-trim

# Preview without changing anything:
gadget-client-trim --report

# CI gate — exits non-zero if the client isn't trimmed or its shape is unrecognized:
gadget-client-trim --check

# Re-apply automatically whenever the client is regenerated (dev):
gadget-client-trim --watch

You don't tell it where your client is. It finds .gadget/client automatically by walking up from the current directory (and also recognizes a single node_modules/@gadget-client/*), so it works from the repo root or any subdirectory. The only thing you configure is which folders to scan for api.<model> usage — that's what determines which models/actions are kept vs. removed. The default scan is web/, extensions/, and shared/.

Options

| Option | Description | | ------------------------ | --------------------------------------------------------------------------------------- | | --scan <a,b,c> | Folders to scan for api.<member> usage (default: web,extensions,shared). | | --keep <a,b> | Extra members to always keep. | | --force-trim <a,b> | Remove these even if used (never overrides session/currentSession). | | --kinds <a,b> | Member kinds to trim (default: all): model,namespace,globalAction,computedView. | | --no-delete | Strip references but keep model files on disk. | | --no-prune-types | Don't edit Client.d.ts. | | --config <path> | Config file (default: gadget-trim.config.{json,js,mjs} or package.json#gadgetTrim). | | --report / --dry-run | Print the plan, change nothing. | | --check | CI gate (see recipes). | | --watch | Re-apply on regeneration. | | --client-dir <path> | Advanced. Override client auto-detection (unusual monorepo layouts). | | --cwd <path> | Advanced. Directory to start searching from. |

Config file

gadget-trim.config.json, gadget-trim.config.js, or gadget-trim.config.mjs (default export), or a gadgetTrim key in package.json. The first one found in the current directory wins, in that order:

{
  "scan": ["web", "extensions", "shared"],
  "keep": [],
  "forceTrim": [],
  "deleteFiles": true,
  "pruneTypes": true
}

When to run it

The generated client is regenerated by ggt (and overwritten on every sync), so run the trim after the client is generated and before your bundler reads it — never after the bundle is built.

  • Dev: run gadget-client-trim --watch alongside your dev server. It re-applies after every regeneration.
  • Build / CI: run gadget-client-trim as an explicit step before your build. Don't rely on npm's prebuild lifecycle hook — it won't fire for the Shopify CLI or for build pipelines that invoke your bundler directly.

ℹ️ You are responsible for generating the client first (e.g. ggt pull) and for any auth/secrets that requires. This tool neither needs nor reads them.

Integration recipes

These are examples — copy what fits. See examples/ for full files.

// package.json
{
  "scripts": {
    "dev": "run-p dev:server trim:watch",
    "trim:watch": "gadget-client-trim --watch",
  },
}
gadget-client-trim && your-build-command
# after the client exists (e.g. ggt pull) and deps are installed:
gadget-client-trim
shopify app deploy
# inside a job that has already generated the client (your ggt step):
- run: npx gadget-client-trim # trim
- run: npx gadget-client-trim --check # fail the build if not trimmed / shape changed

Safety

  • Fail-open: if the usage scan can't read a source file, it keeps everything (only forceTrim models are removed). Wrongly keeping a model costs a little size; wrongly removing one would break at runtime — so it always biases toward keeping.
  • Fail-safe: the client is matched against a known set of anchor lines per model. If a model's shape isn't exactly recognized (e.g. a future Gadget codegen change), that model is skipped with a warning rather than partially edited. --check turns that into a CI failure so you notice.
  • All-or-nothing: a model is trimmed only if it can be cleanly removed from every target file (ESM, CJS, and the .d.ts), so the outputs never drift out of sync.
  • session / currentSession are always kept (auth), even via --force-trim.
  • Types stay consistent: only the trimmed managers' fields/imports are pruned from Client.d.ts; the large cross-referential type graph (types.ts) is never touched, and record/selection type re-exports for trimmed models are preserved.

Programmatic API

import { analyze, applyPlan } from "gadget-client-trim";

const { plan } = analyze({
  clientDir: ".gadget/client",
  scan: ["web", "extensions"],
});
console.log(plan.trim); // ["shopifyFile", ...]
if (process.env.WRITE) applyPlan(plan);

How it works

Members are discovered generically by parsing the client (no hardcoded list), so it tracks your schema automatically. For each unused member it removes the relevant declarations from Client.js (ESM + CJS) and Client.d.ts:

  • models — manager import, constructor instantiation, internal-manager entry, and selection re-export; deletes the orphaned models/<Model>.js (record/selection type re-exports are kept).
  • namespaces — import + constructor; deletes the orphaned namespaces/<name>.js.
  • global actions / computed views — the inline buildGlobalAction / buildComputedView block (removed via string-aware, brace-balanced matching) and the corresponding Client.d.ts field.

A member is only trimmed if it can be cleanly removed from every target file; otherwise it's skipped with a warning (see Safety).

License

MIT