-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added AsyncHTML type * moved initializers to elements * added async convenience initializers * renamed AsyncHTML to AsyncContent * added async to readme
- Loading branch information
Showing
10 changed files
with
207 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/// An element that awaits its content before rendering. | ||
/// | ||
/// The this element can only be rendered in an async context (ie: by calling ``HTML/render(into:chunkSize:)`` or ``HTML/renderAsync()``). | ||
/// All HTML tag types (``HTMLElement``) support async content closures in their initializers, so you don't need to use this element directly in most cases. | ||
public struct AsyncContent<Content: HTML>: HTML, Sendable { | ||
var content: @Sendable () async throws -> Content | ||
public typealias Tag = Content.Tag | ||
|
||
/// Creates a new async HTML element with the specified content. | ||
/// | ||
/// - Parameters: | ||
/// - content: The future content of the element. | ||
/// | ||
/// ```swift | ||
/// AsyncContent { | ||
/// let value = await fetchValue() | ||
/// "Waiting for " | ||
/// span { value } | ||
/// } | ||
/// ``` | ||
public init(@HTMLBuilder content: @escaping @Sendable () async throws -> Content) { | ||
self.content = content | ||
} | ||
|
||
@_spi(Rendering) | ||
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) { | ||
context.assertionFailureNoAsyncContext(self) | ||
} | ||
|
||
@_spi(Rendering) | ||
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws { | ||
try await Content._render(await html.content(), into: &renderer, with: context) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
public extension HTMLElement { | ||
/// Creates a new HTML element with the specified tag and async content. | ||
/// | ||
/// The async content closure is automatically wrapped in an ``AsyncContent`` element and can only be rendered in an async context. | ||
/// | ||
/// - Parameters: | ||
/// - attributes: The attributes to apply to the element. | ||
/// - content: The future content of the element. | ||
init<AwaitedContent: HTML>(_ attributes: HTMLAttribute<Tag>..., @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent) | ||
where Self.Content == AsyncContent<AwaitedContent> | ||
{ | ||
self.attributes = .init(attributes) | ||
self.content = AsyncContent(content: content) | ||
} | ||
|
||
/// Creates a new HTML element with the specified tag and async content. | ||
/// | ||
/// The async content closure is automatically wrapped in an ``AsyncContent`` element and can only be rendered in an async context. | ||
/// | ||
/// - Parameters: | ||
/// - attributes: The attributes to apply to the element. | ||
/// - content: The future content of the element. | ||
init<AwaitedContent: HTML>(attributes: [HTMLAttribute<Tag>], @HTMLBuilder content: @escaping @Sendable () async throws -> AwaitedContent) | ||
where Self.Content == AsyncContent<AwaitedContent> | ||
{ | ||
self.attributes = .init(attributes) | ||
self.content = AsyncContent(content: content) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import Elementary | ||
import XCTest | ||
|
||
final class AsyncRenderingTests: XCTestCase { | ||
func testRendersAsyncContent() async throws { | ||
try await HTMLAssertEqualAsyncOnly( | ||
AsyncContent { | ||
let text = await getValue() | ||
"Waiting for " | ||
span { text } | ||
}, | ||
"Waiting for <span>late response</span>" | ||
) | ||
} | ||
|
||
func testAsyncElementInTuple() async throws { | ||
try await HTMLAssertEqualAsyncOnly( | ||
div { | ||
AwaitedP(number: 1) | ||
AwaitedP(number: 2) | ||
AwaitedP(number: 3) | ||
}, | ||
"<div><p>1</p><p>2</p><p>3</p></div>" | ||
) | ||
} | ||
|
||
func testImplicitlyAsyncContent() async throws { | ||
try await HTMLAssertEqualAsyncOnly( | ||
p(.id("hello")) { | ||
let text = await getValue() | ||
"Waiting for \(text)" | ||
}, | ||
#"<p id="hello">Waiting for late response</p>"# | ||
) | ||
} | ||
|
||
func testNestedImplicitAsyncContent() async throws { | ||
try await HTMLAssertEqualAsyncOnly( | ||
div(attributes: [.class("c1")]) { | ||
p { | ||
await getValue() | ||
} | ||
"again \(await getValue())" | ||
p(.class("c2")) { | ||
"and again \(await getValue())" | ||
} | ||
}, | ||
#"<div class="c1"><p>late response</p>again late response<p class="c2">and again late response</p></div>"# | ||
) | ||
} | ||
} | ||
|
||
private struct AwaitedP: HTML { | ||
var number: Int | ||
var content: some HTML { | ||
AsyncContent { | ||
let _ = try await Task.sleep(for: .milliseconds(1)) | ||
p { "\(number)" } | ||
} | ||
} | ||
} | ||
|
||
private func getValue() async -> String { | ||
await Task.yield() // just for fun | ||
return "late response" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters