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

@smi-digital/web-kit

v1.5.0

Published

Shared Astro and React components for SMI websites.

Readme

@smi-digital/web-kit

Shared Astro and React components for SMI websites.

The package exists so a fix lands once instead of six times. Before it, the same problems were solved repeatedly and independently across the fleet: a working responsive-image helper lived in one component of seven, unicode-range was applied correctly to one font and not to the one forty-five times larger. The knowledge was never missing — it just was not the default.

Install

npm i @smi-digital/web-kit

Then add one line to the consuming site's astro.config.mjs:

export default defineConfig({
  vite: { ssr: { noExternal: ["@smi-digital/web-kit"] } },
});

This is required, not optional. .astro files cannot be pre-compiled for distribution — the consumer's Astro compiles them — so this package ships source rather than build output. Without noExternal, Vite externalises the package during SSR and hands raw .astro to Node, which fails at runtime.

Usage

---
import Icon from "@smi-digital/web-kit/Icon.astro";
---

<Icon name="arrow_right_alt" class={styles.arrow} />

Inside a React island:

import Icon from "@smi-digital/web-kit/Icon";

<Icon name="chevron_left" className={styles.chevron} />;

Imports are explicit subpaths rather than a barrel file — a .ts barrel cannot cleanly re-export Astro components, and the .astro in the specifier tells you at the import site what kind of thing you are getting.

Components

CmsImage

A Strapi image as <picture> — AVIF offered first, WebP (or the original) catching everything else.

<CmsImage
  media={data.heroImage}
  sizes="(max-width: 767px) 100vw, 50vw"
  baseUrl={import.meta.env.PUBLIC_STRAPI_URL}
  priority
  class={styles.hero}
/>

| Prop | | | ---------- | ------------------------------------------------------------- | | media | The Strapi media object, never a URL string | | sizes | Required. How wide the box will be in your layout | | alt | Defaults to media.alternativeText; pass "" for decorative | | priority | Marks the page's LCP element — eager + fetchpriority="high" | | baseUrl | Origin for Strapi-relative URLs |

It takes the object, not a URL. Given a URL, a caller has already chosen a derivative — and the one they reach for is formats.large, regardless of how big the image is actually displayed. Demanding the object makes that mistake impossible to express.

sizes is required because the browser picks a candidate before it has laid the page out, so it cannot measure the box itself. Omit it and the browser assumes 100vw and takes the largest file, silently defeating the srcset. It describes your CSS layout, not the image, so only the person building that section can supply it.

priority on at most one image per page. Prioritising several prioritises none. One prop rather than three separate attributes so it is greppable — you can count them per page.

Formats are grouped by the mime on each derivative, not by a key-naming convention, so the Strapi upload pipeline can name its format keys however it likes.

Two consequences of how <picture> actually selects, both load-bearing:

  • A browser commits to the first <source> whose type it supports and then selects within it — it never falls through to a later source. So every format must carry a complete set of widths. A partial AVIF ladder would hand an AVIF-capable phone the smallest AVIF available, potentially a desktop-sized file, rather than a small WebP.
  • For the same reason the most broadly-supported format goes on the <img> itself rather than in a <source>.

Media with no derivatives — SVG, which Strapi does not resize — renders as a plain <img> with no <picture> wrapper. An empty CMS slot renders nothing.

Icon

Inline SVG replacement for an icon webfont.

name is a Material Symbols ligature name — the same string editors type into Strapi's icon_MSO field, so CMS content needs no migration. Unknown names render nothing (with a dev warning) rather than the literal ligature text an icon font shows while it loads.

The svg is 1em square and filled with currentColor, so it inherits whatever font-size and color the caller's class sets. That makes it a drop-in for <span class="icon">arrow_right_alt</span> markup with no CSS changes.

Adding an icon: copy the d attribute from google/material-design-icons at symbols/web/<name>/materialsymbolsoutlined/<name>_24px.svg into src/components/Icon/icons.ts, keeping the list alphabetical.

Note that the icon map is one object, so bundlers cannot tree-shake per icon — a site ships the whole set. That is deliberate: CMS-authored names are only known at runtime, so the lookup must be complete. At ~15 icons it is ~4 KB. If the set passes roughly fifty entries, revisit whether it should be split.

What belongs in this package

Three questions, and all three must be yes:

  1. Does it render the same markup on every site?
  2. Is all its styling supplied by the caller?
  3. Would a bug in it be a bug on all six sites?

Icon passes — it renders one <svg> and takes class straight through. A Button fails the first question and would become a prop explosion across six client designs. Components with design opinions stay in the sites.

Development

npm run check   # format, lint, typecheck, test — what CI runs
npm run test:watch

astro.config.mjs exists only so astro check and the container API used by the tests have a project to work against. It is not published; files in package.json ships src alone.

Tests render real .astro components through Astro's container API rather than hand-written fixtures, so a renamed class breaks the test that depends on it.

Releasing

Pushes to main run semantic-release, which derives the version and changelog from the commit messages — there is no manual version bump.

| Commit prefix | Release | | ---------------------------- | ------- | | fix: | patch | | feat: | minor | | feat!: / BREAKING CHANGE | major | | docs: chore: test: … | none |

.releaserc.json pins the conventionalcommits preset explicitly. semantic-release defaults to the Angular preset, which is not what @commitlint/config-conventional enforces on the way in — leaving it on the default makes the two ends of the pipeline disagree.

A release reaches six client sites at once, so feat!: deserves deliberation. Renovate is configured to automerge patch and minor and to open a PR for majors.

Publishing uses no token

There is no NPM_TOKEN secret. @semantic-release/npm 13 requests a GitHub OIDC token and exchanges it with the npm registry for short-lived publish credentials — npm's Trusted Publishing. Nothing long-lived is stored anywhere.

The exchange happens in JavaScript inside the plugin, not in the npm CLI, so the runner's bundled npm version is irrelevant. Two things must hold:

  1. The workflow grants id-token: write — it does.
  2. npmjs.com has a trusted publisher configured for this package pointing at SMI-Digital/web-kit and the workflow file release.yml.

Set that up under the package's Settings → Trusted Publisher on npmjs.com.

Bootstrap note: a trusted publisher is configured on a package, so the package generally has to exist first. If npm will not let you pre-configure it for a name that has never been published, do the first publish once from a local npm login session:

npm publish --access public

then add the trusted publisher and let every subsequent release run tokenless from CI. That keeps the "no secrets in CI" property intact — the one-off credential is your interactive login, not something stored in the repo.

Optional hardening once the first publish is green: add "provenance": true to publishConfig. Trusted publishing already provides the OIDC identity it needs.