Supported bundlers
Vite

Usage with Vite

Installation

Since Vite supports Rollup plugin Since Vite provides more features and flexibility, WyW-in-JS has a separate plugin for it @wyw-in-js/vite. Vite handles CSS by itself, you don't need a css plugin.

# npm
npm i -D @wyw-in-js/vite
# yarn
yarn add --dev @wyw-in-js/vite
# pnpm
pnpm add -D @wyw-in-js/vite
# bun
bun add -d @wyw-in-js/vite

Configuration

import { defineConfig } from 'vite';
import wyw from '@wyw-in-js/vite';
 
export default defineConfig(() => ({
  // ...
  plugins: [wyw()],
}));

import.meta.env during evaluation

WyW-in-JS evaluates a subset of your code at build time to extract styles. When that code relies on Vite's import.meta.env.*, the Vite plugin injects Vite env values into the evaluation context so the expressions behave like they do in Vite runtime.

The injected object is available as __wyw_import_meta_env and includes:

  • Vite's MODE, BASE_URL, DEV, PROD, SSR
  • variables loaded via loadEnv() respecting envPrefix (by default VITE_)

If you need to override values, you can provide your own overrideContext option and set __wyw_import_meta_env explicitly.

SSR dev FOUC (styles after hydration)

When using Vite SSR in dev mode (middlewareMode + ssrLoadModule), the server-rendered HTML may not include the generated WyW CSS. The page then renders unstyled until the client runtime loads the CSS after hydration (FOUC).

To avoid this, enable ssrDevCss to make the plugin:

  • collect generated CSS in memory,
  • expose it as a virtual stylesheet (/_wyw-in-js/ssr.css by default),
  • inject <link rel="stylesheet"> via transformIndexHtml().
export default defineConfig(() => ({
  // ...
  plugins: [wyw({ ssrDevCss: true }), react()],
}));

You can customize the URL via ssrDevCssPath:

plugins: [wyw({ ssrDevCss: true, ssrDevCssPath: '/_wyw-in-js/ssr.css' }), react()],

This is dev-only and does not change production builds.

Notes:

  • The served stylesheet is the raw WyW output (before Vite's CSS pipeline), so some edge cases like url(...) rewriting may behave differently.
  • The <link> tag is injected on each HTML transform; CSS HMR does not “re-inject” the link on the client without a page reload (which is fine for avoiding initial FOUC).

Keeping CSS comments

Stylis strips CSS comments by default. To preserve them (for example, /*rtl:ignore*/), pass keepComments:

export default defineConfig(() => ({
  // ...
  plugins: [wyw({ keepComments: true })],
}));

Vite also strips CSS comments during build minification by default. To keep them in the final output, disable CSS minification:

export default defineConfig(() => ({
  // ...
  build: { cssMinify: false },
  plugins: [wyw({ keepComments: true })],
}));

If you are using language features that requires a babel transform (such as typescript), ensure the proper babel presets or plugins are passed to wyw.

import { defineConfig } from 'vite';
import wyw from '@wyw-in-js/vite';
 
// example to support typescript syntax:
export default defineConfig(() => ({
  // ...
  plugins: [
    wyw({
      include: ['**/*.{ts,tsx}'],
      babelOptions: {
        presets: ['@babel/preset-typescript', '@babel/preset-react'],
      },
    }),
  ],
}));