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 2x to 5x faster than Babel
(performance).
This is a community project and is not affiliated with Meta. It tracks the official StyleX releases
(currently compatible with StyleX v0.19.0)and requires Node.js 20 or newer.
npm install --save-dev @stylexswc/unpluginThe Rust compiler (@stylexswc/rs-compiler) is installed automatically as a
dependency. Your application still needs the StyleX runtime:
npm install @stylexjs/stylexImport 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
// vite.config.ts
import StylexRsPlugin from '@stylexswc/unplugin/vite';
export default defineConfig({
plugins: [StylexRsPlugin({/* options */})],
});Rollup
// rollup.config.js
import StylexRsPlugin from '@stylexswc/unplugin/rollup';
export default {
plugins: [StylexRsPlugin({/* options */})],
};Webpack
// webpack.config.js
module.exports = {
/* ... */
plugins: [require('@stylexswc/unplugin/webpack')({/* options */})],
};Rspack
// rspack.config.js
module.exports = {
/* ... */
plugins: [require('@stylexswc/unplugin/rspack')({/* options */})],
};Nuxt
// nuxt.config.js
export default defineNuxtConfig({
modules: [['@stylexswc/unplugin/nuxt', {/* options */}]],
});This module works for both Nuxt 2 and Nuxt Vite
Vue CLI
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [require('@stylexswc/unplugin/webpack')({/* options */})],
},
};esbuild
// esbuild.config.js
import { build } from 'esbuild';
import StylexRsPlugin from '@stylexswc/unplugin/esbuild';
build({
plugins: [StylexRsPlugin()],
});- 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.
- 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.
- 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
includepattern. Patterns are matched against paths relative to the current working directory.
- Type:
string - Default:
'stylex.css' - Description: Name of the generated CSS file.
- Type:
UseLayersType - Default:
false - Description: Wraps the generated CSS in cascade layers for better style isolation.
- Type:
boolean - Default:
true - Description: Controls whether the generated CSS is extracted into a separate file.
- Type:
string[] - Default:
['js', 'jsx', 'ts', 'tsx', 'mjs', 'mts'] - Description: File extensions to process for StyleX transformations.
- Type:
CSSTransformer - Optional
- Description: Transforms the extracted StyleX CSS before it is emitted or
injected. Matches the
@stylexswc/webpack-pluginAPI. 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) undefinedwhen 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.
- 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
- When set to
Routing the StyleX output through a real CSS file has practical benefits:
- The generated CSS goes through the bundler's CSS pipeline (PostCSS, Lightning CSS, css-loader, and so on)
- Deterministic builds — no race conditions or hash instability from virtual modules
- All CSS follows the same processing rules and bundling strategy
- CSS can be code-split and optimized alongside other stylesheets
- The same approach works for Vite, webpack, Rspack, esbuild, Rollup, and Farm
How to use it:
- 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;- Import the CSS file in your entry point:
// src/main.ts
import './global.css';
import { App } from './App';- 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.
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).
// 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,
}),
],
});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__/**'],
},
});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.
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.
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.
Yes. The plugin participates in each bundler's standard transform pipeline, so style changes update through the dev server like any other module.
No. It is a community-maintained alternative to the official tooling and is not affiliated with or supported by Meta.
MIT — see LICENSE