Set up nacara

This commit is contained in:
Alfonso Garcia-Caro
2021-09-25 22:49:29 +09:00
parent 2ee086cbdd
commit 28cad1333b
18 changed files with 11799 additions and 185 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
build/sample/*
build/test/*
!build/test/__snapshots__
build/sample/*
build/docs/*
dist/
.idea/

196
README.md
View File

@@ -1,196 +1,26 @@
# Fable.Lit
Fable.Lit is a collection of tools to help you write [Fable](https://fable.io/) apps by embedding HTML code into your F# code with the power of [Lit](https://lit.dev/). Thanks to this, you can use HTML from a designer or a [component library](https://ionicframework.com/docs/api/) right away, without any kind of conversion to F#. Lit only weighs 5KB minified and gzipped so it's very cheap to integrate in your existing app (see below for React integration). If you're using VS Code and install the [Highlight HTML/SQL templates in F#](https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight) extension the integration will be even smoother.
Fable.Lit is a collection of tools to help you write [Fable](https://fable.io/) apps by embedding HTML code into your F# code with the power of [Lit](https://lit.dev/).
There's an example in the `sample` directory, but you can find [here a more detailed tutorial](https://dev.to/tunaxor/using-lit-html-with-f-3i8b) by Angel Munoz.
Before doing anything make sure to install the dependencies after cloning the repository by running:
## Requirements
`npm install`
Fable.Lit packages require **fable 3.3** dotnet tool and **Lit 2** from npm which, at the time of writing, is in release candidate state.
## How to test locally ?
```
dotnet tool update fable
npm install lit@next
```
`npm run test`
Then, in the directory of your .fsproj, install the packages you need (see below for details), which are also in prelease. Note the package ids are prefixed by `Fable.` but not the actual namespaces.
## How to publish a new version of the package ?
```
dotnet add package Fable.Lit --prerelease
dotnet add package Fable.Lit.React --prerelease
dotnet add package Fable.Lit.Elmish --prerelease
```
`npm run publish`
## Lit
## How to work on the documentation ?
Fable.Lit contains bindings and extra helpers for Lit. Please read [Lit documentation](https://lit.dev/docs/templates/overview/) to learn how Lit templates work.
1. `npm run docs -- watch`
2. Go to [http://localhost:8080/](http://localhost:8080/)
When you open the `Lit` namespace, you will have access to:
## How to update the documentation ?
- `html` and `svg` helpers, which convert F# interpolated strings into Lit templates
- `LitBindings` static class containing _raw_ bindings for Lit (normally you don't need to use this)
- `Lit` static class containing wrappers for Lit in a more F# idiomatic fashion
Deployment should be done automatically when pushing to `main` branch.
E.g. Lit [repeat](https://lit.dev/docs/templates/directives/#repeat) directive becomes `Lit.mapUnique` to map a sequence of items into `Lit.TemplateResult` and assign each a unique id. This is important to identify the items when the list is going to be sorted or filtered. For static lists passing the sequence directly just works.
```fsharp
let renderList items =
let renderItem item =
html $"""<li>Value: <strong>{item.Value}</strong></li>"""
html $"""<ul>{items |> Lit.mapUnique (fun x -> x.Id) renderItem}</ul>"""
```
### HookComponent
Fable.Lit includes the `HookComponent` attribute. When you decorate a view function with it, this lets you use [hooks](https://reactjs.org/docs/hooks-overview.html) in a similar way as [ReactComponent](https://zaid-ajaj.github.io/Feliz/#/Feliz/React/NotJustFunctions) attribute does. Hook support is included in Fable.Lit's F# code and doesn't require any extra JS dependency besides Lit.
```fsharp
[<HookComponent>]
let NameInput() =
// Lit.Hook API is currently evolving, we try to emulate React's API but there may be some differences
let value, setValue = Hook.useState "World"
let inputRef = Hook.useRef<HTMLInputElement>()
html $"""
<div class="content">
<p>Hello {value}!</p>
<input
value={value}
{Lit.refValue inputRef}
@focus={fun _ ->
inputRef.value |> Option.iter (fun el -> el.select())}
@keyup={fun (ev: Event) ->
ev.target.Value |> setValue}>
</div>
"""
```
> Note that hook components are just a way to keep state between renders and are not [web components](https://www.webcomponents.org/introduction). We plan to add bindings to define web components with [lit](https://lit.dev) in the near future. Also check [Fable.Haunted](https://github.com/AngelMunoz/Fable.Haunted) by Angel Munoz to define actual web components with React-style hooks.
### Hook.useElmish
Thanks to the great work by [Cody Johnson](https://twitter.com/Cody_S_Johnson) with [Feliz.UsElmish](https://zaid-ajaj.github.io/Feliz/#/Hooks/UseElmish), Fable.Lit HookComponents also include `useElmish` hook to manage the internal state of your components using the model-view-update architecture.
```fsharp
open Elmish
open Lit
type Model = ..
type Msg = ..
let init() = ..
let update msg model = ..
let view model dispatch = ..
[<HookComponent>]
let Clock(): TemplateResult =
let model, dispatch = Hook.useElmish(init, update)
view model dispatch
```
## Lit.React
Fable.Lit.React package contains helpers to integrate Lit with React in both directions: either by rendering a React component with an HTML template or by embedding a React component in an HTML template. This makes it possible to add raw HTML to your apps whenever you need it, no matter you're using [Fable.React](https://github.com/fable-compiler/fable-react/) bindings or [Zaid Ajaj](https://twitter.com/zaid_ajaj)'s [Feliz](https://zaid-ajaj.github.io/Feliz/) API.
> If you're comfortable with [JSX](https://reactjs.org/docs/introducing-jsx.html) and Typescript/JS, it's also easy to [invoke them from Feliz](https://zaid-ajaj.github.io/Feliz/#/Feliz/UsingJsx) if that suits your needs better.
Use `React.lit_html` (or svg) to include the string template directly. Or transform an already-compiled template with `React.ofLit: Lit.TemplateResult -> ReactElement`. These helpers use hooks so they must be called directly in the root of a React component.
```fsharp
[<ReactComponent>]
let Clock () =
let time, setTime = React.useState DateTime.Now
React.useEffectOnce(fun () ->
let id = JS.setInterval (fun _ -> DateTime.Now |> setTime) 1000
React.createDisposable(fun () ->
JS.clearInterval id))
// If the template were in another function we would call
// view time |> React.ofLit
React.lit_html $"""
<svg viewBox="0 0 100 100"
width="350px">
<circle
cx="50"
cy="50"
r="45"
fill="#0B79CE"></circle>
{clockHand time.AsHour}
{clockHand time.AsMinute}
{clockHand time.AsSecond}
<circle
cx="50"
cy="50"
r="3"
fill="#0B79CE"
stroke="#023963"
stroke-width="1">
</circle>
</svg>
"""
```
Use `React.toLit` to transform a React component into a Lit renderer function. Store the transformed function in a static value to make sure a new React component is not instantiated for every render:
```fsharp
module ReactLib =
open Fable.React
open Fable.React.Props
[<ReactComponent>]
let MyComponent showClock =
let state = Hooks.useState 0
div [ Class "card" ] [
div [ Class "card-content" ] [
div [ Class "content" ] [
p [] [str $"""I'm a React component. Clock is {if showClock then "visible" else "hidden"}"""]
button [
Class "button"
OnClick (fun _ -> state.update(state.current + 1))
] [ str $"""Clicked {state.current} time{if state.current = 1 then "" else "s"}!"""]
]
]
]
open Lit
let ReactLitComponent =
React.toLit ReactLib.MyComponent
// Now you can embed the React component into your Lit template
let view model dispatch =
html $"""
<div class="vertical-container">
{ReactLitComponent model.ShowClock}
{if model.ShowClock then Clock.Clock() else Lit.nothing}
</div>
"""
```
## Lit.Elmish
Fable.Lit.Elmish allows you to write a frontend app using the popular [Elmish](https://elmish.github.io/) library by [Eugene Tolmachev](https://github.com/et1975) with a view function returning `Lit.TemplateResult`. The package also includes support for [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) out-of-the-box thanks to [Maxime Mangel](https://twitter.com/MangelMaxime) original work with Elmish.HMR.
```fsharp
open Elmish
open Lit
type Model = ..
type Msg = ..
let init() = ..
let update msg model = ..
let view model dispatch = ..
open Lit.Elmish
open Lit.Elmish.HMR
Program.mkProgram initialState update view
|> Program.withLit "app-container"
|> Program.run
```
If the CI is broken, you can manually deploy it by running `npm run docs:deploy`.

View File

@@ -0,0 +1,182 @@
---
title: Computation expression
layout: nacara-standard
---
## Introduction
The `promise` computation expression makes it really easy to create and compose promise using F#.
<div class="columns" date-disable-copy-button="true">
<div class="column is-half-desktop">
<div class="has-text-centered mb-2 has-text-weight-semibold">Pipeline API</div>
```fsharp
fetch "https://x.x/users"
|> Promise.map (fun response ->
fetch "https://x.x/posts"
)
|> Promise.map (fun response ->
// Done, do something with the result
)
```
</div>
<div class="column is-half-desktop">
<div class="has-text-centered mb-2 has-text-weight-semibold">Computation expression</div>
```fsharp
promise {
let! users = fetch "https://x.x/users"
let! posts = fetch "https://x.x/posts"
// Done, do something with the result
return //...
}
```
</div>
</div>
## Guides
Here is a quick guides of what you can do with `promise` computations.
You can read more about computation expression in F# [here](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions).
### Create a promise
Creating a promise is as simple as writing `promise { }`.
```fsharp
let double (value : int) =
promise {
return value * 2
}
```
### Chaining promises
If you need the result of a promise before calling another one, use the `let!` keyword
```fsharp
promise {
let! user = fetchUsers session
let! permission = fetchPermission user
return permission
}
```
You can also directly return the result of a promise avoiding to use `let!` and `return`
`return!` will evaluate the promise and return the result value when completed
```fsharp
promise {
let! user = fetchUsers session
return! fetchPermission user
}
```
### Nesting promises
You can nest `promise` computation as needed.
```fsharp
promise {
// Nested promise which returns a value
let! isValid =
promise {
// Do something
return aValue
}
// Nested promise which return unit
do! promise {
// Do something
return ()
}
}
```
### Parallel
If you have independent promise, you can use `let!` and `and!` to run them in parallel.
```fsharp
let p1 =
promise {
do! Promise.sleep 100
return 1
}
let p2 =
promise {
do! Promise.sleep 200
return 2
}
let p3 =
promise {
do! Promise.sleep 300
return 3
}
promise {
let! a = p1
and! b = p2
and! c = p3
return a + b + c // 1 + 2 + 3 = 6
}
```
### Support non-native promise (aka Thenable)
In JavaScript, a thenable is an object that has a `then()` function. All promises are thenables, but not all thenables are promises.
For example, this is the case when working on a VSCode extensions, or with mongoose, axios.
[Source](https://masteringjs.io/tutorials/fundamentals/thenable)
You can extends the `promise` extension to make it easy to works with your thenable.
Example:
```fsharp
/// This is the definition of a thenable from ts2fable's generation VsCode API
type [<AllowNullLiteral>] Thenable<'T> =
abstract ``then``: ?onfulfilled: ('T -> U2<'TResult, Thenable<'TResult>>) * ?onrejected: (obj option -> U2<'TResult, Thenable<'TResult>>) -> Thenable<'TResult>
abstract ``then``: ?onfulfilled: ('T -> U2<'TResult, Thenable<'TResult>>) * ?onrejected: (obj option -> unit) -> Thenable<'TResult>
module Thenable =
// Transform a thenable into a promise
let toPromise (t: Thenable<'t>): JS.Promise<'t> = unbox t
type Promise.PromiseBuilder with
/// To make a value interop with the promise builder, you have to add an
/// overload of the `Source` member to convert from your type to a promise.
/// Because thenables are trivially convertible, we can just unbox them.
member x.Source(t: Thenable<'t>): JS.Promise<'t> = Thenable.toPromise t
// Also provide these cases for overload resolution
member _.Source(p: JS.Promise<'T1>): JS.Promise<'T1> = p
member _.Source(ps: #seq<_>): _ = ps
// You can now works with instance of Thenable from the promise computation
// Dummy thenable for the example
let sampleThenable () =
promise {
return 1
}
|> Thenable.ofPromise
// See how you can use the thenable directly from the promise computation
promise {
let! initialValue = sampleThenable()
// ...
}
```

View File

@@ -0,0 +1,39 @@
---
title: Getting started
layout: nacara-standard
---
Add `Fable.Promise` package to your project.
If you are using nuget to manage you dependencies:
`dotnet add package Fable.Promise`
If you are using [paket](https://fsprojects.github.io/Paket/):
`dotnet paket add Fable.Promise`
You are ready to go, you can directly access `Promise` module or the `promise` computation.
Example:
```fsharp
let helloPromise =
Promise.create (fun resolve reject ->
resolve "Hello, from a promise"
)
// Pipeline style
helloPromise
|> Promise.iter (fun message ->
printfn message
// Output: Hello, from a promise
)
// Using computation expression
promise {
let! message = helloPromise
printfn message
// Output: Hello, from a promise
}
```

11
docsrc/docs/menu.json Normal file
View File

@@ -0,0 +1,11 @@
[
"docs/getting-started",
{
"type": "category",
"label": "API",
"items": [
"docs/pipeline",
"docs/computation-expression"
]
}
]

476
docsrc/docs/pipeline.md Normal file
View File

@@ -0,0 +1,476 @@
---
title: Pipeline
layout: nacara-standard
---
## Introduction
Pipeline style allows you use to chain your promise using the pipe operator `|>`.
Writing your code using the pipeline style makes your code looks similar to what you would write in JavaScript.
<div class="columns" date-disable-copy-button="true">
<div class="column is-half-desktop">
<div class="has-text-centered mb-2 has-text-weight-semibold">JavaScript</div>
```js
fetch('https://my-api.com/users')
.then(function (response) {
return fetch('https://my-api.com/posts')
})
.then(function (response) {
// Done, do something with the result
})
.catch(function (req) {
// An error ocurred
})
```
</div>
<div class="column is-half-desktop">
<div class="has-text-centered mb-2 has-text-weight-semibold">F#</div>
```fsharp
fetch "https://my-api.com/users"
|> Promise.map (fun response ->
fetch "https://my-api.com/posts"
)
|> Promise.map (fun response ->
// Done, do something with the result
)
|> Promise.catch (fun error ->
// An error ocurred
)
```
</div>
</div>
## API
Description and examples for all the pipeline API.
### Promise.create
`create: f: (('T -> unit) -> (exn -> unit) -> unit) -> Promise<'T>`
Create a promise from a function.
```fsharp
let write (path: string) (content: string) =
Promise.create (fun resolve reject ->
Node.Api.fs.writeFile(path, content, (fun res ->
match res with
| Some res -> reject (res :?> System.Exception)
| None -> resolve ()
))
)
```
### Promise.sleep
`sleep: ms: int -> Promise<unit>`
Create a promise which wait `X` ms before resolving.
```fsharp
// Do something
doSomething ()
// Sleep for 1 second
|> Promise.sleep 1000
// Do another thing
|> Promise.map (fun _ ->
doAnotherThing ()
)
|> Promise.map ...
```
### Promise.lift
`lift: a: 'T -> Promise<'T>`
Create a promise (in resolved state) with supplied value.
```fsharp
Promise.lift {| Firstname = "John" |}
|> Promise.map (fun user ->
console.log $"Hello, %s{user.Firstname}"
// Expected output: "Hello, John"
)
|> Promise.map ...
```
### Promise.reject
`reject: reason: exn -> Promise<'T>`
Create a promise (in rejected state) with supplied reason.
```fsharp
Promise.reject "User not found"
|> Promise.map (fun _ ->
// This promise is skipped
)
|> Promise.catch (fun errorMessage ->
console.error $"An error ocurred: %s{errorMessage}"
// Expected output: "An error ocurred: User not found"
)
|> Promise.map ...
```
### Promise.bind
`bind: a : ('T1 -> JS.Promise<'T2>) -> pr: Promise<'T1> -> Promise<'T2>`
Bind a value into a promise of a new type.
```fsharp
Promise.lift {| Firstname = "John" |}
|> Promise.bind (fun user ->
// Do something with user and returns a promise
Promise.create (fun resolve reject ->
resolve $"Hello, %s{user.Firstname}"
)
)
|> Promise.map (fun message ->
console.log message
// Expected output: "Hello, John"
)
|> Promise.map ...
```
### Promise.map
`map: a : ('T1 -> 'T2) -> pr: Promise<'T1> -> Promise<'T2>`
Map a value into another type, the result will be wrapped in a promise for you.
```fsharp
Promise.lift {| Firstname = "John" |}
|> Promise.map (fun user ->
user.Firstname
) // Returns a Promise<string> with the value "John"
|> Promise.map ...
```
### Promise.iter
`iter: a : ('T -> unit) -> pr: Promise<'T> -> unit`
Call a function with the result of a promise and stop the promise chain.
This is equivalent to `Promise.map ... |> ignore`
```fsharp
fetchUser ()
|> Promise.iter (fun user ->
console.log "User firstname is user.Firstname"
) // unit
```
### Promise.catch
`catch: fail: (exn -> 'T) -> pr : Promise<'T> -> Promise<'T>`
Handle an errored promise allowing you pass a return value.
This version of `catch` fakes a function returning just `'T`, as opposed to `Promise<'T>`. If you need to return `Promise<'T>`, use `catchBind`.
```fsharp
Promise.create (fun resolve reject ->
reject (System.Exception "User not found")
)
|> Promise.catch (fun error ->
// Log the error
console.error error
// Do something to recover from the error
Error error.Message
)
|> Promise.map ...
```
### Promise.catchBind
`catchBind: fail: (Exception -> JS.Promise<'T>) -> pr : Promise<'T> -> Promise<'T>`
Handle an errored promise allowing to call a promise.
This version of `catch` expects a function returning `Promise<'T>` as opposed to just `'T`. If you need to return just `'T`, use `catch`.
```fsharp
Promise.create (fun resolve reject ->
reject (System.Exception "User not found")
)
|> Promise.catchBind (fun error ->
// Recover from the error, here we call another promise and returns it's result
logErrorToTheServer error
)
|> Promise.map ...
```
### Promise.catchEnd
`catchEnd: fail: (exn -> unit) -> pr : Promise<'T> -> unit`
Used to catch errors at the end of a promise chain.
```fsharp
Promise.create (fun resolve reject ->
reject (System.Exception "User not found")
)
|> Promise.map (fun _ ->
// ...
)
|> Promise.map (fun _ ->
// ...
)
|> Promise.catchEnd (fun error ->
// ...
) // Returns unit
```
### Promise.either
`either: success: ('T1 -> 'T2) -> fail : (exn -> 'T2) -> pr : Promise<'T1> -> Promise<'T2>`
A combination of `map` and `catch`, this function applies the `success` continuation when the input promise resolves successfully, or `fail` continuation when the input promise fails.
```fsharp
somePromise
|> Promise.either
(fun x -> string x)
(fun err -> Promise.lift err.Message)
|> Promise.map ...
```
### Promise.eitherBind
`eitherBind: success: ('T1 -> JS.Promise<'T2>) -> fail : (exn -> JS.Promise<'T2>) -> pr : Promise<'T1> -> Promise<'T2>`
A combination of `bind` and `catchBind`, this function applies the `success` continuation when the input promise resolves successfully, or `fail` continuation when the input promise fails.
```fsharp
somePromise
|> Promise.eitherBind
(fun x -> string x |> Promise.lift)
(fun err -> Promise.lift err.Message)
|> Promise.map ...
```
### Promise.eitherEnd
`eitherEnd: success: ('T -> unit) -> fail : ('E -> unit) -> pr : Promise<'T> -> unit`
Same as [`Promise.either`](#Promise.either) but stopping the promise execution.
```fsharp
somePromise
|> Promise.eitherEnd
(fun x -> string x)
(fun err -> Promise.lift err.Message)
```
### Promise.start
`start: pr: Promise<'T> -> unit`
Start a promise.
In version before XXX, it was used because the promise CE was originally cold, so it didn't start until `then` was called. Now it's hot same as native promises, so `Promise.start` is equivalent to `promise |> ignore`
```fsharp
myPromise
|> Promise.start
```
### Promise.tryStart
`tryStart: fail: (exn -> unit) -> pr : Promise<'T> -> unit`
Same as [Promise.start](#Promise.start) but forcing you to handle the rejected state.
```fsharp
myPromise
|> Promise.tryStart
(fun error ->
// Do something on error
)
```
### Promise.Parallel
`Parallel: pr: seq<JS.Promise<'T>> -> Promise<'T array>`
Same as [Promise.all](#Promise.all)
```fsharp
let p1 =
promise {
do! Promise.sleep 100
return 1
}
let p2 =
promise {
do! Promise.sleep 200
return 2
}
let p3 =
promise {
do! Promise.sleep 300
return 3
}
Promise.Parallel [p1; p2; p3]
|> Promise.map (fun res ->
// res = [|1; 2; 3 |]
)
|> Promise.map ...
```
### Promise.all
`all: pr: seq<JS.Promise<'T>> -> Promise<'T array>`
`Promise.all` takes a sequence of promises as an input, and returns a single `Promise` that resolves to an array of the results of the input promises.
```fsharp
let p1 =
promise {
do! Promise.sleep 100
return 1
}
let p2 =
promise {
do! Promise.sleep 200
return 2
}
let p3 =
promise {
do! Promise.sleep 300
return 3
}
Promise.all [p1; p2; p3]
|> Promise.map (fun res ->
// res = [|1; 2; 3 |]
)
|> Promise.map ...
```
Note: If you need to return mixed types you can use boxing and unboxing
```fsharp
let users =
promise {
let! users = fetchUsers ()
return box users
}
let posts =
promise {
let! posts = fetchPosts ()
return box posts
}
Promise.all [p1; p2]
|> Promise.map (fun res ->
let users = unbox<User list> res.[0]
let posts = unbox<Post list> res.[1]
// ...
)
```
### Promise.result
`result: a: Promise<'T> -> Promise<Result<'T,exn>>`
Map the `Promise` result into a `Result` type.
```fsharp
// Success example
Promise.lift 42
|> Promise.result
|> Promise.map (fun value ->
// value = Ok 42
)
// Fail example
Promise.reject "Invalid value"
|> Promise.result
|> Promise.map (fun value ->
// value = Error "Invalid value"
)
```
### Promise.mapResult
`mapResult: fn: ('T1 -> 'T2) -> a : Promise<Result<'T1,'E>> -> Promise<Result<'T2,'E>>`
Evaluates to `myPromise |> Promise.map (Result.map fn)`
```fsharp
Promise.lift 42
|> Promise.result
|> Promise.mapResult (fun value ->
value + 10
)
|> Promise.map (fun value ->
// value = Ok 52
)
```
### Promise.bindResult
`bindResult: fn: ('T1 -> JS.Promise<'T2>) -> a : Promise<Result<'T1,'E>> -> Promise<Result<'T2,'E>>`
Transform the success part of a result promise into another promise.
```fsharp
let multiplyBy2 (value : int) =
Promise.create (fun resolve reject ->
resolve (value * 2)
)
Promise.lift 42
|> Promise.result
|> Promise.bindResult (fun value ->
multiplyBy2 value
)
|> Promise.map (fun value ->
// value = Ok 84
)
```
### Promise.mapResultError
`mapResultError: fn: ('E1 -> 'E2) -> a : Promise<Result<'T,'E1>> -> Promise<Result<'T,'E2>>`
Evaluates to `myPromise |> Promise.map (Result.map fn)`
```fsharp
Promise.reject -1
|> Promise.result
|> Promise.mapResultError (fun value ->
$"%s{value} is not a valid value"
)
|> Promise.map (fun value ->
// value = Error "-1 is not a valid value"
)
```
### Promise.tap
`tap: fn: ('A -> unit) -> a : Promise<'A> -> Promise<'A>`
This is an identity function, it calls the given function and return the promise value untouched.
```fsharp
fetchUser ()
|> Promise.tap (fun user ->
// Do something
console.log "The user has been received"
)
|> Promise.map (fun user ->
// user value is available here untouched
)
```

BIN
docsrc/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

35
docsrc/index.md Normal file
View File

@@ -0,0 +1,35 @@
---
title:
layout: nacara-navbar-only
---
<!-- Disable the copy-button on all the elements contained inside the container (all this page) -->
<div class="container mt-5" data-disable-copy-button="true">
<!--
Selling points
For the selling points we use CSS grid instead of Bulma columns because we want all the box to have the same height.
This is not something possible to do dynamically using Flexbox/Bulma columns system
-->
<section class="section">
<h2 class="title is-2 has-text-primary has-text-centered">
Fable.Lit
</h2>
<p class="content is-size-5 has-text-centered">
Fable.Lit is a comprehensive suite of tools to write <a href="https://fable.io">Fable</a> apps by embedding HTML into your F# code with the power of Google's <a href="https://lit.dev">Lit</a>.
</p>
<p class="content is-size-5 has-text-centered">
Fable.Lit lets you take full advantage of modern web development, including blazing-fast hot reloading, best-practice component testing and a VS Code extension.
</p>
<p class="has-text-centered">
<img src="screencast.gif" style="max-width: 600px; border-radius: 10px">
</p>
<br />
<p class="has-text-centered">
<a href="docs/getting-started.html">
<button class="button is-info is-large">
Get started
</button>
</a>
</p>
</section>
</div>

BIN
docsrc/screencast.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 KiB

View File

@@ -0,0 +1,6 @@
@mixin strong-shadow-box {
border-radius: 4px;
box-shadow: 5px 5px 0px 1px lightgrey;
border: 1px solid lightgrey;
background-color: white;
}

46
docsrc/style.scss Normal file
View File

@@ -0,0 +1,46 @@
@import "./../node_modules/bulma/sass/utilities/initial-variables";
$fable-blue: dodgerblue;
$purple: #7a325d;
$blue: #0055a4;
$primary: $fable-blue;
$black: #202020;
// $scheme-main: whitesmoke;
$text: #444;
@import "./../node_modules/bulma/sass/utilities/derived-variables";
$navbar-item-color: $white;
$navbar-background-color: $primary;
$navbar-item-hover-color: $white;
$navbar-item-hover-background-color: lighten($primary, 8%);
$navbar-item-active-background-color: lighten($primary, 8%);
$navbar-item-active-color: $white;
$content-heading-color: $primary;
$menu-item-active-background-color: $primary;
$menu-item-active-color: $white;
$menu-item-hover-color: $primary;
$menu-item-hover-background-color: transparent;
$menu-label-font-size: $size-6;
$menu-item-radius: $radius-large $radius-large;
$toc-item-active-font-weight: $weight-semibold;
$body-size: 14px;
$textual-step-color: lightgrey;
@import "./../node_modules/bulma/sass/utilities/_all.sass";
@import './../node_modules/bulma/bulma.sass';
@import './../node_modules/nacara-layout-standard/scss/nacara.scss';
@import './scss/utilities.scss';
.content {
pre {
@include strong-shadow-box;
}
}

View File

@@ -0,0 +1,211 @@
{
"information_for_contributors": [
"This file has been copied from https://github.com/Microsoft/vscode/blob/master/extensions/json/syntaxes/JSON.tmLanguage.json"
],
"version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
"name": "JSON (Javascript Next)",
"scopeName": "source.json",
"patterns": [
{
"include": "#value"
}
],
"repository": {
"array": {
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.definition.array.begin.json"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.definition.array.end.json"
}
},
"name": "meta.structure.array.json",
"patterns": [
{
"include": "#value"
},
{
"match": ",",
"name": "punctuation.separator.array.json"
},
{
"match": "[^\\s\\]]",
"name": "invalid.illegal.expected-array-separator.json"
}
]
},
"comments": {
"patterns": [
{
"begin": "/\\*\\*(?!/)",
"captures": {
"0": {
"name": "punctuation.definition.comment.json"
}
},
"end": "\\*/",
"name": "comment.block.documentation.json"
},
{
"begin": "/\\*",
"captures": {
"0": {
"name": "punctuation.definition.comment.json"
}
},
"end": "\\*/",
"name": "comment.block.json"
},
{
"captures": {
"1": {
"name": "punctuation.definition.comment.json"
}
},
"match": "(//).*$\\n?",
"name": "comment.line.double-slash.js"
}
]
},
"constant": {
"match": "\\b(?:true|false|null)\\b",
"name": "constant.language.json"
},
"number": {
"match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
"name": "constant.numeric.json"
},
"object": {
"begin": "\\{",
"beginCaptures": {
"0": {
"name": "punctuation.definition.dictionary.begin.json"
}
},
"end": "\\}",
"endCaptures": {
"0": {
"name": "punctuation.definition.dictionary.end.json"
}
},
"name": "meta.structure.dictionary.json",
"patterns": [
{
"comment": "the JSON object key",
"include": "#objectkey"
},
{
"include": "#comments"
},
{
"begin": ":",
"beginCaptures": {
"0": {
"name": "punctuation.separator.dictionary.key-value.json"
}
},
"end": "(,)|(?=\\})",
"endCaptures": {
"1": {
"name": "punctuation.separator.dictionary.pair.json"
}
},
"name": "meta.structure.dictionary.value.json",
"patterns": [
{
"comment": "the JSON object value",
"include": "#value"
},
{
"match": "[^\\s,]",
"name": "invalid.illegal.expected-dictionary-separator.json"
}
]
},
{
"match": "[^\\s\\}]",
"name": "invalid.illegal.expected-dictionary-separator.json"
}
]
},
"string": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.json"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.json"
}
},
"name": "string.quoted.double.json",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"objectkey": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.support.type.property-name.begin.json"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.support.type.property-name.end.json"
}
},
"name": "string.json support.type.property-name.json",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"stringcontent": {
"patterns": [
{
"match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
"name": "constant.character.escape.json"
},
{
"match": "\\\\.",
"name": "invalid.illegal.unrecognized-string-escape.json"
}
]
},
"value": {
"patterns": [
{
"include": "#constant"
},
{
"include": "#number"
},
{
"include": "#string"
},
{
"include": "#array"
},
{
"include": "#object"
},
{
"include": "#comments"
}
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

42
nacara.config.json Normal file
View File

@@ -0,0 +1,42 @@
{
"url": "https://fable.io",
"baseUrl": "/Fable.Lit/",
"editUrl": "https://github.com/fable-compiler/Fable.Lit/edit/main/docsrc",
"source": "docsrc",
"output": "build/docs",
"title": "Fable.Lit",
"navbar": {
"start": [
{
"url": "/Fable.Lit/docs/getting-started.html",
"section": "docs",
"label": "Documentation"
}
],
"end": [
{
"url": "https://github.com/fable-compiler/Fable.Lit",
"icon": "fab fa-github",
"isExternal": true
},
{
"url": "https://twitter.com/FableCompiler",
"icon": "fab fa-twitter",
"isExternal": true
}
]
},
"lightner": {
"backgroundColor": "#FFFFFF",
"textColor": "#000000",
"themeFile": "./lightner/themes/customized_OneLight.json",
"grammars": [
"./lightner/grammars/fsharp.json",
"./lightner/grammars/JSON.tmLanguage.json",
"./lightner/grammars/JavaScript.tmLanguage.json"
]
},
"layouts": [
"nacara-layout-standard"
]
}

1698
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,10 @@
"publish": "dotnet fsi build.fsx publish",
"test": "dotnet fable test -o build/test --run web-test-runner build/test/*Test.js --node-resolve",
"test:watch": "dotnet fable watch test -o build/test --run web-test-runner build/test/*Test.js --node-resolve --watch",
"start": "dotnet fable watch sample -o build/sample --runFast vite --open"
"start": "dotnet fable watch sample -o build/sample --runFast vite --open",
"predocs": "shx rm -rf deploy",
"docs": "nacara",
"docs:deploy": "npm run docs && gh-pages -d build/docs"
},
"dependencies": {
"lit": "^2.0.0",
@@ -15,6 +18,10 @@
"devDependencies": {
"@web/test-runner": "^0.13.16",
"fable-publish-utils": "^2.2.0",
"gh-pages": "^3.2.3",
"nacara": "^1.0.0-beta-014",
"nacara-layout-standard": "^1.0.0-beta-009",
"shx": "^0.3.3",
"vite": "^2.5.4"
}
}