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

vite-plugin-stable-sri

v1.0.1

Published

Vite plugin for stable, reproducible Subresource Integrity manifests.

Readme

vite-plugin-stable-sri

npm version license

A Vite plugin that produces stable, reproducible Subresource Integrity (SRI) hashes — a deterministic, byte-for-byte build outcome similar in spirit to webpack's reproducible output.

Why

Vite (and Rollup) chunk names are content-hashed, but the contents of those chunks can shift between builds because of non-deterministic module order, vendor grouping, or internal export minification. That means an SRI hash computed from a build today may not match the same source rebuilt tomorrow — which defeats the point of pinning integrity hashes for caching, CSP, or supply-chain verification.

This plugin makes the bundle layout deterministic so the SRI hash is too. It:

  • Forces stable entryFileNames, chunkFileNames, and assetFileNames patterns with hex hashCharacters.
  • Applies a deterministic manualChunks strategy (default: one chunk per node_modules package).
  • Computes SRI hashes after the bundle is finalized, in sorted order, and emits a stable JSON manifest.
  • Optionally injects integrity and crossorigin attributes into emitted HTML and augments Vite's own manifest.json.

Why use this over vite-plugin-sri?

Existing SRI plugins (such as vite-plugin-sri) do one thing: after the build, they parse the emitted HTML and inject integrity/crossorigin attributes for the assets it references. If that's all you need, they're simpler and completely non-invasive — reach for one of those.

This plugin targets a larger problem: integrity hashes you can pin, distribute, and reproduce, not just attributes baked into HTML. Choose it when you want:

  • Reproducible hashes. Vite/Rollup chunk contents can drift between builds (module order, vendor grouping, internal export minification), so a hash computed today may not match the same source rebuilt tomorrow. This plugin pins the bundle layout (manualChunks, stable file names, hex hashes, no internal-export minification) so the SRI hash is stable too. Plain SRI plugins hash whatever the build happens to produce and don't address drift.
  • An out-of-band manifest. It emits a sorted, byte-for-byte stable sri-manifest.json and writes integrity into Vite's own manifest.json. That lets a CDN, proxy, CSP pipeline, or separate verification step consume integrity values without scraping HTML. HTML-only plugins leave nothing to consume.
  • Correct link handling. Only rel="stylesheet" and rel="modulepreload" links get integrity. Plugins that target every <link href> can attach crossorigin to icons, preconnect, or manifest links and break those requests.
  • Lossless HTML edits. It inserts attributes via targeted offset-based edits, leaving the rest of the document byte-identical, rather than re-serializing the whole HTML through an HTML parser.
  • Configurability. Hash algorithm, crossorigin mode, hashLength, include filter, and chunk strategy are all options, instead of being hardcoded.

The trade-off: this plugin owns part of your build config (see Side effects on your Vite config) and has no special handling for @vitejs/plugin-legacy. If you don't consume the manifest and don't need reproducibility, that's more machinery than you need — use a plain SRI plugin instead.

Requirements

  • Node.js ≥ 18
  • Vite 5, 6, or 7 (peer dependency)
  • ESM (this package is ESM-only)

Install

npm install --save-dev vite-plugin-stable-sri

Usage

// vite.config.ts
import { defineConfig } from "vite";
import { stableSri } from "vite-plugin-stable-sri";

export default defineConfig({
  plugins: [
    stableSri({
      algorithm: "sha384",
      crossorigin: "anonymous",
      chunkStrategy: "per-package",
      injectHtml: true,
      manifest: true,
    }),
  ],
});

The plugin only runs during vite build (apply: "build").

What gets emitted

.vite/sri-manifest.json

{
  "assets/index-3f1b1d6a7c9e8f02.js": {
    "integrity": "sha384-XzqMnIuKgL...==",
    "bytes": 12843
  },
  "assets/vendor-react-1ab2cd34.js": {
    "integrity": "sha384-aR4mZ8fE9k...==",
    "bytes": 132901
  }
}

Keys are sorted; bytes are the exact source size used to compute the hash.

Injected HTML

<script type="module" crossorigin="anonymous"
  integrity="sha384-XzqMnIuKgL...=="
  src="/assets/index-3f1b1d6a7c9e8f02.js"></script>
<link rel="stylesheet" crossorigin="anonymous"
  integrity="sha384-pL2..." href="/assets/index-9c8d7e6f5a4b3c2d.css">

Augmented .vite/manifest.json

Each entry gets an integrity field alongside Vite's existing file, imports, etc.

Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | algorithm | "sha256" \| "sha384" \| "sha512" | "sha384" | Hash algorithm used for SRI digests. | | crossorigin | "anonymous" \| "use-credentials" \| false | "anonymous" | Value for the injected crossorigin attribute (or false to skip). | | injectHtml | boolean | true | Inject integrity (and crossorigin) attributes into emitted HTML. | | manifest | boolean | true | Emit .vite/sri-manifest.json mapping bundle file → integrity hash. | | manifestFile | string | ".vite/sri-manifest.json" | Path of the emitted SRI manifest. | | include | RegExp | /\.(js\|css)$/ | Files included in the manifest / HTML rewriting. | | chunkStrategy | "none" \| "single-vendor" \| "per-package" \| (id) => string \| undefined | "per-package" | How vendor code is grouped into chunks for stable file names. See below. | | hashLength | number | 16 | Length of the content-hash segment in output file names. | | augmentViteManifest | boolean | true | Add integrity to entries in Vite's own .vite/manifest.json. |

chunkStrategy

  • "none" — no manualChunks; Rollup's default grouping (less stable across dependency upgrades).
  • "single-vendor" — everything in node_modules lands in a single vendor chunk.
  • "per-package" (default) — one chunk per top-level node_modules package (vendor-react, vendor-react-dom, vendor-@scope-pkg, …). Best stability/cacheability trade-off, but note it can emit many small chunks (one request per package) for dependency-heavy apps; switch to "single-vendor" if you'd rather trade granular caching for fewer requests.
  • (id) => string | undefined — supply your own function; return a chunk name for IDs you want to group, or undefined to leave the module ungrouped.

Side effects on your Vite config

This plugin sets the following inside build.rollupOptions.output, overriding any user-supplied values:

  • entryFileNames, chunkFileNames, assetFileNames
  • hashCharacters: "hex"
  • manualChunks (according to chunkStrategy)
  • minifyInternalExports: false

It also sets build.manifest: true so the Vite manifest is available for augmentation.

If you need fine-grained control over chunk file names, pass a custom chunkStrategy function rather than overriding chunkFileNames yourself.

Limitations

  • SSR builds are left untouched. The plugin only acts on client builds. During an SSR build (build.ssr) it makes no changes — server chunks aren't loaded with SRI, and forcing manualChunks there can conflict with the inlineDynamicImports that SSR/library builds use.
  • Browser-enforced SRI covers static references only. Injected integrity protects the entry <script>, the emitted stylesheet, and the static modulepreload/stylesheet links in your HTML. Chunks pulled in at runtime via Vite's preload helper (import()) are not given integrity by the browser, and bare ESM import has no SRI mechanism of its own (it relies on modulepreload links covering the graph). Every chunk's hash is still recorded in sri-manifest.json for out-of-band verification (CDN, proxy, CSP pipeline), but the browser does not enforce integrity on those runtime fetches. If complete browser-enforced integrity is your goal, prefer fewer, statically-referenced chunks.
  • No special handling for @vitejs/plugin-legacy.

License

MIT © Justin Formentin