Vue-style SFCs, built for React. Co-locate script, template, and styles in a single .rsfc file with full TypeScript and CSS Modules support.
<script setup lang="ts">
import { useState } from 'react'
const [count, setCount] = useState(0)
const double = count * 2
</script>
<template>
<div className={styles.card}>
<p>{count} × 2 = {double}</p>
<button className={styles.btn} onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
</template>
<style module>
.card { padding: 2rem; border-radius: 8px; background: #1b211d; }
.btn { background: #42b883; color: #003823; border: none; cursor: pointer; }
</style>| Package | Description |
|---|---|
@g-casau/rsfc-vite-plugin |
Vite plugin |
@g-casau/rsfc-webpack-loader |
Webpack / Next.js loader |
@g-casau/rsfc-core |
Parser + generator — zero runtime deps |
@g-casau/rsfc-cli |
CLI (rsfc compile, rsfc parse) |
npm install -D @g-casau/rsfc-vite-plugin @vitejs/plugin-react// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import rsfc from '@g-casau/rsfc-vite-plugin'
export default defineConfig({ plugins: [rsfc(), react()] })npm install -D @g-casau/rsfc-webpack-loader// next.config.ts
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.rsfc$/,
use: [
{ loader: 'babel-loader', options: { presets: [['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'] } },
{ loader: '@g-casau/rsfc-webpack-loader' },
],
})
return config
},
}
export default nextConfigAdd a declaration file so TypeScript recognises .rsfc imports:
// rsfc.d.ts
declare module '*.rsfc' {
import type { FC } from 'react'
const Component: FC
export default Component
}| Block | Description |
|---|---|
<script setup lang="ts"> |
Shorthand: imports hoisted, code runs in component scope, default export generated automatically |
<script lang="ts"> |
Full control — write and export the component function yourself |
<template> |
JSX returned by the component. styles is in scope automatically when a <style module> block is present |
<style module> |
CSS Modules — class names are hashed, styles.* injected into scope |
<style scoped> |
Scoped via a unique data-v-* attribute — no class renaming |
<style lang="scss"> |
Preprocessor support (Sass, Less, Stylus) |
<clientScript> |
Browser-only code, skipped during SSR |
<docs> |
Embedded Markdown documentation, readable via parseFile() |
<anything> |
Custom blocks — handle via customBlockTransforms in the plugin |
Install the RSFC extension for syntax highlighting, IntelliSense, and CSS Module autocompletion.
Download .vsix from GitHub Releases
Install via Extensions → Install from VSIX… in VS Code.
| File | Demonstrates |
|---|---|
Counter.rsfc |
<script setup>, <style module>, useState |
TodoList.rsfc |
List rendering, conditional classes, form handling |
UserCard.rsfc |
Explicit <script>, <style scoped>, props typing, <docs> |
ThemeToggle.rsfc |
<clientScript> for browser-only side effects |
UserProfile.rsfc |
Async data, <graphql> custom block |
pnpm install # install workspace dependencies
pnpm build # build all packages
pnpm test # run tests
pnpm test:coverage # coverage report
pnpm dev:playground-vite # http://localhost:5173
pnpm dev:playground-next # http://localhost:3000Requires Node ≥ 18 and pnpm ≥ 9.
MIT © 2026 Geoffrey Casaubon