A zero-dependency Vite plugin and standalone utility to inline CSS, JavaScript, and SVG assets into HTML for single-file deployment.
- ๐ Zero Dependencies - No external packages required
- ๐ Dual Mode - Use as Vite plugin or standalone function
- ๐จ Inline CSS - Converts
<link>tags to<style>tags - ๐ฆ Inline JavaScript - Converts
<script src>to inline<script> - ๐ผ๏ธ Inline SVG - Converts SVG files to base64 data URIs
- ๐ฏ Selective Inlining - Exclude specific files with patterns
- ๐งน Auto Cleanup - Removes inlined files and empty directories
- ๐ Custom Logger - Bring your own logger or use the built-in one
- ๐ง TypeScript Support - Full type definitions included
npm install @ropean/inline-assets -Dyarn add @ropean/inline-assets -Dpnpm add @ropean/inline-assets -D// vite.config.ts
import { defineConfig } from 'vite';
import inlineAssets from '@ropean/inline-assets';
export default defineConfig({
plugins: [
inlineAssets({
css: true,
js: true,
svg: { img: false, link: true },
excludes: ['assets/large-file.js'],
}),
],
});Perfect for use with any build tool (Webpack, Rollup, esbuild, etc.):
import { inlineAssets } from '@ropean/inline-assets';
// After your build process
await inlineAssets({
htmlPath: './dist/index.html',
css: true,
js: true,
svg: { img: true, link: true },
});import { inlineAssets } from '@ropean/inline-assets';
const myLogger = {
info: (msg) => console.log('[INFO]', msg),
success: (msg) => console.log('[โ]', msg),
warning: (msg) => console.warn('[โ ]', msg),
error: (msg) => console.error('[โ]', msg),
};
await inlineAssets({
htmlPath: './dist/index.html',
logger: myLogger,
});
// Or disable logging completely
await inlineAssets({
htmlPath: './dist/index.html',
logger: false,
});interface VitePluginOptions {
/** Whether to inline CSS files (default: true) */
css?: boolean;
/** Whether to inline JavaScript files (default: true) */
js?: boolean;
/** SVG inlining options (default: { img: false, link: true }) */
svg?:
| boolean
| {
img?: boolean; // Inline SVG in <img> tags
link?: boolean; // Inline SVG in <link> tags (favicon)
};
/** File patterns to exclude from inlining (default: []) */
excludes?: string[];
/** Distribution directory name (default: 'dist') */
distDir?: string;
/** HTML file name to process (default: 'index.html') */
htmlFileName?: string;
/** Where to insert inlined CSS (default: 'original') */
cssInsertPosition?: 'original' | 'head-start' | 'head-end';
/** Custom logger or false to disable (default: built-in logger) */
logger?: LoggerInterface | false;
}interface InlineAssetsOptions {
/** Path to the HTML file to process (required) */
htmlPath: string;
/** Base directory for resolving asset paths (default: HTML file's directory) */
baseDir?: string;
/** Whether to inline CSS files (default: true) */
css?: boolean;
/** Whether to inline JavaScript files (default: true) */
js?: boolean;
/** SVG inlining options (default: true) */
svg?:
| boolean
| {
img?: boolean;
link?: boolean;
};
/** File patterns to exclude from inlining (default: []) */
excludes?: string[];
/** Whether to delete inlined asset files (default: true) */
removeInlinedFiles?: boolean;
/** Whether to remove empty directories (default: true) */
cleanupEmptyDirs?: boolean;
/** Where to insert inlined CSS (default: 'original') */
cssInsertPosition?: 'original' | 'head-start' | 'head-end';
/** Custom logger or false to disable (default: built-in logger) */
logger?: LoggerInterface | false;
}Implement this interface to create your own logger:
interface LoggerInterface {
info(message: string): void;
success(message: string): void;
warning(message: string): void;
error(message: string): void;
event?(message: string): void; // Optional
file?(path: string): string; // Optional
newline?(count?: number): void; // Optional
}Control where inlined CSS is placed in your HTML:
inlineAssets({
cssInsertPosition: 'original', // Default: keep CSS at original <link> position
});-
'original'(default) - Keeps CSS at the original<link>tag position- โ Preserves the order of CSS and JS
- โ CSS appears before JS if that's how you structured it
โ ๏ธ May create multiple<style>tags if you have multiple CSS files
-
'head-start'- Moves all CSS to the beginning of<head>- โ Optimal for performance (CSS loads first)
- โ
Single merged
<style>tag โ ๏ธ Changes the original order
-
'head-end'- Moves all CSS to the end of<head>- โ
Single merged
<style>tag โ ๏ธ CSS loads after other head elements
- โ
Single merged
// Keep CSS before JS (preserves order)
inlineAssets({
cssInsertPosition: 'original',
});
// Optimize for performance (CSS at top)
inlineAssets({
cssInsertPosition: 'head-start',
});Exclude specific files from inlining:
inlineAssets({
excludes: [
'index.js', // Matches any file named 'index.js'
'assets/vendor.js', // Matches 'assets/vendor.js' specifically
'large-image.svg', // Matches any file named 'large-image.svg'
],
});- Config Hook: Automatically configures Vite to extract CSS as a single file
- Build: Vite builds your project normally
- Close Bundle Hook: After build completes, inlines assets into HTML
- Cleanup: Removes inlined files and empty directories
- Reads the HTML file
- Finds all CSS, JS, and SVG references
- Inlines their content (CSS/JS as text, SVG as base64)
- Writes the modified HTML back
- Optionally removes inlined files
Before (3 files):
dist/
โโโ index.html
โโโ assets/
โ โโโ index.css
โ โโโ index.js
After (1 file):
dist/
โโโ index.html (with inlined CSS and JS)
See the examples/ directory for complete, runnable examples:
- ๐ Vite Plugin Usage - Basic and advanced Vite plugin configurations
- ๐ง Standalone Function - Use with any build tool
- ๐ฆ Webpack Integration - Webpack plugin example
- ๐ฏ Rollup Integration - Rollup plugin example
- ๐ Gulp Integration - Gulp task example
- ๐ npm Scripts - Post-build script with error handling
- ๐จ CSS Insert Position - CSS positioning strategies
Webpack Integration
// webpack.config.js
const { inlineAssets } = require('@ropean/inline-assets');
module.exports = {
plugins: [
{
apply: (compiler) => {
compiler.hooks.done.tap('InlineAssets', async () => {
await inlineAssets({ htmlPath: './dist/index.html' });
});
},
},
],
};๐ View full example
Rollup Integration
// rollup.config.js
import { inlineAssets } from '@ropean/inline-assets';
export default {
plugins: [
{
name: 'inline-assets',
closeBundle: async () => {
await inlineAssets({ htmlPath: './dist/index.html' });
},
},
],
};๐ View full example
npm Scripts
{
"scripts": {
"build": "vite build",
"postbuild": "node inline-assets.js"
}
}// inline-assets.js
import { inlineAssets } from '@ropean/inline-assets';
const result = await inlineAssets({
htmlPath: './dist/index.html',
});
if (!result.success) {
console.error('Failed to inline assets');
process.exit(1);
}Gulp Integration
// gulpfile.js
import { inlineAssets } from '@ropean/inline-assets';
import gulp from 'gulp';
gulp.task('inline', async () => {
await inlineAssets({ htmlPath: './dist/index.html' });
});
gulp.task('build', gulp.series('your-build-task', 'inline'));Make sure you're not using cssCodeSplit: true in your Vite config. The plugin automatically sets this to false.
Check that baseDir points to the correct directory where your assets are located.
Use the excludes option to prevent specific files from being inlined.
MIT ยฉ ropean
Contributions are welcome! Please feel free to submit a Pull Request.
Found a bug or have a feature request? Open an issue