a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
7.14k stars 236 forks source link

Issue: Unexpected Script Placement in Generated HTML #780

Closed denartha10 closed 1 month ago

denartha10 commented 1 month ago

Issue: Unexpected Script Placement in Generated HTML

Description

When using the templ package to generate HTML content, I encountered an unexpected behavior where both <script> tags, one intended for the <head> and one for the <body>, are placed within the <head> section unless I explicitly wrap the body content in a <body> tag. Below are the relevant code snippets demonstrating this issue.

Code Snippets

components/script.go

package components

templ head() {
  <head>
    <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
  </head>
}

templ body() {
  <script>
    const chart = LightweightCharts.createChart(document.body, {width: 400, height: 300});
    const lineSeries = chart.addLineSeries();
    lineSeries.setData([
      {time: '2019-04-11', value: 80.01},
      {time: '2019-04-12', value: 96.63},
      {time: '2019-04-13', value: 76.64},
      {time: '2019-04-14', value: 81.89},
      {time: '2019-04-15', value: 74.43},
      {time: '2019-04-16', value: 80.01},
      {time: '2019-04-17', value: 96.63},
      {time: '2019-04-18', value: 76.64},
      {time: '2019-04-19', value: 81.89},
      {time: '2019-04-20', value: 74.43},
    ]);
  </script>
}

templ Page() {
  @head()
  @body()
}

main.go

package main

import (
  "fmt"
  "net/http"
  "components"
)

func main() {
  http.Handle("/", templ.Handler(components.Body()))

  fmt.Println("Listening on port 3000!")
  http.ListenAndServe(":3000", nil)
}

Issue

When I run the server and inspect the generated HTML, I observe the following structure:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
  <script>
    const chart = LightweightCharts.createChart(document.body, {width: 400, height: 300});
    const lineSeries = chart.addLineSeries();
    lineSeries.setData([
      {time: '2019-04-11', value: 80.01},
      {time: '2019-04-12', value: 96.63},
      {time: '2019-04-13', value: 76.64},
      {time: '2019-04-14', value: 81.89},
      {time: '2019-04-15', value: 74.43},
      {time: '2019-04-16', value: 80.01},
      {time: '2019-04-17', value: 96.63},
      {time: '2019-04-18', value: 76.64},
      {time: '2019-04-19', value: 81.89},
      {time: '2019-04-20', value: 74.43},
    ]);
  </script>
</head>
<body>
</body>
</html>

Both scripts appear in the <head> section, while the <body> section is empty. However, if I explicitly wrap the body content in a <body> tag in the templ body() function, the scripts are placed correctly.

Question

Is this the intended behavior of the templ package? Shouldn't the scripts be placed as defined in their respective templ functions without the need for explicit <body> tags? Please clarify if there is a specific design decision behind this or if it might be an issue that needs addressing.

Steps to Reproduce

  1. Define the head and body templates as shown above.
  2. Define a Page template that includes both head and body.
  3. Use the Page template in a basic HTTP server setup.
  4. Inspect the generated HTML in the browser.

Expected Behavior

The script defined in the body template should be placed inside the <body> tag, without requiring an explicit <body> tag in the template function.

Actual Behavior

Both scripts are placed inside the <head> tag, and the <body> tag is empty.

Thank you for looking into this.

joerdav commented 1 month ago

@denartha10 thankyou for taking the time to file this.

I don't think templ is doing anything incorrect here. I think the unexpected behaviour is a product of 2 things:

  1. templ components don't add any undefined html. If you want your body component to contain a body tag then it should be defined as such:

    templ body() {
    <body>
    <script>
      const chart = LightweightCharts.createChart(document.body, {width: 400, height: 300});
      const lineSeries = chart.addLineSeries();
      lineSeries.setData([
        {time: '2019-04-11', value: 80.01},
        {time: '2019-04-12', value: 96.63},
        {time: '2019-04-13', value: 76.64},
        {time: '2019-04-14', value: 81.89},
        {time: '2019-04-15', value: 74.43},
        {time: '2019-04-16', value: 80.01},
        {time: '2019-04-17', value: 96.63},
        {time: '2019-04-18', value: 76.64},
        {time: '2019-04-19', value: 81.89},
        {time: '2019-04-20', value: 74.43},
      ]);
    </script>
    </body>
    }
  2. The script is added to the head by the browser, not by templ. If you paste the following into https://play.templ.guide/, you will see that the resulting HTML is in the same structure as your template, browsers sometimes change things to fit into the DOM model, like adding a body tag if there isn't one already.

    
    package main 

import ( "os" )

templ head() {

}

templ body() {

}

templ Page() { @head() @body() }

func main() { component := Page() component.Render(context.Background(), os.Stdout) }



I really hope this explains what you are seeing!
joerdav commented 1 month ago

I'll close this as there is no change required for templ here, but please do let me know if there are any more questions :)

denartha10 commented 4 weeks ago

Hi joerdav,

Thanks for taking the time to respond to my question, its always nice to learn something new. So this is down more to default browser behavior than it is to how templ generates html. After playing around more I did find that adding the body tag resolved the issue so I am happy that we came to an independent consensus.

Thanks again!