Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,57 @@ end
puts ClassView.to_html
```

### Compile-time Control Flow (MacroIf / MacroFor)

In addition to runtime `if`/`#each`, you can also use Crystal's macro control flow (`{% if %}` / `{% for %}`) inside templates. This runs at compile-time and can be used to generate markup based on compile-time information such as compiler flags or `@type`.

#### `MacroIf` (`{% if %}`)

```crystal
require "to_html"

class BuildVariantView
ToHtml.class_template do
head do
{% if flag?(:release) %}
script src: "/assets/app.min.js"
{% else %}
script src: "/assets/app.js"
{% end %}
end
end
end

puts BuildVariantView.to_html
```

#### `MacroFor` (`{% for %}`)

```crystal
require "to_html"

class AutoFormView
getter first_name : String
getter last_name : String

def initialize(@first_name, @last_name); end

ToHtml.instance_template do
form do
{% for ivar in @type.instance_vars %}
label for: {{ivar.name.stringify}} do
{{ivar.name.stringify}}
end

input type: :text, name: {{ivar.name.stringify}}, value: {{ivar.name.id}}
{% end %}
end
end
end

puts AutoFormView.new(first_name: "Ada", last_name: "Lovelace").to_html
```

### Attributes

#### Named Arguments
Expand Down Expand Up @@ -262,12 +313,12 @@ Have a look into the `benchmark/` folder to find out how these numbers were dete
Execute `crystal run --release benchmark/benchmark.cr` to reproduce.

```
ecr 1.55M (643.71ns) (± 3.02%) 4.27kB/op fastest
to_html 884.07k ( 1.13µs) (± 3.45%) 5.52kB/op 1.76× slower
blueprint 457.54k ( 2.19µs) (± 3.70%) 4.9kB/op 3.40× slower
markout 191.89k ( 5.21µs) (± 3.37%) 8.08kB/op 8.10× slower
html_builder 59.03k ( 16.94µs) (± 3.86%) 10.4kB/op 26.32× slower
water 57.48k ( 17.40µs) (± 4.22%) 11.2kB/op 27.03× slower
ecr 999.84k ( 1.00µs) (± 6.97%) 4.27kB/op fastest
to_html 576.96k ( 1.73µs) (± 7.51%) 5.52kB/op 1.73× slower
blueprint 377.61k ( 2.65µs) (± 8.35%) 4.9kB/op 2.65× slower
markout 120.59k ( 8.29µs) (± 8.54%) 8.08kB/op 8.29× slower
html_builder 49.94k ( 20.02µs) (± 7.38%) 10.4kB/op 20.02× slower
water 46.83k ( 21.35µs) (± 8.31%) 11.2kB/op 21.35× slower
```

Compared shards taken from [awesome-crystal](https://github.com/veelenga/awesome-crystal#html-builders)
Expand Down
2 changes: 1 addition & 1 deletion shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2.0
shards:
blueprint:
git: https://github.com/stephannv/blueprint.git
version: 1.0.0
version: 1.1.0

html_builder:
git: https://github.com/crystal-lang/html_builder.git
Expand Down
15 changes: 1 addition & 14 deletions src/instance_template.cr
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,7 @@ module ToHtml
{% elsif blk.body.is_a?(MacroFor) %}
\{% for {{blk.body.vars.splat}} in {{blk.body.exp}} %}
ToHtml.to_html_eval_exps({{io}}, {{indent_level}}) do
# Crystal can turn inline macro expressions (`{{...}}`) inside a macro-for body into
# "line-based" output (newlines around the expansion), which breaks calls without
# parentheses (e.g. `name:\n"value"\n, value:`). We keep real statement newlines, but
# collapse the ones that can't be statement separators.
#
# Upstream: https://github.com/crystal-lang/crystal/issues/16544
{%
body = blk.body.body.stringify
.gsub(/\s*\n\s*,\s*/, ", ")
.gsub(/\s*\n\s*\./, ".")
.gsub(/\s*\n\s*do\b/, " do")
.gsub(/([:,(\[{=])\s*\n\s*/, "\\1 ")
%}
{{body.id}}
{{blk.body.body.expressions.join("").id}}
end
\{% end %}
{% elsif blk.body.is_a?(MacroExpression) || blk.body.is_a?(MacroLiteral) %}
Expand Down