JSPM

rspack-unocss-plugin

0.3.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 227
  • Score
    100M100P100Q78020F
  • License MIT

A native Rspack integration for UnoCSS that generates virtual UnoCSS styles and feeds them into Rspack's native CSS pipeline.

Package Exports

  • rspack-unocss-plugin
  • rspack-unocss-plugin/loader

Readme

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 1.5+ and Rspack 2.x.

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 directly and write it into Rspack's virtual module store.
  • 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, processAssets generation 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 Runs transformers and token extraction on modules in the build graph, skips query sub-requests and virtual CSS 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, layer/hash placeholders 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, placeholders, transformer rewriting, source filter, and per-module extraction 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.
  • Native CSS warning: Emits an infrastructure warning if experiments.css is not enabled and no explicit CSS handling is expected.
  • 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.
  • Placeholder-based generation: Replaces layer/hash placeholders in emitted CSS assets during processAssets, invalidating chunk hashes so watch mode re-emits changed CSS.
  • 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 via placeholders: Virtual CSS modules carry layer/hash placeholders that flow through Rspack's native CSS pipeline and are replaced with the real generated CSS during processAssets. The hash placeholder invalidates the chunk so watch mode re-emits changed CSS.
  • 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

  • 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.

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",
  },
  experiments: {
    css: true,
  },
  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({
  experiments: {
    css: true,
  },
  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

  • 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

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 test
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

The current test suite covers virtual CSS ID matching, layer/hash placeholders, transformer source rewriting, the source filter, and per-module token extraction.

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.

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 tests with real Rspack compilation fixtures.
  • Add broader coverage for Vue scoped styles, content filesystem, inline content, and layer outputs.
  • 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.