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

astro-sprites

v0.1.1

Published

Build-time sprite atlas generation for instant Astro UI states.

Readme

astro-sprites

astro-sprites banner

Build-time sprite sheets for Astro interfaces where many finite UI states need to feel instant.

astro-sprites takes a known set of images, packs them into content-hashed WebP atlas sheets during the Astro build, emits a typed manifest, and lets the browser select frames by stable item ID. Runtime work stays tiny: item lookup, sheet URL, frame coordinates, CSS transform.

It is not an Astro Image replacement, not a runtime image optimizer, and not a hover plugin. The primitive is:

stable item ID -> generated sheet -> frame coordinates -> UI state

Install

Use the package manager your Astro project already uses:

npm install astro-sprites
pnpm add astro-sprites
yarn add astro-sprites
bun add astro-sprites

If you use the Cloudflare R2/S3-compatible output adapter, also install the AWS SDK:

npm install @aws-sdk/client-s3
pnpm add @aws-sdk/client-s3
yarn add @aws-sdk/client-s3
bun add @aws-sdk/client-s3

Basic Astro Setup

import { defineConfig } from 'astro/config';
import sprites, { sprite } from 'astro-sprites';
import { files } from 'astro-sprites/adapters/source';

export default defineConfig({
  integrations: [
    sprites({
      collections: {
        projects: sprite({
          source: files('./src/assets/work/*.webp'),
          frame: { width: 400, height: 300, fit: 'cover' },
          layout: { strategy: 'grid', columns: 'auto' },
        }),
      },
    }),
  ],
});

Then render the selected sheet and let the runtime update coordinates:

---
import SpriteFrame from 'astro-sprites/components/SpriteFrame.astro';
import projects from 'virtual:astro-sprites/projects';

const first = projects.first();
---

<section data-sprite-root>
  {projects.itemsList.map((project) => (
    <button type="button" {...projects.trigger(project)}>{project.title}</button>
  ))}

  {first && <SpriteFrame manifest={projects} item={first} />}
</section>

<script>
  import projects from 'virtual:astro-sprites/projects';
  import { mountSpriteSelector } from 'astro-sprites/runtime';

  const root = document.querySelector('[data-sprite-root]');

  if (root) {
    mountSpriteSelector(root, {
      manifest: projects,
      trigger: 'hover-focus-click',
      initialItem: projects.first(),
    });
  }
</script>

CSS Contract

The frame owns the crop window. The image stays in atlas pixel space.

[data-sprite-frame] {
  aspect-ratio: var(--sprite-aspect-ratio);
  width: min(var(--sprite-frame-width), 100%);
  overflow: hidden;
}

[data-sprite-image] {
  display: block;
  max-width: none;
  width: calc(var(--sprite-sheet-width) / var(--sprite-dpr, 1) * 1px);
  height: calc(var(--sprite-sheet-height) / var(--sprite-dpr, 1) * 1px);
  transform: translate(
    calc(var(--sprite-x, 0) * -1px),
    calc(var(--sprite-y, 0) * -1px)
  );
}

Do not squeeze the crop frame without also scaling the atlas image and coordinates. If a parent grid is narrower than the compiled frame, reserve space or use a deliberate scaled recipe. Random responsive shrinking causes gaps, clipped frames, and false compositor bugs.

For baseDpr atlases, the manifest emits logical item coordinates and includes frame.dpr. The runtime and SpriteFrame.astro render the sheet image at sheet.width / frame.dpr by sheet.height / frame.dpr; do the same if you hand-write the DOM.

Use Cases

Good fits are finite visual state systems:

  • Work indexes where hover/focus changes a large preview instantly.
  • CMS-driven editorial rails with stable item IDs.
  • Product swatches, material finishes, trim selectors, and small configurators.
  • Character rosters with one portrait atlas and one thumbnail atlas.
  • Icon/state atlases for dashboards and status-heavy tools.
  • Documentation demos where selected examples need screenshots.
  • Cursor trails or custom interactions that reuse one generated sheet.
  • Transparent or irregular generated assets where alpha and crop rules must be tested.

Bad fits:

  • Article hero images.
  • Open-ended user uploads.
  • Huge unbounded galleries.
  • Runtime image transformation.
  • General responsive image delivery.

Use Astro Image, Cloudflare Images, Imgix, or your normal image service for those.

Source Models

The public API accepts simple sprite inputs and normalizes them into internal SpriteItem[] records at build time. Where that list comes from is your architecture choice.

Collection keys are the public collection IDs. In other words, collections.projects becomes virtual:astro-sprites/projects; do not repeat id: 'projects' unless you are using the older array form.

type SpriteInputItem =
  | string
  | URL
  | {
      src: string | URL;
      id?: string;
      name?: string;
      title?: string;
      cacheKey?: string;
      alt?: string;
      href?: string;
    };

The dumb-interface path is:

const projects = [
  './src/assets/work/north-observatory.webp',
  {
    src: './src/assets/work/courtyard-house.webp',
    name: 'Courtyard House',
    href: '/work/courtyard-house',
  },
];

The compiler infers id from src, uses name/title as display metadata, and defaults cacheKey to the source URL/path. Add explicit id only when the generated ID is not the contract you want, and explicit cacheKey when a CMS/CDN can change pixels while keeping the same URL.

The normalized internal shape remains:

type SpriteItem = {
  id: string;
  title?: string;
  href?: string;
  image: {
    src: string | URL;
    cacheKey: string;
    alt?: string;
  };
};

Supported source patterns:

  • files() for local image globs where filenames can be the source of truth.
  • Plain arrays for local files.
  • Async loaders for JSON, databases, or internal services.
  • defineSource() for custom build-time loaders.
  • mapSource() and cmsSource() for CMS-shaped records.
  • dataAttributeSource() for static Astro/HTML markup scanning.

The important rule: normalize CMS, CDN, R2, Bunny, S3, or database concepts at build time. Do not leak provider-specific data models into UI components.

Delivery: Astro, Cloudflare, CDNs, CMS, or No CDN

astro-sprites is Astro and build-time first.

Think in layers:

source records -> Astro build -> generated sheets -> origin -> optional CDN
  • No CDN: write sheets to public/sprites/** and deploy them with the site.
  • Cloudflare Pages: same static output model, deployed with the Astro artifact.
  • Cloudflare R2: upload generated sheets to object storage and serve them from a custom domain.
  • Cloudflare Worker: serve R2 or another origin, but do not compile sprites per request.
  • Bunny / S3 / Fastly / CloudFront / KeyCDN: upload hashed sheets to the provider's storage/origin and point publicBaseUrl at the CDN URL.
  • CMS: use the CMS as a source adapter. The output can still be local, R2, Bunny, S3, or any other adapter.
  • Fastify/custom Node server: serve generated files as an origin. Fastify is not the CDN and not the compiler.

See the provider guide in docs/src/content/docs/docs/delivery.md.

Compared With astro-sprite

astro-sprite is an existing Astro package for small icon sprite generation. It scans a folder, creates one sprite image, writes CSS classes, and can emit a preload component.

astro-sprites is positioned differently: it emits typed atlas manifests for interactive UI state. The runtime selects frames by item ID, swaps sheets safely, supports source-image fallback, handles DPR-aware coordinates, and accepts source adapters for CMS/CDN/local records.

Run the local comparison:

npm run benchmark:astro-sprite

The benchmark installs [email protected] into .temp, generates identical PNG inputs, and measures the common path both packages share: finite image packing into a WebP sheet. On the local Windows dev machine, the current result is astro-sprites at 32.0ms vs astro-sprite at 179.4ms for 16 small icons, and 163.9ms vs 250.2ms for 64 medium thumbnails. Treat benchmark numbers as machine-local; treat the benchmark command as the contract.

Edge Cases

The demo surface covers cases that break lazy atlas implementations:

  • Transparent triangles and circles: alpha must survive packing.
  • Irregular shapes: padding must be deliberate.
  • Intentional crops: crop metadata belongs in source config, not hand-written CSS.
  • Multi-sheet output: runtime must swap sheet URL and coordinates together.
  • Missing sheet at the edge: fallback can recover with the selected source image.
  • CMS URLs changing content under the same URL: add cacheKey update metadata.

Runtime Fallback

Runtime fallback is opt-in recovery for a generated sheet that fails after deployment.

mountSpriteSelector(root, {
  manifest,
  trigger: 'click',
  fallback: 'source-image',
  initialItem: manifest.item('north-observatory'),
});

The selector first tries the sheet. If that sheet fails, it uses manifest.items[itemId].source.src for the active item and emits astro-sprites:fallback.

Development

This repository currently uses npm for contributor installs and CI because package-lock.json is committed.

npm ci
npm run typecheck
npm run lint
npm run test
npm audit

Docs dev server:

npm --prefix docs install
npm run docs:dev

Package dry-run without rebuilding:

npm pack --dry-run --json --ignore-scripts

Full package validation, including build and package smoke test, runs in CI and in the release workflow. Locally, do not treat a stale dist/ folder as proof that the package is publishable.

CI/CD and Release Ceremony

Release should be boring:

  1. Make sure main is clean.
  2. Confirm docs and package metadata are accurate.
  3. Run local non-build checks:
    npm run typecheck
    npm run lint
    npm run test
    npm audit
    npm pack --dry-run --json --ignore-scripts
  4. Commit with a conventional commit.
  5. Push and wait for CI.
  6. Create an annotated tag:
    git tag -a v0.1.0 -m "release: v0.1.0"
    git push origin v0.1.0
  7. Create the GitHub release from the tag.
  8. Run the Release workflow with that tag, or publish with provenance from a clean checkout.
  9. Verify npm:
    npm view astro-sprites version
    npm view astro-sprites dist-tags

The release workflow builds, tests, smoke-tests the package, and publishes with npm provenance. That is the right place for build work.

Package Exports

import sprites from 'astro-sprites';
import { mountSpriteSelector } from 'astro-sprites/runtime';
import { cmsSource, defineSource, files, mapSource } from 'astro-sprites/adapters/source';

Astro component:

---
import SpriteFrame from 'astro-sprites/components/SpriteFrame.astro';
---

Stability

astro-sprites targets Astro 6 and Node 22.12.0+.

The contracts to protect are:

  • manifest v1 shape
  • stable item ID selection
  • output adapter contract
  • runtime selector behavior
  • CSS frame/image coordinate contract

License

MIT