- Embed htmx library to `Giraffe.Htmx.Common`, add links to load package-provided script
- Update CDN links for v2.0.8
- Add .NET 10 support

Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
2025-12-28 16:53:44 +00:00
parent 6b7458070b
commit 121eb95d87
13 changed files with 103 additions and 34 deletions

View File

@@ -14,6 +14,8 @@ htmx uses attributes and HTTP headers to attain its interactivity; the libraries
|---|---| |---|---|
|[![Nuget](https://img.shields.io/nuget/v/Giraffe.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.Htmx/)|[![Nuget](https://img.shields.io/nuget/v/Giraffe.ViewEngine.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/)| |[![Nuget](https://img.shields.io/nuget/v/Giraffe.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.Htmx/)|[![Nuget](https://img.shields.io/nuget/v/Giraffe.ViewEngine.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/)|
Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_.
## Server Side (`Giraffe.Htmx`) ## Server Side (`Giraffe.Htmx`)
In addition to the regular HTTP request payloads, htmx sets [one or more headers](https://htmx.org/docs/#request_headers) along with the request. Once `Giraffe.Htmx` is opened, these are available as properties on `HttpContext.Request.Headers`. These consist of the header name, translated to a .NET name (ex. `HX-Current-URL` becomes `HxCurrentUrl`), and a strongly-typed property based on the expected value of that header. Additionally, they are all exposed as `Option`s, as they may or may not be present for any given request. In addition to the regular HTTP request payloads, htmx sets [one or more headers](https://htmx.org/docs/#request_headers) along with the request. Once `Giraffe.Htmx` is opened, these are available as properties on `HttpContext.Request.Headers`. These consist of the header name, translated to a .NET name (ex. `HX-Current-URL` becomes `HxCurrentUrl`), and a strongly-typed property based on the expected value of that header. Additionally, they are all exposed as `Option`s, as they may or may not be present for any given request.
@@ -42,6 +44,8 @@ let theHandler : HttpHandler =
Of note is that the `HX-Trigger` headers can take either one or more events. For a single event with no parameters, use `withHxTrigger`; for a single event with parameters, or multiple events, use `withHxTriggerMany`. Both these have `AfterSettle` and `AfterSwap` versions as well. Of note is that the `HX-Trigger` headers can take either one or more events. For a single event with no parameters, use `withHxTrigger`; for a single event with parameters, or multiple events, use `withHxTriggerMany`. Both these have `AfterSettle` and `AfterSwap` versions as well.
`HtmxScript.local` provides an `HtmlString` with a script tag to load the package-provided htmx library. This can be used in code, Razor templates, etc. (If you're using Giraffe.ViewEngine, see below.)
## View Engine (`Giraffe.ViewEngine.Htmx`) ## View Engine (`Giraffe.ViewEngine.Htmx`)
As htmx uses [attributes](https://htmx.org/docs/#attributes) to extend HTML, the primary part of this library defines attributes that can be used within Giraffe views. Simply open `Giraffe.ViewEngine.Htmx`, and these attributes, along with support modules, will be visible. As htmx uses [attributes](https://htmx.org/docs/#attributes) to extend HTML, the primary part of this library defines attributes that can be used within Giraffe views. Simply open `Giraffe.ViewEngine.Htmx`, and these attributes, along with support modules, will be visible.
@@ -66,7 +70,7 @@ let shiftClick =
] ]
``` ```
If you want to load htmx from unpkg, `Htmx.Script.minified` or `Htmx.Script.unminified` can be used to load the script in your HTML trees. If you want to use the package-provided htmx library, `Htmx.Script.local` will create the `script` tag for you. To load htmx from jsDelivr, `Htmx.Script.cdnMinified` or `Htmx.Script.cdnUnminified` can be used to load the script in your HTML trees. In this case, if you are using a Content Security Policy (CSP) header, `cdn.jsdelivr.net` will need to be added to the `script-src` list.
## Feedback / Help ## Feedback / Help

View File

@@ -2,6 +2,12 @@
[<AutoOpen>] [<AutoOpen>]
module Giraffe.Htmx.Common module Giraffe.Htmx.Common
/// <summary>The version of htmx embedded in the package</summary>
let HtmxVersion = "2.0.8"
/// <summary>The path for the provided htmx script</summary>
let internal htmxLocalScript = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"
/// <summary>Serialize a list of key/value pairs to JSON (very rudimentary)</summary> /// <summary>Serialize a list of key/value pairs to JSON (very rudimentary)</summary>
/// <param name="pairs">The key/value pairs to be serialized to JSON</param> /// <param name="pairs">The key/value pairs to be serialized to JSON</param>
/// <returns>A string with the key/value pairs serialized to JSON</returns> /// <returns>A string with the key/value pairs serialized to JSON</returns>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup> <PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@@ -1,5 +1,5 @@
## Giraffe.Htmx.Common ## Giraffe.Htmx.Common
This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. It also contains htmx as a static web asset, allowing it to be loaded from your local (or published) project.
**htmx version: 2.0.6** **htmx version: 2.0.8**

1
src/Common/wwwroot/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<VersionPrefix>2.0.6</VersionPrefix> <VersionPrefix>2.0.8</VersionPrefix>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReleaseNotes>- All packages now have full XML documentation <PackageReleaseNotes>- Adds package-provided script (available via Giraffe.Htmx.Common); use app.MapStaticAssets() for publish support, use builder.UseStaticWebAssets() for non-development/non-published execution
- Adds HxSync module and attribute helper to view engine - [View Engine] Deprecates Script.minified and Script.unminified (use Script.cdnMinified and Script.cdnUnminified instead)
- Updates script tags to pull htmx 2.0.6 (no header or attribute changes) - Updates script tags to pull htmx 2.0.8 (no header or attribute changes)
- Drops .NET 6 support - Adds .NET 10 support
NOTE: The CDN for htmx changed from unpkg.com to cdn.jsdelivr.net; sites with Content-Security-Policy headers will want to update their allowed domains accordingly See full release notes, including more info about the package-provided script, at https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx/releases/tag/v2.0.8
NOTE: As of 2.0.6, the CDN for htmx changed from unpkg.com to cdn.jsdelivr.net; sites with Content-Security-Policy headers will want to update their allowed script-src domains accordingly
</PackageReleaseNotes> </PackageReleaseNotes>
<Authors>danieljsummers</Authors> <Authors>danieljsummers</Authors>
<Company>Bit Badger Solutions</Company> <Company>Bit Badger Solutions</Company>

View File

@@ -170,3 +170,15 @@ module Handlers =
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso> /// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
let withHxTriggerManyAfterSwap evts : HttpHandler = let withHxTriggerManyAfterSwap evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger-After-Swap" toJson evts |> setHttpHeader "HX-Trigger-After-Swap"
/// <summary>Load the package-provided version of the htmx script</summary>
[<RequireQualifiedAccess>]
module HtmxScript =
open Giraffe.Htmx.Common
open Microsoft.AspNetCore.Html
/// <summary><c>script</c> tag to load the package-provided version of the htmx script</summary>
let local = HtmlString $"""<script src="{htmxLocalScript}"></script>"""

View File

@@ -2,7 +2,7 @@
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`. This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
**htmx version: 2.0.6** **htmx version: 2.0.8**
_Upgrading from v1.x: the [migration guide](https://htmx.org/migration-guide-htmx-1/) does not currently specify any request or response header changes. This means that there are no required code changes in moving from v1.* to v2.*._ _Upgrading from v1.x: the [migration guide](https://htmx.org/migration-guide-htmx-1/) does not currently specify any request or response header changes. This means that there are no required code changes in moving from v1.* to v2.*._
@@ -34,6 +34,8 @@ let myHandler : HttpHander =
The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information. The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information.
To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`.
### Learn ### Learn
The naming conventions of this library were selected to mirror those provided by htmx. The header properties become `Hx*` on the `ctx.Request.Headers` object, and the response handlers are `withHx*` based on the header being set. The only part that does not line up is `withHxTrigger*` and `withHxTriggerMany`; the former set work with a single string (to trigger a single event with no arguments), while the latter set supports both arguments and multiple events. The naming conventions of this library were selected to mirror those provided by htmx. The header properties become `Hx*` on the `ctx.Request.Headers` object, and the response handlers are `withHx*` based on the header being set. The only part that does not line up is `withHxTrigger*` and `withHxTriggerMany`; the former set work with a single string (to trigger a single event with no arguments), while the latter set supports both arguments and multiple events.

View File

@@ -3,6 +3,12 @@ module Common
open Expecto open Expecto
open Giraffe.Htmx open Giraffe.Htmx
/// Test to ensure the version was updated
let version =
test "HtmxVersion is correct" {
Expect.equal HtmxVersion "2.0.8" "htmx version incorrect"
}
/// Tests for the HxSwap module /// Tests for the HxSwap module
let swap = let swap =
testList "HxSwap" [ testList "HxSwap" [
@@ -30,4 +36,4 @@ let swap =
] ]
/// All tests for this module /// All tests for this module
let allTests = testList "Htmx.Common" [ swap ] let allTests = testList "Htmx.Common" [ version; swap ]

View File

@@ -3,6 +3,7 @@ module Htmx
open System open System
open Expecto open Expecto
open Giraffe.Htmx open Giraffe.Htmx
open Microsoft.AspNetCore.Html
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open NSubstitute open NSubstitute
@@ -354,5 +355,16 @@ let handlers =
} }
] ]
/// Tests for the HtmxScript module
let script =
testList "HtmxScript" [
test "local generates correct link" {
Expect.equal
(string HtmxScript.local)
$"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
"htmx script link is incorrect"
}
]
/// All tests for this module /// All tests for this module
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers ] let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script ]

View File

@@ -842,22 +842,31 @@ let attributes =
} }
] ]
open Giraffe.Htmx.Common
/// Tests for the Script module /// Tests for the Script module
let script = let script =
testList "Script" [ testList "Script" [
test "minified succeeds" { test "local succeeds" {
let html = RenderView.AsString.htmlNode Script.minified let html = RenderView.AsString.htmlNode Script.local
Expect.equal Expect.equal
html html
"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js" integrity="sha384-Akqfrbj/HpNVo8k11SXBb6TlBWmXXlYQrCSqEWmyKJe+hDm3Z/B2WVG4smwBkRVm" crossorigin="anonymous"></script>""" $"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
"Minified script tag is incorrect" "Local script tag is incorrect"
} }
test "unminified succeeds" { test "cdnMinified succeeds" {
let html = RenderView.AsString.htmlNode Script.unminified let html = RenderView.AsString.htmlNode Script.cdnMinified
Expect.equal Expect.equal
html html
"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.js" integrity="sha384-ksKjJrwjL5VxqAkAZAVOPXvMkwAykMaNYegdixAESVr+KqLkKE8XBDoZuwyWVUDv" crossorigin="anonymous"></script>""" $"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.min.js" integrity="sha256-Iig+9oy3VFkU8KiKG97cclanA9HVgMHSVSF9ClDTExM=" crossorigin="anonymous"></script>"""
"Unminified script tag is incorrect" "CDN minified script tag is incorrect"
}
test "cdnUnminified succeeds" {
let html = RenderView.AsString.htmlNode Script.cdnUnminified
Expect.equal
html
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.js" integrity="sha256-upUwYnay6R+DA68rROTAP+EdfO3NvOqtE513PgDyAYM=" crossorigin="anonymous"></script>"""
"CDN unminified script tag is incorrect"
} }
] ]

View File

@@ -788,18 +788,33 @@ module HtmxAttrs =
/// <summary>Script tags to pull htmx into a web page</summary> /// <summary>Script tags to pull htmx into a web page</summary>
module Script = module Script =
/// <summary>Script tag to load the minified version from unpkg.com</summary> open System
let minified =
script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js" /// <summary>Script tag to load the package-provided version of htmx</summary>
_integrity "sha384-Akqfrbj/HpNVo8k11SXBb6TlBWmXXlYQrCSqEWmyKJe+hDm3Z/B2WVG4smwBkRVm" let local = script [ _src htmxLocalScript ] []
/// <summary>Script tag to load the minified version from jsdelivr.net</summary>
/// <remarks>Ensure <c>cdn.jsdelivr.net</c> is in your CSP <c>script-src</c> list (if applicable)</remarks>
let cdnMinified =
script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"
_integrity "sha256-Iig+9oy3VFkU8KiKG97cclanA9HVgMHSVSF9ClDTExM="
_crossorigin "anonymous" ] [] _crossorigin "anonymous" ] []
/// <summary>Script tag to load the unminified version from unpkg.com</summary> /// <summary>Script tag to load the unminified version from jsdelivr.net</summary>
let unminified = /// <remarks>Ensure <c>cdn.jsdelivr.net</c> is in your CSP <c>script-src</c> list (if applicable)</remarks>
script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.js" let cdnUnminified =
_integrity "sha384-ksKjJrwjL5VxqAkAZAVOPXvMkwAykMaNYegdixAESVr+KqLkKE8XBDoZuwyWVUDv" script [ _src "https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.js"
_integrity "sha256-upUwYnay6R+DA68rROTAP+EdfO3NvOqtE513PgDyAYM="
_crossorigin "anonymous" ] [] _crossorigin "anonymous" ] []
/// <summary>Script tag to load the minified version from jsdelivr.net</summary>
[<Obsolete "Use cdnMinified instead; this will be removed in v4">]
let minified = cdnMinified
/// <summary>Script tag to load the unminified version from jsdelivr.net</summary>
[<Obsolete "Use cdnUnminified instead; this will be removed in v4">]
let unminified = cdnUnminified
/// <summary>Functions to extract and render an HTML fragment from a document</summary> /// <summary>Functions to extract and render an HTML fragment from a document</summary>
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]

View File

@@ -2,7 +2,7 @@
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine. This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
**htmx version: 2.0.6** **htmx version: 2.0.8**
_Upgrading from v1.x: see [the migration guide](https://htmx.org/migration-guide-htmx-1/) for changes_ _Upgrading from v1.x: see [the migration guide](https://htmx.org/migration-guide-htmx-1/) for changes_
@@ -29,7 +29,7 @@ Support modules include:
- `HxTrigger` - `HxTrigger`
- `HxVals` - `HxVals`
There are two `XmlNode`s that will load the htmx script from jsdelivr; `Htmx.Script.minified` loads the minified version, and `Htmx.Script.unminified` loads the unminified version (useful for debugging). `Htmx.Script.local` creates an `XmlNode` to load the package-provided htmx library. There are also two `XmlNode`s that will load the htmx script from jsdelivr; `Htmx.Script.cdnMinified` loads the minified version, and `Htmx.Script.cdnUnminified` loads the unminified version (useful for debugging). When using the CDN nodes and a Content Security Policy (CSP) header, `cdn.jsdelivr.net` needs to be listed as an allowable `script-src`.
This also supports [fragment rendering](https://bitbadger.solutions/blog/2022/fragment-rendering-in-giraffe-view-engine.html), providing the flexibility to render an entire template, or only a portion of it (based on the element's `id` attribute). This also supports [fragment rendering](https://bitbadger.solutions/blog/2022/fragment-rendering-in-giraffe-view-engine.html), providing the flexibility to render an entire template, or only a portion of it (based on the element's `id` attribute).