Add Subresource Integrity (SRI) hashes to your Vite build HTML output automatically.
vite-plugin-sri-gen
- Adds integrity attribute to script tags, stylesheet links, and modulepreload links in index.html
- Works out of the box in production builds
- Optionally injects rel="modulepreload" with integrity for lazy-loaded chunks
- Optionally injects a tiny CSP-safe runtime that adds integrity/crossorigin to dynamically inserted <script>/<link>
- Build-only by design (no dev server SRI)
- Supports SPA, MPA, and prerendered SSR/SSG HTML (logs a warning when a pure SSR server emits no HTML)
- Fast and network-friendly: in-memory HTTP cache with in-flight dedupe; optional fetch timeouts
- ESM-only, Node 18+ (uses global fetch)
Table of Contents
- Install
- Quick Start
- Dev Mode
- Configuration
- Skipping Resources
- Lazy-loaded Chunks and Dynamic Tags
- Runtime Patching
- Compatibility
- Examples
- Contributing
- Security
- License
Install
npm i -D vite-plugin-sri-gen
Quick start
vite.config.ts / vite.config.js:
import sri from "vite-plugin-sri-gen";
export default {
plugins: [
sri({
algorithm: "sha384", // 'sha256' | 'sha384' | 'sha512' (default: 'sha384')
crossorigin: "anonymous", // 'anonymous' | 'use-credentials' | undefined
fetchCache: true, // cache remote fetches in-memory and dedupe concurrent requests (default: true)
fetchTimeoutMs: 5000, // abort remote fetches after N ms; 0 disables timeout (default: 5000)
skipResources: [], // skip SRI for resources matching these patterns (default: [])
}),
],
};
During build, the plugin updates index.html in memory and adds an integrity attribute to scripts, stylesheets, and modulepreload links. If crossorigin is provided, it is also added.
Advanced (optional): you can enable automatic rel="modulepreload" injection for lazy-loaded chunks and a CSP-safe runtime patch that sets integrity on dynamically inserted tags. See the Configuration section.
TypeScript/ESM notes:
- The package ships ESM only with types. Import as
import sri from 'vite-plugin-sri-gen'
.
Dev mode
SRI is intentionally disabled during the Vite dev server. Use this plugin for build output only.
Why dev SRI doesn’t help:
- HMR bypasses SRI entirely: code updates are delivered over WebSocket and inlined into the page, not via normal
script
/link
fetches that support integrity checks. - Non-stable module URLs: the dev server rewrites ESM imports and appends timestamps/query params. Content changes frequently during edits, so any integrity value would break on each save.
- No transitive guarantees: browsers don’t enforce SRI for modules imported by a script with integrity. Each module request would need its own integrity, which the browser won’t verify in dev.
- Partial coverage is misleading: hashing only top-level tags (or some assets) provides a false sense of security while leaving the rest of the module graph and HMR updates unverified.
Conclusion: SRI is enforced only for build outputs, where assets are content-addressed and stable. That’s when browsers can reliably validate integrity and you get real protection.
Configuration
type SriPluginOptions = {
algorithm?: "sha256" | "sha384" | "sha512"; // default: 'sha384'
crossorigin?: "anonymous" | "use-credentials"; // default: undefined
fetchCache?: boolean; // default: true (in-memory cache + in-flight dedupe for remote assets)
fetchTimeoutMs?: number; // default: 5000 (5 seconds). Abort remote fetches after N ms, 0 to disable timeout
preloadDynamicChunks?: boolean; // default: true. Inject rel="modulepreload" with integrity for discovered lazy chunks
runtimePatchDynamicLinks?: boolean; // default: true. Inject a tiny runtime that adds integrity to dynamically created <script>/<link>
skipResources?: string[]; // default: []. Skip SRI for resources matching these patterns (by id or src/href)
};
Notes:
- Remote assets (http/https) are fetched at build to compute hashes. Protocol-relative URLs (//cdn.example.com/foo.js) are supported and treated as https.
- Local assets are read from the build bundle output.
- Existing integrity attributes are preserved and not overwritten.
- If an asset cannot be found in the bundle, it is skipped.
- Invalid or unsupported algorithms are automatically replaced with 'sha384' and a warning is logged.
- Caching: when enabled, remote fetches are cached in-memory per build and concurrent requests are deduplicated.
- Timeout: when a non-zero fetchTimeoutMs is set, slow remote fetches are aborted and the affected elements are left unchanged (a warning is logged).
Skipping Resources
You can exclude specific resources from SRI generation using the skipResources
option. This is useful for third-party scripts, analytics, or dynamically-loaded content that may change frequently:
sri({
skipResources: [
"analytics-script", // Skip by element ID
"https://www.googletagmanager.com/*", // Skip by URL pattern
"vendor-*", // Skip vendor assets by pattern
"*.googleapis.com/*", // Skip Google APIs
],
});
Pattern Types:
- Element ID: Matches the
id
attribute value exactly ('analytics-script'
) - URL Exact Match: Matches
src
orhref
attribute exactly ('https://example.com/script.js'
) - URL Glob Pattern: Use
*
as wildcard in URL patterns ('*.googleapis.com/*'
,'vendor-*'
)
Use Cases:
- Third-party analytics scripts that change frequently
- A/B testing scripts with dynamic content
- CDN resources that may be modified by the provider
- Development/staging resources that shouldn't have integrity checks
[!WARNING]
Skipped elements will not haveintegrity
attributes added, allowing them to be modified by CDNs or served with different content without breaking the page.
Lazy-loaded chunks and dynamic tags
- If
preloadDynamicChunks
is enabled (default), the plugin scans Rollup output for dynamically imported chunks and injects<link rel="modulepreload" integrity=...>
for them into emitted HTML, honoring Vitebase
andcrossorigin
. - If
runtimePatchDynamicLinks
is enabled (default), a tiny runtime is prepended to entry chunks. It setsintegrity
(andcrossorigin
if configured) on dynamically created<script>
and<link>
elements for eligible resources (scripts, stylesheets, modulepreload, or preload as=script/style/font) before the network request happens. This is bundled code (not inline) and is CSP-safe.
Runtime Patching
When runtimePatchDynamicLinks
is enabled (default), the plugin injects a small runtime into entry chunks that patches DOM manipulation methods to automatically add integrity attributes to dynamically created elements.
How it works
The runtime patches these DOM methods:
Element.prototype.setAttribute
- Interceptssrc
andhref
attribute assignmentsNode.prototype.appendChild
- Intercepts element insertionElement.prototype.append/prepend/insertBefore
- Intercepts element insertion
When code dynamically creates <script>
or <link>
elements and sets their src
/href
attributes, the runtime automatically adds the appropriate integrity
and crossorigin
attributes if a matching hash exists in the SRI map.
Eligible elements
The runtime only processes elements that are eligible for SRI:
<script>
elements withsrc
attributes<link>
elements withrel="stylesheet"
<link>
elements withrel="modulepreload"
<link>
elements withrel="preload"
andas="script|style|font"
Skip patterns
The runtime respects skipResources
patterns, allowing you to exclude specific resources from automatic SRI injection even when created dynamically.
Compatibility
- SPA (appType: 'spa'): supported. transformIndexHtml runs at build to add SRI to index.html.
- MPA: supported. generateBundle scans emitted .html files and injects SRI.
- SSR/SSG: prerendered/static HTML is supported via generateBundle. For pure SSR server output (no .html emitted), there's nothing to modify at build time.
- Node 18+ only (uses global fetch). ESM-only package.
When building for SSR, if no HTML files are emitted, the plugin logs a warning to help diagnose why SRI wasn't applied:
No emitted HTML detected during SSR build. SRI can only be added to HTML files; pure SSR server output will be skipped.
Examples
MPA (multiple HTML entry points)
Configure multiple HTML inputs so Vite emits several .html files. The plugin will inject SRI into all of them:
// vite.config.ts
import { defineConfig } from "vite";
import sri from "vite-plugin-sri-gen";
export default defineConfig({
build: {
rollupOptions: {
input: {
main: "index.html",
about: "about/index.html",
admin: "admin/index.html",
},
},
},
plugins: [sri({ crossorigin: "anonymous" })],
});
SSR/SSG with prerendered HTML
If your SSR/SSG setup emits static HTML during build (for example via a prerender step), the plugin will add SRI to those files automatically via generateBundle:
// vite.config.ts
import { defineConfig } from "vite";
import sri from "vite-plugin-sri-gen";
export default defineConfig({
// Your SSR/SSG tooling may set build.ssr, use a prerender plugin/step, etc.
plugins: [sri()],
});
Notes:
- If no HTML is emitted (pure SSR server output), you’ll see a warning and nothing is changed.
- You don’t need extra configuration for SRI; inclusion of the plugin is enough when HTML files are produced.
Advanced: networking controls
Tune caching and timeouts for remote assets:
import sri from "vite-plugin-sri-gen";
export default {
plugins: [
sri({
crossorigin: "anonymous",
fetchCache: true, // keep enabled for best performance
fetchTimeoutMs: 5000, // fail fast if a CDN becomes slow or unresponsive
}),
],
};
Why ESM-only?
Vite and modern Node tooling are native ESM-first. Dropping CommonJS simplifies the package and aligns with Vite expectations.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md for the fork/branch/PR workflow (git-flow style with prefixes like feat/
, fix/
, bug/
) and the testing/linting expectations.
Security
See SECURITY.md for supported versions and how to report vulnerabilities via the repository’s Security section.
License
MIT — see LICENSE.