npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stylexswc/unplugin

v0.19.0

Published

Universal StyleX plugin for Vite, webpack, Rspack, Rollup, esbuild, Farm, Rsbuild, Nuxt, and Astro, powered by a Rust NAPI-RS/SWC compiler.

Readme

@stylexswc/unplugin

Universal StyleX plugin for Vite, webpack, Rspack, Rollup, esbuild, Farm, Rsbuild, Nuxt, and Astro — powered by a Rust compiler (NAPI-RS + SWC). Part of the StyleX SWC Plugin workspace.

Built on unplugin, this package gives every major bundler the same StyleX integration: it compiles your StyleX code with @stylexswc/rs-compiler, a Rust implementation of the StyleX transform, and extracts the generated CSS. Your StyleX code stays exactly the same — only the build step changes, with per-file transforms 5x to 10x faster than Babel (performance).

This is a community project and is not affiliated with Meta. It tracks the official StyleX releases

and requires Node.js 20 or newer.

Installation

npm install --save-dev @stylexswc/unplugin

The Rust compiler (@stylexswc/rs-compiler) is installed automatically as a dependency. Your application still needs the StyleX runtime:

npm install @stylexjs/stylex

Usage

Import the entry point matching your build tool and add it to the plugin list. A working example for each bundler lives in the apps/{pluginName}-unplugin-example folders.

// vite.config.ts
import StylexRsPlugin from '@stylexswc/unplugin/vite';

export default defineConfig({
  plugins: [StylexRsPlugin({/* options */})],
});

// rollup.config.js
import StylexRsPlugin from '@stylexswc/unplugin/rollup';

export default {
  plugins: [StylexRsPlugin({/* options */})],
};

// webpack.config.js
module.exports = {
  /* ... */
  plugins: [require('@stylexswc/unplugin/webpack')({/* options */})],
};

// rspack.config.js
module.exports = {
  /* ... */
  plugins: [require('@stylexswc/unplugin/rspack')({/* options */})],
};

// nuxt.config.js
export default defineNuxtConfig({
  modules: [['@stylexswc/unplugin/nuxt', {/* options */}]],
});

This module works for both Nuxt 2 and Nuxt Vite

// vue.config.js
module.exports = {
  configureWebpack: {
    plugins: [require('@stylexswc/unplugin/webpack')({/* options */})],
  },
};

// esbuild.config.js
import { build } from 'esbuild';
import StylexRsPlugin from '@stylexswc/unplugin/esbuild';

build({
  plugins: [StylexRsPlugin()],
});

Plugin Options

Basic Options

rsOptions

  • Type: Partial<StyleXOptions>
  • Optional
  • Description: StyleX compiler options passed to @stylexswc/rs-compiler. For the standard options, see the official StyleX documentation.

[!NOTE] The include and exclude options are exclusive to the Rust compiler and are not available in the official StyleX Babel plugin.

rsOptions.include
  • Type: (string | RegExp)[]
  • Optional
  • Description: Glob patterns or regular expressions selecting the files to transform. When specified, only files matching at least one pattern are transformed. Patterns are matched against paths relative to the current working directory.
rsOptions.exclude
  • Type: (string | RegExp)[]
  • Optional
  • Description: Glob patterns or regular expressions excluding files from the transform. A file matching any exclude pattern is skipped even if it matches an include pattern. Patterns are matched against paths relative to the current working directory.

fileName

  • Type: string
  • Default: 'stylex.css'
  • Description: Name of the generated CSS file.

useCSSLayers

  • Type: UseLayersType
  • Default: false
  • Description: Wraps the generated CSS in cascade layers for better style isolation.

extractCSS

  • Type: boolean
  • Default: true
  • Description: Controls whether the generated CSS is extracted into a separate file.

pageExtensions

  • Type: string[]
  • Default: ['js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx', 'mts', 'cts']
  • Description: File extensions to process for StyleX transformations. The default holds every extension that the compiler transforms. A shorter list leaves StyleX in the other extensions uncompiled.

transformCss

  • Type: CSSTransformer
  • Optional
  • Description: Transforms the extracted StyleX CSS before it is emitted or injected. Matches the @stylexswc/webpack-plugin API. Use it to run the generated CSS through PostCSS, Lightning CSS, a minifier, or any custom post-processing step.
type CSSTransformer = (
  css: string,
  filePath: string | undefined
) => string | Buffer | Promise<string | Buffer>;
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

StylexRsPlugin({
  async transformCss(css) {
    const result = await postcss([autoprefixer]).process(css, {
      from: undefined,
    });
    return result.css;
  },
});

The filePath argument identifies the CSS destination and is bundler-specific:

  • webpack/rspack/rollup injection: the output asset name (e.g. app.css)
  • esbuild disk writes: the absolute path of the written file
  • Vite placeholder replacement: the id of the CSS module being loaded
  • generated assets: the configured fileName, with [hash] left unresolved (the hash is computed from the transformed CSS, so it cannot be known earlier)
  • undefined when no destination is known

[!NOTE] Buffer results are decoded as UTF-8. Results are memoized per filePath while the input CSS is unchanged, so the callback must be a pure function of its arguments — the same input may be served from cache instead of invoking the callback again.

useCssPlaceholder

  • Type: boolean | string
  • Default: false
  • Description: Injects the generated CSS into an existing CSS file via a placeholder marker.
    • When set to true, the plugin looks for the default @stylex; marker
    • When set to a string, the plugin uses that string as the custom marker

Routing the StyleX output through a real CSS file has practical benefits:

  • Your stylesheet keeps going through the bundler's CSS pipeline (PostCSS, Lightning CSS, css-loader, and so on), and the StyleX rules land inside it at the marker
  • Deterministic builds — no race conditions or hash instability from virtual modules
  • One stylesheet, so the rules follow the same bundling strategy as the rest of your CSS
  • CSS can be code-split and optimized alongside other stylesheets
  • The same approach works for Vite, webpack, Rspack, esbuild, and Rollup

How to use it:

  1. Create a CSS file with a marker (e.g. global.css):
/* global.css */
:root {
  --brand-color: #663399;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
}

@stylex;
  1. Import the CSS file in your entry point:
// src/main.ts
import './global.css';
import { App } from './App';
  1. Configure the plugin with useCssPlaceholder:
// vite.config.ts (or webpack.config.js, rspack.config.js, etc.)
import StylexRsPlugin from '@stylexswc/unplugin/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    StylexRsPlugin({
      useCssPlaceholder: true, // Uses default '@stylex;' marker
      useCSSLayers: true,
    }),
  ],
});

Or with a custom marker string:

/* global.css */
:root {
  --brand-color: #663399;
}

/* INJECT_STYLEX_HERE */
StylexRsPlugin({
  useCssPlaceholder: '/* INJECT_STYLEX_HERE */',
  useCSSLayers: true,
});

The plugin replaces the marker with the generated StyleX CSS during the build.

[!NOTE] When useCssPlaceholder is enabled, the plugin no longer injects CSS automatically into HTML or emits a separate stylex.css file. The CSS goes into your specified CSS file instead. No bundler emits a standalone stylesheet in this mode any more, since nothing would link it: under Vite, where the plugin can tell the marker was part of the build, a missing injection target fails the build; every other bundler reports a warning.

A plugin that removes CSS assets during the bundle — inlining them into JS, for instance — takes that target away, so combining one with useCssPlaceholder under Vite fails the build. Set onMissingCssPlaceholder to 'warn' when that is expected.

If a stylesheet survives but something strips the marker out of it, the rules are appended to the end of a stylesheet instead of landing at the marker, and the plugin warns. The styles still ship; only their position is lost.

fileName does nothing in this mode: no standalone stylesheet is emitted, and your own keeps the name the bundler gives it.

[!IMPORTANT] Stylesheet names change with the StyleX rules

The rules go in after the bundler has hashed the stylesheet, so the plugin renames it to match the new contents. Without this, a cache can serve old CSS to new JavaScript. Upgrading renames every placeholder stylesheet once.

  • Plugins that read CSS file names from the bundle must run after this one. Documents and Vite manifests are updated in place; data outside the bundle, such as a service-worker precache list, is not.
  • Under Vite, set build.cssCodeSplit: false. The renaming does not apply to the default true.
  • The plugin does not rename when the name template has no hash (assetFileNames, entryNames), or when assetFileNames is a function.
  • webpack and Rspack rename the asset themselves. Keep optimization.realContentHash on, which is the production default.

[!IMPORTANT] What the CSS pipeline does and does not see

Your stylesheet goes through the pipeline in full. The StyleX rules do not: in a build they are spliced in at the marker once the bundle is assembled, because modules behind a dynamic import are transformed long after the stylesheet is loaded, and there is no earlier point where the rule set is complete.

So the rules are minified along with everything else, but per-module steps such as PostCSS or Lightning CSS transpilation never run over them. Use transformCss to run your own processing over the rules. In development the rules are put into the stylesheet before the pipeline runs, so there it sees everything. The bundled mode below uses Vite's built-in CSS preprocessing, not other plugins' CSS transform hooks.

[!NOTE] Vite's bundled dev server (experimental.bundledDev)

The plugin serves the marker stylesheet with current rules and Vite's built-in CSS preprocessing (PostCSS, Lightning CSS and @import). Edits to StyleX modules, shared variables and themes, the stylesheet and its imports refetch the stylesheet over HMR. Rolldown decides whether the module edit itself is a hot update or a reload.

Limits in this mode:

  • The marker must be in a plain stylesheet that JavaScript imports. A CSS module or a build entry gets only the rules known at bundle time.
  • Restart after adding or removing a marker or its stylesheet import.
  • Other Vite plugins' CSS transform hooks do not run over this stylesheet.
  • url() references are not rewritten or served. Keep those assets in public/ or in a stylesheet without the marker.

[!WARNING] Farm does not support useCssPlaceholder yet. Its plugin adapter never receives the bundle hook that replaces the marker, so the marker would stay in the stylesheet. The plugin warns when the two are combined.

[!IMPORTANT] Migration from useViteCssPipeline

The useViteCssPipeline option (which used virtual CSS modules) has been replaced by useCssPlaceholder. The new approach uses real CSS files instead of virtual modules, which provides better compatibility across all bundlers, no race conditions or timing issues, and deterministic builds with stable hashes. To migrate, create a CSS file with a marker and set useCssPlaceholder: true (or use a custom marker string).

onMissingCssPlaceholder

  • Type: 'error' | 'warn' | 'ignore'
  • Default: 'error'
  • Description: How to report a build where useCssPlaceholder is enabled but no stylesheet in the output can carry the StyleX rules.

Placeholder mode never links a standalone stylesheet, so the default fails the build rather than let the styles go missing at runtime. Only Vite can prove the marker was part of the build, so 'error' is fatal there and reported as a warning everywhere else.

StylexRsPlugin({
  useCssPlaceholder: true,
  // Another plugin inlines the CSS into JS, so there is no CSS asset left to
  // inject into by the time the rules are ready.
  onMissingCssPlaceholder: 'warn',
});

'ignore' silences the report entirely. SSR builds are never reported: they have no stylesheet of their own by design.

Example Configuration

// vite.config.ts
import StylexRsPlugin from '@stylexswc/unplugin/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    StylexRsPlugin({
      rsOptions: {
        dev: process.env.NODE_ENV !== 'production',
        include: ['src/**/*.{ts,tsx}', 'components/**/*.{ts,tsx}'],
        exclude: ['**/*.test.*', '**/*.stories.*', '**/__tests__/**'],
      },
      useCSSLayers: true,
      useCssPlaceholder: true,
    }),
  ],
});

Path Filtering Examples

Include only specific directories:

StylexRsPlugin({
  rsOptions: {
    include: ['src/**/*.tsx', 'app/**/*.tsx'],
  },
});

Exclude test and build files:

StylexRsPlugin({
  rsOptions: {
    exclude: ['**/*.test.*', '**/*.spec.*', '**/dist/**'],
  },
});

Exclude node_modules except specific packages (negative lookahead):

StylexRsPlugin({
  rsOptions: {
    exclude: [/node_modules(?!\/@stylexjs)/],
  },
});

Transform only specific packages from node_modules:

StylexRsPlugin({
  rsOptions: {
    include: [
      'src/**/*.{ts,tsx}',
      'node_modules/@stylexjs/open-props/**/*.js',
      'node_modules/@my-org/design-system/**/*.js',
    ],
    exclude: ['**/*.test.*'],
  },
});

Combined include and exclude (exclude takes precedence):

StylexRsPlugin({
  rsOptions: {
    include: ['src/**/*.{ts,tsx}'],
    exclude: ['**/__tests__/**', '**/__mocks__/**'],
  },
});

FAQ

Should I use this package or the bundler-specific plugin?

If you are on Vite, esbuild, Farm, Rsbuild, Nuxt, or Astro, this is the package to use. For webpack and Rspack, the dedicated @stylexswc/webpack-plugin and @stylexswc/rspack-plugin packages offer a few deeper integrations (loader ordering, cache groups); this plugin covers the common cases with one consistent API. For Next.js, use @stylexswc/nextjs-plugin.

Do I still need @stylexjs/babel-plugin?

No. This plugin replaces the Babel plugin in your build. You only keep @stylexjs/stylex as your app's runtime dependency, and your stylex.create / stylex.props code does not change.

How do I run the generated CSS through PostCSS or Tailwind pipelines?

Either pass a transformCss function, or enable useCssPlaceholder so the generated CSS lands in a real CSS file and flows through your bundler's normal CSS pipeline.

Does hot module replacement work?

Yes. The plugin participates in each bundler's standard transform pipeline, so style changes update through the dev server like any other module.

Is this an official StyleX package?

No. It is a community-maintained alternative to the official tooling and is not affiliated with or supported by Meta.

Documentation

License

MIT — see LICENSE