32
loading...
This website collects cookies to deliver better user experience
You can find the sources for the code we'll use in this post in this repository's directory
F#
-> <Lang>
compiler where <Lang>
is any of Javascript
, Typescript
and Python
at the time of writing the last two are more experimental and the main support is for Javascript future iterations of Fable will cover these languages and even more like PHP, Rust, Dart.As mentioned above Fable is an F#
-> <Lang>
compiler but from here on we will talk about fable in the context of an F#
-> Javascript
compiler.
* Fable emits modern javascript so your target needs to at least support the ES2015 ecmascript specification, in some cases (for older environments) further processing will be needed to re-transpile the JS code to ES3/ES5.
npm init -y
// Wrote to /path/to/directory/package.json:
{
"name": "project1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
index.js
as is indicated in the main field, of course you can add the file and adjust the newly created package.json like this// src/index.js
console.log("Hello, World!");
{
"name": "project1",
"version": "1.0.0",
"description": "",
"main": "./src/index.js",
"scripts": {
"start": "node ./src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
npm start
or npm run start
you should see the lovely Hello, World! message.# you can delete the previous src directory just to make this work smoothly
dotnet new console -lang F# -o src
# The following commands are to install the fable .NET tool locally
dotnet new tool-manifest
dotnet tool install fable
{
"name": "project1",
"version": "1.0.0",
"description": "",
"main": "./src/Program.fs.js",
"scripts": {
"start-app": "node ./src/Program.fs.js",
"start": "dotnet fable src --run npm run start-app"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module" // NOTE: this is required to run the fable output
}
npm start
and you'll see Fable compiling then getting a Hello from F# even if it was not run in .NET but node.jsnpm init -y
dotnet new classlib -o src -lang F#
# The following commands are to install the fable .NET tool locally
dotnet new tool-manifest
dotnet tool install fable
{
"name": "project2",
"version": "1.0.0",
"description": "",
"main": "./src/index.js",
"scripts": {
"start-app": "node ./src/index.js",
"start": "dotnet fable src --run npm run start-app"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module"
}
package.json
| src
index.js
Library.fs
src.fsproj
import { hello } from "./Library.fs.js";
hello("Javascript");
npm start
you should see the lovely Hello Javascriptindex.js
can introduce F# in the code base and the reasoning for this is that this is the exact mechanism you can use to introduce typescript in a code base. Although, typescript benefits Javascript code from the editor and other tooling around so it's arguably easier but I digress, the main point is that you can either incrementally add F# code to your javascript project and let them co-exist side by side or you can slowly migrate JS code to F# code, file by file, module by module, however you feel the pace is better for your team.npm init vite@latest project3 --template lit
cd project3 && npm install && npm run dev
src
<!-- App.fsproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Lit" Version="1.4.1" />
</ItemGroup>
</Project>
// Library.fs
open Lit
[<LitElement("my-counter")>]
let Counter() =
// This call is obligatory to initialize the web component
let _, props =
LitElement.init(fun init ->
init.props <- {| initial = Prop.Of(defaultValue = 0) |})
let counter, setCounter = Hook.useState props.initial.Value
html
$"""
<article>
<p>{counter}</p>
<button @click={fun _ -> setCounter(counter + 1)}>+</button>
<button @click={fun _ -> setCounter(counter - 1)}>-</button>
</article>
"""
src/my-element.js
we will import the compiled fable file// src/my-element.js
import { html, css, LitElement } from "lit"; // this should be already there
import "./Library.fs.js"; // this line
// ... the rest of the file
"dev": "dotnet fable src --watch --run vite serve"
.index.html
the following content right inside the body element<my-element>
<p>This is child content</p>
<!-- This content is from our Fable Code -->
<my-counter></my-counter>
</my-element>
npm run dev
and visit localhost:3000
and we should see our counter inside the default"allowJS": true
to the tsconfig.json
's compiler options{
"compilerOptions": {
//... the rest of the config
"allowJs": true
}
//... the rest of the config
}
NOTE: If you have a strict config enabled you might face issues with implicit any errors, you can also add "checkJs": false
so your Fable output doesn't get re-checked by typescript (after all it has already been checked by F#)
JS modules
or directly in the index.html
there are are some caveats about Fable projects with JS dependencies. There are two approaches here you are managing your JS dependencies in any of the following ways-o --outDir Redirect compilation output to a directory
, something along the lines of dotnet fable fable-sources -o wwwroot
and it should just work.import {} from 'lit/some/sub/directory.js
browser imports need to start with /
or ./
or even ../
so they can be valid ES module imports thankfully for this you can check out in a shameless plug one of the projects I'm working on: Perla which handles this precise case but I digress, the ideal situation would be you with npm and already figured out how to serve node dependencies to your compiled code.