Skip to content

Commit 32c78f6

Browse files
added HTMLOptimizedLiteral
- enabled by default, it is supposed to improve dynamic HTML performance - plus other breaking changes
1 parent 0172302 commit 32c78f6

19 files changed

+341
-262
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,4 @@ public macro anyRawHTML(
7979
lookupFiles: [StaticString] = [],
8080
minify: Bool = false,
8181
_ innerHTML: Sendable...
82-
) -> any Sendable = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")
83-
84-
// MARK: HTML Context
85-
@freestanding(expression)
86-
macro htmlContext<T: Sendable>(
87-
_ value: () -> T
88-
) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLContext")
82+
) -> any Sendable = #externalMacro(module: "HTMLKitMacros", type: "RawHTML")

Sources/HTMLKitMacros/EscapeHTML.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ enum EscapeHTML: ExpressionMacro {
1313
encoding: .string,
1414
representation: .literalOptimized,
1515
key: "",
16-
arguments: node.arguments,
17-
escape: true,
18-
escapeAttributes: true
16+
arguments: node.arguments
1917
)
2018
return "\"\(raw: HTMLKitUtilities.escapeHTML(context: &c))\""
2119
}

Sources/HTMLKitMacros/HTMLContext.swift

Lines changed: 0 additions & 12 deletions
This file was deleted.

Sources/HTMLKitMacros/HTMLKitMacros.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ struct HTMLKitMacros: CompilerPlugin {
77
let providingMacros:[any Macro.Type] = [
88
HTMLElementMacro.self,
99
EscapeHTML.self,
10-
RawHTML.self,
11-
HTMLContext.self
10+
RawHTML.self
1211
]
1312
}

Sources/HTMLKitMacros/RawHTML.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ enum RawHTML: ExpressionMacro {
1313
encoding: .string,
1414
representation: .literalOptimized,
1515
key: "",
16-
arguments: node.arguments,
17-
escape: false,
18-
escapeAttributes: false
16+
arguments: node.arguments
1917
)
2018
return "\"\(raw: HTMLKitUtilities.rawHTML(context: &c))\""
2119
}

Sources/HTMLKitParse/ExpandHTMLMacro.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ import SwiftSyntax
66
extension HTMLKitUtilities {
77
public static func expandHTMLMacro(context: HTMLExpansionContext) throws -> ExprSyntax {
88
var context = context
9-
return try expandHTMLMacro(context: &context)
10-
}
11-
public static func expandHTMLMacro(context: inout HTMLExpansionContext) throws -> ExprSyntax {
129
let (string, encoding) = expandMacro(context: &context)
1310
let encodingResult = encodingResult(context: context, node: context.expansion, string: string, for: encoding)
1411
let expandedResult = representationResult(encoding: encoding, encodedResult: encodingResult, representation: context.representation)
@@ -90,7 +87,7 @@ extension HTMLKitUtilities {
9087
break
9188
case .literalOptimized:
9289
if encoding == .string {
93-
// TODO: implement
90+
return optimizedLiteral(encodedResult: encodedResult)
9491
} else {
9592
// TODO: show compiler diagnostic
9693
}
@@ -112,6 +109,36 @@ extension HTMLKitUtilities {
112109
return encodedResult
113110
}
114111

112+
static func optimizedLiteral(encodedResult: String) -> String {
113+
let regex = try! Regex.init("( \\+ String\\(describing: [\\w\\s\\(\\)\\[\\]]+\\) \\+ )")
114+
var interpolation = encodedResult.matches(of: regex)
115+
guard !interpolation.isEmpty else {
116+
return encodedResult
117+
}
118+
var index = encodedResult.startIndex
119+
var reserveCapacity = 0
120+
var values = [String]()
121+
while !interpolation.isEmpty {
122+
let interp = interpolation.removeFirst()
123+
let left = encodedResult[index..<interp.range.lowerBound]
124+
values.append("StaticString(\(left))")
125+
126+
var interpolationValue = encodedResult[interp.range]
127+
interpolationValue.removeFirst(3)
128+
interpolationValue.removeLast(3)
129+
values.append(String(interpolationValue))
130+
index = interp.range.upperBound
131+
132+
reserveCapacity += left.count + 32
133+
}
134+
if index < encodedResult.endIndex {
135+
let slice = encodedResult[index...]
136+
reserveCapacity += slice.count
137+
values.append("StaticString(\(slice))")
138+
}
139+
return "HTMLOptimizedLiteral(reserveCapacity: \(reserveCapacity)).render((\n\(values.joined(separator: ",\n"))\n))"
140+
}
141+
115142
static func chunks(
116143
encoding: HTMLEncoding,
117144
encodedResult: String,

Sources/HTMLKitParse/HTMLExpansionResult.swift

Lines changed: 0 additions & 4 deletions
This file was deleted.

Sources/HTMLKitParse/ParseArguments.swift

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extension HTMLKitUtilities {
88
otherAttributes: [String:String] = [:]
99
) -> ElementData {
1010
var context = context
11-
return parseArguments(context: &context)
11+
return parseArguments(context: &context, otherAttributes: otherAttributes)
1212
}
1313

1414
public static func parseArguments(
@@ -20,44 +20,52 @@ extension HTMLKitUtilities {
2020
var innerHTML = [Sendable]()
2121
var trailingSlash = false
2222
for element in context.arguments.children(viewMode: .all) {
23-
if let child = element.labeled {
24-
context.key = ""
25-
if let key = child.label?.text {
26-
context.key = key
27-
switch key {
28-
case "encoding":
29-
context.encoding = parseEncoding(expression: child.expression) ?? .string
30-
case "representation":
31-
context.representation = parseRepresentation(expr: child.expression) ?? .literalOptimized
32-
case "lookupFiles":
33-
context.lookupFiles = Set(child.expression.array!.elements.compactMap({ $0.expression.stringLiteral?.string(encoding: context.encoding) }))
34-
case "attributes":
35-
(globalAttributes, trailingSlash) = parseGlobalAttributes(context: context, array: child.expression.array!.elements)
36-
default:
37-
context.key = otherAttributes[key] ?? key
38-
if let test = HTMLAttribute.Extra.parse(context: context, expr: child.expression) {
39-
attributes[key] = test
40-
} else if let literal = parseLiteralValue(context: context, expr: child.expression) {
41-
switch literal {
42-
case .boolean(let b): attributes[key] = b
43-
case .string, .interpolation, .interpolationDescribed:
44-
attributes[key] = literal.value(key: key, escape: context.escape, escapeAttributes: context.escapeAttributes)
45-
case .int(let i): attributes[key] = i
46-
case .float(let f): attributes[key] = f
47-
case .arrayOfLiterals(let literals):
48-
attributes[key] = literals.compactMap({ $0.value(key: key, escape: context.escape, escapeAttributes: context.escapeAttributes) }).joined()
49-
case .array:
50-
switch literal.escapeArray() {
51-
case .array(let a): attributes[key] = a
52-
default: break
53-
}
23+
guard let child = element.labeled else { continue }
24+
context.key = ""
25+
if let key = child.label?.text {
26+
context.key = key
27+
switch key {
28+
case "encoding":
29+
context.encoding = parseEncoding(expression: child.expression) ?? .string
30+
case "representation":
31+
context.representation = parseRepresentation(expr: child.expression) ?? .literalOptimized
32+
case "lookupFiles":
33+
if let elements = child.expression.array?.elements {
34+
context.lookupFiles = Set(elements.compactMap({ $0.expression.stringLiteral?.string(encoding: context.encoding) }))
35+
}
36+
case "attributes":
37+
if let elements = child.expression.array?.elements {
38+
(globalAttributes, trailingSlash) = parseGlobalAttributes(context: context, array: elements)
39+
}
40+
default:
41+
context.key = otherAttributes[key] ?? key
42+
if let test = HTMLAttribute.Extra.parse(context: context, expr: child.expression) {
43+
attributes[key] = test
44+
} else if let literal = parseLiteral(context: context, expr: child.expression) {
45+
switch literal {
46+
case .boolean(let b):
47+
attributes[key] = b
48+
case .string, .interpolation:
49+
attributes[key] = literal.value(key: key, escape: context.escape, escapeAttributes: context.escapeAttributes)
50+
case .int(let i):
51+
attributes[key] = i
52+
case .float(let f):
53+
attributes[key] = f
54+
case .arrayOfLiterals(let literals):
55+
attributes[key] = literals.compactMap({ $0.value(key: key, escape: context.escape, escapeAttributes: context.escapeAttributes) }).joined()
56+
case .array:
57+
switch literal.escapeArray() {
58+
case .array(let a):
59+
attributes[key] = a
60+
default:
61+
break
5462
}
5563
}
5664
}
57-
// inner html
58-
} else if let inner_html = parseInnerHTML(context: context, child: child) {
59-
innerHTML.append(inner_html)
6065
}
66+
// inner html
67+
} else if let inner_html = parseInnerHTML(context: context, expr: child.expression) {
68+
innerHTML.append(inner_html)
6169
}
6270
}
6371
if let statements = context.trailingClosure?.statements {

Sources/HTMLKitParse/ParseData.swift

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,42 @@ import SwiftSyntax
66

77
extension HTMLKitUtilities {
88
// MARK: Escape HTML
9-
public static func escapeHTML(context: inout HTMLExpansionContext) -> String {
9+
public static func escapeHTML(
10+
context: HTMLExpansionContext
11+
) -> String {
12+
var context = context
13+
return escapeHTML(context: &context)
14+
}
15+
public static func escapeHTML(
16+
context: inout HTMLExpansionContext
17+
) -> String {
1018
context.escape = true
1119
context.escapeAttributes = true
1220
context.elementsRequireEscaping = true
13-
return html(context: context)
21+
return html(
22+
context: context
23+
)
1424
}
1525

1626
// MARK: Raw HTML
17-
public static func rawHTML(context: inout HTMLExpansionContext) -> String {
27+
public static func rawHTML(
28+
context: HTMLExpansionContext
29+
) -> String {
30+
var context = context
31+
return rawHTML(context: &context)
32+
}
33+
public static func rawHTML(
34+
context: inout HTMLExpansionContext
35+
) -> String {
1836
context.escape = false
1937
context.escapeAttributes = false
2038
context.elementsRequireEscaping = false
21-
return html(context: context)
39+
return html(
40+
context: context
41+
)
2242
}
2343

2444
// MARK: HTML
25-
/// - Parameters:
26-
/// - context: `HTMLExpansionContext`.
2745
public static func html(
2846
context: HTMLExpansionContext
2947
) -> String {
@@ -32,21 +50,20 @@ extension HTMLKitUtilities {
3250
var innerHTML = ""
3351
innerHTML.reserveCapacity(children.count)
3452
for e in children {
35-
if let child = e.labeled {
36-
if let key = child.label?.text {
37-
switch key {
38-
case "encoding": context.encoding = parseEncoding(expression: child.expression) ?? .string
39-
case "representation": context.representation = parseRepresentation(expr: child.expression) ?? .literalOptimized
40-
case "minify": context.minify = child.expression.boolean(context) ?? false
41-
default: break
42-
}
43-
} else if var c = HTMLKitUtilities.parseInnerHTML(context: context, child: child) {
44-
if var element = c as? HTMLElement {
45-
element.escaped = context.elementsRequireEscaping
46-
c = element
47-
}
48-
innerHTML += String(describing: c)
53+
guard let child = e.labeled else { continue }
54+
if let key = child.label?.text {
55+
switch key {
56+
case "encoding": context.encoding = parseEncoding(expression: child.expression) ?? .string
57+
case "representation": context.representation = parseRepresentation(expr: child.expression) ?? .literalOptimized
58+
case "minify": context.minify = child.expression.boolean(context) ?? false
59+
default: break
60+
}
61+
} else if var c = HTMLKitUtilities.parseInnerHTML(context: context, expr: child.expression) {
62+
if var element = c as? HTMLElement {
63+
element.escaped = context.elementsRequireEscaping
64+
c = element
4965
}
66+
innerHTML += String(describing: c)
5067
}
5168
}
5269
if context.minify {

Sources/HTMLKitParse/ParseInnerHTML.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ import HTMLKitUtilities
44
import SwiftSyntax
55

66
extension HTMLKitUtilities {
7-
public static func parseInnerHTML(
8-
context: HTMLExpansionContext,
9-
child: LabeledExprSyntax
10-
) -> (any Sendable)? {
11-
return parseInnerHTML(context: context, expr: child.expression)
12-
}
13-
147
public static func parseInnerHTML(
158
context: HTMLExpansionContext,
169
expr: ExprSyntax
@@ -31,9 +24,9 @@ extension HTMLKitUtilities {
3124
default:
3225
return "" // TODO: fix?
3326
}
34-
} else if let element = parse_element(context: context, expr: expr) {
27+
} else if let element = parseElement(context: context, expr: expr) {
3528
return element
36-
} else if let literal = parseLiteralValue(context: context, expr: expr) {
29+
} else if let literal = parseLiteral(context: context, expr: expr) {
3730
return literal.value(key: "", escape: context.escape, escapeAttributes: context.escapeAttributes)
3831
} else {
3932
unallowedExpression(context: context, node: expr)
@@ -44,7 +37,10 @@ extension HTMLKitUtilities {
4437

4538
// MARK: Parse element
4639
extension HTMLKitUtilities {
47-
public static func parse_element(context: HTMLExpansionContext, expr: ExprSyntax) -> (any HTMLElement)? {
40+
public static func parseElement(
41+
context: HTMLExpansionContext,
42+
expr: ExprSyntax
43+
) -> (any HTMLElement)? {
4844
guard let function = expr.functionCall else { return nil }
4945
return HTMLElementValueType.parseElement(context: context, function)
5046
}

0 commit comments

Comments
 (0)