I propose adding an apply function to text/template which would execute a template and send its output to a pipeline:

{{ apply markdown | highlight "html" }}
    Hello, **Gopher**!
{{ end }}
<pre class="code">
    Hello, <span class="tag">&lt;strong&gt;</span>Gopher<span class="tag">&lt;/strong&gt;</span>!
</pre>

The motivation for this feature is essentially the same as the motivation for Hugo's shortcodes. When authoring content in Go templates, it is often desirable to run templated content through a template function for post-processing - for example, for syntax highlighting, processing markdown, creating "accordion" sections, etc.

Different flavors of this behavior have been proposed before (see below), and have been rejected with workarounds. Unfortunately, the best workaround available today is to add an eval "template name" . function and then use named templates, which is too cumbersome in practice:

{{ template "base.html" . }}

{{ define "content 1" }}
    Hello, **{{ .Name }}**!

    Watch this _sweet_ video to get started:
{{ end }}
{{ define "content 2" }}
    Don't forget to [like and subscribe](https://video.com/gopher)!
{{ end }}

{{ define "some block from base.html" }}
    {{ eval "content 1" . | markdown }}
    <iframe src="https://video.com/gopher/123456/embed"></iframe>
    {{ eval "content 2" . | markdown }}
{{ end }}

I propose for apply to simply be sugar for this workaround, albeit without defining a named template. With the proposed feature, this example would look like:

{{ template "base.html" . }}

{{ define "some block from base.html" }}
    {{ apply markdown }}
        Hello, **{{ .Name }}**!

        Watch this _sweet_ video to get started:
    {{ end }}
    <iframe src="https://video.com/gopher/123456/embed"></iframe>
    {{ apply markdown }}
        Don't forget to [like and subscribe](https://video.com/gopher)!
    {{ end }}
{{ end }}

I have created a prototype implementation of this feature.

Prior proposals

  • https://github.com/golang/go/issues/33273 This issue proposed modifications to the behavior of block and template which were deemed unacceptable. A workaround was suggested where the user can supply an eval function to execute a named template and get its result as a value. Unfortunately this workaround is still too cumbersome for the workflows described in this proposal.
  • https://github.com/golang/go/issues/44144 This issue was, in my opinion, erroneously closed as a duplicate of the previous issue. However, it did not redefine any existing syntax, so it was not really proposing the same thing. I hope that my proposal is a more complete and thoughtful version of this idea.

Open questions

Is this API sufficient? It may be wise to additionally expose APIs like the following, but I will leave that for further discussion.

  • eval "template name" .: evaluates a template by name and returns the output as a string. This behavior is implicitly present in apply, and is part of the commonly-suggested workaround, so it may be worth exposing explicitly. Example:

    ``` {{ define "greet" }}Hello, {{ . }}!{{ end }} {{ $greeting := eval "greet" .Name }} {{ urlquery $greeting }}

    // Output (when executed with Name = "Gopher"): Hello%2C+Gopher! ```

Comment From: seankhliao

cc @robpike

Comment From: earthboundkid

I have a draft in my TODOs to propose a similar feature.

Compared to, say, classic Mustache-style (see also ERB, Django, Jinja, etc.) templates, the Go templating system is pretty similar, if superficially different in some ways. But compared to newer React, Vue, etc. style component template systems, Go is missing a key ability: the ability to send children out for further processing. A hypothetical React template might looks like

<div>
  <p>Normal template stuff. Some variable { val }.</p>
  <Button kind="also normal and doable" value={otherVal} /> 
  <ButtonGroup>
    <h2>Whoa, new capability</h2>
    <p>This button group has children!</p>
    <Button kind="etc" value={otherVal} /> 
    <Button kind="etc" value={otherVal} /> 
  </ButtonGroup>
</div>

In the function for ButtonGroup, the h2, p, and Buttons get passed in as the argument children, and the component can decide whether, where, and how to show the children. Vue goes further and lets you have multiple named child groups and pass values from the parent component into the child templates.

You can do the equivalent interpolation in Go with {{ .val }} and the equivalent of Button with {{ template "button" (dict "kind" "normal" "value" .otherVal) }}, but there's no good way to make the equivalent of ButtonGroup. This turns out to be a very powerful abstraction that I would love to see Go have.

Comment From: earthboundkid

Here is another fork of Go templates that add component-like functionality: https://github.com/philippta/go-template

In this case, the repo adds component and slot keywords that can be used in templates like:

{{ define "button-group" }}
  <div class="wrapper">
    {{ slot }}
  </div>
{{ end }}

{{/* elsewhere */}}

{{ component "button-group" . }}
   {{ template "button" .one }}
   {{ template "button" .two }}
   {{ template "button" .three }}
{{ end }}

Comment From: bvisness

My proposal really isn't about component-like functionality, although you can implement component-like functionality within my proposal.

The whole idea of "components" is too much of an HTML / React / web dev idea to fit within text/template anyway. I believe my proposal is still useful even for unstructured text, and a web-focused system like Hugo could easily implement "components" on top, e.g.

{{ define "button-group" }}
  <div class="wrapper">
    {{ .Children }}
  </div>
{{ end }}

{{/* elsewhere */}}

{{ apply component "button-group" }}
   {{ template "button" .one }}
   {{ template "button" .two }}
   {{ template "button" .three }}
{{ end }}

Comment From: earthboundkid

I think your proposal is a more general solution that gets at the same problem.

Just having the ability to do {{ $x := template "x" . }} would add a lot, because then you could pass $x to markdown etc. You can fake that, like Hugo does, with a render/partial function that feeds templates back into themselves, but it’s worth really thinking through the problem space to get the best solution here.

Comment From: bvisness

{{ $x := template "x" . }} could be a good way to expose the eval behavior I mentioned, but it doesn't address the core workflow improvement of my proposal, which is evaluating template data without defining it elsewhere. Adding eval is already possible today, and it doesn't work well for my use case.

Comment From: earthboundkid

I agree. I think there are two things that need to be exposed: the ability to treat a rendered template as a value so that it can be piped through e.g. markdown and passed to other templates and the ability to create a block of content in situ without needing an external definition.

I wonder if it would be possible to change the existing block keyword to work this way. Maybe:

{{ block "" . }}
    Hello, **Gopher**!
{{ end | markdown | highlight "html" }}
{{ template "base.html" . }}

{{ define "some block from base.html" }}
    {{ block "" . }}
        Hello, **{{ .Name }}**!

        Watch this _sweet_ video to get started:
    {{ end | markdown }}
    <iframe src="https://video.com/gopher/123456/embed"></iframe>
    {{ block "" . }}
        Don't forget to [like and subscribe](https://video.com/gopher)!
    {{ end | markdown }}
{{ end }}
{{ define "button-group" }}
  <div class="wrapper">
    {{ .Children }}
  </div>
{{ end }}

{{/* elsewhere */}}

{{ $child := block "" . }}
   {{ template "button" .one }}
   {{ template "button" .two }}
   {{ template "button" .three }}
{{ end }}
{{ template "button-group" (dict "Children" $child ) }}

Comment From: c3hb

I too would find a {{component}}{{slot}}{{end}} syntax useful. I've done a custom implementation of .NET Cores tag helpers to support this syntax with named slots as Vue does and it became very useful for our internal components.

The original suggestion here was to add an {{apply}}{{end}} tag. It was added that an {{component}}{{slot}}{{end}} syntax may also but useful, but was cited as being too web/html specific.

A more generic solution to cover these cases could be to support custom actions that accept inner content (single or multiple via names) as function params.

Currently we can specify custom functions via the FuncMap {{doSomething "data"}}.

Could be helpful to provide custom actions that accept block data {{doSomething "data"}}also data{{end}}.

I think the existence of something like https://github.com/philippta/go-template shows a need for this kind of extensibility.