@ngrok/mantle-vite-plugins
v1.0.0
Published
Vite plugins for @ngrok/mantle.
Keywords
Readme
@ngrok/mantle-vite-plugins
Vite plugins for apps built with @ngrok/mantle.
Requirements
- Node.js 24+
- Vite 5+
@ngrok/mantleinstalled in the same project
Installation
pnpm add -D -E @ngrok/mantle-vite-pluginsPlugins
mantleTwSourcePlugin
Scans your app's source files for @ngrok/mantle/* component imports and injects Tailwind CSS @source directives into your global CSS file for only those components — so Tailwind only scans the mantle components your app actually uses, reducing generated CSS bundle size with no manual maintenance.
When to use
Mantle ships source-all.css as the zero-configuration option — a single @source that covers every component in the package:
@import "@ngrok/mantle/mantle.css";
@import "@ngrok/mantle/source-all.css";Use mantleTwSourcePlugin instead when your app uses a subset of mantle components and you want the smallest possible CSS output. The plugin scans your source files, detects which components you import, and writes targeted @source directives for only those components.
| Approach | When to use |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| source-all.css | Your app uses most or all mantle components, or you want zero build tooling configuration. |
| mantleTwSourcePlugin | Your app uses a subset of mantle components and you want the smallest possible CSS output. |
If you are unsure, start with source-all.css.
Setup
1. Add the plugin to vite.config.ts:
import { mantleTwSourcePlugin } from "@ngrok/mantle-vite-plugins";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [mantleTwSourcePlugin()],
});2. In your global CSS, import mantle.css without source-all.css:
@import "tailwindcss";
@import "@ngrok/mantle/mantle.css";
/* do NOT add @import "@ngrok/mantle/source-all.css" */That is all. The plugin writes the correct @source lines into your CSS file before @tailwindcss/vite processes it.
Options
| Option | Type | Default | Description |
| ----------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| include | string[] | ["app"] | Directories to scan recursively for @ngrok/mantle/* imports. Paths are relative to the Vite project root. |
| cssFile | string | First of app/global.css, src/global.css, app/app.css, src/app.css that exists | Path to the CSS file to inject @source directives into. Relative to the Vite project root, or absolute. |
| allowlist | string[] | [] | Component names to always include regardless of scanner detection. Accepts PascalCase ("AlertDialog") or kebab-case ("alert-dialog"). Useful for runtime-only components. |
How it works
configResolved— After Vite resolves its configuration, the plugin locates@ngrok/mantle'sdist/directory by walking up the directory tree from the project root to findnode_modules/@ngrok/mantle(using the symlink path rather than the pnpm content-addressed realpath, so generated paths stay clean). In dev mode, it also seeds its known-component set from any existing@sourceblock so styles are present for all previously-visited routes immediately after a restart. It then walks the directories inincludescanning every.ts,.tsx,.js,.jsx,.mdx, and.mdfile for@ngrok/mantle/<name>import statements and writes a clearly-marked block of@sourcedirectives directly into the target CSS file on disk, immediately after the last@importline. The write is idempotent — the file is only touched when the content would actually change.resolveId— The plugin intercepts every@ngrok/mantle/<name>import that Vite actually resolves during the session. This is module-graph-aware and catches imports the directory scan misses: components imported from workspace packages outsideinclude, and transitive mantle-internal imports (e.g. a component that internally imports another mantle component). In dev mode, newly-seen components trigger a debounced CSS write. In production, the full set is written incloseBundle.closeBundle— At the end of a production build, the plugin writes the precise set of components from the directory scan,resolveIdintercepts, andallowlist— no prior-run knowledge is included, so removing a component from the app will shrink the CSS on the next build. SSR builds are skipped entirely: in SSR double-build setups (e.g. React Router), the server build resolves fewer components than the client build, and writing during it would overwrite the correct client-built set with a reduced one.configureServer— In development, the plugin also watches your source files via Vite's built-in file watcher. When a file change introduces a mantle import for a component not previously detected, the CSS file is updated in place and Tailwind's own watcher picks up the change automatically — no server restart required.
Why write to disk: @tailwindcss/vite reads CSS files from disk at dev-server startup to initialize its file-system scanner and set up watchers, before Vite's transform pipeline runs. Injecting @source via a transform hook works in production builds but is invisible to Tailwind's dev-mode scanner. Writing to disk ensures the directives are present when Tailwind first reads the file.
The generated block
The plugin writes a deterministic, human-readable block that is safe to commit:
@import "tailwindcss";
@import "@ngrok/mantle/mantle.css";
/* @ngrok/mantle-vite-plugins:source:start */
@source "../node_modules/@ngrok/mantle/dist/button.js";
@source "../node_modules/@ngrok/mantle/dist/button-*.js";
@source "../node_modules/@ngrok/mantle/dist/input.js";
@source "../node_modules/@ngrok/mantle/dist/input-*.js";
/* @ngrok/mantle-vite-plugins:source:end */To remove it, delete the lines between the source:start and source:end markers, or remove the plugin from your Vite config and it will be cleaned up on the next build.
TypeScript
Type declarations are included. No @types/* package is needed.
