Important
This project is up for adoption. I'm looking for eager people to maintain this.
Please open a discussion if you are interested!
Compile F# with Fable from inside Vite, so a .fs file is just another module Vite can import.
The usual setup puts Fable in front of your dev server (dotnet fable watch --run vite). This plugin does not. You run vite, and F# is compiled on demand and updated over HMR, the same way Vite treats TypeScript, JSX or Sass.
- The .NET 10 SDK on your
PATH. Reading your.fsprojmeans asking MSBuild about it. Check withdotnet --version. - Vite 8 (peer dependency).
You do not need Fable as a dotnet tool. The compiler ships prebuilt inside the package, so there is no post-install step and installing with --ignore-scripts is fine.
npm install -D vite-plugin-fable
bun install -D vite-plugin-fableAdd the plugin to your Vite config:
// vite.config.js
import { defineConfig } from "vite";
import fable from "vite-plugin-fable";
export default defineConfig({
plugins: [fable()],
});The plugin compiles the single .fsproj next to your Vite config. If there is more than one, point at the one you want with fable({ fsproj: "./src/App.fsproj" }).
Import your F# entry point as a module:
<script type="module">
import "/App.fs";
</script><script type="module" src="/App.fs"> does not work: Vite only resolves the .fs extension inside module resolution. See vitejs/vite#9981.
Now start Vite. The plugin stays quiet and prints one line per compile:
VITE v8.2.2 ready in 376 ms
➜ Local: http://localhost:5173/
12:32:44 PM [vite] [fable] compiled App.fsproj in 1.53s
The most common setup, using Fable.Core.JSX:
// vite.config.js
import { defineConfig } from "vite";
import fable from "vite-plugin-fable";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [fable({ jsx: "automatic" }), react({ include: /\.fs$/ })],
});Two things to know about that line:
- Order matters.
fablegoes beforereact. - The two options do different jobs.
fable({ jsx })is what turns Fable's JSX into JavaScript; Vite cannot do it for a.fsmodule.react({ include: /\.fs$/ })is what makes.fscomponents Fast Refresh boundaries, so an edit updates in place instead of reloading the page.
Using Feliz.CompilerPlugins or plain Fable.React instead? Those emit classic-runtime React and need a different react() filter. The recipes page has both, plus the React Compiler.
- Getting started
- Plugin options, every option the plugin accepts
- Recipes, including React, an alternative
fsproj, and Debug versus Release - Debugging, what the plugin prints and how to ask the daemon what it is doing
- How does this work?
- Changelog
I talked a little bit about this project during this stream:

