F# и React.js через Fable 🐉 Фелиз

  • Fable 🐲 — это библиотека F# и инструмент dotnet, который предотвращает компиляцию кода F# на множество разных языков и фреймворков, в первую очередь F# может компилироваться в Javascript, Fable изначально означает F#-(B)abel. Начиная с Fable 4, он также может компилироваться в Python, Rust и Swift.
  • Feliz — это адаптер/API-библиотека React.js для Fable, которая позволяет писать код на React с упрощенным синтаксисом.
  • F# — удивительный первый функциональный (а также объектно-ориентированный) язык программирования с потрясающим выводом типов и системой типов, унаследованной от своего дедушки OCaml, он работает на .NET, как и его двоюродный брат C#, и он хорошо совместим со смешанными решениями C#/F#, вот некоторая информация о F# для веб-разработки.

Поехали 🐲🔥

Прежде всего, установите dotnet-sdk, если у вас его нет, я на Mac, так что… F# — это язык dotnet, поэтому он работает в среде выполнения .NET, и вы понадобится sdk, чтобы сделать с ним что-нибудь полезное.

brew install dotnet

Затем мы хотим установить последние шаблоны feliz, которые также используют удивительный сборщик и сервер разработки Vite вместо старого веб-пакета.

dotnet new -i Feliz.Template

и создайте новое приложение Feliz, представляющее собой приложение dotnet, которое будет скомпилировано в Javascript с использованием компонентов React.js с использованием Fable.js 🐉

dotnet new feliz -n AwesomeApp

вот как выглядит наш package.json

{
  "private": true,
  "scripts": {
    "start": "dotnet tool restore && dotnet fable watch src --runFast vite",
    "build": "dotnet tool restore && dotnet fable src --run vite build",
    "clean": "dotnet fable clean src --yes"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^3.1.0",
    "vite": "^4.1.0"
  },
  "engines": {
    "node": ">=18"
  }
}

Все готово, теперь вы можете установить и запустить, это, в свою очередь, также скомпилирует наши файлы .fs в .js и запустит сервер vite dev.

npm i && npm start

Ваше приложение Main.fs должно выглядеть так, как показано ниже, если вы хотите использовать маршрутизатор. После каждой компиляции fable выходные данные javascript сохраняются рядом с исходными файлами в виде .js, но нам не нужно об этом особо заботиться.

Следует иметь в виду, что порядок файлов имеет значение в F# для исключения циклических зависимостей (это был выбор компилятора), так что вот он. Всегда включайте файлы в порядке их зависимости (основной или корень всегда внизу).

    <ItemGroup>
        <None Include="index.html" />
        <Compile Include="Extensions.fs" />
        <Compile Include="Components.fs" />
        <Compile Include="Main.fs" />
    </ItemGroup>

Затем вы можете вызвать маршруты в своем браузере, используя маршруты на основе хэша (#) (маршруты только для FE) для перехода к другим маршрутам, например /#hello.

    // router
    [<ReactComponent>]
    static member Router() =
        let (currentUrl, updateUrl) = React.useState(Router.currentUrl())
        React.router [
            router.onUrlChanged updateUrl
            router.children [
                match currentUrl with
                | [ ] -> 
                    Html.div [
                        Html.h1 "Welcome!"
                    ]
                | [ "hello" ] -> Components.HelloWorld(). // #hello
                | [ "counter" ] -> Components.Counter() // #counter
                | otherwise -> Html.h1 "Not found"
            ]
        ]

Если вам интересно, вы также можете использовать встроенный JSX через эту статью и некоторые изменения в вашем файле .fsproj (или добавив пакеты через cli)

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework> <!-- 6.0 is LTS atm -->
    </PropertyGroup>
    <ItemGroup>
        <None Include="index.html" />
        <Compile Include="Extensions.fs" />
        <Compile Include="Components.fs" />
        <Compile Include="Main.fs" />
    </ItemGroup>
    <ItemGroup>
        <PackageReference Include="Feliz" Version="2.6.0" />
<!-- add compiler plugins -->
        <PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
        <PackageReference Include="Feliz.Router" Version="4.0.0" />
<!-- add latest fable.core theta (maybe also latest just works in your case) -->
        <PackageReference Include="Fable.Core" Version="4.0.0-theta-006" />
    </ItemGroup>
</Project>

Благодаря этим дополнениям мы можем создавать образцы встроенных компонентов .jsx как таковые, но также возможно загружать их с диска для интеграции с внешними react компоненты или библиотеки!

namespace App

open Feliz
open Feliz.Router
open Fable.Core // this is needed to create some extensions
open Fable.React

[<AutoOpen>]
module JsxHelpers =
    let inline toJsx (el: ReactElement) : JSX.Element = unbox el
    let inline toReact (el: JSX.Element) : ReactElement = unbox el

type Components =

    [<ReactComponent>]
    static member HelloWorld() = 
        JSX.jsx $"""
        <>
        <h1>HELLO WORLD!</h1>
        <div>this is a div from JSX</div>
        </>
        """
        |> toReact

Подробнее о взаимодействии внешних компонентов здесь:

Получайте удовольствие от React и Feliz и отличной недели!