@ngrok/mantle-vite-plugins
v1.0.10
Published
Vite plugins for @ngrok/mantle.
Downloads
5,188
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.
mantleCodeBlockPlugins(options?)
Unified helper that returns plugin lists for both Vite and MDX integration surfaces — runtime tagged template transforms and rehype-based fenced code block highlighting.
import { mantleCodeBlockPlugins } from "@ngrok/mantle-vite-plugins";
import mdx from "@mdx-js/rollup";
import { defineConfig } from "vite";
const codeBlockPlugins = mantleCodeBlockPlugins();
export default defineConfig({
plugins: [
...codeBlockPlugins.vitePlugins,
mdx({
rehypePlugins: [...codeBlockPlugins.rehypePlugins],
}),
],
});Options
| Option | Type | Default | Description |
| --------- | --------- | ------- | ---------------------------------------------------------------------- |
| runtime | boolean | true | Enable runtime transforms for mantleCode("lang")`...` templates. |
| mdx | boolean | true | Enable MDX fenced code block highlighting via a rehype plugin. |
Return Value
| Field | Type | Description |
| --------------- | ---------------- | ------------------------------------------------- |
| vitePlugins | PluginOption[] | Vite plugins to spread into your plugins array. |
| rehypePlugins | Plugin[] | Rehype plugins to spread into your MDX pipeline. |
mantleCodeRehypePlugin
Rehype plugin that pre-renders MDX fenced code blocks with Shiki and attaches the resulting HTML to <pre> props. Use directly in a rehypePlugins array, or via mantleCodeBlockPlugins().
import { mantleCodeRehypePlugin } from "@ngrok/mantle-vite-plugins";
mdx({
rehypePlugins: [mantleCodeRehypePlugin],
});Supports metastring options on fenced code blocks:
```typescript showLineNumbers highlightLines={[1, 3]} title="example.ts"
const x = 1;
const y = 2;
const z = 3;
```mantleCodeVitePlugin
Vite plugin that transforms mantleCode("lang")`...` tagged template literals at build time into pre-rendered Shiki HTML objects. Included automatically when using mantleCodeBlockPlugins().
TypeScript
Type declarations are included. No @types/* package is needed.
Related Packages
@ngrok/mantle— UI component library (npm)@ngrok/mantle-server-syntax-highlighter— Server-side highlighting engine (npm)
