Documentation ¶
Overview ¶
Command dyd compiles a mix of HTML and/or Go to additonal Go files that all together represent an executable file - a self-contained web server. The server reflects the directory structure under the web root.
The tool is only a proof of concept at the moment.
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>
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] 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.
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.
0:~/tmp/dyd$ dyd -v -modinit example.com/hello -serve running [go mod init example.com/hello] preprocessing running [/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_indexd_dhtml() generating dyd.go running [/home/jnml/bin/goimports -l -w dyd.go] generating /home/jnml/tmp/dyd/main.go running [/home/jnml/bin/goimports -l -w /home/jnml/tmp/dyd/main.go] running [go mod tidy] starting server at :6226 running [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.