Header menu logo vite-plugin-fable

Recipes

There are a few things you can configure in the plugin configuration.

Alternative fsproj

By default, the plugin will look for a single .fsproj file inside the folder that holds your vite.config.js file. If you deviate from this setup you can specify the entry fsproj file:

import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";
import fable from "vite-plugin-fable";

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const fsproj = path.join(currentDir, "fsharp/FantomasTools.fsproj");

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [fable({ fsproj })],
});

Using React

There are a couple of ways to deal with React and JSX in Fable.

⚠️ When using the vite-plugin-fable in combination with @vitejs/plugin-react, you do want to specify the fable plugin first! ⚠️

Feliz.CompilerPlugins

If you are using Feliz.CompilerPlugins, Fable output React Classic Runtime code.
Stuff like React.createElement. You will need to tailor your @vitejs/plugin-react accordingly:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import fable from "vite-plugin-fable";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    fable(),
    react({ include: /\.(fs|js|jsx|ts|tsx)$/, jsxRuntime: "classic" }),
  ],
});

Note that the react plugin will only apply the fast-refresh wrapper when you specify the fs extension in the include.

Fable.Core.JSX

Fable can also produce JSX (see blog). In this case, you need to tell the fable plugin it should transform the JSX using Babel. The @vitejs/plugin-react won't interact with any .fs files, so that transformation needs to happen in the fable plugin:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import fable from "vite-plugin-fable";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    // See `jsx` option from https://esbuild.github.io/api/#transformation
    fable({ jsx: "automatic" }),
    react({ include: /\.(fs|js|jsx|ts|tsx)$/ }),
  ],
});

Note that you will still need to tweak the react plugin with include to enable the fast refresh transformation.

Plain Fable.React

If you are for some reason using Fable.React without Feliz.CompilerPlugins, there is one gotcha to get fast refresh working.

Fable.React will use the old JSX output. The @vitejs/plugin-react needs to respect that in the configuration:

import react from "@vitejs/plugin-react";
import fable from "vite-plugin-fable";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    fable(),
    react({ include: /\.(fs|js|jsx|ts|tsx)$/, jsxRuntime: "classic" }),
  ],
});

However, this is not enough for the fast refresh wrapper to be added.
⚠️ The React plugin will specifically look for a import React from "react" statement.

module Component

open Fable.React
open Fable.React.Props

// Super important for fast refresh to work in "classic" mode.
// The [<ReactComponent>] attribute from Feliz.CompilerPlugin will add for you.
// But here, we are not using that and we need to add this ourselves.
Fable.Core.JsInterop.emitJsStatement () "import React from \"react\""

let App () =
    let counterHook = Hooks.useState(0)

    div [] [
        h1 [] [ str "Hey you!" ]
        p [] [
            ofInt counterHook.current
        ]
        button [ OnClick (fun _ -> counterHook.update(fun c -> c + 1))] [
            str "Increase"
        ]
    ]

Next

Type something to start searching.