Falco.Markup 1.1.1

dotnet add package Falco.Markup --version 1.1.1                
NuGet\Install-Package Falco.Markup -Version 1.1.1                
此命令旨在在 Visual Studio 的包管理器控制台中使用,因为它使用 NuGet 模块的 Install-Package 版本。
<PackageReference Include="Falco.Markup" Version="1.1.1" />                
对于支持 PackageReference 的项目,将此 XML 节点复制到项目文件中以引用软件包。
paket add Falco.Markup --version 1.1.1                
#r "nuget: Falco.Markup, 1.1.1"                
#r 指令可用于 F# Interactive 和 Polyglot Notebooks。将此复制到交互式工具或脚本的源代码中,以引用软件包。
// Install Falco.Markup as a Cake Addin
#addin nuget:?package=Falco.Markup&version=1.1.1

// Install Falco.Markup as a Cake Tool
#tool nuget:?package=Falco.Markup&version=1.1.1                

Falco.Markup

NuGet Version build

open Falco.Markup

let doc =
    Elem.html [] [
        Elem.body [ Attr.class' "100-vh" ] [
            Text.h1 "Hello world!" ] ]

renderHtml doc

Falco.Markup 是一个 XML 标记模块,可以用来生成任何形式的尖括号标记(即 HTML、SVG、XML 等)。

关键特性

  • 使用原生的 F# 生成任何形式的 尖括号标记
  • 简单创建可重用的代码块(例如,部分视图和组件)。
  • 通过创建 自定义标签和属性 来轻松扩展。
  • 作为您的程序集的一部分编译,从而提高了 性能 并简化了部署。
  • 提供与完全匹配 HTML 规范 的强类型函数。

设计目标

  • 提供一种生成任何形式 尖括号标记 的工具。
  • 必须 高效能 并且内存效率高。
  • 应该简单、可扩展且与现有的 .NET 库集成。
  • 可以轻松学习。
  • 尽可能匹配 HTML 规范

概述

Falco.Markup 被分解为三个主要模块,分别是 ElemAttrText,它们分别用于生成元素、属性和文本节点。每个模块都包含一系列与各种元素/属性/节点名称对应的函数。也可以扩展以创建自定义元素和属性。

主要元素分为两种类型,ParentNodeSelfClosingNode

ParentNode 元素是可以包含其他元素的元素。以接收两个输入的函数表示:属性以及可选元素。

let markup =
    Elem.div [ Attr.class' "heading" ] [
        Text.h1 "Hello world!" ]

SelfClosingNode 元素是自闭合标签。以接收单个输入的函数表示:属性。

let markup =
    Elem.div [ Attr.class' "divider" ] [
        Elem.hr [] ]

文本使用 TextNode 表示,并通过 Text 模块中的一个函数创建。

let markup =
    Elem.div [] [
        Text.comment "An HTML comment"
        Text.p "A paragraph"
        Elem.p [] [ Text.rawf "Hello %s" "Jim" ]
        Elem.code [] [ Text.enc "<div>Hello</div>" ] // HTML encodes text before rendering
    ]

属性也包含两个子类型,即代表键/值属性的 KeyValueAttr 或代表布尔属性的 NonValueAttr

let markup =
    Elem.input [ Attr.type' "text"; Attr.required ]

大多数 JavaScript 事件 也已在 Attr 模块中映射。所有这些事件都以单词 "on" 为前缀(例如,Attr.onclickAttr.onfocus 等)。

let markup =
    Elem.button [ Attr.onclick "console.log(\"hello world\")" ] [ Text.raw "Click me" ]

HTML

尽管 Falco.Markup 可以用于生成任何标记。它首先是一个 HTML 库。

组合视图以创建复杂输出

open Falco.Markup

// Components
let divider =
    Elem.hr [ Attr.class' "divider" ]

// Template
let master (title : string) (content : XmlNode list) =
    Elem.html [ Attr.lang "en" ] [
        Elem.head [] [
            Elem.title [] [ Text.raw title ]
        ]
        Elem.body [] content
    ]

// Views
let homeView =
    master "Homepage" [
        Text.h1 "Homepage"
        divider
        Text.p "Lorem ipsum dolor sit amet, consectetur adipiscing."
    ]

let aboutView =
    master "About Us" [
        Text.h1 "About"
        divider
        Text.p "Lorem ipsum dolor sit amet, consectetur adipiscing."
    ]

强类型视图

open Falco.Markup

type Person =
    { FirstName : string
      LastName : string }

let doc (person : Person) =
    Elem.html [ Attr.lang "en" ] [
        Elem.head [] [
            Elem.title [] [ Text.raw "Sample App" ]
        ]
        Elem.body [] [
            Elem.main [] [
                Text.h1 "Sample App"
                Text.p $"{person.First} {person.Last}"
            ]
        ]
    ]

表单

表单是 HTML 应用的命脉。以下是一个使用 markup 模块的基本表单示例

let dt = DateTime.Now

Elem.form [ Attr.methodPost; Attr.action "/submit" ] [
    Elem.label [ Attr.for' "name" ] [ Text.raw "Name" ]
    Elem.input [ Attr.id "name"; Attr.name "name"; Attr.typeText ]

    Elem.label [ Attr.for' "birthdate" ] [ Text.raw "Birthday" ]
    Elem.input [ Attr.id "birthdate"; Attr.name "birthdate"; Attr.typeDate; Attr.valueDate dt ]

    Elem.input [ Attr.typeSubmit ]
]

在此基础上,我们可以创建一个更复杂的表单,涉及多个输入和输入类型,如下所示

Elem.form [ Attr.method "post"; Attr.action "/submit" ] [
    Elem.label [ Attr.for' "name" ] [ Text.raw "Name" ]
    Elem.input [ Attr.id "name"; Attr.name "name" ]

    Elem.label [ Attr.for' "bio" ] [ Text.raw "Bio" ]
    Elem.textarea [ Attr.name "id"; Attr.name "bio" ] []

    Elem.label [ Attr.for' "hobbies" ] [ Text.raw "Hobbies" ]
    Elem.select [ Attr.id "hobbies"; Attr.name "hobbies"; Attr.multiple ] [
        Elem.option [ Attr.value "programming" ] [ Text.raw "Programming" ]
        Elem.option [ Attr.value "diy" ] [ Text.raw "DIY" ]
        Elem.option [ Attr.value "basketball" ] [ Text.raw "Basketball" ]
    ]

    Elem.fieldset [] [
        Elem.legend [] [ Text.raw "Do you like chocolate?" ]
        Elem.label [] [
            Text.raw "Yes"
            Elem.input [ Attr.typeRadio; Attr.name "chocolate"; Attr.value "yes" ] ]
        Elem.label [] [
            Text.raw "No"
            Elem.input [ Attr.typeRadio; Attr.name "chocolate"; Attr.value "no" ] ]
    ]

    Elem.fieldset [] [
        Elem.legend [] [ Text.raw "Subscribe to our newsletter" ]
        Elem.label [] [
            Text.raw "Receive updates about product"
            Elem.input [ Attr.typeCheckbox; Attr.name "newsletter"; Attr.value "product" ] ]
        Elem.label [] [
            Text.raw "Receive updates about company"
            Elem.input [ Attr.typeCheckbox; Attr.name "newsletter"; Attr.value "company" ] ]
    ]

    Elem.input [ Attr.typeSubmit ]
]

一个简单但有用的 元素 Elem.control 可以减少创建表单输出所需的冗余。此表单的外观如下所示

Elem.form [ Attr.method "post"; Attr.action "/submit" ] [
    Elem.control "name" [] [ Text.raw "Name" ]

    Elem.controlTextarea "bio" [] [ Text.raw "Bio" ] []

    Elem.controlSelect "hobbies" [ Attr.multiple ] [ Text.raw "Hobbies" ] [
        Elem.option [ Attr.value "programming" ] [ Text.raw "Programming" ]
        Elem.option [ Attr.value "diy" ] [ Text.raw "DIY" ]
        Elem.option [ Attr.value "basketball" ] [ Text.raw "Basketball" ]
    ]

    Elem.fieldset [] [
        Elem.legend [] [ Text.raw "Do you like chocolate?" ]
        Elem.control "chocolate" [ Attr.id "chocolate_yes"; Attr.typeRadio ] [ Text.raw "yes" ]
        Elem.control "chocolate" [ Attr.id "chocolate_no"; Attr.typeRadio ] [ Text.raw "no" ]
    ]

    Elem.fieldset [] [
        Elem.legend [] [ Text.raw "Subscribe to our newsletter" ]
        Elem.control "newsletter" [ Attr.id "newsletter_product"; Attr.typeCheckbox ] [ Text.raw "Receive updates about product" ]
        Elem.control "newsletter" [ Attr.id "newsletter_company"; Attr.typeCheckbox ] [ Text.raw "Receive updates about company" ]
    ]

    Elem.input [ Attr.typeSubmit ]
]

属性值

在语法复杂性较高的地方之一是 Attr.value,它期望(与所有 Attr 函数一样)string 输入。存在一些辅助工具可以简化这一点。

let dt = DateTime.Now

Elem.input [ Attr.typeDate; Attr.valueStringf "yyyy-MM-dd" dt ]

// you could also just use:
Elem.input [ Attr.typeDate; Attr.valueDate dt ] // formatted to ISO-8601 yyyy-MM-dd

// or,
Elem.input [ Attr.typeMonth; Attr.valueMonth dt ] // formatted to ISO-8601 yyyy-MM

// or,
Elem.input [ Attr.typeWeek; Attr.valueWeek dt ] // formatted to Gregorian yyyy-W#

// it works for TimeSpan too:
let ts = TimeSpan(12,12,0)
Elem.input [ Attr.typeTime; Attr.valueTime ts ] // formatted to hh:mm

// there is a helper for Option too:
let someTs = Some ts
Elem.input [ Attr.typeTime; Attr.valueOption Attr.valueTime someTs ]

合并属性

markup 模块允许您轻松创建组件,这是在 UI 中减少代码重复的一个很好的方法。为了支持运行时自定义化,建议确保组件(或可重用标记块)保留与标准元素类似的功能 "形状"。即 XmlAttribute list -> XmlNode list -> XmlNode

这意味着您不可避免地需要将预定义的 XmlAttribute list 与在运行时提供的列表组合。为此,Attr.merge 函数将按键对属性进行分组,并在增量属性的情况下智能地连接值(例如,classstyleaccept)。

open Falco.Markup

// Components
let heading (attrs : XmlAttribute list) (content : XmlNode list) =
    // safely combine the default XmlAttribute list with those provided
    // at runtime
    let attrs' =
        Attr.merge [ Attr.class' "text-large" ] attrs

    Elem.div [] [
        Elem.h1 [ attrs' ] content
    ]

// Template
let master (title : string) (content : XmlNode list) =
    Elem.html [ Attr.lang "en" ] [
        Elem.head [] [
            Elem.title [] [ Text.raw title ]
        ]
        Elem.body [] content
    ]

// Views
let homepage =
    master "Homepage" [
        heading [ Attr.class' "red" ] [ Text.raw "Welcome to the homepage" ]
        Text.p "Lorem ipsum dolor sit amet, consectetur adipiscing."
    ]

let homepage =
    master "About Us" [
        heading [ Attr.class' "purple" ] [ Text.raw "This is what we're all about" ]
        Text.p "Lorem ipsum dolor sit amet, consectetur adipiscing."
    ]

自定义元素 & 属性

为了确保 HTML 和 SVG 规范与模块中的函数相匹配,已尽力而为。如果您需要缺少的元素或属性,可以提交一个 问题,或者在项目中扩展该模块更为简单。

创建自定义 XML 元素并使用它们创建结构化 XML 文档的示例

open Falco.Makrup

module Elem =
    let books = Elem.create "books"
    let book = Elem.create "book"
    let name = Elem.create "name"

module Attr =
    let soldOut = Attr.createBool "soldOut"

let xmlDoc =
    Elem.books [] [
        Elem.book [ Attr.soldOut ] [
            Elem.name [] [ Text.raw "To Kill A Mockingbird" ]
        ]
    ]

let xml = renderXml xmlDoc

SVG

SVG 规范的大部分功能已被映射到元素和属性函数中。还有一个 SVG 模板,可以帮助初始化一个新的绘制,具有有效的视图框。

open Falco.Markup
open Falco.Markup.Svg

// https://mdn.org.cn/en-US/docs/Web/SVG/Element/text#example
let svgDrawing =
    Templates.svg (0, 0, 240, 80) [
        Elem.style [] [
            Text.raw ".small { font: italic 13px sans-serif; }"
            Text.raw ".heavy { font: bold 30px sans-serif; }"
            Text.raw ".Rrrrr { font: italic 40px serif; fill: red; }"
        ]
        Elem.text [ Attr.x "20"; Attr.y "35"; Attr.class' "small" ] [ Text.raw "My" ]
        Elem.text [ Attr.x "40"; Attr.y "35"; Attr.class' "heavy" ] [ Text.raw "cat" ]
        Elem.text [ Attr.x "55"; Attr.y "55"; Attr.class' "small" ] [ Text.raw "is" ]
        Elem.text [ Attr.x "65"; Attr.y "55"; Attr.class' "Rrrrr" ] [ Text.raw "Grumpy!" ]
    ]

let svg = renderNode svgDrawing

性能

以下是一个简单的 基准测试 的结果,其中Falco.Markup 与原生的 StringBuilder 使用以及一些其他标记库进行了比较。

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.2604 (21H2)
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=7.0.201
  [Host]     : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT DEBUG
  DefaultJob : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT


|        Method |      Mean |     Error |    StdDev | Ratio | RatioSD |   Gen 0 | Allocated |
|-------------- |----------:|----------:|----------:|------:|--------:|--------:|----------:|
| StringBuilder |  2.419 us | 0.0481 us | 0.0591 us |  1.00 |    0.00 |  6.6643 |     14 KB |
|         Falco |  3.829 us | 0.0338 us | 0.0300 us |  1.58 |    0.04 |  8.1253 |     17 KB |
|       Giraffe |  7.402 us | 0.0735 us | 0.0688 us |  3.04 |    0.08 |  9.0027 |     18 KB |
|       Scriban | 26.125 us | 0.3734 us | 0.2915 us | 10.73 |    0.38 | 16.5405 |     34 KB |

发现了一个错误?

为此有一个 问题

许可证

在多伦多由 Pim Brouwers 制作。根据 Apache License 2.0 许可。

产品 兼容的和额外的计算目标框架版本。
.NET net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。
.NET Core netcoreapp2.0 已计算。 netcoreapp2.1 已计算。 netcoreapp2.2 已计算。 netcoreapp3.0 已计算。 netcoreapp3.1 已计算。
.NET Standard netstandard2.0 兼容。 netstandard2.1 已计算。
.NET Framework net461 已计算。 net462 已计算。 net463 已计算。 net47 已计算。 net471 已计算。 net472 已计算。 net48 已计算。 net481 已计算。
MonoAndroid monoandroid 已计算。
MonoMac monomac 已计算。
MonoTouch monotouch 已计算。
Tizen tizen40 已计算。 tizen60 已计算。
Xamarin.iOS xamarinios 已计算。
Xamarin.Mac xamarinmac 已计算。
Xamarin.TVOS xamarintvos 已计算。
Xamarin.WatchOS xamarinwatchos 已计算。
兼容的目标框架
包含的目标框架(在包中)
了解更多关于 目标框架.NET Standard 的信息。

NuGet 包 (3)

显示依赖 Falco.Markup 的前 3 个 NuGet 包

下载
Falco

一款以函数优先的工具包,用于使用 F# 构建出色的 ASP.NET Core 应用程序。

Fss-lib.Falco

为使用 Fss 和 Falco 视图引擎提供辅助函数。

Falco.Bulma

包描述

GitHub 仓库

此包未用于任何流行的 GitHub 仓库。

版本 下载 最后更新
1.1.1 512 3/28/2024
1.1.0 96 3/25/2024
1.0.2 6,297 2/3/2023
1.0.1 2,271 9/14/2022
1.0.0 420 9/13/2022
1.0.0-alpha1 211 8/17/2022