(摘) templ 用Go构建HTML

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

在学习新项目BluePrint时,又遇到知识盲点:templ,补一补。

templ即用golang来构建html,想必更加灵活和“熟悉”,功能也更强大。

以下是直接机翻的templ特点:

  1. 服务器端渲染:部署为无服务器函数、Docker 容器或标准 Go 程序。
  2. 静态渲染:创建静态 HTML 文件以根据您的选择进行部署。
  3. 编译代码:组件被编译成高性能的 Go 代码。
  4. 使用 Go:调用任何 Go 代码,并使用标准的 if、switch 和 for 语句。
  5. 无 JavaScript:不需要任何客户端或服务器端 JavaScript。
  6. 出色的开发人员体验:附带 IDE 自动完成功能。

示例代码:

package main

templ Hello(name string) {
  <div>Hello, { name }</div>
}

templ Greeting(person Person) {
  <div class="greeting">
    @Hello(person.Name)
  </div>
}

看起来是golang中允许html代码使用

安装工具:go install github.com/a-h/templ/cmd/templ@latest 似乎应该是将golang代码转换为html的。

创建简单组件

  1. 创建项目
mkdir hello
go mod init hello 
go get github.com/a-h/templ    # 支持库
  1. 创建hello.templ文件
package main

templ hello(name string) {
	<div>Hello, { name }</div>
}
  1. 生成go代码
    templ generate
    将产生一个hello_templ.go文件。代码使用了一些无意义的变量,看着有点晕。

  2. 创建主程序main.go

package main

import (
	"context"
	"os"
)

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

运行主程序,将在命令行输出:Hello, John
此时修改hello.templ也没什么影响了,因为golang去调用hello_templ.go去了。


以上只是作简单演示过程,实际上我们是需要建立一个Web服务器。将main.go作一些修改:

package main

import (
	"fmt"
	"net/http"

	"github.com/a-h/templ"
)

func main() {
	component := hello("John")
	
	http.Handle("/", templ.Handler(component))

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

这样就可以访问web,在浏览器中得到输出。


如hello示例中看到的 hello 定义为了一个组件,关键词 templ 。在组件中可以使用golang语句。

package main

templ headerTemplate(name string) {
  <header data-testid="headerTemplate">
    <h1>{ name }</h1>
  </header>
}

在hello示例中添加一个button组件

package main

templ hello(name string) {
	<div>Hello, { name }</div>
}

templ button(text string) {
	<button class="button">{ text }</button>
}

templ generate 后修改main.go (没有templ转go的过程,在IDE工具中,main.go中就不会有自动提示。当然也可以盲写后再生成,再运行)

package main

import (
	"fmt"
	"net/http"

	"github.com/a-h/templ"
)

func main() {

	component := hello("John")

	http.Handle("/", templ.Handler(component))
	http.Handle("/button", templ.Handler(button("ClickMe")))

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

文档中着重强调了要有关闭标记, 这种都是允许的。

组件的调用:

templ component(testID string) {
  <p data-testid={ testID }>Text</p>
}

templ page() {
  @component("testid-123")
}

组件也可以使用golang定义的函数:

func testID(isTrue bool) string {
    if isTrue {
        return "testid-123"
    }
    return "testid-456"
}
templ component() {
  <p data-testid={ testID(true) }>Text</p>
}

条件属性:

templ component() {
  <hr style="padding: 10px"
    if true {
      class="itIsTrue"
    }
  />
}

输出为:

为标签增加附加值

类似于 1111

这个位置的解释有点不好理解,后面再说。看示例直接点。

templ component(shouldBeUsed bool, attrs templ.Attributes) {
  <p { attrs... }>Text</p>
  <hr
    if shouldBeUsed {
      { attrs... }
    }
  />
}

templ usage() {
  @component(false, templ.Attributes{"data-testid": "paragraph"}) 
}

输出为:

<p data-testid="paragraph">Text</p>
<hr>

templ.Attributes是map[string]any,是一个键/值对。

URL属性

对URL属性要求特殊一些,它希望自己来处理/过滤链接,而不是直接给字符串。即使用templ.URL函数:

templ component(p Person) {
  <a href={ templ.URL(p.URL) }>{ strings.ToUpper(p.Name) }</a>
}

JavaScript脚本

script withParameters(a string, b string, c int) {
	console.log(a, b, c);
}

script withoutParameters() {
	alert("hello");
}

templ Button(text string) {
	<button onClick={ withParameters("test", text, 123) } onMouseover={ withoutParameters() } type="button">{ text }</button>
}

JSON

要将属性的值设置为 JSON 字符串,请使用函数将值序列化为字符串。

func countriesJSON() string {
	countries := []string{"Czech Republic", "Slovakia", "United Kingdom", "Germany", "Austria", "Slovenia"}
	bytes, _ := json.Marshal(countries)
	return string(bytes)
}

templ SearchBox() {
	<search-webcomponent suggestions={ countriesJSON() } />
}

表达式

注意:以下代码可能将templ中的内容和调用它的部份混合到了一起,为了简单。认真读应该看得出来。

直接字符串

package main

templ component() {
  <div>{ "print this" }</div>
  <div>{ `and this` }</div>
}

变量

package main

templ greet(prefix string, p Person) {
  <div>{ prefix } { p.Name }{ exclamation }</div>
}

// ---------------

type Person struct {
  Name string
}

func main() {
  p := Person{ Name: "John" }
  component := greet("Hello", p) 
  component.Render(context.Background(), os.Stdout)
}

function 即函数,它可以返回string或string,error

package main

import "strings"
import "strconv"

func getString() (string, error) {
  return "DEF", nil
}

templ component() {
  <div>{ strings.ToUpper("abc") }</div>
  <div>{ getString() }</div>
}

转义

package main

templ component() {
  <div>{ `</div><script>alert('hello!')</script><div>` }</div>
}

将输出

<div>&lt;/div&gt;&lt;script&gt;alert(&#39;hello!&#39;)&lt;/script&gt;&lt;div&gt;</div>

流程控制

if/else
switch
for loops

templ login(isLoggedIn bool) {
  if isLoggedIn {
    <div>Welcome back!</div>
  } else {
    <input name="login" type="button" value="Log in"/>
  }
}

templ userTypeDisplay(userType string) {
	switch userType {
		case "test":
			<span>{ "Test user" }</span>
		case "admin":
			<span>{ "Admin user" }</span>
		default:
			<span>{ "Unknown user" }</span>
	}
}


templ nameList(items []Item) {
  <ul>
  for _, item := range items {
    <li>{ item.Name }</li>
  }
  </ul>
}

模板组成

templ showAll() {
	@left()
	@middle()
	@right()
}

templ left() {
	<div>Left</div>
}

templ middle() {
	<div>Middle</div>
}

templ right() {
	<div>Right</div>
}

子组件

templ showAll() {
	@wrapChildren() {
		<div>Inserted from the top</div>
	}
}

templ wrapChildren() {
	<div id="wrapper">
		{ children... }
	</div>
}

输出为:

<div id="wrapper">
 <div>
  Inserted from the top
 </div>
</div>

有点像点内容直接传递到组件中,代替 { children… } 的位置

组件作为参数

package main

templ heading() {
    <h1>Heading</h1>
}

templ layout(contents templ.Component) {
	<div id="heading">
		@heading()
	</div>
	<div id="contents">
		@contents
	</div>
}

templ paragraph(contents string) {
	<p>{ contents }</p>
}


func main() {
	c := paragraph("Dynamic contents")
	layout(c).Render(context.Background(), os.Stdout)
}

输出:

<div id="heading">
	<h1>Heading</h1>
</div>
<div id="contents">
	<p>Dynamic contents</p>
</div>

这里有点不好理解。
PS:运行它时,被火绒报病毒….

看起来 layout 是框架定义,必然会首先运行。在@contents时才会引用paragraph组件到当前位置。

导入导出组件

package components

templ Hello() {
	<div>Hello</div>
}

引用,同golang类似:

package main

import ".../components"

templ Home() {
	@components.Hello()
}

CSS

package main

css red() {
	background-color: #ff0000;
}

templ button(text string, isPrimary bool) {
	<button class={ "button", templ.KV("is-primary", isPrimary), templ.KV(red(), isPrimary) }>{ text }</button>
}
button("Click me", true).Render(context.Background(), os.Stdout)

输出:

<style type="text/css">.red_1cbd{background-color:#ff0000;}</style><button class="button is-primary red_1cbd">ClickMe</button>

即templ.KV为一个键/值对,当后者为true时,则输出前者。
当调用button(“Click me”, false).Render(context.Background(), os.Stdout)时,输出为: Click me 。即它自动的识别并取消无用的css内容。

templ.KV(“hover:red”, true) 应该是类似css的 .btn:hover { color: white; border: 0; }

代码里也支持直接定义css

templ page() {
	<style type="text/css">
		p {
			font-family: sans-serif;
		}
		.button {
			background-color: black;
			foreground-color: white;
		}
	</style>
	<p>
		Paragraph contents.
	</p>
}

CSS组件

除了之前见到的html组件,还支持CSS组件

package main

var red = "#ff0000"
var blue = "#0000ff"

css primaryClassName() {
	background-color: #ffffff;
	color: { red };
}

css className() {
	background-color: #ffffff;
	color: { blue };
}

templ button(text string, isPrimary bool) {
	<button class={ "button", className(), templ.KV(primaryClassName(), isPrimary) }>{ text }</button>
}

CSS组件参数

package main

css loading(percent int) {
	width: { fmt.Sprintf("%d%%", percent) };
}

templ index() {
    <div class={ loading(50) }></div>
    <div class={ loading(100) }></div>
}

这样就更灵活了,减少了一些不必要的定义。

CSS清理

为了防止CSS注入攻击,它会自动清理动态CSS。使用SafeCSSProperty来标记安全。

css windVaneRotation(degrees float64) {
	transform: { templ.SafeCSSProperty(fmt.Sprintf("rotate(%ddeg)", int(math.Round(degrees)))) };
}

templ Rotate(degrees float64) {
	<div class={ windVaneRotation(degrees) }>Rotate</div>
}

CSS中间件

要提供全局样式表,可以使用CSS中间件,并在应用程序启动注册templ类。
中间件将 HTTP 路由添加到 Web 服务器(默认为 /styles/templ.css)。
在 HTML 中添加 a 以包含生成的 CSS 类名称
要阻止将 className CSS 类添加到输出中,可以使用 HTTP 中间件。

c1 := className()
handler := NewCSSMiddleware(httpRoutes, c1)
http.ListenAndServe(":8000", handler)

JavaScript

templ body() {
  <script type="text/javascript">
    function handleClick(event) {
      alert(event + ' clicked');
    }
  </script>
  <button onclick="handleClick(this)">Click me</button>
}

要确保 templ 组件中的 标记在每个 HTTP 响应中仅呈现一次,使用 templ.OnceHandle

package main

import "net/http"

var helloHandle = templ.NewOnceHandle()

templ hello(label, name string) {
  // This script is only rendered once per HTTP request.
  @helloHandle.Once() {
    <script type="text/javascript">
      function hello(name) {
        alert('Hello, ' + name + '!');
      }
    </script>
  }
  <div>
    <input type="button" value={ label } data-name={ name }/>
    <script type="text/javascript">
      // To prevent the variables from leaking into the global scope,
      // this script is wrapped in an IIFE (Immediately Invoked Function Expression).
      (() => {
        let scriptElement = document.currentScript;
        let parent = scriptElement.closest('div');
        let nearestButtonWithName = parent.querySelector('input[data-name]');
        nearestButtonWithName.addEventListener('click', function() {
          let name = nearestButtonWithName.getAttribute('data-name');
          hello(name);
        })
      })()
    </script>
  </div>
}

templ page() {
  @hello("Hello User", "user")
  @hello("Hello World", "world")
}

func main() {
  http.Handle("/", templ.Handler(page()))
  http.ListenAndServe("127.0.0.1:8080", nil)
}

另一个示例,这两个都有点不太明白,留待以后

var helloHandle = templ.NewOnceHandle()
var surrealHandle = templ.NewOnceHandle()

templ hello(label, name string) {
  @helloHandle.Once() {
    <script type="text/javascript">
      function hello(name) {
        alert('Hello, ' + name + '!');
      }
    </script>
  }
  @surrealHandle.Once() {
    <script src="https://cdn.jsdelivr.net/gh/gnat/surreal@3b4572dd0938ce975225ee598a1e7381cb64ffd8/surreal.js"></script>
  }
  <div>
    <input type="button" value={ label } data-name={ name }/>
    <script type="text/javascript">
      // me("-") returns the previous sibling element.
      me("-").addEventListener('click', function() {
        let name = this.getAttribute('data-name');
        hello(name);
      })
    </script>
  </div>
}

将服务端数据传递给脚本

templ body(data any) {
  <button id="alerter" alert-data={ templ.JSONString(data) }>Show alert</button>
}

可以调用

	component := body(123)
	component.Render(context.Background(), os.Stdout)

输出

<button id="alerter" alert-data="123">Show alert</button>

若调用

        component := body("123")
        component.Render(context.Background(), os.Stdout)

则输出,即会转义字符

<button id="alerter" alert-data="&#34;123&#34;">Show alert</button>

这样可以在客户端JavaScript访问该属性中的数据

const button = document.getElementById('alerter');
const data = JSON.parse(button.getAttribute('alert-data'));

这其实是常规JavaScript访问,只是在模板中定义了附加数据

另一个示例:

templ body(data any) {
  @templ.JSONScript("id", data)
}

调用body(123)输出例如:123

在客户端JavaScript访问数据:

const data = JSON.parse(document.getElementById('id').textContent);

脚本模板

这里示例也讲了如何把服务端的数据传给前端

package main

script graph(data []TimeValue) {
	const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
	const lineSeries = chart.addLineSeries();
	lineSeries.setData(data);
}

templ page(data []TimeValue) {
	<html>
		<head>
			<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
		</head>
		<body onload={ graph(data) }></body>
	</html>
}

主程序建立服务器

package main

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

type TimeValue struct {
	Time  string  `json:"time"`
	Value float64 `json:"value"`
}

func main() {
	mux := http.NewServeMux()

	// Handle template.
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		data := []TimeValue{
			{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},
		}
		page(data).Render(r.Context(), w)
	})

	// Start the server.
	fmt.Println("listening on :8080")
	if err := http.ListenAndServe(":8080", mux); err != nil {
		log.Printf("error listening: %v", err)
	}
}

这里生成了一个折线图

我也试过随机生成数据

		var data []TimeValue
		now := time.Now()

		for i := 0; i < 120; i++ {
			date := now.AddDate(0, 0, +i).Format("2006-01-02")
			value := math.Round(50 + rand.Float64()*(100-50)) // 随机生成50到100之间的值
			data = append(data, TimeValue{Time: date, Value: value})
		}

在组件中引用script组件

package main

import "fmt"

script printToConsole(content string) {
	console.log(content)
}

templ page(content string) {
	<html>
		<body>
		  @printToConsole(content)
		  @printToConsole(fmt.Sprintf("Again: %s", content))
		</body>
	</html>
}
	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Format the current time and pass it into our template
		page(time.Now().String()).Render(r.Context(), w)
	})

	fmt.Println("listening on :8080")
	if err := http.ListenAndServe(":8080", mux); err != nil {
		log.Printf("error listening: %v", err)
	}

JSExpression 类型用于将任意 JavaScript 表达式传递给 templ 脚本模板。
一个常见的用例是将事件或 this 对象传递给事件处理程序。

package main

script showButtonWasClicked(event templ.JSExpression) {
	const originalButtonText = event.target.innerText
	event.target.innerText = "I was Clicked!"
	setTimeout(() => event.target.innerText = originalButtonText, 2000)
}

templ page() {
	<html>
		<body>
			<button type="button" onclick={ showButtonWasClicked(templ.JSExpression("event")) }>Click Me</button>
		</body>
	</html>
}

实现了一个Button,当点击后显示新内容在Button上,2秒后恢复原标题

注释也是用

<!-- Single line -->

<!--
Single or multiline.
-->

使用上下文

在 templ 组件中,使用隐式 ctx 变量来访问上下文。

templ themeName() {
	<div>{ ctx.Value(themeContextKey).(string) }</div>
}
type contextKey string
var themeContextKey contextKey = "theme"
ctx := context.WithValue(context.Background(), themeContextKey, "test")
themeName().Render(ctx, w)

作一些安全处理,例如判断是否存在。这也便于整洁化模板。

func GetTheme(ctx context.Context) string {
	if theme, ok := ctx.Value(themeContextKey).(string); ok {
		return theme
	}
	return ""
}

将上下文与中间件结合

type contextKey string
var contextClass = contextKey("class")

func Middleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request ) {
    ctx := context.WithValue(r.Context(), contextClass, "red")
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

templ Page() {
  @Show()
}

templ Show() {
  <div class={ ctx.Value(contextClass) }>Display</div>
}

func main() {
  h := templ.Handler(Page())
  withMiddleware := Middleware(h)
  http.Handle("/", withMiddleware)
  http.ListenAndServe(":8080", h)
}

与html/template一起使用

Templ 组件可以与 Go 标准库 html/template 包一起使用。

package testgotemplates

import "html/template"

var goTemplate = template.Must(template.New("example").Parse("<div>{{ . }}</div>"))

templ Example() {
	<!DOCTYPE html>
	<html>
		<body>
			@templ.FromGoHTML(goTemplate, "Hello, World!")
		</body>
	</html>
}
package testgotemplates

import "html/template"

var example = template.Must(template.New("example").Parse(`<!DOCTYPE html>
<html>
	<body>
		{{ . }}
	</body>
</html>
`))

templ greeting() {
	<div>Hello, World!</div>
}

直接使用HTML

@templ.Raw可以直接包含认为安全的HTML

templ Example() {
	<!DOCTYPE html>
	<html>
		<body>
			@templ.Raw("<div>Hello, World!</div>")
		</body>
	</html>
}

*OnceHandler.Once()

*OnceHandler.Once() 方法确保内容在传递给组件的 Render 方法的每个不同上下文中仅呈现一次,即使组件被多次呈现

package deps

var jqueryHandle = templ.NewOnceHandle()

templ JQuery() {
  @jqueryHandle.Once() {
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  }
}

这里定义了deps包和JQuery组件,即使JQuery组件调用多次,也只会调用内容一次,即加载jquery。

package main

import "deps"

templ page() {
  <html>
    <head>
      @deps.JQuery()
    </head>
    <body>
      <h1>Hello, World!</h1>
      @button()
    </body>
  </html>
}

templ button() {
  @deps.JQuery()
  <button>Click me</button>
}

纯代码组件

package main

import (
	"context"
	"io"
	"os"

	"github.com/a-h/templ"
)

func button(text string) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		_, err := io.WriteString(w, "<button>"+text+"</button>")
		return err
	})
}

func main() {
	button("Click me").Render(context.Background(), os.Stdout)
}

方法组件

package main

import "os"

type Data struct {
	message string
}

templ (d Data) Method() {
	<div>{ d.message }</div>
}

func main() {
	d := Data{
		message: "You can implement methods on a type.",
	}
	d.Method().Render(context.Background(), os.Stdout)
}
package main

import "os"

type Data struct {
	message string
}

templ (d Data) Method() {
	<div>{ d.message }</div>
}

templ Message() {
    <div>
        @Data{
            message: "You can implement methods on a type.",
        }.Method()
    </div>
}

func main() {
	Message().Render(context.Background(), os.Stdout)
}

使用 templ 创建 HTTP 服务器

也就是直接路由到模板函数

package main

templ hello() {
	<div>Hello</div>
}
package main

import (
	"net/http"

	"github.com/a-h/templ"
)

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

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

用户会话状态

这是与另一个代码相关联的,这里主要看session的使用。

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/alexedwards/scs/v2"
)

type GlobalState struct {
	Count int
}

var global GlobalState
var sessionManager *scs.SessionManager

func getHandler(w http.ResponseWriter, r *http.Request) {
	userCount := sessionManager.GetInt(r.Context(), "count")
	component := page(global.Count, userCount)
	component.Render(r.Context(), w)
}

func postHandler(w http.ResponseWriter, r *http.Request) {
	// Update state.
	r.ParseForm()

	// Check to see if the global button was pressed.
	if r.Form.Has("global") {
		global.Count++
	}
	if r.Form.Has("user") {
		currentCount := sessionManager.GetInt(r.Context(), "count")
		sessionManager.Put(r.Context(), "count", currentCount+1)
	}

	// Display the form.
	getHandler(w, r)
}

func main() {
	// Initialize the session.
	sessionManager = scs.New()
	sessionManager.Lifetime = 24 * time.Hour

	mux := http.NewServeMux()

	// Handle POST and GET requests.
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodPost {
			postHandler(w, r)
			return
		}
		getHandler(w, r)
	})

	// Add the middleware.
	muxWithSessionMiddleware := sessionManager.LoadAndSave(mux)

	// Start the server.
	fmt.Println("listening on http://localhost:8000")
	if err := http.ListenAndServe("localhost:8000", muxWithSessionMiddleware); err != nil {
		log.Printf("error listening: %v", err)
	}
}

HTMX

https://htmx.org 可用于有选择地替换网页中的内容。AJAX?

引用:

要在不进行完整回发的情况下更新页面上的计数,必须将 hx-post=“/” 和 hx-select=“#countsForm” 属性添加到 元素中,并添加 id 属性以唯一标识该元素。

以下只是部份代码,具体使用还得看HTMX相关

templ counts(global, session int) {
	<form id="countsForm" action="/" method="POST" hx-post="/" hx-select="#countsForm" hx-swap="outerHTML">
		<div class="columns">
			<div class={ "column", "has-text-centered", "is-primary", border }>
				<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(global) }</h1>
				<p class="subtitle has-text-centered">Global</p>
				<div><button class="button is-primary" type="submit" name="global" value="global">+1</button></div>
			</div>
			<div class={ "column", "has-text-centered", border }>
				<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(session) }</h1>
				<p class="subtitle has-text-centered">Session</p>
				<div><button class="button is-secondary" type="submit" name="session" value="session">+1</button></div>
			</div>
		</div>
	</form>
}

基本看完一遍,了解了七七八八。还是需要在实例中学习。自带的示例还是比较丰富。
比如在实例中就看到了templ工具这样的用法:templ generate –watch –proxy=“http://localhost:8080” –cmd=“go run .”

于是又回去看了看templ命令行

-path
-f <file> 可以选择为单个文件生成代码
-sourceMapVisualisations  设置为true以生成html文件
-include-version 设置为false,跳过生成的代码中包含的templ版本(没发现有作用)
-include-timestamp 设置为true,在生成的代码中包含当前时间。
-watch 设置为true,监视更改的路径并重新生成代码。
-cmd 生成代码后运行的命令
-proxy 生成代码并执行命令后,将 URL 设置为代理。
-proxyport 代理将监听的端口
-proxybind 代理将监听的地址
-w 生成代码时要使用的工人数量。
-lazy 只有源 .templ 文件更新时,才能生成 .go 文件。
-pprof 运行 pprof 服务器的端口。
-keep-orphaned-files 保留已生成的模板文件
-v 将日志冗余级别设置为 “调试”。
-log-level 设置日志冗余级别
-help

虽然机翻,但基本是明白了。为什么用个proxy,不得而知,反正就是启动了一个服务,可以通过另一端口访问main.go中的服务端口。

相关文章