Skip to content

Commit

Permalink
Added a Provider, unsafeVariable and other improvments
Browse files Browse the repository at this point in the history
  • Loading branch information
MatsMoll committed Mar 11, 2019
1 parent 656f37a commit fb1e139
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 53 deletions.
160 changes: 160 additions & 0 deletions Package.resolved
@@ -0,0 +1,160 @@
{
"object": {
"pins": [
{
"package": "Console",
"repositoryURL": "https://github.com/vapor/console.git",
"state": {
"branch": null,
"revision": "d6cf07af59ae63cd95c4b5f98cf1f25627750fd1",
"version": "3.1.0"
}
},
{
"package": "Core",
"repositoryURL": "https://github.com/vapor/core.git",
"state": {
"branch": null,
"revision": "439d6dcd6c520451ae30d39b2ca9f2aba96c22f4",
"version": "3.7.0"
}
},
{
"package": "Crypto",
"repositoryURL": "https://github.com/vapor/crypto.git",
"state": {
"branch": null,
"revision": "45bb12d13cdec80dbd1cc0685ea002e51ab83439",
"version": "3.3.2"
}
},
{
"package": "DatabaseKit",
"repositoryURL": "https://github.com/vapor/database-kit.git",
"state": {
"branch": null,
"revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4",
"version": "1.3.3"
}
},
{
"package": "HTTP",
"repositoryURL": "https://github.com/vapor/http.git",
"state": {
"branch": null,
"revision": "b57005e0de30ba36372ac41bfce1ac12b2bc3272",
"version": "3.1.8"
}
},
{
"package": "Multipart",
"repositoryURL": "https://github.com/vapor/multipart.git",
"state": {
"branch": null,
"revision": "bd7736c5f28e48ed8b683dcc9df3dcd346064c2b",
"version": "3.0.3"
}
},
{
"package": "Routing",
"repositoryURL": "https://github.com/vapor/routing.git",
"state": {
"branch": null,
"revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c",
"version": "3.0.2"
}
},
{
"package": "Service",
"repositoryURL": "https://github.com/vapor/service.git",
"state": {
"branch": null,
"revision": "4907311d7d7f609365982fa302b8b17ffdeb46da",
"version": "1.0.1"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "87dbd0216c47ea2e7ddb1b545271b716e03b943e",
"version": "1.13.1"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
"state": {
"branch": null,
"revision": "0f3999f3e3c359cc74480c292644c3419e44a12f",
"version": "1.4.0"
}
},
{
"package": "swift-nio-ssl-support",
"repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git",
"state": {
"branch": null,
"revision": "c02eec4e0e6d351cd092938cf44195a8e669f555",
"version": "1.0.0"
}
},
{
"package": "swift-nio-zlib-support",
"repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
"state": {
"branch": null,
"revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
"version": "1.0.0"
}
},
{
"package": "TemplateKit",
"repositoryURL": "https://github.com/vapor/template-kit.git",
"state": {
"branch": null,
"revision": "aff2d6fc65bfd04579b0201b31a8d6720239c1cf",
"version": "1.1.1"
}
},
{
"package": "URLEncodedForm",
"repositoryURL": "https://github.com/vapor/url-encoded-form.git",
"state": {
"branch": null,
"revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c",
"version": "1.0.5"
}
},
{
"package": "Validation",
"repositoryURL": "https://github.com/vapor/validation.git",
"state": {
"branch": null,
"revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6",
"version": "2.1.1"
}
},
{
"package": "Vapor",
"repositoryURL": "https://github.com/vapor/vapor",
"state": {
"branch": null,
"revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6",
"version": "3.3.0"
}
},
{
"package": "WebSocket",
"repositoryURL": "https://github.com/vapor/websocket.git",
"state": {
"branch": null,
"revision": "21eb4773e25a8ff96fe347a31fe106900a69fa6a",
"version": "1.1.1"
}
}
]
},
"version": 1
}
5 changes: 4 additions & 1 deletion Package.swift
Expand Up @@ -14,13 +14,16 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),

// For creating the providers, HTTP response and Request extension
.package(url: "https://github.com/vapor/vapor", from: "3.3.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "HTMLKit",
dependencies: []),
dependencies: ["Vapor"]),
.testTarget(
name: "HTMLKitTests",
dependencies: ["HTMLKit"]),
Expand Down
44 changes: 28 additions & 16 deletions README.md
Expand Up @@ -3,15 +3,24 @@
Render **lightning fast** HTML templates in a *typesafe* way!
By using Swift's powerful language features and a pre-rendering algorithm, will HTMLKit render insanely fast templates but also be able to catch bugs that otherwise might occur with other templating options.

## Swift PM
## Getting Started

Add the following in your `Package.swift` file
```swift
.package(url: "https://github.com/vapor-community/HTMLKit.git", from: "1.0.0"),
```
And register the provider and the different templates with in `configure.swift`
```swift
var renderer = HTMLRenderer()
try renderer.add(template: MyTemplates())

try services.register(HTMLKitProvider())
services.register(renderer)
```

## How fast is HTMLKit? ⚡
## Some benchmarks? ⚡

As mentioned HTMLKit is extremely fast, but exactly how fast?
As mentioned HTMLKit is extremely fast since it pre-renders most of the template, and uses `KeyPath`'s instead of decoding the context with `Codable`. But how much will faster will this make the rendering?
By using the *Leaf* templating language as a benchmark, HTMLKit was **150x** faster, and compared to *Pointfree* **16-25x** faster.

The *Leaf* template used was a fairly complex template and HTMLKit rendered 128 documents in *0.00548 sec*.
Expand All @@ -22,9 +31,8 @@ The *Leaf* template used was a fairly complex template and HTMLKit rendered 128

Let's get started with the two main protocols to know.

- `TemplateBuilder`: This is a protocol making it easy to render HTML views by giving you access to a lot of helper functions.
- `ContextualTemplate`: This is a protocol conforms to `TemplateBuilder` but also needs a `Context` to render. This could be a struct, protocol etc.
- `StaticView`: This is a protocol conforms to `TemplateBuilder` but needs no `Context` to render.
- `ContextualTemplate`: This is a protocol making it easy to render HTML views by giving you access to a lot of helper functions. But this needs a `Context` to render. This could be a struct, protocol etc.
- `StaticView`: This is a protocol conforms to `ContextualTemplate` but needs no `Context` to render.

When creating a view, it is recommend to use either `StaticView` of `ContextualTemplate`, since the `HTMLRenderer` has functions that is tailored for these two protocols.

Expand Down Expand Up @@ -74,7 +82,7 @@ Bellow is an example of how to render a view with a context:
var renderer = HTMLRenderer()
try renderer.add(template: SimpleView())
...
try renderer.render(SimpleView.self, with: .init(value: "hello world"))
try req.renderer().render(SimpleView.self, with: .init(value: "hello world"))
```
This would render:
```html
Expand Down Expand Up @@ -188,25 +196,29 @@ This would render:

## More Feature Syntax

- Variables:
* A variable that is HTML safe = `variable(\.title)`
* A variable that do not escape anything = `variable(\.title, escaping: .unsafeNone)`
* A variable that is not in the current `Context` (example get a variable in superview) `unsafeVariable(in: BaseTemplate.self, for: \.title)` or `unsafeVariable(... escaping: .unsafeNone)`
- Embed:
* Where the sub view's `Context` is equal to the super view's `Context` = `embed(SubView())`
* Where the sub view's `Context`is variable of the super view's `Context`= `embed(Subview(), withPath: \.subContext)`
- ForEach:
* Where the super view's `Context` is an array of the sub view's `Context` = `forEachInContext(render: SubView())`
* Where the super view's `Context` is an array of the sub view's `Context` = `forEach(render: SubView())`
* Where the super view's `Context` variable is an array of the sub view's `Context` = `forEach(in \.subContext, render: Subview()`
- If:
* If value is a `Bool` = `runtimeIf(\.bool, div.child(...))`
* If value is `nil` = `runtimeIf(isNil: \.optional, div.child(...))`
* If value is not `nil` = `runtimeIf(isNotNil: \.optional, div.child(...))`
* If value conforms to `Equatable` = `runtimeIf(\.int == 2, div.child(...))`
* If value conforms to `Equatable` = `runtimeIf(\.int != 2, div.child(...))`
* If value conforms to `Comparable` = `runtimeIf(\.int < 2, div.child(...))`
* If value conforms to `Comparable` = `runtimeIf(\.int > 2, div.child(...))`
* If the context is a `Bool` = `runtimeIf(\.bool, div.child(...))`
* If the context is `nil` = `runtimeIf(isNil: \.optional, div.child(...))`
* If the context is not `nil` = `runtimeIf(isNotNil: \.optional, div.child(...))`
* If the context conforms to `Equatable` = `runtimeIf(\.int == 2, div.child(...))`
* If the context conforms to `Equatable` = `runtimeIf(\.int != 2, div.child(...))`
* If the context conforms to `Comparable` = `runtimeIf(\.int < 2, div.child(...))`
* If the context conforms to `Comparable` = `runtimeIf(\.int > 2, div.child(...))`
* It is also possible to use `||` and `&&` for more complex statments. `runtimeIf(\.bool || \.otherBool, div.child(...))`
* `elseIf`has the same statments and is a method on the returned if. `runtimeIf(...).elseIf(...)`
* and lastly `else`. `runtimeIf(...).else(div.child(...))`
- Dynamic Attributes
* In order to add attributes based on the `Context` you will need to use `dynamic(div)`. This will create a dynamic node and after this you can use if's. `dynamic(div).if(\.bool, add: .checked)`
* In order to add attributes based on the `Context` you can use if's. `div.if(\.bool, add: .checked)`

Add custom node types by extending `TemplateBuilder`.

Expand Down
68 changes: 68 additions & 0 deletions Sources/HTMLKit/Conditions.swift
Expand Up @@ -287,6 +287,40 @@ public func && <Root>(lhs: TemplateIF<Root>.Condition, rhs: TemplateIF<Root>.Con
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhs, second: rhs))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func && <Root>(lhs: KeyPath<Root.Context, Bool>, rhs: TemplateIF<Root>.Condition) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let lhsCondition = BoolCondition<Root>(path: lhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhsCondition, second: rhs))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func && <Root>(lhs: TemplateIF<Root>.Condition, rhs: KeyPath<Root.Context, Bool>) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let rhsCondition = BoolCondition<Root>(path: rhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhs, second: rhsCondition))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func && <Root>(lhs: KeyPath<Root.Context, Bool>, rhs: KeyPath<Root.Context, Bool>) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let lhsCondition = BoolCondition<Root>(path: lhs)
let rhsCondition = BoolCondition<Root>(path: rhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhsCondition, second: rhsCondition))
}

/// Creates a `OrCondition` condition
///
/// - Parameters:
Expand All @@ -296,3 +330,37 @@ public func && <Root>(lhs: TemplateIF<Root>.Condition, rhs: TemplateIF<Root>.Con
public func || <Root>(lhs: TemplateIF<Root>.Condition, rhs: TemplateIF<Root>.Condition) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
return TemplateIF<Root>.Condition(condition: OrCondition(first: lhs, second: rhs))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func || <Root>(lhs: KeyPath<Root.Context, Bool>, rhs: TemplateIF<Root>.Condition) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let lhsCondition = BoolCondition<Root>(path: lhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhsCondition, second: rhs))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func || <Root>(lhs: TemplateIF<Root>.Condition, rhs: KeyPath<Root.Context, Bool>) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let rhsCondition = BoolCondition<Root>(path: rhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhs, second: rhsCondition))
}

/// Creates a `AndCondition` condition
///
/// - Parameters:
/// - lhs: The key path
/// - rhs: The constant value
/// - Returns: A `TemplateIF.Condition` object
public func || <Root>(lhs: KeyPath<Root.Context, Bool>, rhs: KeyPath<Root.Context, Bool>) -> TemplateIF<Root>.Condition where Root: ContextualTemplate {
let lhsCondition = BoolCondition<Root>(path: lhs)
let rhsCondition = BoolCondition<Root>(path: rhs)
return TemplateIF<Root>.Condition(condition: AndCondition(first: lhsCondition, second: rhsCondition))
}
13 changes: 13 additions & 0 deletions Sources/HTMLKit/HTMLDocument.swift
@@ -0,0 +1,13 @@


struct HTMLDocument: StaticView {

let content: CompiledTemplate

func build() -> CompiledTemplate {
return [
doctype("html"),
content
]
}
}
26 changes: 26 additions & 0 deletions Sources/HTMLKit/HTMLKitProvider.swift
@@ -0,0 +1,26 @@
//
// HTMLTemplateConfig.swift
// HTMLKit
//
// Created by Mats Mollestad on 11/03/2019.
//

import Service

extension HTMLRenderer: Service {}

/// A provider for the HTMLKit Library
public final class HTMLKitProvider: Provider {

public init() {}

public func register(_ services: inout Services) throws {
services.register { (container) in
return try container.make(HTMLRenderer.self)
}
}

public func didBoot(_ container: Container) throws -> EventLoopFuture<Void> {
return .done(on: container)
}
}

0 comments on commit fb1e139

Please sign in to comment.