rspack-unocss-plugin

A native Rspack integration for UnoCSS that generates virtual UnoCSS styles and feeds them into Rspack's native CSS pipeline.
756 1
Install
npm install rspack-unocss-plugin
Latest Version:0.4.0
License:MIT
Last Updated:Sep 20, 2026
Links: GitHub  ·  npm
Maintainer: raiden-yin

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.css and uno:* 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-request ENOENT issues.
  • Works with native CSS: Sends generated UnoCSS output through Rspack's native CSS pipeline via type: "css/auto".
  • Simple application usage: Users import uno.css from 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, and uno:utilities.css with 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 through builtin:lightningcss-loader.
  • Rspack 2 native CSS: Uses CSS module rules directly; experiments.css is no longer required.
  • Config loading: Loads unocss.config.* or uno.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/post order, rewriting module source and forwarding combined source maps.
  • CSS generation before sealing: Generates styles in finishModules and uses compilation.rebuildModule to 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.filesystem files 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, finishModules rebuilds modules whose generated CSS changed. Rspack sees the final styles before calculating CSS hashes, including the css.c entries in its HMR manifest. Content updates do not write virtual files or schedule a second watch compilation.
  • Dynamic layer registration: Any static uno:<layer>.css request 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 through builtin:lightningcss-loader.

Current Limitations

  • Native .mjs and .cjs UnoCSS configs can remain cached by the upstream @unocss/config / Jiti loader after edits. Use uno.config.ts / unocss.config.ts for 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 lightningcss loader cover common cases. Users who require PostCSS-specific behavior should configure postcss-loader explicitly 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.md and Benchmark/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:

  1. Open an issue or discussion describing the Rspack, UnoCSS, or framework behavior you want to improve.
  2. Add or update a focused fixture, test, or example that reproduces the behavior.
  3. Keep changes aligned with the native Rspack design: virtual CSS, UnoCSS generator APIs, and Rspack native CSS processing.
  4. 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

@vizejs/rspack-plugin

High-performance Rspack plugin for Vue SFC compilation powered by Vize

60,208 908
@vizejs/vite-plugin

High-performance native Vite plugin for Vue SFC compilation powered by Vize

113,453 909
@vizejs/unplugin

Experimental Vue SFC integrations for Rollup, Rolldown, Webpack, esbuild, and Ba...

89,545 909
@morgul/snowpack-plugin-vue2

A snowpack vue2 compiler based on `@snowpack/plugin-vue`.

9,507 6

Version History

Version Released License
0.4.0 MIT
0.3.0 MIT
0.2.0 MIT
0.1.0 MIT