From 552539bed1f54bd9dbbab90224d890d0c8a69701 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 1 Oct 2020 16:06:16 +0300 Subject: [PATCH] Add View Engine Benchmarks: https://github.com/kataras/iris/tree/master/_benchmarks/view --- _benchmarks/README.md | 3 +- _benchmarks/view/README.md | 56 +++++++++++++++ _benchmarks/view/ace/main.go | 24 +++++++ _benchmarks/view/ace/views/index.ace | 2 + _benchmarks/view/ace/views/layouts/main.ace | 8 +++ .../view/ace/views/partials/footer.ace | 2 + _benchmarks/view/amber/main.go | 26 +++++++ _benchmarks/view/amber/views/index.amber | 5 ++ .../view/amber/views/layouts/main.amber | 8 +++ .../view/amber/views/partials/footer.amber | 2 + _benchmarks/view/blocks/main.go | 26 +++++++ _benchmarks/view/blocks/views/index.html | 1 + .../view/blocks/views/layouts/main.html | 1 + .../view/blocks/views/partials/footer.html | 1 + _benchmarks/view/chart.png | Bin 0 -> 10183 bytes _benchmarks/view/django/main.go | 25 +++++++ _benchmarks/view/django/views/index.html | 1 + .../view/django/views/layouts/main.html | 1 + .../view/django/views/partials/footer.html | 1 + _benchmarks/view/handlebars/main.go | 23 ++++++ _benchmarks/view/handlebars/views/index.html | 1 + .../view/handlebars/views/layouts/main.html | 1 + .../handlebars/views/partials/footer.html | 1 + _benchmarks/view/html/main.go | 24 +++++++ _benchmarks/view/html/views/index.html | 1 + _benchmarks/view/html/views/layouts/main.html | 1 + .../view/html/views/partials/footer.html | 1 + _benchmarks/view/jet/main.go | 25 +++++++ _benchmarks/view/jet/views/index.jet | 1 + _benchmarks/view/jet/views/layouts/main.jet | 1 + .../view/jet/views/partials/footer.jet | 1 + _benchmarks/view/pug/main.go | 26 +++++++ _benchmarks/view/pug/views/index.pug | 5 ++ _benchmarks/view/pug/views/layouts/main.pug | 8 +++ .../view/pug/views/partials/footer.pug | 2 + _benchmarks/view/tests.yml | 27 +++++++ _examples/README.md | 9 +++ _examples/view/layout/ace/main.go | 26 +++++++ _examples/view/layout/ace/views/index.ace | 2 + .../view/layout/ace/views/layouts/main.ace | 8 +++ .../view/layout/ace/views/partials/footer.ace | 2 + _examples/view/layout/amber/main.go | 25 +++++++ _examples/view/layout/amber/views/index.amber | 5 ++ .../layout/amber/views/layouts/main.amber | 8 +++ .../layout/amber/views/partials/footer.amber | 2 + _examples/view/layout/blocks/main.go | 26 +++++++ _examples/view/layout/blocks/views/index.html | 2 + .../layout/blocks/views/layouts/main.html | 11 +++ .../layout/blocks/views/partials/footer.html | 2 + _examples/view/layout/django/main.go | 25 +++++++ _examples/view/layout/django/views/index.html | 6 ++ .../layout/django/views/layouts/main.html | 10 +++ .../layout/django/views/partials/footer.html | 2 + _examples/view/layout/handlebars/main.go | 24 +++++++ .../view/layout/handlebars/views/index.html | 2 + .../layout/handlebars/views/layouts/main.html | 10 +++ .../handlebars/views/partials/footer.html | 2 + _examples/view/layout/html/main.go | 24 +++++++ _examples/view/layout/html/views/index.html | 2 + .../view/layout/html/views/layouts/main.html | 11 +++ .../layout/html/views/partials/footer.html | 2 + _examples/view/layout/jet/main.go | 26 +++++++ _examples/view/layout/jet/views/index.jet | 5 ++ .../view/layout/jet/views/layouts/main.jet | 9 +++ .../view/layout/jet/views/partials/footer.jet | 2 + _examples/view/layout/pug/main.go | 25 +++++++ _examples/view/layout/pug/views/index.pug | 5 ++ .../view/layout/pug/views/layouts/main.pug | 8 +++ .../view/layout/pug/views/partials/footer.pug | 2 + view/README.md | 2 + view/ace.go | 42 ++++++++--- view/amber.go | 66 ++++++++++++++---- view/django.go | 2 +- view/html.go | 35 ++++++---- view/jet.go | 35 +++++++--- 75 files changed, 807 insertions(+), 47 deletions(-) create mode 100644 _benchmarks/view/README.md create mode 100644 _benchmarks/view/ace/main.go create mode 100644 _benchmarks/view/ace/views/index.ace create mode 100644 _benchmarks/view/ace/views/layouts/main.ace create mode 100644 _benchmarks/view/ace/views/partials/footer.ace create mode 100644 _benchmarks/view/amber/main.go create mode 100644 _benchmarks/view/amber/views/index.amber create mode 100644 _benchmarks/view/amber/views/layouts/main.amber create mode 100644 _benchmarks/view/amber/views/partials/footer.amber create mode 100644 _benchmarks/view/blocks/main.go create mode 100644 _benchmarks/view/blocks/views/index.html create mode 100644 _benchmarks/view/blocks/views/layouts/main.html create mode 100644 _benchmarks/view/blocks/views/partials/footer.html create mode 100644 _benchmarks/view/chart.png create mode 100644 _benchmarks/view/django/main.go create mode 100644 _benchmarks/view/django/views/index.html create mode 100644 _benchmarks/view/django/views/layouts/main.html create mode 100644 _benchmarks/view/django/views/partials/footer.html create mode 100644 _benchmarks/view/handlebars/main.go create mode 100644 _benchmarks/view/handlebars/views/index.html create mode 100644 _benchmarks/view/handlebars/views/layouts/main.html create mode 100644 _benchmarks/view/handlebars/views/partials/footer.html create mode 100644 _benchmarks/view/html/main.go create mode 100644 _benchmarks/view/html/views/index.html create mode 100644 _benchmarks/view/html/views/layouts/main.html create mode 100644 _benchmarks/view/html/views/partials/footer.html create mode 100644 _benchmarks/view/jet/main.go create mode 100644 _benchmarks/view/jet/views/index.jet create mode 100644 _benchmarks/view/jet/views/layouts/main.jet create mode 100644 _benchmarks/view/jet/views/partials/footer.jet create mode 100644 _benchmarks/view/pug/main.go create mode 100644 _benchmarks/view/pug/views/index.pug create mode 100644 _benchmarks/view/pug/views/layouts/main.pug create mode 100644 _benchmarks/view/pug/views/partials/footer.pug create mode 100644 _benchmarks/view/tests.yml create mode 100644 _examples/view/layout/ace/main.go create mode 100644 _examples/view/layout/ace/views/index.ace create mode 100644 _examples/view/layout/ace/views/layouts/main.ace create mode 100644 _examples/view/layout/ace/views/partials/footer.ace create mode 100644 _examples/view/layout/amber/main.go create mode 100644 _examples/view/layout/amber/views/index.amber create mode 100644 _examples/view/layout/amber/views/layouts/main.amber create mode 100644 _examples/view/layout/amber/views/partials/footer.amber create mode 100644 _examples/view/layout/blocks/main.go create mode 100644 _examples/view/layout/blocks/views/index.html create mode 100644 _examples/view/layout/blocks/views/layouts/main.html create mode 100644 _examples/view/layout/blocks/views/partials/footer.html create mode 100644 _examples/view/layout/django/main.go create mode 100644 _examples/view/layout/django/views/index.html create mode 100644 _examples/view/layout/django/views/layouts/main.html create mode 100644 _examples/view/layout/django/views/partials/footer.html create mode 100644 _examples/view/layout/handlebars/main.go create mode 100644 _examples/view/layout/handlebars/views/index.html create mode 100644 _examples/view/layout/handlebars/views/layouts/main.html create mode 100644 _examples/view/layout/handlebars/views/partials/footer.html create mode 100644 _examples/view/layout/html/main.go create mode 100644 _examples/view/layout/html/views/index.html create mode 100644 _examples/view/layout/html/views/layouts/main.html create mode 100644 _examples/view/layout/html/views/partials/footer.html create mode 100644 _examples/view/layout/jet/main.go create mode 100644 _examples/view/layout/jet/views/index.jet create mode 100644 _examples/view/layout/jet/views/layouts/main.jet create mode 100644 _examples/view/layout/jet/views/partials/footer.jet create mode 100644 _examples/view/layout/pug/main.go create mode 100644 _examples/view/layout/pug/views/index.pug create mode 100644 _examples/view/layout/pug/views/layouts/main.pug create mode 100644 _examples/view/layout/pug/views/partials/footer.pug diff --git a/_benchmarks/README.md b/_benchmarks/README.md index 717a2654..3a64195c 100644 --- a/_benchmarks/README.md +++ b/_benchmarks/README.md @@ -1,3 +1,4 @@ # Benchmarks -Moved to . +- [HTTP/2 Benchmarks](https://github.com/kataras/server-benchmarks#benchmarks) +- [View Engine Benchmarks](./view) diff --git a/_benchmarks/view/README.md b/_benchmarks/view/README.md new file mode 100644 index 00000000..62c075c6 --- /dev/null +++ b/_benchmarks/view/README.md @@ -0,0 +1,56 @@ +# View Engine Benchmarks + +Benchmark between all 8 supported template parsers. + +Amber, Ace and Pug parsers minifies the template before render. So, to have a fair benchmark, we must make sure that the byte amount of the total response body is exactly the same across all. Therefore, all other template files are minified too. + +![Benchmarks Chart Graph](chart.png) + +> Last updated: Oct 1, 2020 at 12:46pm (UTC) + +## System + +| | | +|----|:---| +| Processor | Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz | +| RAM | 15.85 GB | +| OS | Microsoft Windows 10 Pro | +| [Bombardier](https://github.com/codesenberg/bombardier) | v1.2.4 | +| [Go](https://golang.org) | go1.15.2 | + +## Terminology + +**Name** is the name of the framework(or router) used under a particular test. + +**Reqs/sec** is the avg number of total requests could be processed per second (the higher the better). + +**Latency** is the amount of time it takes from when a request is made by the client to the time it takes for the response to get back to that client (the smaller the better). + +**Throughput** is the rate of production or the rate at which data are transferred (the higher the better, it depends from response length (body + headers). + +**Time To Complete** is the total time (in seconds) the test completed (the smaller the better). + +## Results + +### Test:Template Layout, Partial and Data + +📖 Fires 1000000 requests with 125 concurrent clients. It receives HTML response. The server handler sets some template **data** and renders a template file which consists of a **layout** and a **partial** footer. + +| Name | Language | Reqs/sec | Latency | Throughput | Time To Complete | +|------|:---------|:---------|:--------|:-----------|:-----------------| +| [Amber](./amber) | Go |125698 |0.99ms |44.67MB |7.96s | +| [Blocks](./blocks) | Go |123974 |1.01ms |43.99MB |8.07s | +| [Django](./django) | Go |118831 |1.05ms |42.17MB |8.41s | +| [Handlebars](./handlebars) | Go |101214 |1.23ms |35.91MB |9.88s | +| [Pug](./pug) | Go |89002 |1.40ms |31.81MB |11.24s | +| [Ace](./ace) | Go |64782 |1.93ms |22.98MB |15.44s | +| [HTML](./html) | Go |53918 |2.32ms |19.13MB |18.55s | +| [Jet](./jet) | Go |4829 |25.88ms |1.71MB |207.07s | + +## How to Run + +```sh +$ go get -u github.com/kataras/server-benchmarks +$ go get -u github.com/codesenberg/bombardier +$ server-benchmarks --wait-run=3s -o ./results +``` diff --git a/_benchmarks/view/ace/main.go b/_benchmarks/view/ace/main.go new file mode 100644 index 00000000..b5f32ea6 --- /dev/null +++ b/_benchmarks/view/ace/main.go @@ -0,0 +1,24 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + // By default Ace engine minifies the template before render. + app.RegisterView(iris.Ace("./views", ".ace").SetIndent("")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_benchmarks/view/ace/views/index.ace b/_benchmarks/view/ace/views/index.ace new file mode 100644 index 00000000..38b81f08 --- /dev/null +++ b/_benchmarks/view/ace/views/index.ace @@ -0,0 +1,2 @@ +h1 Index Body +h3 Message: {{.Message}} \ No newline at end of file diff --git a/_benchmarks/view/ace/views/layouts/main.ace b/_benchmarks/view/ace/views/layouts/main.ace new file mode 100644 index 00000000..d90a58fe --- /dev/null +++ b/_benchmarks/view/ace/views/layouts/main.ace @@ -0,0 +1,8 @@ += doctype html +html + head + title {{.Title}} + body + {{ yield }} + footer + = include partials/footer.ace . diff --git a/_benchmarks/view/ace/views/partials/footer.ace b/_benchmarks/view/ace/views/partials/footer.ace new file mode 100644 index 00000000..aefe45fd --- /dev/null +++ b/_benchmarks/view/ace/views/partials/footer.ace @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 {{.FooterText}} \ No newline at end of file diff --git a/_benchmarks/view/amber/main.go b/_benchmarks/view/amber/main.go new file mode 100644 index 00000000..742fd668 --- /dev/null +++ b/_benchmarks/view/amber/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + // By default Amber engine minifies the template before render. + app.RegisterView(iris.Amber("./views", ".amber")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Amber this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_benchmarks/view/amber/views/index.amber b/_benchmarks/view/amber/views/index.amber new file mode 100644 index 00000000..e92604dc --- /dev/null +++ b/_benchmarks/view/amber/views/index.amber @@ -0,0 +1,5 @@ +extends layouts/main.amber + +block content + h1 Index Body + h3 Message: #{Message} \ No newline at end of file diff --git a/_benchmarks/view/amber/views/layouts/main.amber b/_benchmarks/view/amber/views/layouts/main.amber new file mode 100644 index 00000000..b0b751a8 --- /dev/null +++ b/_benchmarks/view/amber/views/layouts/main.amber @@ -0,0 +1,8 @@ +doctype html +html + head + title #{Title} + body + block content + footer + #{render("partials/footer.amber", $)} \ No newline at end of file diff --git a/_benchmarks/view/amber/views/partials/footer.amber b/_benchmarks/view/amber/views/partials/footer.amber new file mode 100644 index 00000000..a202d2c0 --- /dev/null +++ b/_benchmarks/view/amber/views/partials/footer.amber @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 #{FooterText} \ No newline at end of file diff --git a/_benchmarks/view/blocks/main.go b/_benchmarks/view/blocks/main.go new file mode 100644 index 00000000..5a9957ed --- /dev/null +++ b/_benchmarks/view/blocks/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Blocks("./views", ".html")) + // Note, in Blocks engine, layouts + // are used by their base names, the + // blocks.LayoutDir(layoutDir) defaults to "./layouts". + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("main") + ctx.View("index", data) +} diff --git a/_benchmarks/view/blocks/views/index.html b/_benchmarks/view/blocks/views/index.html new file mode 100644 index 00000000..8fafce11 --- /dev/null +++ b/_benchmarks/view/blocks/views/index.html @@ -0,0 +1 @@ +

Index Body

Message: {{.Message}}

\ No newline at end of file diff --git a/_benchmarks/view/blocks/views/layouts/main.html b/_benchmarks/view/blocks/views/layouts/main.html new file mode 100644 index 00000000..934080b2 --- /dev/null +++ b/_benchmarks/view/blocks/views/layouts/main.html @@ -0,0 +1 @@ +{{.Title}}{{ template "content" . }}
{{ partial "partials/footer" .}}
\ No newline at end of file diff --git a/_benchmarks/view/blocks/views/partials/footer.html b/_benchmarks/view/blocks/views/partials/footer.html new file mode 100644 index 00000000..b2ddf793 --- /dev/null +++ b/_benchmarks/view/blocks/views/partials/footer.html @@ -0,0 +1 @@ +

Footer Partial

{{.FooterText}}

\ No newline at end of file diff --git a/_benchmarks/view/chart.png b/_benchmarks/view/chart.png new file mode 100644 index 0000000000000000000000000000000000000000..0403fea8dadb1e7190ab5678c1bcfc4d1429f653 GIT binary patch literal 10183 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 QdMu}o8z0L*ivHvO0h0+sRsaA1 literal 0 HcmV?d00001 diff --git a/_benchmarks/view/django/main.go b/_benchmarks/view/django/main.go new file mode 100644 index 00000000..4c625c49 --- /dev/null +++ b/_benchmarks/view/django/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Django("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Django this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_benchmarks/view/django/views/index.html b/_benchmarks/view/django/views/index.html new file mode 100644 index 00000000..b2e6c4f7 --- /dev/null +++ b/_benchmarks/view/django/views/index.html @@ -0,0 +1 @@ +{% extends "layouts/main.html" %}{% block content %}

Index Body

Message: {{Message}}

{% endblock %} \ No newline at end of file diff --git a/_benchmarks/view/django/views/layouts/main.html b/_benchmarks/view/django/views/layouts/main.html new file mode 100644 index 00000000..ca27a3a4 --- /dev/null +++ b/_benchmarks/view/django/views/layouts/main.html @@ -0,0 +1 @@ +{{Title}}{% block content %} {% endblock %}
{% include "../partials/footer.html" %}
\ No newline at end of file diff --git a/_benchmarks/view/django/views/partials/footer.html b/_benchmarks/view/django/views/partials/footer.html new file mode 100644 index 00000000..8de644d7 --- /dev/null +++ b/_benchmarks/view/django/views/partials/footer.html @@ -0,0 +1 @@ +

Footer Partial

{{FooterText}}

\ No newline at end of file diff --git a/_benchmarks/view/handlebars/main.go b/_benchmarks/view/handlebars/main.go new file mode 100644 index 00000000..0cd5c701 --- /dev/null +++ b/_benchmarks/view/handlebars/main.go @@ -0,0 +1,23 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Handlebars("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_benchmarks/view/handlebars/views/index.html b/_benchmarks/view/handlebars/views/index.html new file mode 100644 index 00000000..14d3812a --- /dev/null +++ b/_benchmarks/view/handlebars/views/index.html @@ -0,0 +1 @@ +

Index Body

Message: {{Message}}

\ No newline at end of file diff --git a/_benchmarks/view/handlebars/views/layouts/main.html b/_benchmarks/view/handlebars/views/layouts/main.html new file mode 100644 index 00000000..50a79f87 --- /dev/null +++ b/_benchmarks/view/handlebars/views/layouts/main.html @@ -0,0 +1 @@ +{{Title}}{{ yield }}
{{ render "partials/footer.html" .}}
\ No newline at end of file diff --git a/_benchmarks/view/handlebars/views/partials/footer.html b/_benchmarks/view/handlebars/views/partials/footer.html new file mode 100644 index 00000000..8de644d7 --- /dev/null +++ b/_benchmarks/view/handlebars/views/partials/footer.html @@ -0,0 +1 @@ +

Footer Partial

{{FooterText}}

\ No newline at end of file diff --git a/_benchmarks/view/html/main.go b/_benchmarks/view/html/main.go new file mode 100644 index 00000000..214c4b11 --- /dev/null +++ b/_benchmarks/view/html/main.go @@ -0,0 +1,24 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + app.RegisterView(iris.HTML("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_benchmarks/view/html/views/index.html b/_benchmarks/view/html/views/index.html new file mode 100644 index 00000000..8fafce11 --- /dev/null +++ b/_benchmarks/view/html/views/index.html @@ -0,0 +1 @@ +

Index Body

Message: {{.Message}}

\ No newline at end of file diff --git a/_benchmarks/view/html/views/layouts/main.html b/_benchmarks/view/html/views/layouts/main.html new file mode 100644 index 00000000..379a5ffa --- /dev/null +++ b/_benchmarks/view/html/views/layouts/main.html @@ -0,0 +1 @@ +{{.Title}}{{ yield }}
{{ render "partials/footer.html" }}
\ No newline at end of file diff --git a/_benchmarks/view/html/views/partials/footer.html b/_benchmarks/view/html/views/partials/footer.html new file mode 100644 index 00000000..b2ddf793 --- /dev/null +++ b/_benchmarks/view/html/views/partials/footer.html @@ -0,0 +1 @@ +

Footer Partial

{{.FooterText}}

\ No newline at end of file diff --git a/_benchmarks/view/jet/main.go b/_benchmarks/view/jet/main.go new file mode 100644 index 00000000..c5888759 --- /dev/null +++ b/_benchmarks/view/jet/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Jet("./views", ".jet")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Jet this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_benchmarks/view/jet/views/index.jet b/_benchmarks/view/jet/views/index.jet new file mode 100644 index 00000000..ff230019 --- /dev/null +++ b/_benchmarks/view/jet/views/index.jet @@ -0,0 +1 @@ +{{ extends "../layouts/main.jet" }}{{ block documentBody() }}

Index Body

Message: {{.Message}}

{{ end }} \ No newline at end of file diff --git a/_benchmarks/view/jet/views/layouts/main.jet b/_benchmarks/view/jet/views/layouts/main.jet new file mode 100644 index 00000000..672f4e6d --- /dev/null +++ b/_benchmarks/view/jet/views/layouts/main.jet @@ -0,0 +1 @@ +{{.Title}}{{ yield documentBody() }}
{{ include "../partials/footer.jet" . }}
\ No newline at end of file diff --git a/_benchmarks/view/jet/views/partials/footer.jet b/_benchmarks/view/jet/views/partials/footer.jet new file mode 100644 index 00000000..b2ddf793 --- /dev/null +++ b/_benchmarks/view/jet/views/partials/footer.jet @@ -0,0 +1 @@ +

Footer Partial

{{.FooterText}}

\ No newline at end of file diff --git a/_benchmarks/view/pug/main.go b/_benchmarks/view/pug/main.go new file mode 100644 index 00000000..fd4e340e --- /dev/null +++ b/_benchmarks/view/pug/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + // Ace engine minifies the template before render. + app.RegisterView(iris.Pug("./views", ".pug")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Pug this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_benchmarks/view/pug/views/index.pug b/_benchmarks/view/pug/views/index.pug new file mode 100644 index 00000000..26ddcbc5 --- /dev/null +++ b/_benchmarks/view/pug/views/index.pug @@ -0,0 +1,5 @@ +extends layouts/main.pug + +block content + h1 Index Body + h3 Message: {{.Message}} \ No newline at end of file diff --git a/_benchmarks/view/pug/views/layouts/main.pug b/_benchmarks/view/pug/views/layouts/main.pug new file mode 100644 index 00000000..c9c46c2d --- /dev/null +++ b/_benchmarks/view/pug/views/layouts/main.pug @@ -0,0 +1,8 @@ +doctype html +html + head + title {{.Title}} + body + block content + footer + include ../partials/footer.pug \ No newline at end of file diff --git a/_benchmarks/view/pug/views/partials/footer.pug b/_benchmarks/view/pug/views/partials/footer.pug new file mode 100644 index 00000000..aefe45fd --- /dev/null +++ b/_benchmarks/view/pug/views/partials/footer.pug @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 {{.FooterText}} \ No newline at end of file diff --git a/_benchmarks/view/tests.yml b/_benchmarks/view/tests.yml new file mode 100644 index 00000000..00ee224c --- /dev/null +++ b/_benchmarks/view/tests.yml @@ -0,0 +1,27 @@ +- Name: Template Layout, Partial and Data + Description: > + Fires {{.NumberOfRequests}} requests with {{.NumberOfConnections}} concurrent clients. + It receives HTML response. + The server handler sets some template **data** and renders a + template file which consists of a **layout** and a **partial** footer. + NumberOfRequests: 1000000 + NumberOfConnections: 125 + Method: GET + URL: http://localhost:8080 + Envs: + - Name: Ace + Dir: ./ace + - Name: Amber + Dir: ./amber + - Name: Blocks + Dir: ./blocks + - Name: Django + Dir: ./django + - Name: Handlebars + Dir: ./handlebars + - Name: HTML + Dir: ./html + - Name: Jet + Dir: ./jet + - Name: Pug + Dir: ./pug diff --git a/_examples/README.md b/_examples/README.md index 8c107700..13bfefdd 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -108,6 +108,15 @@ * [Upload Multiple Files](file-server/upload-files/main.go) * View * [Overview](view/overview/main.go) + * [Layout](view/layout) + * [Ace](view/layout/ace) + * [Amber](view/layout/amber) + * [Blocks](view/layout/blocks) + * [Django](view/layout/django) + * [Handlebars](view/layout/handlebars) + * [HTML](view/layout/html) + * [Jet](view/layout/jet) + * [Pug](view/layout/pug) * [Basic](view/template_html_0/main.go) * [A simple Layout](view/template_html_1/main.go) * [Layouts: `yield` and `render` tmpl funcs](view/template_html_2/main.go) diff --git a/_examples/view/layout/ace/main.go b/_examples/view/layout/ace/main.go new file mode 100644 index 00000000..a73cebe5 --- /dev/null +++ b/_examples/view/layout/ace/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + // By default Ace minifies the template before render, + // using the SetIndent method, we make it to match + // the rest of the template results. + app.RegisterView(iris.Ace("./views", ".ace").SetIndent(" ")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_examples/view/layout/ace/views/index.ace b/_examples/view/layout/ace/views/index.ace new file mode 100644 index 00000000..38b81f08 --- /dev/null +++ b/_examples/view/layout/ace/views/index.ace @@ -0,0 +1,2 @@ +h1 Index Body +h3 Message: {{.Message}} \ No newline at end of file diff --git a/_examples/view/layout/ace/views/layouts/main.ace b/_examples/view/layout/ace/views/layouts/main.ace new file mode 100644 index 00000000..010cb9fe --- /dev/null +++ b/_examples/view/layout/ace/views/layouts/main.ace @@ -0,0 +1,8 @@ += doctype html +html + head + title {{.Title}} + body + {{ yield }} + footer + = include partials/footer.ace . \ No newline at end of file diff --git a/_examples/view/layout/ace/views/partials/footer.ace b/_examples/view/layout/ace/views/partials/footer.ace new file mode 100644 index 00000000..aefe45fd --- /dev/null +++ b/_examples/view/layout/ace/views/partials/footer.ace @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 {{.FooterText}} \ No newline at end of file diff --git a/_examples/view/layout/amber/main.go b/_examples/view/layout/amber/main.go new file mode 100644 index 00000000..c0b95b3b --- /dev/null +++ b/_examples/view/layout/amber/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Amber("./views", ".amber")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Amber this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_examples/view/layout/amber/views/index.amber b/_examples/view/layout/amber/views/index.amber new file mode 100644 index 00000000..e92604dc --- /dev/null +++ b/_examples/view/layout/amber/views/index.amber @@ -0,0 +1,5 @@ +extends layouts/main.amber + +block content + h1 Index Body + h3 Message: #{Message} \ No newline at end of file diff --git a/_examples/view/layout/amber/views/layouts/main.amber b/_examples/view/layout/amber/views/layouts/main.amber new file mode 100644 index 00000000..2dbc577a --- /dev/null +++ b/_examples/view/layout/amber/views/layouts/main.amber @@ -0,0 +1,8 @@ +doctype html +html + head + title #{Title} + body + block content + footer + #{render("partials/footer.amber", $)} \ No newline at end of file diff --git a/_examples/view/layout/amber/views/partials/footer.amber b/_examples/view/layout/amber/views/partials/footer.amber new file mode 100644 index 00000000..a202d2c0 --- /dev/null +++ b/_examples/view/layout/amber/views/partials/footer.amber @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 #{FooterText} \ No newline at end of file diff --git a/_examples/view/layout/blocks/main.go b/_examples/view/layout/blocks/main.go new file mode 100644 index 00000000..5a9957ed --- /dev/null +++ b/_examples/view/layout/blocks/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Blocks("./views", ".html")) + // Note, in Blocks engine, layouts + // are used by their base names, the + // blocks.LayoutDir(layoutDir) defaults to "./layouts". + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("main") + ctx.View("index", data) +} diff --git a/_examples/view/layout/blocks/views/index.html b/_examples/view/layout/blocks/views/index.html new file mode 100644 index 00000000..f35b8eac --- /dev/null +++ b/_examples/view/layout/blocks/views/index.html @@ -0,0 +1,2 @@ +

Index Body

+

Message: {{.Message}}

\ No newline at end of file diff --git a/_examples/view/layout/blocks/views/layouts/main.html b/_examples/view/layout/blocks/views/layouts/main.html new file mode 100644 index 00000000..e033deef --- /dev/null +++ b/_examples/view/layout/blocks/views/layouts/main.html @@ -0,0 +1,11 @@ + + + {{.Title}} + + +{{ template "content" . }} +
+{{ partial "partials/footer" .}} +
+ + \ No newline at end of file diff --git a/_examples/view/layout/blocks/views/partials/footer.html b/_examples/view/layout/blocks/views/partials/footer.html new file mode 100644 index 00000000..6ea1a8a2 --- /dev/null +++ b/_examples/view/layout/blocks/views/partials/footer.html @@ -0,0 +1,2 @@ +

Footer Partial

+

{{.FooterText}}

\ No newline at end of file diff --git a/_examples/view/layout/django/main.go b/_examples/view/layout/django/main.go new file mode 100644 index 00000000..4c625c49 --- /dev/null +++ b/_examples/view/layout/django/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Django("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Django this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_examples/view/layout/django/views/index.html b/_examples/view/layout/django/views/index.html new file mode 100644 index 00000000..e960e91d --- /dev/null +++ b/_examples/view/layout/django/views/index.html @@ -0,0 +1,6 @@ +{% extends "layouts/main.html" %} + +{% block content %} +

Index Body

+

Message: {{Message}}

+{% endblock %} \ No newline at end of file diff --git a/_examples/view/layout/django/views/layouts/main.html b/_examples/view/layout/django/views/layouts/main.html new file mode 100644 index 00000000..043e7c22 --- /dev/null +++ b/_examples/view/layout/django/views/layouts/main.html @@ -0,0 +1,10 @@ + + + {{Title}} + + + {% block content %} {% endblock %} + +
{% include "../partials/footer.html" %}
+ + \ No newline at end of file diff --git a/_examples/view/layout/django/views/partials/footer.html b/_examples/view/layout/django/views/partials/footer.html new file mode 100644 index 00000000..c078f0d6 --- /dev/null +++ b/_examples/view/layout/django/views/partials/footer.html @@ -0,0 +1,2 @@ +

Footer Partial

+

{{FooterText}}

\ No newline at end of file diff --git a/_examples/view/layout/handlebars/main.go b/_examples/view/layout/handlebars/main.go new file mode 100644 index 00000000..8d30373d --- /dev/null +++ b/_examples/view/layout/handlebars/main.go @@ -0,0 +1,24 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + app.RegisterView(iris.Handlebars("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_examples/view/layout/handlebars/views/index.html b/_examples/view/layout/handlebars/views/index.html new file mode 100644 index 00000000..b6b44104 --- /dev/null +++ b/_examples/view/layout/handlebars/views/index.html @@ -0,0 +1,2 @@ +

Index Body

+

Message: {{Message}}

\ No newline at end of file diff --git a/_examples/view/layout/handlebars/views/layouts/main.html b/_examples/view/layout/handlebars/views/layouts/main.html new file mode 100644 index 00000000..dcb98620 --- /dev/null +++ b/_examples/view/layout/handlebars/views/layouts/main.html @@ -0,0 +1,10 @@ + + + {{Title}} + + + {{ yield }} + +
{{ render "partials/footer.html" .}}
+ + \ No newline at end of file diff --git a/_examples/view/layout/handlebars/views/partials/footer.html b/_examples/view/layout/handlebars/views/partials/footer.html new file mode 100644 index 00000000..c078f0d6 --- /dev/null +++ b/_examples/view/layout/handlebars/views/partials/footer.html @@ -0,0 +1,2 @@ +

Footer Partial

+

{{FooterText}}

\ No newline at end of file diff --git a/_examples/view/layout/html/main.go b/_examples/view/layout/html/main.go new file mode 100644 index 00000000..214c4b11 --- /dev/null +++ b/_examples/view/layout/html/main.go @@ -0,0 +1,24 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + app.RegisterView(iris.HTML("./views", ".html")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + ctx.ViewLayout("layouts/main") + ctx.View("index", data) +} diff --git a/_examples/view/layout/html/views/index.html b/_examples/view/layout/html/views/index.html new file mode 100644 index 00000000..f35b8eac --- /dev/null +++ b/_examples/view/layout/html/views/index.html @@ -0,0 +1,2 @@ +

Index Body

+

Message: {{.Message}}

\ No newline at end of file diff --git a/_examples/view/layout/html/views/layouts/main.html b/_examples/view/layout/html/views/layouts/main.html new file mode 100644 index 00000000..fe646117 --- /dev/null +++ b/_examples/view/layout/html/views/layouts/main.html @@ -0,0 +1,11 @@ + + + {{.Title}} + + +{{ yield }} +
+{{ render "partials/footer.html" }} +
+ + \ No newline at end of file diff --git a/_examples/view/layout/html/views/partials/footer.html b/_examples/view/layout/html/views/partials/footer.html new file mode 100644 index 00000000..6ea1a8a2 --- /dev/null +++ b/_examples/view/layout/html/views/partials/footer.html @@ -0,0 +1,2 @@ +

Footer Partial

+

{{.FooterText}}

\ No newline at end of file diff --git a/_examples/view/layout/jet/main.go b/_examples/view/layout/jet/main.go new file mode 100644 index 00000000..50350396 --- /dev/null +++ b/_examples/view/layout/jet/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + app.RegisterView(iris.Jet("./views", ".jet")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Jet this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_examples/view/layout/jet/views/index.jet b/_examples/view/layout/jet/views/index.jet new file mode 100644 index 00000000..5ecb4b58 --- /dev/null +++ b/_examples/view/layout/jet/views/index.jet @@ -0,0 +1,5 @@ +{{ extends "../layouts/main.jet" }} +{{ block documentBody() }} +

Index Body

+

Message: {{.Message}}

+{{ end }} \ No newline at end of file diff --git a/_examples/view/layout/jet/views/layouts/main.jet b/_examples/view/layout/jet/views/layouts/main.jet new file mode 100644 index 00000000..e896d1e0 --- /dev/null +++ b/_examples/view/layout/jet/views/layouts/main.jet @@ -0,0 +1,9 @@ + + + {{.Title}} + + + {{ yield documentBody() }} +
{{ include "../partials/footer.jet" . }}
+ + \ No newline at end of file diff --git a/_examples/view/layout/jet/views/partials/footer.jet b/_examples/view/layout/jet/views/partials/footer.jet new file mode 100644 index 00000000..6ea1a8a2 --- /dev/null +++ b/_examples/view/layout/jet/views/partials/footer.jet @@ -0,0 +1,2 @@ +

Footer Partial

+

{{.FooterText}}

\ No newline at end of file diff --git a/_examples/view/layout/pug/main.go b/_examples/view/layout/pug/main.go new file mode 100644 index 00000000..bb43e07d --- /dev/null +++ b/_examples/view/layout/pug/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + app.RegisterView(iris.Pug("./views", ".pug")) + + app.Get("/", index) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + data := iris.Map{ + "Title": "Page Title", + "FooterText": "Footer contents", + "Message": "Main contents", + } + + // On Pug this is ignored: ctx.ViewLayout("layouts/main") + // Layouts are only rendered from inside the index page itself + // using the "extends" keyword. + ctx.View("index", data) +} diff --git a/_examples/view/layout/pug/views/index.pug b/_examples/view/layout/pug/views/index.pug new file mode 100644 index 00000000..26ddcbc5 --- /dev/null +++ b/_examples/view/layout/pug/views/index.pug @@ -0,0 +1,5 @@ +extends layouts/main.pug + +block content + h1 Index Body + h3 Message: {{.Message}} \ No newline at end of file diff --git a/_examples/view/layout/pug/views/layouts/main.pug b/_examples/view/layout/pug/views/layouts/main.pug new file mode 100644 index 00000000..c9c46c2d --- /dev/null +++ b/_examples/view/layout/pug/views/layouts/main.pug @@ -0,0 +1,8 @@ +doctype html +html + head + title {{.Title}} + body + block content + footer + include ../partials/footer.pug \ No newline at end of file diff --git a/_examples/view/layout/pug/views/partials/footer.pug b/_examples/view/layout/pug/views/partials/footer.pug new file mode 100644 index 00000000..aefe45fd --- /dev/null +++ b/_examples/view/layout/pug/views/partials/footer.pug @@ -0,0 +1,2 @@ +h3 Footer Partial +h4 {{.FooterText}} \ No newline at end of file diff --git a/view/README.md b/view/README.md index 9807b329..bc547ac0 100644 --- a/view/README.md +++ b/view/README.md @@ -19,6 +19,8 @@ Parse using embedded assets, Layouts and Party-specific layout, Template Funcs, [List of Examples](https://github.com/kataras/iris/tree/master/_examples/view). +[Benchmarks](https://github.com/kataras/iris/tree/master/_benchmarks/view). + You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `Context.ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example. ## Overview diff --git a/view/ace.go b/view/ace.go index 83cee012..01c3182a 100644 --- a/view/ace.go +++ b/view/ace.go @@ -6,10 +6,28 @@ import ( "github.com/yosssi/ace" ) -// Ace returns a new ace view engine. +// AceEngine represents the Ace view engine. +// See the `Ace` package-level function for more. +type AceEngine struct { + *HTMLEngine + + indent string +} + +// SetIndent string used for indentation. +// Do NOT use tabs, only spaces characters. +// Defaults to minified response, no indentation. +func (s *AceEngine) SetIndent(indent string) *AceEngine { + s.indent = indent + return s +} + +// Ace returns a new Ace view engine. // It shares the same exactly logic with the // html view engine, it uses the same exactly configuration. // The given "extension" MUST begin with a dot. +// Ace minifies the response automatically unless +// SetIndent() method is set. // // Read more about the Ace Go Parser: https://github.com/yosssi/ace // @@ -17,13 +35,14 @@ import ( // Ace("./views", ".ace") or // Ace(iris.Dir("./views"), ".ace") or // Ace(AssetFile(), ".ace") for embedded data. -func Ace(fs interface{}, extension string) *HTMLEngine { - s := HTML(fs, extension) +func Ace(fs interface{}, extension string) *AceEngine { + s := &AceEngine{HTMLEngine: HTML(fs, extension), indent: ""} s.name = "Ace" funcs := make(map[string]interface{}, 0) once := new(sync.Once) + s.middleware = func(name string, text []byte) (contents string, err error) { once.Do(func() { // on first template parse, all funcs are given. for k, v := range emptyFuncs { @@ -43,17 +62,20 @@ func Ace(fs interface{}, extension string) *HTMLEngine { []*ace.File{}, ) - rslt, err := ace.ParseSource(src, nil) - if err != nil { - return "", err - } - - t, err := ace.CompileResult(name, rslt, &ace.Options{ + opts := &ace.Options{ Extension: extension[1:], FuncMap: funcs, DelimLeft: s.left, DelimRight: s.right, - }) + Indent: s.indent, + } + + rslt, err := ace.ParseSource(src, opts) + if err != nil { + return "", err + } + + t, err := ace.CompileResult(name, rslt, opts) if err != nil { return "", err } diff --git a/view/amber.go b/view/amber.go index 1e112e4e..7fddc252 100644 --- a/view/amber.go +++ b/view/amber.go @@ -1,6 +1,7 @@ package view import ( + "bytes" "fmt" "html/template" "io" @@ -9,6 +10,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "github.com/eknkc/amber" ) @@ -23,6 +25,7 @@ type AmberEngine struct { // rmu sync.RWMutex // locks for `ExecuteWiter` when `reload` is true. templateCache map[string]*template.Template + bufPool *sync.Pool Options amber.Options } @@ -32,6 +35,8 @@ var ( _ EngineFuncer = (*AmberEngine)(nil) ) +var amberOnce = new(uint32) + // Amber creates and returns a new amber view engine. // The given "extension" MUST begin with a dot. // @@ -40,6 +45,12 @@ var ( // Amber(iris.Dir("./views"), ".amber") or // Amber(AssetFile(), ".amber") for embedded data. func Amber(fs interface{}, extension string) *AmberEngine { + if atomic.LoadUint32(amberOnce) > 0 { + panic("Amber: cannot be registered twice as its internal implementation share the same template functions across instances.") + } else { + atomic.StoreUint32(amberOnce, 1) + } + fileSystem := getFS(fs) s := &AmberEngine{ fs: fileSystem, @@ -51,6 +62,20 @@ func Amber(fs interface{}, extension string) *AmberEngine { LineNumbers: false, VirtualFilesystem: fileSystem, }, + bufPool: &sync.Pool{New: func() interface{} { + return new(bytes.Buffer) + }}, + } + + builtinFuncs := template.FuncMap{ + "render": func(name string, binding interface{}) (template.HTML, error) { + result, err := s.executeTemplateBuf(name, binding) + return template.HTML(result), err + }, + } + + for k, v := range builtinFuncs { + amber.FuncMap[k] = v } return s @@ -86,6 +111,14 @@ func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine { return s } +// SetPrettyPrint if pretty printing is enabled. +// Pretty printing ensures that the output html is properly indented and in human readable form. +// Defaults to false, response is minified. +func (s *AmberEngine) SetPrettyPrint(pretty bool) *AmberEngine { + s.Options.PrettyPrint = true + return s +} + // AddFunc adds the function to the template's function map. // It is legal to overwrite elements of the default actions: // - url func(routeName string, args ...string) string @@ -157,18 +190,11 @@ func (s *AmberEngine) ParseTemplate(name string, contents []byte) error { name = strings.TrimPrefix(name, "/") - /* Sadly, this does not work, only builtin amber.FuncMap - can be executed as function, the rest are compiled as data (prepends a "call"), - relative code: - https://github.com/eknkc/amber/blob/cdade1c073850f4ffc70a829e31235ea6892853b/compiler.go#L771-L794 - - tmpl := template.New(name).Funcs(amber.FuncMap).Funcs(s.funcs) - if len(funcs) > 0 { - tmpl.Funcs(funcs) - } - - We can't add them as binding data of map type - because those data can be a struct by the caller and we don't want to messup. + /* + New(...).Funcs(s.builtinFuncs): + This won't work on amber, it loads only amber.FuncMap which is global. + Relative code: + https://github.com/eknkc/amber/blob/cdade1c073850f4ffc70a829e31235ea6892853b/compiler.go#L771-L794 */ tmpl := template.New(name).Funcs(amber.FuncMap) @@ -180,6 +206,22 @@ func (s *AmberEngine) ParseTemplate(name string, contents []byte) error { return err } +func (s *AmberEngine) executeTemplateBuf(name string, binding interface{}) (string, error) { + buf := s.bufPool.Get().(*bytes.Buffer) + buf.Reset() + + tmpl := s.fromCache(name) + if tmpl == nil { + s.bufPool.Put(buf) + return "", ErrNotExist{name, false} + } + + err := tmpl.ExecuteTemplate(buf, name, binding) + result := strings.TrimSuffix(buf.String(), "\n") // on amber it adds a new line. + s.bufPool.Put(buf) + return result, err +} + func (s *AmberEngine) fromCache(relativeName string) *template.Template { if s.reload { s.rmu.RLock() diff --git a/view/django.go b/view/django.go index fbb83cb6..2f8bc4c8 100644 --- a/view/django.go +++ b/view/django.go @@ -286,7 +286,7 @@ func (s *DjangoEngine) fromCache(relativeName string) *pongo2.Template { // ExecuteWriter executes a templates and write its results to the w writer // layout here is useless. -func (s *DjangoEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error { +func (s *DjangoEngine) ExecuteWriter(w io.Writer, filename string, _ string, bindingData interface{}) error { // re-parse the templates if reload is enabled. if s.reload { if err := s.Load(); err != nil { diff --git a/view/html.go b/view/html.go index cb6ed4e2..ccbfae32 100644 --- a/view/html.go +++ b/view/html.go @@ -36,6 +36,7 @@ type HTMLEngine struct { middleware func(name string, contents []byte) (string, error) Templates *template.Template customCache []customTmp // required to load them again if reload is true. + bufPool *sync.Pool // } @@ -92,6 +93,9 @@ func HTML(fs interface{}, extension string) *HTMLEngine { layout: "", layoutFuncs: make(template.FuncMap), funcs: make(template.FuncMap), + bufPool: &sync.Pool{New: func() interface{} { + return new(bytes.Buffer) + }}, } return s @@ -322,11 +326,14 @@ func (s *HTMLEngine) initRootTmpl() { // protected by the caller. } } -func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) - err := s.Templates.ExecuteTemplate(buf, name, binding) +func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (string, error) { + buf := s.bufPool.Get().(*bytes.Buffer) + buf.Reset() - return buf, err + err := s.Templates.ExecuteTemplate(buf, name, binding) + result := buf.String() + s.bufPool.Put(buf) + return result, err } func (s *HTMLEngine) layoutFuncsFor(lt *template.Template, name string, binding interface{}) { @@ -334,9 +341,9 @@ func (s *HTMLEngine) layoutFuncsFor(lt *template.Template, name string, binding funcs := template.FuncMap{ "yield": func() (template.HTML, error) { - buf, err := s.executeTemplateBuf(name, binding) + result, err := s.executeTemplateBuf(name, binding) // Return safe HTML here since we are rendering our own template. - return template.HTML(buf.String()), err + return template.HTML(result), err }, } @@ -352,11 +359,11 @@ func (s *HTMLEngine) runtimeFuncsFor(t *template.Template, name string, binding "part": func(partName string) (template.HTML, error) { nameTemp := strings.Replace(name, s.extension, "", -1) fullPartName := fmt.Sprintf("%s-%s", nameTemp, partName) - buf, err := s.executeTemplateBuf(fullPartName, binding) + result, err := s.executeTemplateBuf(fullPartName, binding) if err != nil { return "", nil } - return template.HTML(buf.String()), err + return template.HTML(result), err }, "current": func() (string, error) { return name, nil @@ -364,8 +371,8 @@ func (s *HTMLEngine) runtimeFuncsFor(t *template.Template, name string, binding "partial": func(partialName string) (template.HTML, error) { fullPartialName := fmt.Sprintf("%s-%s", partialName, name) if s.Templates.Lookup(fullPartialName) != nil { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err + result, err := s.executeTemplateBuf(fullPartialName, binding) + return template.HTML(result), err } return "", nil }, @@ -378,14 +385,14 @@ func (s *HTMLEngine) runtimeFuncsFor(t *template.Template, name string, binding root := name[:len(name)-len(ext)] fullPartialName := fmt.Sprintf("%s%s%s", root, partialName, ext) if s.Templates.Lookup(fullPartialName) != nil { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err + result, err := s.executeTemplateBuf(fullPartialName, binding) + return template.HTML(result), err } return "", nil }, "render": func(fullPartialName string) (template.HTML, error) { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err + result, err := s.executeTemplateBuf(fullPartialName, binding) + return template.HTML(result), err }, } diff --git a/view/jet.go b/view/jet.go index ab97c20d..32f5707e 100644 --- a/view/jet.go +++ b/view/jet.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "net/http" + "os" "path/filepath" "reflect" "strings" @@ -18,9 +19,10 @@ const jetEngineName = "jet" // JetEngine is the jet template parser's view engine. type JetEngine struct { - fs http.FileSystem - rootDir string - extension string + fs http.FileSystem + rootDir string + extension string + left, right string loader jet.Loader @@ -72,6 +74,7 @@ func Jet(fs interface{}, extension string) *JetEngine { } s := &JetEngine{ + fs: getFS(fs), rootDir: "/", extension: extension, loader: &jetLoader{fs: getFS(fs)}, @@ -109,7 +112,8 @@ func (s *JetEngine) Ext() string { // corresponding default: {{ or }}. // Should act before `Load` or `iris.Application#RegisterView`. func (s *JetEngine) Delims(left, right string) *JetEngine { - s.Set.Delims(left, right) + s.left = left + s.right = right return s } @@ -217,12 +221,24 @@ func (l *jetLoader) Exists(name string) (string, bool) { // Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata). func (s *JetEngine) Load() error { - s.initSet() - // Note that, unlike the rest of template engines implementations, - // we don't call the Set.GetTemplate to parse the templates, - // we let it to the jet template parser itself which does that at serve-time and caches each template by itself. + return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error { + if info == nil || info.IsDir() { + return nil + } - return nil + if s.extension != "" { + if !strings.HasSuffix(path, s.extension) { + return nil + } + } + + buf, err := asset(s.fs, path) + if err != nil { + return fmt.Errorf("%s: %w", path, err) + } + + return s.ParseTemplate(path, string(buf)) + }) } // ParseTemplate accepts a name and contnets to parse and cache a template. @@ -238,6 +254,7 @@ func (s *JetEngine) initSet() { s.mu.Lock() if s.Set == nil { s.Set = jet.NewHTMLSetLoader(s.loader) + s.Set.Delims(s.left, s.right) if s.developmentMode && !isNoOpFS(s.fs) { // this check is made to avoid jet's fs lookup on noOp fs (nil passed by the developer). // This can be produced when nil fs passed