Quickly preview React components with Vite.
- 🍃 Lightweight: Thin layer on top of Vite functionality.
- 🛠️ Minimal configuration: Add one plugin, and you’re ready.
- ⚡️ Hot-reload: Instant preview updates.
- 🚀 Non-intrusive: Doesn’t interfere with your production bundle.
- 🔌 Extensible: Wrap previews with your React components for theming or visual helpers.
- 🤝 Compatible with StoryBook
- ✅ No Telemetry
# npm
npm install @ethereal-ui/vite-plugin-react-preview
# yarn
yarn add @ethereal-ui/vite-plugin-react-preview
# pnpm
pnpm add @ethereal-ui/vite-plugin-react-preview
Once you install the plugin, add it to your Vite configuration:
import { defineConfig } from 'vite';
import reactPreview from '@ethereal-ui/vite-plugin-react-preview';
export default defineConfig({
// ...
plugins: [reactPreview()],
});
Create a file with the extension .preview.tsx
(or .preview.jsx
) in your
source directory. The plugin will treat each export containing a valid React
component type as a view to visualize.
Run Vite and open a browser using the route /_preview
. Your preview file will
be listed there; click it to view it. The preview uses Vite’s hot reload,
keeping it up-to-date as you change the code.
[!TIP] Combine this plugin with VSCode built-in Simple Browser -or a similar extension- to get a side-by-side instant preview of your React component.
In most cases, the preview files are compatible with the Storybook CSFv2 format, so you can include them as another story. The plugin will ignore any exports that are not React component types, allowing you to use CSFv3 objects side-by-side with the exported previews.
The plugin will look for files matching **/*.preview.{jsx,tsx}
by default, but
you can change that with the include
option:
export default defineConfig({
plugins: [
reactPreview({
include: '**/*.stories.tsx',
}),
],
});
[!NOTE] While it’s possible to include any file exporting a valid React component, using
**/*.tsx
is not recommended. A project may contain many React components; not all will render correctly without the proper setup.If you like to embed the preview alongside your component code, see the Previews in the same component file section.
The include
option supports string arrays and excludes patterns (the plugin
uses Vite glob imports):
export default defineConfig({
plugins: [
reactPreview({
include: ['./dir/*.js', '!**/bar.js'],
}),
],
});
The plugin hooks into the dev server’s /_preview
route, but you can change
that with the route
option:
export default defineConfig({
plugins: [
reactPreview({
route: '/other', // respond to http://localhost:<port>/other
}),
],
});
While this plugin provides minimal functionality for previewing React components, you can extend it and customize how components are obtained and rendered from a module.
The plugin supports an option called viewResolverFactory
. This option receives
an object that imitates the JavaScript import
statement. The from
field
indicates the module to import, and the import
field indicates the name to
import:
export default defineConfig({
plugins: [
reactPreview({
viewResolverFactory: {
import: 'myViewResolverFactory',
from: './src/myViewResolverFactory',
},
}),
],
});
A viewResolverFactory
is a function that receives the loaded preview module
and returns an object implementing the ViewResolver
interface. To parse the
previous sentence, we'll need to explain some of the terms used by the plugin
implementation.
A preview module exports views. A View
is an object with a name, title, and
the React component to render.
type View = Readonly<{
name: string;
title: string;
component: ComponentType<ViewComponentProps>;
}>;
A ViewResolver
translates a module (an unknown object loaded by a dynamic
import) into instances of View
:
type ViewResolver = {
readonly views: readonly View[];
findView(viewName?: string): View | undefined;
};
A ViewResolverFactory
is a function that receives the loaded module and
returns a ViewResolver
:
type ViewResolverFactory = {
(loadedModule: unknown): ViewResolver;
};
If you like to wrap the views with your component, you can create a custom
ViewResolverFactory
that uses the default factory included in the plugin:
import {
defaultViewResolverFactory,
type ViewResolverFactory,
type ViewWrapperProps,
} from '@ethereal-ui/vite-plugin-react-preview/viewer';
const MyViewWrapper = ({ viewTitle, children }: ViewWrapperProps) => (
<div>
<h1>{viewTitle}</h1>
<div>{children}</div>
</div>
);
export const myViewResolverFactory: ViewResolverFactory = loadedModule =>
defaultViewResolverFactory(loadedModule, { viewWrapper: MyViewWrapper });
Then, set up that ViewResolverFactory
in the plugin options:
export default defineConfig({
plugins: [
reactPreview({
viewResolverFactory: {
import: 'myViewResolverFactory',
from: './src/myViewResolverFactory',
},
}),
],
});
A complex ViewResolverFactory
implementation can take the loaded module and
create a React component based on its exported value (like StoryBook's CSFv3
does):
import type { ViewResolverFactory } from '@ethereal-ui/vite-plugin-react-preview/viewer';
export const myViewResolverFactory: ViewResolverFactory = loadedModule => {
// This is an example; these functions don't exist in the plugin
const views = transformCSFObjectsIntoViews(loadedModule);
const findView = createFindViewImpl(views);
return {
views,
findView,
};
};
Writing the preview alongside your component code might be handy during development:
export const MyComponent = props => <MyComponentContents />;
export const preview = () => {
const props = setUpMyComponentPreview();
return <MyComponent {...props} />;
};
[!NOTE] The main reason why this is not the plugin default is that while tree-shaking can ignore unused exports, the setup code may refer to problematic dependencies that break your modularization layers or cause cycles.
However, this may be useful for a quick-and-dirty component playground.
To have previews alongside your component code, change the default include
plugin option to include every React component. Also, add a custom view resolver
factory that only includes the preview
export.
Plugin Configuration (vite.config.ts
)
import { defineConfig } from 'vitest/config';
import reactPreview from '@ethereal-ui/vite-plugin-react-preview';
export default defineConfig({
plugins: [
reactPreview({
include: '**/*.tsx',
viewResolverFactory: {
import: 'viewResolverFactory',
from: './src/viewResolverFactory',
},
}),
],
});
Custom view resolver factory (./src/viewResolverFactory.ts
):
import {
defaultViewResolverFactory,
type ViewResolverFactory,
} from '@ethereal-ui/vite-plugin-react-preview/viewer';
export const viewResolverFactory: ViewResolverFactory = loadedModule =>
defaultViewResolverFactory(loadedModule, {
viewFilter: name => name === 'preview',
});