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-userscript-plugin

v2.5.0

Published

Downloads

637

Readme

vite-userscript-plugin

npm license template

A Vite plugin for developing and building Tampermonkey, Greasemonkey and Violentmonkey userscripts.

Features

  • 🔥 Vite HMR
  • 🔧 Configure Userscript header
  • 🎨 Inject CSS from imports and SFC components (Vue, Svelte)
  • 💨 All @grants in the header in dev mode
  • 📝 Only used @grants in the production build
  • 📦 Built-in types for Tampermonkey, Greasemonkey and Violentmonkey
  • 📄 Virtual module with script metadata
  • 🧵 Support Web Workers

Getting started

Requires Vite 8 and Node >=22.

pnpm add vite-userscript-plugin -D
import { defineConfig } from 'vite'
import userscript from 'vite-userscript-plugin'
import pkg from './package.json' with { type: 'json' }

export default defineConfig({
  plugins: [
    userscript({
      entry: 'src/index.ts',
      header: {
        name: pkg.name,
        version: pkg.version,
        match: [
          'https://example.com/',
          'https://example.org/'
        ]
      }
    })
  ]
})
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

Add types: Vite, one manager (tampermonkey, greasemonkey, or violentmonkey), and the virtual module.

src/vite-env.d.ts:

/// <reference types="vite/client" />
/// <reference types="vite-userscript-plugin/types/tampermonkey" />
/// <reference types="vite-userscript-plugin/virtual" />

Or tsconfig.json:

{
  "compilerOptions": {
    "types": [
      "vite/client",
      "vite-userscript-plugin/types/tampermonkey",
      "vite-userscript-plugin/virtual"
    ]
  }
}

Details: types/README.md.

vite prints /{fileName}.dev.user.js — install that URL once. HMR covers code and styles.

server.file: true skips HMR. The same vite watch-builds {fileName}.user.js (headed IIFE), {fileName}.js, and {fileName}.proxy.user.js (@require file:// to the IIFE). Install the printed /{fileName}.user.js URL (Firefox-safe). The Proxy line is for managers that can poll file://. See examples/serve-file.

[!IMPORTANT] Changing @match, @grant, or @name needs a reinstall.

vite build writes {fileName}.user.js to dist/. One-shot builds do not emit the proxy.

Multiple scripts

Pass an array of configs. Each item is one full script — no shared header.

See examples/multiple-entries.

Styles

CSS from imports and SFC <style> (Vue, Svelte) is collected and injected into the userscript.

import './style.css'

[!NOTE] Do not put userscript assets in public/ — those URLs hit the host site and 404. Import the file so Vite inlines it.

Web Workers

HMR serve rewrites import Worker from './w?worker' (and ?worker&inline) to a data-URI module worker that imports Vite's ?worker_file. The host page cannot load /src/w.ts?worker_file from localhost.

vite build and server.file keep Vite's worker emit. A plain ?worker is a separate file the match site cannot serve — use ?worker&inline. ?worker&url and ?sharedworker stay as Vite emits them.

See examples/web-worker.

HTML pages

index.html is a normal Vite app next to the userscript. vite serves it at /. vite build writes it to dist/ beside {fileName}.user.js.

The plugin sets build.assetsInlineLimit very high so userscript assets become data URLs. The HTML app’s assets will too, unless you set build.assetsInlineLimit yourself.

[!WARNING] Keep the page's <script> entries distinct from entry.

Script metadata: import virtual:vite-userscript-plugin (name, version, file).

See examples/sourcemap.

Production

vite build writes {fileName}.user.js and {fileName}.meta.js. index.html is written too, when present.

Minify is off (build.minify). Sourcemaps are inlined into .user.js when build.sourcemap is on.

See examples/sourcemap.

Options

userscript(config) or userscript([config, config, …]). Options are not shared across the array.

| Option | Default | Description | | --- | --- | --- | | entry | — | Userscript entry. Required. | | header | — | Metablock. Required: name, version, match. Relative icon / require / resource / supportURL / updateURL / downloadURL join homepage (homepageURL / website / source). Absolute http(s): URLs stay as-is. updateURL / downloadURL of none disable updates and are not joined. | | fileName | sanitized header.name | Output base name ({fileName}.user.js). | | server.open | false | Open the install target when Vite starts (true, 'user', 'proxy'). HMR: .dev.user.js. file: .user.js or .proxy.user.js. Vite opens one URL. | | server.prefix | 'server:' | Prefix for @name in serve mode. false disables it. | | server.file | false | Watch-build {fileName}.user.js + {fileName}.js + {fileName}.proxy.user.js. Install .user.js (or the proxy if file:// @require works). No HMR. | | headerAlign | 1 | Extra spaces after the longest @key. false — one space. | | generate | — | Rewrite the generated metablock. | | autoMetaUrls | false | Fill empty updateURL / downloadURL from homepage / homepageURL / website / source. | | metaFile | true | Emit {fileName}.meta.js. | | external | — | Keep these packages out of the bundle and load them via @require. Keys are specifiers (jquery, vue). A string value is the CDN URL (global name from the specifier). Pass { global, url } for $ / Vue. Install @types/… for tsc; do not install the runtime package. See examples/external-cdn. |

Everything else on header follows the manager metablock (@grant, @require, @connect, …).

userscript({
  entry: 'src/index.ts',
  header: {
    name: pkg.name,
    version: pkg.version,
    match: 'https://example.com/*',
  },
  external: {
    jquery: {
      global: '$',
      url: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js',
    },
  },
})

For types without bundling the package: add @types/jquery (not jquery) and a d.ts that references those types. The import type-checks; the plugin maps it to the CDN global. Full setup: examples/external-cdn.

In serve mode the header lists every grant. In production the plugin scans the bundle and writes only the grants in use. window.focus, window.close, and window.onurlchange are not auto-detected (they collide with DOM APIs) — list them in header.grant when you need them. grant: "none" disables GM APIs and is never mixed with the scan.

[!WARNING] Keep metaFile: true if you use autoMetaUrls. Otherwise @updateURL points at a file that is not emitted.

Examples

| Example | What it shows | | --- | --- | | basic | Vanilla + SCSS. | | react | JSX, CSS, React refresh. | | vue | SFC <style>, minify, sourcemap. | | svelte | SFC <style>. | | multiple-entries | Two scripts. | | sourcemap | Inline map, HTML page, virtual module. | | serve-file | server.file, install .user.js or the proxy from the printed URLs. | | external-cdn | @types/jquery only; runtime $ from a CDN @require. | | web-worker | ?worker&inline, data-URI bridge in HMR. |

FAQ

Scripts fail on Firefox because of CSP

[!WARNING] The host page can block Vite modules from localhost. Use a CSP-disable extension, or a browser profile without the site CSP.

https://github.com/Tampermonkey/tampermonkey/issues/952#issuecomment-638373937

HTTPS site, HTTP Vite — mixed content

[!WARNING] https://example.com will not load http://localhost:5173. Serve Vite over HTTPS: vite-plugin-mkcert before userscript(), or server.https.

public/ assets 404 on the target site

[!NOTE] Userscripts run on someone else’s origin. Import the file so Vite inlines it. public/ only works for the index.html app on the Vite origin.

@icon, @require, and @resource are different: the manager fetches them from the metablock URL, not from the match site. Write icon: 'greasify.svg' plus homepage (GitHub Pages, etc.) — the plugin joins them. Leave https://… as-is.

@run-at document-start feels late in dev

[!NOTE] Serve injects type="module" (async). Production is a synchronous IIFE unless you use top-level await. server.file uses that IIFE in dev too.

file:// @require is blocked

[!NOTE] Firefox extensions cannot read file://. Install the printed {fileName}.user.js URL instead of the proxy. Tampermonkey on Chromium must allow local file access for the proxy @require. Violentmonkey can poll the local IIFE after you install the proxy.

Migration from v1

| v1 | v2 | | --- | --- | | vite build --watch | vite (HMR) or vite + server.file | | esbuildTransformOptions | removed | | server.port | Vite server.port | | minify on by default | off; set build.minify | | *.proxy.user.js + file:// | server.file: true, or HMR .dev.user.js | | Vite 3–7 | Vite 8 | | scripts + shared header | userscript([config, config, …]) | | ScriptOptions | removed | | cssInject | removed; imported CSS always appends a <style> node | | align | headerAlign |

License

MIT © crashmax