Skip to content

Commit

Permalink
Implement Script view. (#84)
Browse files Browse the repository at this point in the history
Part of #25
  • Loading branch information
jverkoey authored Aug 5, 2024
1 parent 7c2f996 commit 0187aea
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``Script``

## Topics

### Supporting types

- ``ScriptExecutionMode``
4 changes: 4 additions & 0 deletions Sources/Slipstream/Documentation.docc/Views/W3C/W3CViews.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ The complete W3C HTML elements standard can be found [here](https://html.spec.wh

- ``Linebreak``
- ``Link``

### Scripting

- <doc:Script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
import SwiftSoup

/// A view that represents various kinds of metadata that cannot be expressed
/// using the ``Title``, ``Base``, ``Stylesheet``, ``Style``, and ``Script`` views.
/// using the ``Title``, ``Base``, ``Stylesheet``, and ``Script`` views.
///
/// ```swift
/// struct MySiteMetadata: View {
Expand Down
72 changes: 72 additions & 0 deletions Sources/Slipstream/W3C/Elements/Scripting/Script.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Foundation

import SwiftSoup

/// Constants defining how ``Script`` views initialized with a url should
/// be executed.
///
/// - SeeAlso: W3C [`body`](https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer) specification.
@available(iOS 17.0, macOS 14.0, *)
public enum ScriptExecutionMode: String {
/// The script will be fetched in parallel to document parsing and
/// evaluated as soon as it is available (potentially before parsing
/// completes).
case async

/// The script will be fetched in parallel and evaluated when the
/// page has finished parsing.
case `defer`
}

/// A view that represents a script.
///
/// ```swift
/// struct MySiteContent: View {
/// var body: some View {
/// Body {
/// Script(URL(string: "/js/main.js"), executionMode: .async)
/// }
/// }
/// }
/// ```
///
/// - SeeAlso: W3C [`script`](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element) specification.
@available(iOS 17.0, macOS 14.0, *)
public struct Script: View {
/// Creates a script view pointing to a URL.
public init(_ url: URL?, executionMode: ScriptExecutionMode? = nil) {
self.storage = .url(url, executionMode: executionMode)
}

/// Creates a script view with inline source.
public init(_ source: String) {
self.storage = .inline(source)
}

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {

switch storage {
case .url(let url, let executionMode):
guard let url else {
return
}
let element = try container.appendElement("script")
try element.attr("src", url.absoluteString)
if let executionMode {
try element.attr(executionMode.rawValue, "")
}
case .inline(let string):
let element = try container.appendElement("script")
// DataNode is required to avoid escaping the script source.
try element.appendChild(DataNode(string, container.getBaseUri()))
}
}

private enum Storage {
case url(URL?, executionMode: ScriptExecutionMode?)
case inline(String)
}

private let storage: Storage
}
7 changes: 7 additions & 0 deletions Tests/SlipstreamTests/Sites/CatalogSiteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private struct CatalogSite: View {
.textColor(.red, darkness: 800, condition: .dark)
.padding(.horizontal, 48, condition: .startingAt(.large) + .hover)
.animation(.easeInOut(duration: 0.3))

Script(URL(string: "/main.js"), executionMode: .async)
Script("""
alert("Hello, world!");
""")
}
.id("root")
}
Expand Down Expand Up @@ -93,6 +98,8 @@ struct CatalogSiteTests {
<h6 class="my-8 text-white hidden">Heading 6</h6>
</div>
</div>
<script src="/main.js" async></script>
<script>alert("Hello, world!");</script>
</body>
</html>
""")
Expand Down
23 changes: 23 additions & 0 deletions Tests/SlipstreamTests/W3C/ScriptTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation
import Testing

import Slipstream

struct ScriptTests {
@Test func nilURL() throws {
try #expect(renderHTML(Script(nil)) == "")
}

@Test func url() throws {
try #expect(renderHTML(Script(URL(string: "/main.js"))) == #"<script src="/main.js"></script>"#)

try #expect(renderHTML(Script(URL(string: "/main.js"), executionMode: .async)) == #"<script src="/main.js" async></script>"#)
try #expect(renderHTML(Script(URL(string: "/main.js"), executionMode: .defer)) == #"<script src="/main.js" defer></script>"#)
}

@Test func source() throws {
try #expect(renderHTML(Script("""
alert("Hello, world!");
""")) == #"<script>alert("Hello, world!");</script>"#)
}
}

0 comments on commit 0187aea

Please sign in to comment.