rspack-unocss-plugin
| Install | |
|---|---|
npm install rspack-unocss-plugin |
|
| Latest Version: | 0.4.0 |
| License: | MIT |
| Last Updated: | Sep 20, 2026 |
| Links: | GitHub · npm |
rspack-unocss-plugin
A native Rspack integration for UnoCSS that generates virtual UnoCSS styles and feeds them into Rspack's native CSS pipeline.
This package is designed for projects that want UnoCSS to work as a first-class Rspack plugin instead of going through the webpack compatibility integration. It provides uno.css virtual modules, layer-specific CSS entries, config loading, filesystem extraction, transformer execution, and automatic CSS rule integration for Rspack 2.2.6 and later within the 2.x release line.
Status: this project is currently a focused native-integration prototype. The core virtual CSS, extraction, transformer, layer, watch dependency, benchmark, and example workflows are implemented. Some advanced parity features from the official webpack integration are still being refined.
Why This Plugin Exists
UnoCSS already provides @unocss/webpack/rspack, but that integration is implemented through the webpack-oriented unplugin layer. In Rspack + Vue SFC projects, especially when native CSS handling is enabled, webpack-style assumptions can break down. A representative failure mode is:
ENOENT: no such file or directory, open '.../ExampleCounter.vue?vue&type=style&index=0&lang=css&scoped=...'
That path is not a real file on disk. It is a Vue SFC style sub-request generated by the loader pipeline. The official Rspack entry reuses webpack integration logic that may inspect module.resource and attempt filesystem reads. When the resource contains a query such as ?vue&type=style, treating it as a real path causes invalid reads and build/dev-server failures.
This plugin takes a Rspack-native approach instead:
- Resolve
uno.cssanduno:*layer requests to Rspack virtual modules. - Generate CSS through UnoCSS after source modules finish, then rebuild imported virtual CSS modules before Rspack seals the compilation.
- Let Rspack native CSS process the generated CSS instead of relying on webpack CSS assumptions.
- Avoid reading Vue style sub-requests as real files.
- Keep Vue, native CSS, scoped styles, CSS Modules, and extraction behavior on the standard Rspack pipeline.
The result is not intended to replace UnoCSS itself. UnoCSS still owns scanning, presets, rules, variants, shortcuts, transformers, layers, and CSS generation. Rspack owns bundling, native CSS parsing, CSS assets, and optional Lightning CSS minimization.
Core Value
- Native Rspack semantics: Uses
rspack.experiments.VirtualModulesPlugin,rspack.NormalModuleReplacementPlugin, and Rspack plugin hooks instead of relying on webpack-specific module-resource behavior. - Fixes query-resource pitfalls: The loader operates on the source Rspack passes in and skips requests carrying a query (such as
?vue&type=style), avoiding Vue SFC style sub-requestENOENTissues. - Works with native CSS: Sends generated UnoCSS output through Rspack's native CSS pipeline via
type: "css/auto". - Simple application usage: Users import
uno.cssfrom an entry file and register one Rspack plugin. - Layer-aware CSS output: Supports full CSS and layer-specific virtual modules such as
uno:utilities.css. - Ecosystem bridge: Gives Rspack users a more direct UnoCSS integration while preserving UnoCSS config and transformer APIs.
- Benchmarkable design: Includes a benchmark workspace and GitHub Actions workflow for comparing this plugin with
@unocss/webpack/rspack.
Features
Implemented Modules
| Module | Status | Core capabilities | Main files |
|---|---|---|---|
| Plugin entry | Implemented | Rspack plugin class, option resolution, virtual module setup, request replacement, loader/CSS rule injection, finishModules generation and native CSS module rebuilds |
src/index.ts |
| Context | Implemented | UnoCSS generator creation, config loading, per-module token caches, inline/filesystem content extraction, CSS generation | src/context.ts |
| Loader | Implemented | Transforms source modules, extracts tokens, supplies virtual CSS, and skips query sub-requests | src/loader.ts |
| Registry | Implemented | Connects the loader to the plugin's UnoCSS context via a per-plugin id | src/registry.ts |
| Virtual CSS | Implemented | uno.css, virtual:uno.css, uno:<layer>.css, query-tolerant matching, deterministic paths and layer selection |
src/virtual.ts |
| Transformers | Implemented | Runs UnoCSS source transformers in pre, default, and post order, rewrites source, combines maps, supports idFilter and skip comments |
src/transformers.ts |
| Options | Implemented | Public plugin options and resolved internal options | src/types.ts |
| Tests | Implemented | Virtual CSS matching, transformers, extraction, real compilations, CSS HMR, and cache coverage | test |
| Example app | Implemented | Vue + Rspack + native CSS + UnoCSS plugin usage | example |
| Benchmark | Implemented | Local-vs-official plugin comparison with generated Vue fixtures and CI summary output | Benchmark, .github/workflows/benchmark.yml |
Plugin Capabilities
- Virtual module replacement: Replaces static imports for
uno.css,virtual:uno.css,uno:preflights.css,uno:default.css,uno:shortcuts.css, anduno:utilities.csswith virtual CSS module paths. - Dynamic layer path creation: Handles layer requests matching
^(?:virtual:)?uno(?::(.+))?\.css(?:\?.*)?$, maps them to deterministic internal paths, and registers unknown layers as virtual modules on first sight. - Automatic CSS rule injection: Adds a fallback
{ test: /\.css$/, type: "css/auto" }rule when no existing CSS rule is detected, optionally routed throughbuiltin:lightningcss-loader. - Rspack 2 native CSS: Uses CSS module rules directly;
experiments.cssis no longer required. - Config loading: Loads
unocss.config.*oruno.config.*through@unocss/config, or accepts an inline UnoCSS config object. - Loader-based extraction: A Rspack loader scans modules as they pass through the build graph, with default extensions for Vue, Svelte, Astro, JSX, TSX, JavaScript, TypeScript, HTML, Markdown, and MDX, and per-module token caches for fine-grained watch rebuilds.
- Custom include/exclude: Supports string glob includes and RegExp includes/excludes for focused extraction.
- UnoCSS
content.filesystem: Includes filesystem patterns declared by UnoCSS config. - UnoCSS
content.inline: Extracts tokens from inline content entries, including async content functions. - Transformer pipeline: Runs configured UnoCSS transformers in
pre/default/postorder, rewriting module source and forwarding combined source maps. - CSS generation before sealing: Generates styles in
finishModulesand usescompilation.rebuildModuleto feed them through CSS loaders, native parsing, content hashing, and HMR in the same compilation. - Production minification: Uses
uno.generate(tokens, { minify })based on Rspack production mode. - Watch dependencies: Registers config files and
content.filesystemfiles as compilation dependencies and reloads config on change when watch mode is enabled.
Resolved Architecture Notes
- Module-graph extraction: Tokens are extracted by a Rspack loader as modules pass through the build graph, with per-module token caches. Watch rebuilds only re-run changed modules instead of rescanning the whole filesystem.
- Transformer source maps: UnoCSS transformers now rewrite the actual module source and their source maps are combined and forwarded to Rspack, so variant groups and directives are reflected at runtime with accurate maps.
- Generated CSS as module source: A pre-loader supplies CSS from the compiler's UnoCSS context. After token extraction,
finishModulesrebuilds modules whose generated CSS changed. Rspack sees the final styles before calculating CSS hashes, including thecss.centries in its HMR manifest. Content updates do not write virtual files or schedule a second watch compilation. - Dynamic layer registration: Any static
uno:<layer>.cssrequest is registered as a virtual module on first sight, so layers beyond the common set work without manual configuration. - Optional Lightning CSS: Set
lightningcss: true(or pass loader options) to route the fallback CSS rule throughbuiltin:lightningcss-loader.
Current Limitations
-
Native
.mjsand.cjsUnoCSS configs can remain cached by the upstream@unocss/config/ Jiti loader after edits. Useuno.config.ts/unocss.config.tsfor live config reloads, or restart the dev server after editing a native JavaScript config. -
Layer virtual modules must still be imported with static string literals. Fully dynamic specifiers such as
import(`uno:${name}.css`)whose layer name is only known at runtime cannot be resolved by any static bundler. -
PostCSS is intentionally not part of the default path. Native CSS, the Lightning CSS minimizer, and the optional
lightningcssloader cover common cases. Users who require PostCSS-specific behavior should configurepostcss-loaderexplicitly in their Rspack CSS pipeline.
Requirements and Migration
- Rspack 2.2.6+ within 2.x (
@rspack/core: ^2.2.6). Rspack 1.x is no longer supported. - Node.js 22.12+ for consumers. Workspace development requires 22.18+, 24.11+, or 26+ to run the current build tools; CI uses Node.js 24.
- UnoCSS packages are aligned at 66.10.5.
Remove experiments: { css: true } from Rspack configuration. Native CSS is configured through module.rules; the plugin still injects a fallback css/auto rule by default. Rspack's v2 migration guide documents this change.
Generated UnoCSS now passes through the CSS loader chain before native parsing. lightningcss and explicitly configured CSS loaders therefore process the generated rules, including prefixes, imports, and URLs. Relative CSS references resolve from the virtual CSS module directory returned by getVirtualPath(root).
Rspack 2.2's CSS HMR optimization compares CSS module content before assets are emitted. The plugin supplies generated CSS before that comparison so class changes refresh styles on the first rebuild. CSS-only entries such as entry: "uno.css" also work with inline content or a safelist and emit a stylesheet without a JavaScript bundle.
Installation
This repository is currently organized as a pnpm workspace. Inside this workspace, examples use the package through workspace:*.
pnpm add rspack-unocss-plugin unocss
Peer dependency:
pnpm add -D @rspack/core
For Vue projects, install the Rspack Vue loader as usual:
pnpm add -D rspack-vue-loader @rspack/cli
Add the UnoCSS presets you need, for example:
pnpm add -D @unocss/preset-wind4
Quick Start
1. Add the Plugin
import { defineConfig } from "@rspack/cli";
import { rspack } from "@rspack/core";
import { VueLoaderPlugin } from "rspack-vue-loader";
import { UnoCSSRspackNativePlugin } from "rspack-unocss-plugin";
export default defineConfig({
entry: {
index: "./src/main.ts",
},
module: {
rules: [
{
test: /\.vue$/,
loader: "rspack-vue-loader",
options: {
experimentalInlineMatchResource: true,
},
},
],
},
plugins: [
new UnoCSSRspackNativePlugin(),
new VueLoaderPlugin(),
new rspack.HtmlRspackPlugin({
template: "./index.html",
}),
],
});
2. Import Virtual CSS
import { createApp } from "vue";
import "uno.css";
import App from "./App.vue";
createApp(App).mount("#app");
3. Configure UnoCSS
import { defineConfig } from "unocss";
import presetWind4 from "@unocss/preset-wind4";
export default defineConfig({
presets: [presetWind4()],
});
4. Build or Start Dev Server
pnpm rspack build
pnpm rspack dev
In this repository, you can run the example with:
pnpm example:build
pnpm example:dev
Configuration
Options
import type { UserConfig } from "unocss";
import type { LightningcssLoaderOptions } from "@rspack/core";
export interface UnoCSSRspackNativePluginOptions {
configOrPath?: string | UserConfig;
defaults?: UserConfig;
root?: string;
include?: Array<string | RegExp>;
exclude?: Array<string | RegExp>;
virtualModuleId?: string;
autoCssRule?: boolean;
lightningcss?: boolean | LightningcssLoaderOptions;
watch?: boolean;
}
| Option | Default | Description |
|---|---|---|
configOrPath |
Auto-detected config | UnoCSS config object or config file path. |
defaults |
undefined |
Defaults passed to UnoCSS generator creation. |
root |
Rspack compiler context | Project root used for config resolution and filesystem scanning. |
include |
Default source extensions | Glob strings or regular expressions used to include source files. |
exclude |
CSS, node_modules, .git |
Strings or regular expressions used to exclude files from extraction. |
virtualModuleId |
"uno.css" |
Main virtual CSS module ID. |
autoCssRule |
true |
Injects a fallback Rspack native CSS rule if no CSS rule exists. |
lightningcss |
false |
Routes the fallback CSS rule through builtin:lightningcss-loader. |
watch |
true |
Registers config/filesystem dependencies and reloads config on change. |
Focused Extraction
Use include to limit scanning in large projects:
new UnoCSSRspackNativePlugin({
include: ["src/**/*.{vue,ts,tsx,html}"],
});
Regular expressions are matched against both absolute paths and paths relative to root:
new UnoCSSRspackNativePlugin({
include: [/src\/.*\.(vue|ts)$/],
});
Explicit Config Path
new UnoCSSRspackNativePlugin({
configOrPath: "./unocss.config.ts",
});
Inline UnoCSS Config
new UnoCSSRspackNativePlugin({
configOrPath: {
shortcuts: {
btn: "rounded px-4 py-2 font-medium",
},
},
});
Manual CSS Rule Control
By default, the plugin adds a fallback native CSS rule when no CSS rule is present:
{
test: /\.css$/,
type: "css/auto",
}
Disable this behavior if your project owns all CSS handling explicitly:
new UnoCSSRspackNativePlugin({
autoCssRule: false,
});
If autoCssRule is disabled, make sure generated virtual CSS can still be processed by Rspack:
export default defineConfig({
module: {
rules: [
{
test: /\.css$/,
type: "css/auto",
},
],
},
});
Lightning CSS and PostCSS
By default the plugin relies on Rspack native CSS plus LightningCssMinimizerRspackPlugin. To run the fallback CSS rule through builtin:lightningcss-loader for transform/prefix/minify, enable lightningcss:
new UnoCSSRspackNativePlugin({
lightningcss: true,
});
Pass an options object to forward loader options:
new UnoCSSRspackNativePlugin({
lightningcss: {
targets: ["last 2 versions", "> 0.2%", "not dead"],
},
});
PostCSS is never enabled by default. If you need PostCSS-specific behavior, disable autoCssRule and configure postcss-loader yourself:
new UnoCSSRspackNativePlugin({ autoCssRule: false });
// in module.rules
{
test: /\.css$/,
type: "css/auto",
use: ["postcss-loader"],
}
Layer Virtual Modules
The plugin supports UnoCSS layer entry points:
import "uno.css";
import "uno:preflights.css";
import "uno:default.css";
import "uno:shortcuts.css";
import "uno:utilities.css";
Supported request forms include:
import "uno.css";
import "virtual:uno.css";
import "uno:utilities.css";
import "virtual:uno:utilities.css";
Layer virtual modules must be imported with static string literals. Any static uno:<layer>.css specifier is registered on first sight, but a fully dynamic specifier whose layer name is only known at runtime cannot be resolved by a static bundler.
Feature Demonstration
Full Virtual CSS
<template>
<main class="min-h-screen bg-slate-950 p-8 text-slate-100">
<h1 class="text-4xl font-bold tracking-tight">Rspack + UnoCSS</h1>
</main>
</template>
With import "uno.css", the plugin extracts the class tokens, generates UnoCSS output, writes it to a virtual CSS module, and lets Rspack emit the final CSS asset.
Transformer Support
When configured in unocss.config.ts, UnoCSS transformers run in the loader and rewrite the module source (so the runtime markup matches the generated selectors), then tokens are extracted from the transformed code:
import { defineConfig, transformerVariantGroup } from "unocss";
import presetWind4 from "@unocss/preset-wind4";
export default defineConfig({
presets: [presetWind4()],
transformers: [transformerVariantGroup()],
});
Template usage:
<template>
<button class="hover:(text-red-500 underline)">Hover me</button>
</template>
Filesystem Content
UnoCSS content.filesystem patterns are included during extraction:
import { defineConfig } from "unocss";
export default defineConfig({
content: {
filesystem: ["content/**/*.md"],
},
});
Inline Content
UnoCSS content.inline entries are also extracted:
import { defineConfig } from "unocss";
export default defineConfig({
content: {
inline: [
'<div class="text-blue-500 font-bold"></div>',
async () => ({
code: '<div class="rounded-lg p-4"></div>',
id: "inline-fixture",
}),
],
},
});
Benchmarking
This repository includes a benchmark workspace that compares this plugin with the official @unocss/webpack/rspack integration.
Rspack UnoCSS Plugin Benchmark
These historical measurements predate the Rspack 2.2 migration. Run pnpm benchmark:ci for results with the current dependencies.
- Vue files: 20000
- Runs: 3
| plugin | avg | median | min | max |
|---|---|---|---|---|
| rspack-unocss-plugin | 25.016s | 24.164s | 23.989s | 26.893s |
| @unocss/webpack/rspack | 31.771s | 33.681s | 27.851s | 33.782s |
Average delta: -21.26% versus @unocss/webpack/rspack Median delta: -28.26% versus @unocss/webpack/rspack
pnpm benchmark:ci
Default benchmark settings:
- Generates 10,000 Vue files at runtime.
- Builds the local plugin project and official plugin project.
- Runs each build 3 times by default.
- Reports average, median, minimum, and maximum build time.
- Writes
Benchmark/results/benchmark-summary.mdandBenchmark/results/benchmark-results.json.
Use smaller values for local smoke tests:
BENCHMARK_VUE_COUNT=100 BENCHMARK_RUNS=1 pnpm benchmark:ci
GitHub Actions support is provided in .github/workflows/benchmark.yml. The workflow generates fixtures in CI instead of committing large generated files to the repository.
Benchmark results should be interpreted as end-to-end Rspack build numbers. Vue loader, Rspack module graph creation, filesystem I/O, JavaScript minification, CSS processing, and UnoCSS generation all contribute to the total time. Small percentage differences are expected because the UnoCSS integration is only one part of the full build pipeline.
Development
The workspace uses pnpm 10.34.5 through the root packageManager field. Node.js types track the Node.js 24 CI runtime.
Repository Layout
.
├── packages/rspack-unocss-plugin # Plugin source, package metadata, tests
├── example # Vue + Rspack example project
├── Benchmark # Local-vs-official benchmark projects and scripts
└── .github/workflows/benchmark.yml # CI benchmark workflow
Scripts
pnpm build
pnpm typecheck
pnpm test
pnpm examples:build
pnpm example:build
pnpm example:dev
pnpm benchmark:ci
Package-level scripts:
pnpm --filter rspack-unocss-plugin build
pnpm --filter rspack-unocss-plugin test
Running Tests
pnpm test
pnpm test runs unit tests, builds the package, and exercises the published ESM plugin and loader with real Rspack compilations. Integration tests cover layer deduplication, transformer output, filesystem and inline content, CSS-only entries, Lightning CSS, config-driven JavaScript changes, and CSS HMR with the default watcher, native watcher, and experimental new cache.
Design Notes
Native CSS Boundary
The recommended pipeline is:
source files -> UnoCSS generator -> virtual uno.css -> Rspack native CSS -> CSS asset
UnoCSS is responsible for:
- Source scanning and token extraction.
- Presets, rules, shortcuts, variants, and layers.
- UnoCSS transformers.
- CSS generation.
Rspack is responsible for:
- Module graph and bundling.
- Native CSS parsing and asset emission.
- Optional CSS transformation/minification through Lightning CSS.
This separation avoids forcing UnoCSS through PostCSS or webpack compatibility when Rspack can process generated CSS natively.
Vue SFC Compatibility
The native integration deliberately avoids reading resources with query strings as files. The loader operates on the source Rspack passes in and skips sub-requests that carry a query, so Vue SFC style sub-requests (which are loader-generated module requests, not disk paths) never trigger ENOENT, while class tokens are still extracted from the actual .vue source module.
Parallel Loaders
Rspack lets individual loaders opt into worker-thread execution via the per-loader parallel option. Token extraction in this plugin relies on an in-process context shared between the loader and the plugin instance, which a worker thread cannot reach. The plugin therefore pins its own loader to the main thread (parallel: false) and warns loudly if it ever finds itself without that context.
This is scoped to the UnoCSS loader only. Other loaders in your config (for example builtin:swc-loader or rspack-vue-loader) can still set parallel: true independently; doing so does not affect UnoCSS extraction.
Loader and Module Caching
The plugin sets use.cache: false for its loader because a per-loader cache hit would skip updates to the compiler's token and CSS state. Ordinary source modules still use Rspack's module cache and UnoCSS's token cache. Generated virtual CSS modules call cacheable(false) because their content depends on the compilation's token union; this allows Rspack's incremental code generation to observe updated styles. Other loaders can enable Rspack's per-loader cache independently.
Performance Expectations
The plugin can reduce overhead caused by webpack compatibility behavior and provide a cleaner Rspack-native path. However, in full application builds the total runtime includes many shared costs outside UnoCSS. Benchmarks should therefore focus on stability, parity, and regression detection rather than assuming large end-to-end speedups in every project.
In watch mode the plugin caches the generated CSS keyed by the union of extracted tokens (and the active config), so a rebuild that does not change which classes are present reuses the previous result instead of regenerating. This helps incremental rebuilds where edits do not alter the token set; it has no effect on a single production build.
Roadmap
- Expand integration coverage for additional frameworks and CSS dependency resolution.
- Add broader coverage for Vue scoped styles and filesystem content discovery.
- Explore resolver-hook based virtual module resolution as an alternative to replacement-plugin based request mapping.
Contributing
Contributions are welcome. A good contribution workflow is:
- Open an issue or discussion describing the Rspack, UnoCSS, or framework behavior you want to improve.
- Add or update a focused fixture, test, or example that reproduces the behavior.
- Keep changes aligned with the native Rspack design: virtual CSS, UnoCSS generator APIs, and Rspack native CSS processing.
- Run the package tests and relevant example or benchmark command before submitting a pull request.
Useful commands:
pnpm test
pnpm build
pnpm example:build
BENCHMARK_VUE_COUNT=100 BENCHMARK_RUNS=1 pnpm benchmark:ci
License
MIT License. See LICENSE for details.
Related Packages
High-performance Rspack plugin for Vue SFC compilation powered by Vize
High-performance native Vite plugin for Vue SFC compilation powered by Vize
Experimental Vue SFC integrations for Rollup, Rolldown, Webpack, esbuild, and Ba...
Version History
| Version | Released | License |
|---|---|---|
| 0.4.0 | MIT | |
| 0.3.0 | MIT | |
| 0.2.0 | MIT | |
| 0.1.0 | MIT |