A Vite plugin that automatically scans your static assets directory and serves a type-safe virtual module (virtual:static-assets) with all asset paths, directory-aware types, and a helper function to get asset URLs. It validates asset references during build and updates live during development.
- 🚀 Automatic Recursive Scanning: Scans a directory (default:
public) for all static assets. - 🛡 Type-Safe API: Generates a union type
StaticAssetPathof all valid asset paths. - 📁 Directory-Aware Types: Generates
StaticAssetDirectoryand a powerfulFilesInFolder<Dir>generic for directory-specific asset typing. - 🔗 Helper Function: Provides
staticAssets()to get the URL for an asset, with runtime validation. - 🛠 Highly Configurable: Customize directory, output file, ignore patterns, debounce, directory depth, and more.
- 🔄 Live Updates: Watches the directory in development mode and regenerates types on changes.
- 🧭 Validation: Validates
staticAssets()calls during build, with detailed error messages. - ⚡ Fast: Minimal overhead, optimized for large projects.
Built with Bun – the ultra-fast JavaScript runtime & toolkit
Import from the virtual module:
import { staticAssets, type StaticAssetPath, type StaticAssetDirectory, type FilesInFolder } from 'virtual:static-assets';
// Use the helper function
const logoUrl = staticAssets('images/logo.svg');
// Type-safe variables
const assetPath: StaticAssetPath = 'fonts/roboto.woff2';
const dir: StaticAssetDirectory = 'images/';
// Type-safe list of files directly inside 'icons/brands/'
type Icons = FilesInFolder<'icons/brands/'>;
// use Icons type in your code
type Brands = {
icon: Icons,
name: string
}
// Create a list of brands with their icons and names
// get autocompletion and type checking!
const brands: Brands[] = [
{
icon: "icons/brands/coke.svg",
name: "Coke"
},
{
icon: "icons/brands/pepsi.svg",
name: "Pepsi"
},
{
icon: "icons/brands/rc-cola.svg",
name: "RC Cola"
},
{
icon: "icons/brands/dr-pepper.svg",
name: "Dr Pepper"
},
]Vite's import.meta.glob with { eager: true, query: '?url' } can give you a record of asset URLs, but it doesn't provide string-based lookup with compile-time validation. This plugin gives you:
- A typed
staticAssets('path')function that validates paths exist at build time - Union types for autocompletion across your entire asset directory
- Directory-scoped types via
FilesInFolder<Dir>for organizing assets by folder
# npm
npm install --save-dev vite-static-assets-plugin
# yarn
yarn add -D vite-static-assets-plugin
# bun
bun add -d vite-static-assets-plugin
# pnpm
pnpm add -D vite-static-assets-pluginRequirements:
- Vite 7 or 8 (Vite 6 dropped in plugin v3)
- Node
^20.19.0 || ^22.12.0 || >=24.0.0 - TypeScript ≥ 5
Upgrading from v2? See MIGRATION.md.
Add the plugin to your Vite config:
import { defineConfig } from 'vite';
import staticAssetsPlugin from 'vite-static-assets-plugin';
export default defineConfig({
plugins: [
staticAssetsPlugin({
// Optional configuration (defaults shown):
directory: 'public',
typesOutputFile: 'src/static-assets.d.ts',
ignore: ['.DS_Store'],
debounce: 200,
enableDirectoryTypes: true,
maxDirectoryDepth: 5,
})
]
});For exact asset-path types, generate the declaration file before typechecking:
vsap generatevsap generate loads your Vite config by default and uses the options from staticAssetsPlugin(...). You can override the config values when needed:
vsap generate -d public -o src/static-assets.d.tsIf you do not want to commit generated declarations, add the output file to .gitignore and run vsap generate before standalone typecheck jobs:
src/static-assets.d.ts{
"scripts": {
"ci:static-assets": "vsap generate",
"type-check": "bun run ci:static-assets && tsc --noEmit"
}
}For loose fallback types before generation, add the plugin's type reference to your src/vite-env.d.ts:
/// <reference types="vite/client" />
/// <reference types="vite-static-assets-plugin/client" />The fallback declares StaticAssetPath as string, so it resolves virtual:static-assets but does not provide typo checking or autocomplete for known assets. Do not use the fallback reference in the same TypeScript program as the generated static-assets.d.ts.
The plugin serves a virtual module (virtual:static-assets) with runtime code and generates a .d.ts file (default: src/static-assets.d.ts) containing:
A union of all asset paths:
export type StaticAssetPath =
'images/logo.svg' |
'images/banner.jpg' |
'fonts/roboto.woff2';A union of all directories containing assets, including '.' for the root:
export type StaticAssetDirectory =
'.' |
'fonts/' |
'images/' ;A generic type representing only the files directly inside a directory:
// Example: all files directly inside 'images/' (not nested)
type ImageFiles = FilesInFolder<'images/'>;
// 'logo.svg' | 'banner.jpg'A function that returns the URL for an asset, with validation:
export function staticAssets(path: StaticAssetPath): string;If you pass an invalid path, it throws an error at runtime and TypeScript will catch it at compile time.
Use it in your components:
<img src={staticAssets('images/logo.svg')} alt="Logo" />Works with any frontend framework that uses Vite: React, Vue, Svelte, Angular, Solid, Lit, and more.
| Option | Type | Default | Description |
|---|---|---|---|
directory |
string |
'public' |
Directory to scan for static assets |
typesOutputFile |
string |
'src/static-assets.d.ts' |
Path to generate the .d.ts type definitions |
ignore |
string[] |
['.DS_Store'] |
Glob patterns to ignore |
debounce |
number |
200 |
Debounce time (ms) for file watcher events |
enableDirectoryTypes |
boolean |
true |
Generate directory-aware types (StaticAssetDirectory, FilesInFolder) |
maxDirectoryDepth |
number |
5 |
Maximum directory nesting level for directory type generation |
The package exposes a short vsap binary for generating precise types without running a Vite dev server or build:
vsap generateBy default, the command loads vite.config.*, finds staticAssetsPlugin(...), and reuses its directory, typesOutputFile, ignore, enableDirectoryTypes, and maxDirectoryDepth options.
CLI flags override config values:
vsap generate -c vite.config.ts -d public -o src/static-assets.d.tsUse --mode <mode> for mode-specific Vite configs. Use --no-config to skip Vite config loading and rely only on CLI flags/defaults.
- Scans the specified directory recursively, ignoring patterns.
- Serves a virtual module (
virtual:static-assets) with the asset set andstaticAssets()function, usingimport.meta.env.BASE_URLfor correct base path handling. - Generates a
.d.tsfile withStaticAssetPath,StaticAssetDirectory, andFilesInFolder<Dir>types during Vite runs or viavsap generate. - Watches the directory in development mode (via Vite's built-in watcher), regenerating on changes.
- Validates
staticAssets()calls during build. - Throws errors with detailed info if a referenced asset is missing.
- If you reference a missing asset in
staticAssets(), the plugin throws a build-time error with details (even if you're skipping TS typechecking before build). - Errors include the file path, missing asset, and suggestions.
- Please note that this message is shown in case you actually skip TS typechecking before build. In case you're not typechecking before build (which is recommended), the error will be thrown at build time and you'll see the full error message in the terminal.
- Run
vsap generatebefore standalone typecheck jobs if the generated.d.tsis gitignored. - The plugin also generates precise union types to
src/static-assets.d.tsduring Vite runs. - Use
/// <reference types="vite-static-assets-plugin/client" />only as a loose fallback when you are not using the generated.d.ts. - Enjoy auto-completion, type checking, and refactoring support for your static assets.
This project uses Vitest:
# Run all tests
npm test
# Watch mode
npm run test:watch
# Coverage
npm run test:coverageTests are in packages/plugin/tests/ and cover core functions and plugin behavior.
MIT
Contributions, issues, and feature requests are welcome! Please open an issue or pull request.
