Skip to content

Commit

Permalink
experimental support for embedded swift (#50)
Browse files Browse the repository at this point in the history
* made a few things public for elementary-dom experiment

* test embedded compatibility mode

* not variadic tuples in embedded : /

* or maybe like this?

* annotate all the things

* argh...

* rename

* moar tuples

* a bit of clean up

* straighten out HTMLText interface

* swift 5.10 compatibility
  • Loading branch information
sliemeobn authored Oct 12, 2024
1 parent 0a0ebb3 commit c1fdbe3
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 64 deletions.
3 changes: 2 additions & 1 deletion .swiftformat
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
--stripunusedargs unnamed-only
--stripunusedargs unnamed-only
--ifdef noindent
2 changes: 1 addition & 1 deletion Examples/HummingbirdDemo/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
.executable(name: "App", targets: ["App"]),
],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0-rc.5"),
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
.package(path: "../../"),
],
targets: [
Expand Down
2 changes: 1 addition & 1 deletion Examples/HummingbirdDemo/Sources/App/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct App {
onServerRunning: { _ in
print("Server running on http://localhost:8080/")
#if DEBUG
browserSyncReload()
browserSyncReload()
#endif
}
)
Expand Down
18 changes: 9 additions & 9 deletions Examples/HummingbirdDemo/Sources/App/BrowserSync.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import Foundation

#if DEBUG
func browserSyncReload() {
let p = Process()
p.executableURL = URL(string: "file:///bin/sh")
p.arguments = ["-c", "browser-sync reload"]
do {
try p.run()
} catch {
print("Could not auto-reload: \(error)")
}
func browserSyncReload() {
let p = Process()
p.executableURL = URL(string: "file:///bin/sh")
p.arguments = ["-c", "browser-sync reload"]
do {
try p.run()
} catch {
print("Could not auto-reload: \(error)")
}
}
#endif
13 changes: 12 additions & 1 deletion Package@swift-6.0.swift → Package@swift-6.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
// swift-tools-version: 6.0
import Foundation
import PackageDescription

let featureFlags: [SwiftSetting] = [
let shouldBuildForEmbedded = ProcessInfo.processInfo.environment["EXPERIMENTAL_EMBEDDED_WASM"].flatMap(Bool.init) ?? false

var featureFlags: [SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny"),
]

if shouldBuildForEmbedded {
featureFlags.append(
.unsafeFlags([
"-Xfrontend", "-emit-empty-object-file",
])
)
}

let package = Package(
name: "elementary",
platforms: [
Expand Down
2 changes: 2 additions & 0 deletions Sources/Elementary/Core/AsyncForEach.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !hasFeature(Embedded)
/// An element that lazily renders HTML for each element of an `AsyncSequence`.
///
/// The this element can only be rendered in an async context (ie: by calling ``HTML/render(into:chunkSize:)`` or ``HTML/renderAsync()``).
Expand Down Expand Up @@ -38,3 +39,4 @@ public struct AsyncForEach<Source: AsyncSequence, Content: HTML>: HTML {
}
}
}
#endif
2 changes: 1 addition & 1 deletion Sources/Elementary/Core/AttributeStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public struct _StoredAttribute: Equatable, Sendable {
}
}

public enum _AttributeStorage: Sendable {
public enum _AttributeStorage: Sendable, Equatable {
case none
case single(_StoredAttribute)
case multiple([_StoredAttribute])
Expand Down
4 changes: 3 additions & 1 deletion Sources/Elementary/Core/Environment.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !hasFeature(Embedded)
/// A property wrapper that reads an environment value from a `TaskLocal`.
///
/// Use `@Environment` to conveniently read a value provided via ``HTML/environment(_:_:)``.
Expand Down Expand Up @@ -105,4 +106,5 @@ public struct _ModifiedTaskLocal<T: Sendable, Content: HTML>: HTML {
}
}

extension _ModifiedTaskLocal: Sendable where Content: Sendable {}
extension _ModifiedTaskLocal: Sendable where Content: Sendable {}
#endif
4 changes: 1 addition & 3 deletions Sources/Elementary/Core/Html+Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ public extension HTMLAttribute {

public struct _AttributedElement<Content: HTML>: HTML {
public var content: Content

@usableFromInline
var attributes: _AttributeStorage
public var attributes: _AttributeStorage

@usableFromInline
init(content: Content, attributes: _AttributeStorage) {
Expand Down
38 changes: 18 additions & 20 deletions Sources/Elementary/Core/Html+Elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag: HTMLTrait.Paired {
/// The type of the HTML tag this element represents.
public typealias Tag = Tag
@usableFromInline
var attributes: _AttributeStorage
public var _attributes: _AttributeStorage

// The content of the element.
public var content: Content

/// Creates a new HTML element with the specified content.
/// - Parameter content: The content of the element.
public init(@HTMLBuilder content: () -> Content) {
attributes = .init()
_attributes = .init()
self.content = content()
}

Expand All @@ -21,7 +20,7 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
/// - content: The content of the element.
@inlinable
public init(_ attribute: HTMLAttribute<Tag>, @HTMLBuilder content: () -> Content) {
attributes = .init(attribute)
_attributes = .init(attribute)
self.content = content()
}

Expand All @@ -31,7 +30,7 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
/// - content: The content of the element.
@inlinable
public init(_ attributes: HTMLAttribute<Tag>..., @HTMLBuilder content: () -> Content) {
self.attributes = .init(attributes)
_attributes = .init(attributes)
self.content = content()
}

Expand All @@ -41,24 +40,24 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
/// - content: The content of the element.
@inlinable
public init(attributes: [HTMLAttribute<Tag>], @HTMLBuilder content: () -> Content) {
self.attributes = .init(attributes)
_attributes = .init(attributes)
self.content = content()
}

@_spi(Rendering)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
html.attributes.append(context.attributes)
html._attributes.append(context.attributes)

renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType))
renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType))
Content._render(html.content, into: &renderer, with: .emptyContext)
renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType))
}

@_spi(Rendering)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
html.attributes.append(context.attributes)
html._attributes.append(context.attributes)

try await renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: false, type: Tag.renderingType))
try await renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: false, type: Tag.renderingType))
try await Content._render(html.content, into: &renderer, with: .emptyContext)
try await renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType))
}
Expand All @@ -68,46 +67,45 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
public struct HTMLVoidElement<Tag: HTMLTagDefinition>: HTML where Tag: HTMLTrait.Unpaired {
/// The type of the HTML tag this element represents.
public typealias Tag = Tag
@usableFromInline
var attributes: _AttributeStorage
public var _attributes: _AttributeStorage

/// Creates a new HTML void element.
@inlinable
public init() {
attributes = .init()
_attributes = .init()
}

/// Creates a new HTML void element with the specified attribute.
/// - Parameter attribute: The attribute to apply to the element.
@inlinable
public init(_ attribute: HTMLAttribute<Tag>) {
attributes = .init(attribute)
_attributes = .init(attribute)
}

/// Creates a new HTML void element with the specified attributes.
/// - Parameter attributes: The attributes to apply to the element.
@inlinable
public init(_ attributes: HTMLAttribute<Tag>...) {
self.attributes = .init(attributes)
_attributes = .init(attributes)
}

/// Creates a new HTML void element with the specified attributes.
/// - Parameter attributes: The attributes to apply to the element as an array.
@inlinable
public init(attributes: [HTMLAttribute<Tag>]) {
self.attributes = .init(attributes)
_attributes = .init(attributes)
}

@_spi(Rendering)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
html.attributes.append(context.attributes)
renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
html._attributes.append(context.attributes)
renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
}

@_spi(Rendering)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
html.attributes.append(context.attributes)
try await renderer.appendToken(.startTag(Tag.name, attributes: html.attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
html._attributes.append(context.attributes)
try await renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
}
}

Expand Down
47 changes: 47 additions & 0 deletions Sources/Elementary/Core/HtmlBuilder+Embedded.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// since variadic generics are currently not supported in Embedded, here are a few old-school hand-rolled tuples

#if hasFeature(Embedded)
public extension HTMLBuilder {
static func buildBlock<V0: HTML, V1: HTML>(_ v0: V0, _ v1: V1) -> _HTMLTuple2<V0, V1> {
return _HTMLTuple2(v0: v0, v1: v1)
}

static func buildBlock<V0: HTML, V1: HTML, V2: HTML>(_ v0: V0, _ v1: V1, _ v2: V2) -> _HTMLTuple3<V0, V1, V2> {
return _HTMLTuple3(v0: v0, v1: v1, v2: v2)
}

static func buildBlock<V0: HTML, V1: HTML, V2: HTML, V3: HTML>(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3) -> _HTMLTuple4<V0, V1, V2, V3> {
return _HTMLTuple4(v0: v0, v1: v1, v2: v2, v3: v3)
}

static func buildBlock<V0: HTML, V1: HTML, V2: HTML, V3: HTML, V4: HTML>(_ v0: V0, _ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4) -> _HTMLTuple5<V0, V1, V2, V3, V4> {
return _HTMLTuple5(v0: v0, v1: v1, v2: v2, v3: v3, v4: v4)
}
}

public struct _HTMLTuple2<V0: HTML, V1: HTML>: HTML {
public let v0: V0
public let v1: V1
}

public struct _HTMLTuple3<V0: HTML, V1: HTML, V2: HTML>: HTML {
public let v0: V0
public let v1: V1
public let v2: V2
}

public struct _HTMLTuple4<V0: HTML, V1: HTML, V2: HTML, V3: HTML>: HTML {
public let v0: V0
public let v1: V1
public let v2: V2
public let v3: V3
}

public struct _HTMLTuple5<V0: HTML, V1: HTML, V2: HTML, V3: HTML, V4: HTML>: HTML {
public let v0: V0
public let v1: V1
public let v2: V2
public let v3: V3
public let v4: V4
}
#endif
Loading

0 comments on commit c1fdbe3

Please sign in to comment.