vite-plugin-inject-scripts
v1.1.0
Published
A Vite plugin that injects `<script>` tags into the generated HTML files
Maintainers
Readme
vite-plugin-inject-scripts
A Vite plugin that injects <script> tags into the generated HTML files.
Overview
This plugin lets you declare a list of "prior" scripts and decide exactly where and how each one is inserted into every (or selected) HTML file:
- Inline the script content or reference it via
src; - Place it at the beginning/end of
<head>or<body>; - Set a
type(module,importmap,speculationrules, IIFE, or a custom value); - Pass through arbitrary attributes (
async,defer,crossorigin,nonce, …); - Restrict injection to specific HTML files;
- Transform TypeScript sources on the fly.
It works both during development (vite serve) and at build time (vite build).
Installation
# npm
npm install --save-dev vite-plugin-inject-scripts
# yarn
yarn add --dev vite-plugin-inject-scripts
# pnpm
pnpm add --save-dev vite-plugin-inject-scripts
# bun
bun add --dev vite-plugin-inject-scriptsUsage
// vite.config.ts
import { defineConfig } from "vite";
import injectScripts from "vite-plugin-inject-scripts";
export default defineConfig({
plugins: [
injectScripts({
scripts: [
// A script file, injected at the end of <head>.
{ src: "src/polyfills.js" },
// An inline module snippet, injected at the start of <body>.
{ src: "config.js", source: `window.__APP__ = {};`, type: "module", injectTo: "body-prepend", inline: true },
],
}),
],
});Options
scripts
Type: (string | PriorScript)[]
(Required)
The scripts to inject. Each entry is either a plain path string (equivalent to { src: "…" }) or a PriorScript object. The order in the array is preserved within each injection position.
entryFile
Type: string | true | RegExp | ((jsFilePath: string) => boolean)
Default: true
Selects the entry <script type="module" crossorigin> element in <head>. Scripts injected at head-append are placed immediately before it, so they execute first. If no element matches, head-append scripts are appended to the end of <head>.
true(default): the last<script type="module" crossorigin>in<head>.string: the script whosesrcends with/nameor equalsname.RegExp: the first script whosesrcmatches the pattern.function: the first script whosesrcmakes the callback returntrue.
minifyHtml
Type: boolean | "terser" | "next" | "swc"
Default: true
Minify the HTML output when building. true uses the default engine terser (html-minifier-terser). Pass an engine name to use a different engine — you must install that engine yourself. Set false to disable HTML minification (inline <script> / <style> content is then left untouched as well).
minifyJS
Type: boolean | "oxc" | "swc" | "esbuild" | "terser"
Default: true
Minify the JavaScript output when building. true uses the default engine oxc (oxc-minify). Pass an engine name to use a different engine — you must install that engine yourself. This also controls the minifier used for inline <script> content during HTML minification. Set false to disable.
Note: JSON scripts (
.json,importmap,speculationrules, …) are always minified and are not affected by this option.
minifyCSS
Type: boolean | "lightningcss" | "esbuild" | "cssnano" | "clean-css" | "csso"
Default: true
Choose the CSS minification engine used during HTML minification (for inline <style> content). true uses the default engine lightningcss. Pass an engine name to use a different engine — you must install that engine yourself. Set false to disable.
PriorScript
Each script object accepts the following fields:
| Field | Type | Default | Description |
| --- | --- | --- | --- |
| src | string | — | Path to the script (js / ts / jsx / tsx / json), resolved from Vite's root.When source is set, the file is not read and src is only used for the output file name/extension;When source is set and inline is true, src is only checked for a .json extension to treat the content as JSON. |
| source | string | — | Override the script content with an inline string.Not recommended unless the code is very simple — prefer a separate file referenced by src, since inline content loses editor highlighting and autocompletion. |
| type | string | "classic" | See script types. |
| strict | boolean | false | Prepend a "use strict" directive. Only effective when type is "classic", "script", "iife", "iifearrow", "iife-arrow", or "block". |
| injectTo | "head-prepend" \| "head-append" \| "body-prepend" \| "body-append" | "head-append" | Where to insert the <script> tag. |
| modify | (code: string) => string | — | Transform the source before it is compiled/minified. |
| filterHtml | Filter | — | Only inject into matching HTML files. See filtering. |
| inline | boolean | false | Inline the content directly (no src attribute). |
| async | boolean | — | The async attribute. |
| blocking | ("render")[] | — | The blocking attribute (values are joined). |
| crossOrigin | "anonymous" \| "use-credentials" | — | The crossorigin attribute. |
| defer | boolean | — | The defer attribute. |
| fetchPriority | "high" \| "low" \| "auto" | — | The fetchpriority attribute. |
| integrity | string | — | The integrity attribute. |
| noModule | boolean | — | The nomodule attribute. |
| nonce | boolean | — | The nonce attribute. |
| referrerPolicy | string | — | The referrerpolicy attribute. |
| (any other) | string | — | Any additional attribute is passed through to the <script> tag. |
type
See HTMLScriptElement.supports() static method.
| Value | Emitted type | Notes |
| --- | --- | --- |
| "classic" / "script" | (none) | A classic script. Every declared variable and function becomes a global, and import/export cannot be used. |
| "module" | type="module" | A JavaScript module. Declared variables and functions are inaccessible from outside, and import/export can be used. With inline: true, export won't error but is meaningless. |
| "iife" | (none) | Wraps the content in an IIFE (self-invoking function). Declared variables and functions are inaccessible from outside, and import/export cannot be used. Unlike "module" — which is deferred and non-blocking, so it can run only after the DOM has loaded even when injected before <body> — an IIFE runs synchronously as the parser reaches it. Use "iife" (or "iifearrow" / "block") to run code before the DOM finishes loading (e.g. set a background color early) without leaking variables. |
| "iifearrow" / "iife-arrow" | (none) | Same as "iife", but uses an arrow function instead of an anonymous function, so the compiled output is a few bytes smaller. ES6+ only. |
| "block" | (none) | Wraps the content in a block scope ({ … }). let, const, class and using declarations do not leak; var does; function declarations leak unless strict is set. A single pair of braces already keeps most declarations from leaking, so it uses fewer bytes than "iife" / "iifearrow" — preferred for inline: true when the code doesn't use var. ES6+ only. |
| "importmap" | type="importmap" | An import map (JSON) that controls which URLs are fetched by JavaScript import statements and import() expressions, through its optional imports and scopes keys. |
| "speculationrules" | type="speculationrules" | Speculation rules (JSON) that let the browser speculatively prefetch or prerender documents matching URL patterns, to speed up subsequent navigations. |
| any other string | type="…" | Passed through verbatim. |
Filtering
filterHtml accepts a string (exact file path match), a RegExp, a function (filename: string) => boolean, or an array of these (matched if any applies). The filter is matched against the resolved path of the HTML file being transformed. Scripts without a filterHtml are injected into every HTML file.
injectScripts({
scripts: [
{ src: "src/analytics.js", filterHtml: /index\.html$/ },
{ src: "src/admin.js", filterHtml: filename => filename.includes("/admin/") },
{ src: "src/one-off.js", filterHtml: ["/project/index.html", /about\.html$/] },
],
});How it works
- Resolving files:
srcis resolved relative to Vite'sroot. Settingsourceskips the file read. - Transformations (in order):
modify→ IIFE/block wrapping (type: "iife"/"iifearrow"/"iife-arrow"/"block", withstrict) → TypeScript compilation (.ts/.tsx/.mts/.cts) → minification (build only). - Minification: controlled by
minifyHtml,minifyJSandminifyCSS; JSON scripts are always minified. - Development: non-inline scripts are served from a
/@inject-scripts/…virtual route. - Build: non-inline scripts are emitted as assets (
.js/.json) and referenced by their hashed file name.
Examples
TypeScript source compiled on the fly
injectScripts({
scripts: [
{ src: "src/bootstrap.ts", type: "module" },
],
});An IIFE with custom attributes
injectScripts({
scripts: [
{ src: "src/legacy.js", type: "iife", injectTo: "body-append", defer: true },
],
});Inline a config module before the app
injectScripts({
scripts: [
{
src: "app.config.js",
source: `export default ${JSON.stringify({ apiBase: "/api" })};`,
type: "module",
injectTo: "head-prepend",
inline: true,
},
],
});Import map
injectScripts({
scripts: [
{
src: "import-map.json",
source: JSON.stringify({ imports: { vue: "https://esm.sh/vue" } }),
type: "importmap",
},
],
});Comparison with vite-plugin-inject-script
The two names differ by a single letter, but the plugins do fundamentally different things:
| | vite-plugin-inject-script | vite-plugin-inject-scripts (this plugin) |
| --- | --- | --- |
| What it modifies | The JavaScript bundle | The HTML document |
| Where it injects | Prepends a JavaScript snippet (as a string) to the top of the entry JS file — the one named by Rollup/Rolldown's output.entryFileNames (commonly index.js) | Inserts <script> elements into <head> / <body> |
| What it injects | Raw JS source code | <script> tags — inline or external, any type |
| Control over placement | Single, fixed position (before all entry code) | Four positions (head-prepend, head-append, body-prepend, body-append) |
In short:
vite-plugin-inject-scriptchanges the JavaScript output — it prepends code into the bundled entry file.vite-plugin-inject-scriptschanges the HTML output — it adds<script>tags to the page.
