Documentation ¶
Overview ¶
Compile HTML with embedded Go to a single binary ¶
Command dyd compiles HTML with embedded Go scripts to a self-contained, go install-able, single binary server producing dynamic web pages.
Hello world ¶
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>dyd Hello world</title> </head> <body> Hello world! <p><? write("req #%d for %s at %s", ctx.ReqSequence, ctx.Request.URL, time.Now().Format(time.RFC1123Z)) ?> </body> </html>
The above file is all you need to produce a web server for a hello world example.
Prerequisities ¶
- A working Go toolchain must be installed to use dyd. You can install Go from here: https://go.dev/dl/.
- The goimports tool is needed. To install goimports issue
$ go install golang.org/x/tools/cmd/goimports@latest
Installation ¶
$ go install modernc.org/dyd@latest
Command line arguments ¶
$ dyd [-v] [-addr=<arg>] [-serve] [-modinit=<arg>] [-clean] [-htmlfmt] path
'path' points to the web server root, it defaults to ".".
-v adds more verbose output.
-addr is the web server address. It's the same argument as the first http.ListeAndServe wants. It defaults to ":6226".
-modinit creates a go.mod file with module path <arg> at 'path'. Ignored if a mod file at 'path' already exists.
-clean removes all generated files in the tree starting at 'path' and exits.
-serve compiles and runs the server.
-htmlfmt attempts to format html files. Use with caution, formatting may in some cases lose information. A backup is made before overwriting the HTML file. For example
<link rel="stylesheet" href=<? write("%q", ctx.Request.URL.Query().Get("style")) ?>>
The preprocessor can handle this just fine, the formatter cannot. Write instead
<? write(`<link rel="stylesheet" href=%q>`, ctx.Request.URL.Query().Get("style")) ?>
Usage example ¶
Using the hello world example from above in index.html.
0:~/tmp/dyd$ ls -la total 12 drwxr-xr-x 2 jnml jnml 4096 May 22 13:02 . drwxr-xr-x 7 jnml jnml 4096 May 22 12:42 .. -rw-r--r-- 1 jnml jnml 269 May 22 12:57 index.html 0:~/tmp/dyd$
Generate and run the server (`-v` is optional).
0:~/tmp/dyd$ dyd -v -modinit example.com/hello -serve executing [go mod init example.com/hello] preprocessing executing [/home/jnml/bin/goimports -l -w /home/jnml/tmp/dyd/index.html.go] building site map url /index.html produced by example.com/hello.Serve_index_1html() generating dyd.go executing [/home/jnml/bin/goimports -l -w dyd.go] generating /home/jnml/tmp/dyd/main.go executing [/home/jnml/bin/goimports -l -w /home/jnml/tmp/dyd/main.go] executing [go mod tidy] starting server at :6226 executing [go run .]
Stop the server.
^C 130:~/tmp/dyd$
How it works ¶
Every directory is - or becomes - a Go package. dyd looks for .html files and generates Go functions serving the content. Any Go code can appear within HTML processing instructions `<?` and `?>`. This code is inserted into the serving function. Go code can refer to two arguments of the serving function. The 'ctx' argument provides context. The type is documented at http://modernc.org/dyd/dyd#Context. The other argument is 'write' and its a function declared as
write func(s string, args ...any) error
The write function is only a small helper writing to the ctx.ResponseWriter.
write("%d %s", 123, s)
is the same as
fmt.Fprintf(ctx.ResponseWriter, "%d %s", 123, s)
The web root directory must be package main, directories under the root cannot be package main.
If there's no main.go/func main() in the web root, dyd will create one. If a manually written main.go/func main() exists, it will be kept. To invoke the web server in your manual func main(), add this as the first line of the function body
defer dydStart()
or
defer func() { log.Fatal(dydStart()) }()
etc. The 'dydStart' will be defined automatically in dyd.go. Consider this file name reserved by dyd.
Let's see what was generated for the Hello world example.
130:~/tmp/dyd$ ls -la total 32 drwxr-xr-x 2 jnml jnml 4096 May 22 13:03 . drwxr-xr-x 7 jnml jnml 4096 May 22 12:42 .. -rw-r----- 1 jnml jnml 328 May 22 13:03 dyd.go -rw-r--r-- 1 jnml jnml 66 May 22 13:03 go.mod -rw-r--r-- 1 jnml jnml 149 May 22 13:03 go.sum -rw-r--r-- 1 jnml jnml 269 May 22 12:57 index.html -rw-r----- 1 jnml jnml 516 May 22 13:03 index.html.go -rw-r----- 1 jnml jnml 107 May 22 13:03 main.go 0:~/tmp/dyd$
The dyd.go file contains the `dydStart` function. It registers all the serving functions to handle the respective URLs.
// Code generated by dyd, DO NOT EDIT. package main import ( "modernc.org/dyd/dyd" ) // dydStart starts the webapp. dydStart always returns a non-nil error. func dydStart() error { app, err := dyd.NewApp() if err != nil { return err } app.Bind("/index.html", Serve_index_1html) return app.ListenAndServe(":6226") }
Preprocessed index.html is in index.html.go.
// Code generated by dyd, DO NOT EDIT. package main import ( "time" "modernc.org/dyd/dyd" ) // Serve_index_1html produces /index.html. func Serve_index_1html(ctx *dyd.Context, write func(s string, args ...any) error) { write(`<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>dyd Hello world</title> </head> <body> Hello world! <p>`) write("req #%d for %s at %s", ctx.ReqSequence, ctx.Request.URL, time.Now().Format(time.RFC1123Z)) write(` </body> </html> `) }
And finally, main.go.
// Code generated by dyd, DO NOT EDIT. package main import "log" func main() { log.Fatal(dydStart()) }
Serving dynamic HTML without a .html file ¶
You can write a function with this signature in any of the directories
func Serve_<X>(ctx *dyd.Context, write func(s string, args ...any) error)
Replace <X> with what this function returns (copied from modernc.org/dyd/etc.go)
func encodeFileName(s string) string { s = strings.ReplaceAll(s, "_", "_0") s = strings.ReplaceAll(s, ".", "_1") return strings.ReplaceAll(s, "-", "_2") }
For example, "index.html" becomes "index_1html" and the complete function name will be "Serve_index_1html".
dyd can find such functions and adds them, using the decoded file name to the site map so the respective URLs will get handled by the user function. The function needs, of course, to produce a complete HTML document.
HTML file specification ¶
From dyd's POV a HTML file is an UTF-8 encoded text file with zero or more processing instructions embedded. Processing instructions between the `<?` and `?>` tags are used as Go code verbatim. The rest of the HTML file is used as HTML and again verbatim. That means a HTML file intended for dyd consumption does not have to be a complete, well-formed HTML document. Sometimes it is not because, for example, opening/closing tags can come from both HTML and Go, possibly intermixed. the preprocessor does not parse anything found in a HTML file, it looks only for the PI tags.
Also, the embedded Go code can contain characters forbidden in a HTML file. The well-formedness of HTML can be checked only after the web server fully produces a complete document and this is not computable at dyd/Go compile time in the general case.
The processing instruction opening and closing tags cannot nest and there's no escape sequence for the PI tags.
File name conventions ¶
HTML file names should preferably match `[a-zA-Z0-9-.]*`.
HTML files matching `^.*_\.html$`, like top_.html, are not externally bound, the web server will not handle their URLs. But all HTML URLs can be used by `ctx.Include()`.
Server side includes ¶
Use `ctx.Include(path)` to insert static/dynamic HTML, for example.
<div> <? ctx.Include("/layout/top_.html") ?> </div>
See examples/include for a full, working demo.
Some batteries included ¶
W3.CSS ¶
Use `ctx.W3CSS()` to get the content of https://www.w3schools.com/w3css/4/w3.css without using an internet connection. It can be used like this
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style><? write("%s", ctx.W3CSS()) ?></style> </head> <body> </body> </html>
More information about W3.CSS can be found at https://www.w3schools.com/w3css/default.asp.
The database ¶
Use `ctx.DBConn()` to get a connection to the persistent database of the web server instance, created on demand in the working directory. It's the CGo-free SQLite database (http://modernc.org/sqlite).
See examples/db for a full, working demo.
The key/value store ¶
Use `ctx.KV()` to access a persistent key/value store, created on demand within the web server instance database. Full documentation available at http://modernc.org/dyd/dyd.
See examples/kv for a full, working demo.
Directories ¶
Path | Synopsis |
---|---|
Package dyd provides runtime support for web servers created by the dyd command.
|
Package dyd provides runtime support for web servers created by the dyd command. |
examples
|
|
Command getdyd.org is the web server for www.getdyd.org.
|
Command getdyd.org is the web server for www.getdyd.org. |