First, let`s check how it can look like:
style {
backgroundColor "#44c767"
borderRadius 30
borderWidth 1
borderStyleSolid
borderColor "#18ab29"
displayInlineBlock
cursorPointer
fontSize 17
}
Benchmarks (I know it is not fair comparison for Fss because Fss is more type safety and will automatically generate classname for you. But I did not find similar libraries to compare, just take as a reference), You can check the code in Benchmark/Benchmarks.fs:
Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Allocated |
---|---|---|---|---|---|---|---|
BuildStyleWithFunCss | 481.0 ns | 20.27 ns | 55.83 ns | 462.5 ns | 0.3266 | - | 1 KB |
BuildStyleWithFeliz | 722.6 ns | 14.00 ns | 14.38 ns | 726.4 ns | 0.4778 | - | 2 KB |
BuildStyleWithFss | 17,913,801.0 ns | 350,775.83 ns | 700,537.24 ns | 17,668,715.6 ns | 906.2500 | 31.2500 | 3,821 KB |
This project is built in Fun.Blazor at first to help build inline style with type safety way.
Before I was using Feliz.Engine, when I was migrating Fun.Blazor to use InlineIfLambda for better performance, I found I can also make style building faster with the same way. So copied the Feliz.Engine basic methods for css and rebuild with computation plus InlineIfLambda.
The basic stuff is like this:
[<CustomOperation("color")>]
member inline _.color([<InlineIfLambda>] comb: CombineKeyValue, color: string) =
comb &>> ("color", color)
CombineKeyValue is defined as:
type CombineKeyValue = delegate of StringBuilder -> StringBuilder
So after you build with release mode, everything should combined in a local functions with a StringBuilder provide to append all the string pieces together.
It depends, take Fun.Blazor as an example, I will just inherit Fun.Css.CssBuilder and add a new Run member to generate the final result. In my case it is a AttrRenderFragment
type StyleBuilder() =
inherit Fun.Css.CssBuilder()
member inline _.Run([<InlineIfLambda>] combine: Fun.Css.Internal.CombineKeyValue) =
AttrRenderFragment(fun _ builder index ->
let sb = stringBuilderPool.Get()
builder.AddAttribute(index, "style", combine.Invoke(sb).ToString())
stringBuilderPool.Return sb
index + 1
)
// With a helper function
let style = StyleBuilder()
Then I can use it in Fun.Blazor like this:
div {
style {
color "red"
height 100
width 100
}
}
Another example is just to generate a string for the style, then you can similar do things like:
type StyleStrBuilder() =
inherit Fun.Css.CssBuilder()
member inline _.Run([<InlineIfLambda>] combine: Fun.Css.Internal.CombineKeyValue) =
let sb = stringBuilderPool.Get()
let str = combine.Invoke(sb).ToString()
stringBuilderPool.Return sb
str
// With a helper function
let styleStr = StyleStrBuilder()
For Fable + React, it does not support, because as what I know React is using an js object for the inline style. So the key value is not the pure css standard instead it use camelCase.
But you can use it in Fable to build pure css inline style string if you want.
[x] Add css selector, pseudo etc. (help wanted 😊)
But we may not need to build that, because it looks pretty complex and very flexible. Maybe we can just do this:
styleElement {
ruleset ".selected span:hover" {
color "red"
}
}
And it it generate things like
<style>
.selected span:hover {
color: red;
}
</style>
Even there is no type safety for the selector and pseudo class or element, but it is very straightforward to do.