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-plugin-html-entry

v0.1.0

Published

Import an .html file with ?html to get its built URL - Vite registers it as a real build entry (added to rollupOptions.input automatically) so its <link> and <script> tags are processed like any other page, not copied as a static asset. Useful for multi-p

Readme

vite-plugin-html-entry

npm version npm downloads license

A Vite plugin that lets you import an .html file with a ?html suffix and get back its built URL - without listing that file in build.rollupOptions.input. The file is registered as a real Vite/Rollup build entry, so its <link> and <script> tags are processed and rewritten exactly like any other page - not just copied as a static asset.

import checkoutUrl from './checkout/index.html?html'

document.querySelector('iframe').src = checkoutUrl

Vite's bundler only knows about HTML files it's told about upfront via build.rollupOptions.input. This plugin discovers ?html imports in your source and wires them in automatically, so you can add a new page - and get a URL for it - without touching vite.config.ts.

It also works during vite dev: the import resolves to the dev server URL for that page, so you get HMR on it like any other page.

Use cases

  • Extra HTML entry points for a multi-page app (MPA) - add a page by importing it, not by editing vite.config.ts.
  • Embedding a page in an <iframe> (checkout flow, widget, live preview) that isn't part of your app's normal routes.
  • Standalone pages outside your SPA's router - a changelog, a print view, a static legal page - that still need to go through Vite's build pipeline.
  • Browser extensions (Manifest V3 in particular), where you often need to open a small HTML page - a permission prompt, an OAuth landing page, an ad-hoc popup - that isn't one of the extension's declared entry points (side panel, action popup, options page). Point toUrl at your platform's own URL resolver (see Options) and the import gives you a ready-to-use URL for chrome.windows.create, browser.windows.create, or whatever your runtime expects.

Install

npm install -D vite-plugin-html-entry

Usage

// vite.config.ts
import { defineConfig } from 'vite'
import { htmlEntry } from 'vite-plugin-html-entry'

export default defineConfig({
	plugins: [htmlEntry()],
})
// anywhere in your source
import changelogUrl from './changelog/index.html?html'

window.open(changelogUrl, '_blank')

changelog/index.html is picked up automatically (see How it works) and built as its own entry, with its own JS/CSS output - even though it's never listed in vite.config.ts and isn't linked from anywhere else in the app. By default, changelogUrl resolves to the root-absolute URL path of the built file (e.g. "/changelog/index.html") - see Options to resolve it differently.

TypeScript

Without a type declaration, tsc won't know what import x from './page.html?html' resolves to. Add the plugin's client types to your tsconfig.json:

{
	"compilerOptions": {
		"types": ["vite-plugin-html-entry/client"]
	}
}

Options

htmlEntry(options?: {
	/**
	 * Turns the built HTML path (relative to Vite's root, e.g.
	 * "src/changelog/index.html") into the expression the import resolves to.
	 * Defaults to the root-absolute URL path (e.g. "/src/changelog/index.html").
	 */
	toUrl?: (relativePath: string) => string

	/** Extra directory names to skip while scanning for `?html` imports. */
	skipDirs?: string[]
})

For a runtime that resolves its own asset URLs - a browser extension, for instance - point toUrl at that API instead of the default absolute path:

htmlEntry({ toUrl: (relativePath) => `myRuntime.resolveAssetUrl(${JSON.stringify(relativePath)})` })

How it works

Rollup/Vite decide which files are build entries before any module resolution happens, so a plugin can't add one lazily from inside resolveId/load the way it can for most virtual modules. To get around that, this plugin's config hook walks your source tree once at build startup, looking for '...html?html' string literals, and adds every HTML file it finds to build.rollupOptions.input. From there, resolveId/load just resolve the import to a virtual module exporting the emitted file's URL.

This means:

  • The import specifier must be a plain relative string literal ('./page.html?html'), not a variable or template expression - it has to be discoverable by scanning source text.
  • Adding a new ?html import requires restarting the dev server or rebuilding, since the scan only runs once at startup.

FAQ

How do I add an HTML file as a Vite build entry without editing vite.config.ts? Install this plugin, add htmlEntry() to plugins, and import the file with a ?html suffix (import url from './page.html?html'). The plugin finds the import and adds the file to build.rollupOptions.input for you.

How do I get a URL for an HTML page in Vite, for use in an <iframe>, window.open, or a link? That's exactly what the default import resolves to - a root-absolute URL path to the built (or dev-server-served) HTML file, ready to assign to iframe.src, pass to window.open, or use as an href.

How do I open a browser extension page (Manifest V3) that isn't declared in manifest.json? Import the page's .html file with ?html and pass a toUrl option that returns your runtime's own URL-resolving call, e.g. toUrl: (p) => `chrome.runtime.getURL(${JSON.stringify(p)})` . See Options.

Does this work with vite dev and HMR? Yes - during dev, the import resolves to the dev server URL for that page, and the page gets HMR like any other Vite entry.

How is this different from just listing the file in build.rollupOptions.input directly? rollupOptions.input requires knowing every entry page upfront and wiring its output path back into your code by hand. This plugin discovers the entries from your ?html imports and gives you the built URL directly at the import site, so adding a page is a one-line change in your source, not a config edit.

License

MIT