Skip to content

Commit 3c03ace

Browse files
can now minify directly from a raw html macro, also...
- failed trying to utilize `SwiftLexicalLookup` for compile-time optimizations
1 parent 64bd071 commit 3c03ace

File tree

7 files changed

+77
-13
lines changed

7 files changed

+77
-13
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public macro uncheckedHTML<T: CustomStringConvertible & Sendable>(
6565
public macro rawHTML<T: CustomStringConvertible & Sendable>(
6666
encoding: HTMLEncoding = .string,
6767
lookupFiles: [StaticString] = [],
68+
minify: Bool = false,
6869
_ innerHTML: CustomStringConvertible & Sendable...
6970
) -> T = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")
7071

@@ -75,5 +76,10 @@ public macro rawHTML<T: CustomStringConvertible & Sendable>(
7576
public macro anyRawHTML(
7677
encoding: HTMLEncoding = .string,
7778
lookupFiles: [StaticString] = [],
79+
minify: Bool = false,
7880
_ innerHTML: CustomStringConvertible & Sendable...
79-
) -> any CustomStringConvertible & Sendable = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")
81+
) -> any CustomStringConvertible & Sendable = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")
82+
83+
// MARK: HTML Context
84+
//@freestanding(expression)
85+
//public macro htmlContext() = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ import SwiftSyntaxMacros
1313

1414
enum HTMLElementMacro : ExpressionMacro {
1515
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
16-
let ignoresCompilerWarnings:Bool = node.macroName.text == "uncheckedHTML"
17-
return try HTMLKitUtilities.expandHTMLMacro(context: HTMLExpansionContext(context: context, expansion: node, ignoresCompilerWarnings: ignoresCompilerWarnings, encoding: .string, key: "", arguments: node.arguments))
16+
let c = HTMLExpansionContext(
17+
context: context,
18+
expansion: node,
19+
ignoresCompilerWarnings: node.macroName.text == "uncheckedHTML",
20+
encoding: .string,
21+
key: "",
22+
arguments: node.arguments,
23+
escape: true,
24+
escapeAttributes: true
25+
)
26+
return try HTMLKitUtilities.expandHTMLMacro(context: c)
1827
}
1928
}

Sources/HTMLKitParse/ParseData.swift

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ extension HTMLKitUtilities {
4848
for e in children {
4949
if let child = e.labeled {
5050
if let key = child.label?.text {
51-
if key == "encoding" {
52-
context.encoding = parseEncoding(expression: child.expression) ?? .string
51+
switch key {
52+
case "encoding": context.encoding = parseEncoding(expression: child.expression) ?? .string
53+
case "minify": context.minify = child.expression.boolean(context) ?? false
54+
default: break
5355
}
5456
} else if var c = HTMLKitUtilities.parseInnerHTML(context: context, child: child) {
5557
if var element = c as? HTMLElement {
@@ -60,6 +62,8 @@ extension HTMLKitUtilities {
6062
}
6163
}
6264
}
65+
guard !context.minify else { return minify(html: innerHTML) }
66+
innerHTML.replace(HTMLKitUtilities.lineFeedPlaceholder, with: "\\n")
6367
return innerHTML
6468
}
6569

@@ -68,10 +72,12 @@ extension HTMLKitUtilities {
6872
let (string, encoding):(String, HTMLEncoding) = expandMacro(context: context)
6973
return "\(raw: encodingResult(context: context, node: context.expansion, string: string, for: encoding))"
7074
}
71-
private static func encodingResult(context: HTMLExpansionContext, node: MacroExpansionExprSyntax, string: String, for encoding: HTMLEncoding) -> String {
72-
func bytes<T: FixedWidthInteger>(_ bytes: [T]) -> String {
73-
return "[" + bytes.map({ "\($0)" }).joined(separator: ",") + "]"
74-
}
75+
private static func encodingResult(
76+
context: HTMLExpansionContext,
77+
node: MacroExpansionExprSyntax,
78+
string: String,
79+
for encoding: HTMLEncoding
80+
) -> String {
7581
switch encoding {
7682
case .utf8Bytes:
7783
guard hasNoInterpolation(context, node, string) else { return "" }
@@ -97,6 +103,14 @@ extension HTMLKitUtilities {
97103
return encoded.replacingOccurrences(of: "$0", with: string)
98104
}
99105
}
106+
private static func bytes<T: FixedWidthInteger>(_ bytes: [T]) -> String {
107+
var string:String = "["
108+
for b in bytes {
109+
string += "\(b),"
110+
}
111+
string.removeLast()
112+
return string.isEmpty ? "[]" : string + "]"
113+
}
100114
private static func hasNoInterpolation(_ context: HTMLExpansionContext, _ node: MacroExpansionExprSyntax, _ string: String) -> Bool {
101115
guard string.firstRange(of: try! Regex("\\((.*)\\)")) == nil else {
102116
if !context.ignoresCompilerWarnings {
@@ -323,7 +337,12 @@ extension HTMLKitUtilities {
323337
// MARK: Expand Macro
324338
static func expandMacro(context: HTMLExpansionContext) -> (String, HTMLEncoding) {
325339
let data = HTMLKitUtilities.parseArguments(context: context)
326-
return (data.innerHTML.map({ String(describing: $0) }).joined(), data.encoding)
340+
var string:String = ""
341+
for v in data.innerHTML {
342+
string += String(describing: v)
343+
}
344+
string.replace(HTMLKitUtilities.lineFeedPlaceholder, with: "\\n")
345+
return (string, data.encoding)
327346
}
328347
}
329348

Sources/HTMLKitUtilities/HTMLExpansionContext.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public struct HTMLExpansionContext : @unchecked Sendable {
2727
/// Complete file paths used for looking up interpolation (when trying to promote to an equivalent `StaticString`).
2828
public var lookupFiles:Set<String>
2929

30+
public package(set) var minify:Bool
31+
3032
public package(set) var ignoresCompilerWarnings:Bool
3133

3234
public package(set) var escape:Bool
@@ -41,6 +43,7 @@ public struct HTMLExpansionContext : @unchecked Sendable {
4143
key: String,
4244
arguments: LabeledExprListSyntax,
4345
lookupFiles: Set<String> = [],
46+
minify: Bool = false,
4447
escape: Bool = true,
4548
escapeAttributes: Bool = true,
4649
elementsRequireEscaping: Bool = true
@@ -52,6 +55,7 @@ public struct HTMLExpansionContext : @unchecked Sendable {
5255
self.key = key
5356
self.arguments = arguments
5457
self.lookupFiles = lookupFiles
58+
self.minify = minify
5559
self.escape = escape
5660
self.escapeAttributes = escapeAttributes
5761
self.elementsRequireEscaping = elementsRequireEscaping

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@
55
// Created by Evan Anderson on 9/19/24.
66
//
77

8+
#if canImport(FoundationEssentials)
9+
import FoundationEssentials
10+
#elseif canImport(Foundation)
11+
import Foundation
12+
#endif
13+
814
#if canImport(SwiftSyntax)
915
import SwiftSyntax
1016
#endif
1117

1218
// MARK: HTMLKitUtilities
1319
public enum HTMLKitUtilities {
20+
@usableFromInline
21+
package static let lineFeedPlaceholder:String = {
22+
#if canImport(FoundationEssentials) || canImport(Foundation)
23+
return "%\(UUID())%"
24+
#else
25+
return "%HTMLKitLineFeed\(Int.random(in: Int.min...Int.max))%"
26+
#endif
27+
}()
1428
}
1529

1630
// MARK: Escape HTML
@@ -81,10 +95,13 @@ extension StringLiteralExprSyntax {
8195
@inlinable
8296
package func string(encoding: HTMLEncoding) -> String {
8397
if openingQuote.debugDescription.hasPrefix("multilineStringQuote") {
84-
var value = segments.compactMap({ $0.as(StringSegmentSyntax.self)?.content.text }).joined()
98+
var value:String = ""
99+
for segment in segments {
100+
value += segment.as(StringSegmentSyntax.self)?.content.text ?? ""
101+
}
85102
switch encoding {
86103
case .string:
87-
value.replace("\n", with: "\\n")
104+
value.replace("\n", with: HTMLKitUtilities.lineFeedPlaceholder)
88105
value.replace("\"", with: "\\\"")
89106
default:
90107
break

Sources/HTMLKitUtilities/Minify.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ extension HTMLKitUtilities {
4848
}
4949
result += originalTag
5050
if let next = tagRanges.get(tagIndex + 1) {
51-
let slice = html[tagRange.upperBound..<next.lowerBound]
51+
var slice = html[tagRange.upperBound..<next.lowerBound]
5252
if !(Self.defaultPreservedWhitespaceTags.contains(tag) || preservingWhitespaceForTags.contains(tag)) {
53+
slice.replace(HTMLKitUtilities.lineFeedPlaceholder, with: "")
5354
for char in slice {
5455
if !(char.isWhitespace || char.isNewline) {
5556
result.append(char)

Tests/HTMLKitTests/RawHTMLTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ struct RawHTMLTests {
1616
var result:String = #rawHTML("<!DOCTYPE html><html>dude&dude</html>")
1717
#expect(expected == result)
1818

19+
result = #rawHTML(minify: true, """
20+
<!DOCTYPE html>
21+
<html>
22+
dude&dude
23+
</html>
24+
""")
25+
#expect(expected == result)
26+
1927
expected = "<!DOCTYPE html><html><p>test<></p>dude&dude bro&amp;bro</html>"
2028
result = #html(html(#anyRawHTML(p("test<>"), "dude&dude"), " bro&bro"))
2129
#expect(expected == result)

0 commit comments

Comments
 (0)