Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: html/template: building pages dynamically with one function call #65924

Open
HarrisonHemstreet opened this issue Feb 24, 2024 · 12 comments
Labels
Milestone

Comments

@HarrisonHemstreet
Copy link

HarrisonHemstreet commented Feb 24, 2024

Enhancing html/template for Dynamic Template Composition

Proposal Details:

The Go programming language has become a favored choice for building robust web services, thanks in part to its efficiency and simplicity. However, when it comes to rendering web pages with the html/template package, developers often face the challenge of managing multiple template files. The current process requires parsing each template file individually before execution, leading to redundant code and decreased maintainability, especially in projects with extensive use of templates.

Objective:

To address this, I propose the introduction of a new feature within the html/template package to enable dynamic and automated parsing of sub-templates directly from a base template. This feature aims to reduce boilerplate code, enhance code cleanliness, and streamline the development process.

Feature Description:

Automated Sub-Template Parsing: Allow a base template to automatically include and parse all associated sub-templates without explicitly listing each file. This mechanism would dynamically identify and load sub-templates referenced within the base template, simplifying the template rendering process.

New Functionality, No Breaking Changes: This feature would be introduced as an additional function within the html/template package, ensuring backward compatibility and no disruption to existing codebases. It would offer developers the choice between the traditional method and this new, more streamlined approach.

Benefits for RESTful Web Services: As Go continues to gain popularity for developing RESTful web services that adhere to the principles of Roy Fielding and HATEOS, standardizing template management could significantly benefit developers. A common, efficient method for handling template parsing would encourage cleaner code, reduce the likelihood of custom, error-prone solutions, and promote best practices within the Go community.

Proposed API Changes

Introduction of a New Function for Dynamic Template Loading

Function Name: ParseBaseTemplate
Signature: func ParseBaseTemplate(filePath string) (*Template, error)
This function initializes and returns a new template based on the base template file specified by filePath. It scans the base template for references to sub-templates and automatically loads these sub-templates into the template object. The function is designed to streamline the process of working with complex templates by eliminating the need to manually parse each file.

Enhancement to Template Delimiters for Sub-Template Inclusion

The function will parse the base template to identify references to sub-templates. This could leverage existing template actions (e.g., {{template "name"}}) or a predefined pattern to denote template inclusion, ensuring the function can accurately discover and load all necessary sub-templates.

Dynamic Sub-Template Loading:

Upon identifying a sub-template reference, the function will resolve the file path (relative to the base template or a predefined template directory) and parse the sub-template. This process repeats recursively for any sub-templates referenced within other sub-templates, ensuring a comprehensive template hierarchy is constructed.

Error Handling and Reporting Enhancements

With the introduction of dynamic sub-template loading, enhanced error handling and reporting mechanisms are necessary to ensure developers can easily debug issues related to template parsing. This includes clear error messages for missing sub-templates, circular references, or parsing errors within dynamically included templates.

Example Usage

Here's a hypothetical example demonstrating how a developer might use the new ParseBaseTemplate function:

package main

import (
    "html/template"
    "net/http"
)

func main() {
    // Dynamically load the base template along with all referenced sub-templates
    tmpl, err := template.ParseBaseTemplate("views/base.html")
    if err != nil {
        panic(err) // Handle error appropriately
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // Execute the base template, with all necessary sub-templates included
        err := tmpl.Execute(w, nil)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    http.ListenAndServe(":8080", nil)
}

In this example, the developer only needs to call ParseBaseTemplate once to load the base template along with all its sub-templates, based on the references within the base template. This significantly reduces boilerplate code and simplifies the template management process.

Before & After

Before:

tmpl, err := template.ParseFiles(
  "template/base.html",
  "template/common/footer.html",
  "template/common/nav/index.html",
  "template/common/nav/search.html",
  "template/page/index.html",
  "template/page/content/thing/index.html",
  ...list of files grows without an end in sight
)

After:

 tmpl, err := template.ParseBaseTemplate("template/base.html")

Rationale:

The motivation behind this proposal is to address a growing need within the Go ecosystem for more efficient template management. As web applications become more complex and the use of templates increases, the ability to dynamically manage these templates becomes crucial. By providing a standard solution within the standard library, we can enhance developer productivity, ensure code quality, and foster innovation in Go-based web services.

Conclusion:

Introducing dynamic template composition into the html/template package represents a significant step forward in improving the developer experience and code quality for Go-based web applications. By reducing redundancy and streamlining the template handling process, we can unlock new possibilities for web development in Go, aligning with the language's philosophy of simplicity and efficiency.

@gopherbot gopherbot added this to the Proposal milestone Feb 24, 2024
@ianlancetaylor
Copy link
Contributor

Can you outline what API changes you are suggesting in the html/template package? Thanks.

@seankhliao
Copy link
Member

please also show examples of before/after code

@HarrisonHemstreet
Copy link
Author

@ianlancetaylor
@seankhliao

Thanks for suggesting improvements! 🙂
Changes made to the description.

@seankhliao
Copy link
Member

So why not something like:

var f []string
fs.WalkDir(os.DirFS("template"), func(p string, d fs.DirEntry, err error) error {
  if strings.HasSuffix(p, ".html") {
    f = append(f, p)
  }
  return nil
})

template.ParseFiles(f...)

or using template.Glob (and less directories everywhere).

@HarrisonHemstreet
Copy link
Author

@seankhliao
I understand that this can be done with fs.Walk, and I am currently doing that. That being said, it would be nice if there was a one-liner in the standard library that would take care of this. That way, we don't have to worry about doing this every time we want to recursively parse templates in directories and subdirectories. I'm sure there are functions in the standard library that could be replaced with eight or nine easy lines of Go, but it's just nice to have quality-of-life functions that take care of tasks that we come across often.

@ianlancetaylor
Copy link
Contributor

CC @robpike

@robpike
Copy link
Contributor

robpike commented Feb 28, 2024

Not sure it's worth adding, but if it is, it would go into text/template first.

@HarrisonHemstreet
Copy link
Author

@robpike I agree, after more study, that it should first go into text/template.

@AlexanderYastrebov
Copy link
Contributor

BTW, example

tmpl, err := template.ParseFiles(
  "template/base.html",
  "template/common/footer.html",
  "template/common/nav/index.html",
  "template/common/nav/search.html",
  "template/page/index.html",
  "template/page/content/thing/index.html",
  ...list of files grows without an end in sight
)

does not work as expected as ParseFiles uses file basename as template name:

log.Printf("Template: %v", t.DefinedTemplates())

prints

Template: ; defined templates are: "base.html", "footer.html", "index.html", "search.html"

(note single index.html)

@HarrisonHemstreet
Copy link
Author

@AlexanderYastrebov
I apologize for the unclear example. In this case, each template is defined with the full file path to avoid this problem. For example, "template/page/index.html" is not read by template.ParseFiles as "index.html", but rather as "template/page/index.html" due to the way the template was defined.

<!-- file path: "template/page/index.html" -->
{{define  "template/page/index.html"}}
<div><h1>Page!</h1></div>
{{end}}

Regardless, the main idea still stands. The heart of what I'm getting at is, though it is straightforward to parse and execute files even when one has many to deal with, adding a quality-of-life function for handling many templates within nested directories will increase consistency between code bases, thereby increasing the reliability of the community's code.

@seankhliao
Copy link
Member

helm offers a similar capability using their custom functions Files and tpl: https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function

{{ tpl (.Files.Get "conf/app.conf") . }}

As helm has a well defined root directory for execution, this works, but the feature in this proposal doesn't make it clear why the paths are named relative to some arbitrary root not corresponding to the template location. I think adding this feature makes template development more confusing.

@xiaokentrl
Copy link

Great, I would like to have this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

7 participants