rename packages, refactor out tailwind compiler,
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
package main
|
||||
|
||||
// Thin CLI wrapper around kjol/bundler. The bundler wires its own Go-native
|
||||
// Solid JSX compiler (see bundler.Build), so this wrapper carries no build logic.
|
||||
// Thin CLI wrapper around kjol/webbundler. The bundler wires its own Go-native
|
||||
// Solid JSX compiler (see webbundler.Build), so this wrapper carries no build logic.
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"kjol/bundler"
|
||||
"kjol/webbundler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -19,15 +19,15 @@ func main() {
|
||||
genTS := flag.String("gen-ts", "", "Directory for the generated TS icon registry (default <app>/src/ui/generated).")
|
||||
flag.Parse()
|
||||
|
||||
cfg := bundler.Config{
|
||||
cfg := webbundler.Config{
|
||||
AppFrontend: *app,
|
||||
WebDir: *web,
|
||||
Output: *out,
|
||||
GenGoDir: *genGo,
|
||||
GenTSDir: *genTS,
|
||||
}
|
||||
if err := bundler.Build(cfg); err != nil {
|
||||
if err := webbundler.Build(cfg); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,30 @@ a `static` route, and fetching only exists on the client, so the fetches no-op
|
||||
during SSR: the server pre-renders the page's **spinner**, and the client runs
|
||||
them for real after hydration.
|
||||
|
||||
Styling is **Tailwind**: the dev server (and `build.sh`) run `kjol/cmd/twcss`,
|
||||
which scans the Go markup + the `webui` kit for utility classes and compiles
|
||||
`css/app.css` → `wwwroot/app.css` with kjol's native Tailwind v4 engine. There is
|
||||
**no Bootstrap and no hand-written CSS**. (`build.sh` does a one-off build.)
|
||||
Styling is **Tailwind**: the build runs `kjol/cmd/twcss`, which scans the Go markup +
|
||||
the `webui` kit for utility classes and compiles `css/app.css` → `wwwroot/app.css` with
|
||||
kjol's native Tailwind v4 engine (`kjol/tw`). There is **no Bootstrap and no
|
||||
hand-written CSS**.
|
||||
|
||||
Saving a `.css` file recompiles **only** Tailwind and swaps the stylesheet into the live
|
||||
page — no wasm rebuild, no reload, no lost state. Saving a `.go` file does the full
|
||||
rebuild and hot-swaps the wasm.
|
||||
|
||||
## Building
|
||||
|
||||
The build is Go, not a shell script — `buildsteps/` holds the four steps (codegen →
|
||||
Tailwind → wasm → `wasm_exec.js` shim), and both the one-off build and the dev server's
|
||||
watch loop call the *same* functions, so they cannot drift apart.
|
||||
|
||||
```
|
||||
go run ./build # one-off: codegen + Tailwind + wasm + shim
|
||||
go run ./server # dev server: does the same build, then watches and hot-reloads
|
||||
```
|
||||
|
||||
In VS Code these are the `gowasm: build` and `gowasm: dev server (hot reload)` tasks;
|
||||
both run through `gowasm: prebuild` (codegen + Tailwind), which is also the
|
||||
`preLaunchTask` of the debug configs — under the debugger the binary is built by Delve,
|
||||
so nothing else would generate `app/*.gen.go`.
|
||||
|
||||
## This is a separate module
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-compile step: directive codegen, Tailwind CSS, WebAssembly build, JS shim.
|
||||
# Run the dev server instead (go run ./server) for hot reload; this is for a
|
||||
# one-off/production-style build. Run from anywhere.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
KJOL_GO="$(cd ../../.. && pwd)"
|
||||
|
||||
echo "==> Generating directive glue (//gowasm:page, //gowasm:layout, //gowasm:server)"
|
||||
go run kjol/cmd/wasmgen ./app
|
||||
|
||||
echo "==> Compiling Tailwind CSS -> wwwroot/app.css (scanning webui + Go markup)"
|
||||
# Run twcss from the kjol module root so the Tailwind engine's deps resolve there
|
||||
# (not in this example module — keeps its go.mod lean).
|
||||
( cd "$KJOL_GO" && go run ./cmd/twcss \
|
||||
-entry cmd/examples/go-wasm-web/css/app.css \
|
||||
-out cmd/examples/go-wasm-web/wwwroot/app.css \
|
||||
-base . \
|
||||
'webui/**/*.go' \
|
||||
'cmd/examples/go-wasm-web/app/**/*.go' \
|
||||
'cmd/examples/go-wasm-web/server/**/*.go' )
|
||||
|
||||
echo "==> Compiling ./wasm to wwwroot/app.wasm (GOOS=js GOARCH=wasm)"
|
||||
GOOS=js GOARCH=wasm go build -o wwwroot/app.wasm ./wasm
|
||||
|
||||
echo "==> Copying Go's wasm_exec.js shim into wwwroot/"
|
||||
GOROOT="$(go env GOROOT)"
|
||||
shim="$GOROOT/lib/wasm/wasm_exec.js" # Go >= 1.24
|
||||
[ -f "$shim" ] || shim="$GOROOT/misc/wasm/wasm_exec.js" # Go <= 1.23
|
||||
rm -f wwwroot/wasm_exec.js # GOROOT copy is read-only; remove before overwriting
|
||||
cp "$shim" wwwroot/wasm_exec.js
|
||||
chmod u+w wwwroot/wasm_exec.js
|
||||
|
||||
echo "==> Done. Run the server with: go run ./server"
|
||||
echo " then open http://localhost:8085"
|
||||
41
go/cmd/examples/go-wasm-web/build/main.go
Normal file
41
go/cmd/examples/go-wasm-web/build/main.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Command build runs the example's full pre-compile step once: directive codegen,
|
||||
// Tailwind, the wasm binary, and Go's JS shim.
|
||||
//
|
||||
// go run ./build # from cmd/examples/go-wasm-web
|
||||
//
|
||||
// For day-to-day work run the dev server instead (`go run ./server`) — it performs
|
||||
// these same steps on every save and hot-swaps the result into the browser. This
|
||||
// command is for a cold build, CI, or an editor's pre-launch task.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gowasmweb/buildsteps"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
steps := []struct {
|
||||
name string
|
||||
run func() ([]byte, error)
|
||||
}{
|
||||
{"generating directive glue (//gowasm:page, //gowasm:layout, //gowasm:server)", buildsteps.Codegen},
|
||||
{"compiling Tailwind CSS -> wwwroot/app.css", buildsteps.Tailwind},
|
||||
{"compiling ./wasm -> wwwroot/app.wasm (GOOS=js GOARCH=wasm)", buildsteps.Wasm},
|
||||
{"copying Go's wasm_exec.js shim into wwwroot/", buildsteps.Shim},
|
||||
}
|
||||
|
||||
for _, s := range steps {
|
||||
log.Println("==>", s.name)
|
||||
if out, err := s.run(); err != nil {
|
||||
os.Stderr.Write(out)
|
||||
log.Fatalln("build failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("==> Done. Run the server with: go run ./server")
|
||||
log.Println(" then open http://localhost:8085")
|
||||
}
|
||||
106
go/cmd/examples/go-wasm-web/buildsteps/buildsteps.go
Normal file
106
go/cmd/examples/go-wasm-web/buildsteps/buildsteps.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Package buildsteps is the example's build pipeline: directive codegen, Tailwind,
|
||||
// the wasm binary, and Go's JS shim.
|
||||
//
|
||||
// It is Go, not a shell script, for three reasons. The dev server needs to call these
|
||||
// steps on every save and cannot shell out to bash on Windows. Editors need to run
|
||||
// them as tasks, and a task that only works on one platform is a task half the team
|
||||
// cannot use. And the one-off build and the watch build must be the SAME steps — the
|
||||
// moment they are two scripts, they drift, and the bug only shows up in whichever one
|
||||
// you use less.
|
||||
//
|
||||
// Run the whole thing with `go run ./build`.
|
||||
package buildsteps
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// kjolRoot is the kjol Go module root, relative to the example directory. The Tailwind
|
||||
// and codegen commands are run FROM there so the engine's dependencies resolve in
|
||||
// kjol's own go.mod, and this example's stays lean.
|
||||
const kjolRoot = "../../.."
|
||||
|
||||
// Wwwroot is where every build artefact lands, and what the dev server serves.
|
||||
const Wwwroot = "wwwroot"
|
||||
|
||||
// Codegen regenerates app/*.gen.go from the //gowasm: directives — the routes, the
|
||||
// layouts, and the client stubs for server components. It runs FIRST: everything after
|
||||
// it compiles the code it writes.
|
||||
func Codegen() ([]byte, error) {
|
||||
return run("go", "run", "kjol/cmd/wasmgen", "./app")
|
||||
}
|
||||
|
||||
// Tailwind compiles css/app.css to wwwroot/app.css, scanning the webui kit and this
|
||||
// example's Go markup for utility candidates.
|
||||
//
|
||||
// It scans .go files, which is the whole point of kjol's native engine: the markup is
|
||||
// written in Go, so that is where the class names are. There is no JS build here at
|
||||
// all.
|
||||
func Tailwind() ([]byte, error) {
|
||||
cmd := exec.Command("go", "run", "./cmd/twcss",
|
||||
"-entry", "cmd/examples/go-wasm-web/css/app.css",
|
||||
"-out", "cmd/examples/go-wasm-web/wwwroot/app.css",
|
||||
"-base", ".",
|
||||
"webui/**/*.go",
|
||||
"cmd/examples/go-wasm-web/app/**/*.go",
|
||||
"cmd/examples/go-wasm-web/server/**/*.go",
|
||||
)
|
||||
cmd.Dir = kjolRoot
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
// Wasm compiles ./wasm to wwwroot/app.wasm.
|
||||
func Wasm() ([]byte, error) {
|
||||
cmd := exec.Command("go", "build", "-o", filepath.Join(Wwwroot, "app.wasm"), "./wasm")
|
||||
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
// Shim copies Go's wasm_exec.js into wwwroot. It is the loader the browser needs to
|
||||
// start a Go wasm binary, it ships with the toolchain, and it must match the compiler
|
||||
// that produced the binary — so it is copied from GOROOT rather than vendored.
|
||||
func Shim() ([]byte, error) {
|
||||
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
goroot := strings.TrimSpace(string(out))
|
||||
|
||||
for _, src := range []string{
|
||||
filepath.Join(goroot, "lib", "wasm", "wasm_exec.js"), // Go >= 1.24
|
||||
filepath.Join(goroot, "misc", "wasm", "wasm_exec.js"), // Go <= 1.23
|
||||
} {
|
||||
b, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dst := filepath.Join(Wwwroot, "wasm_exec.js")
|
||||
// The GOROOT copy is read-only, and so is the copy we made last time. Remove it
|
||||
// first, or the write fails with a permission error that says nothing useful.
|
||||
os.Remove(dst)
|
||||
return nil, os.WriteFile(dst, b, 0o644)
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// All is the full build, in order. It is what the dev server runs on a code change and
|
||||
// what `go run ./build` runs once.
|
||||
//
|
||||
// Returned output is the failing command's combined stdout+stderr, which the dev server
|
||||
// puts straight into the browser's error overlay — so a compile error lands in front of
|
||||
// you rather than in a terminal you were not looking at.
|
||||
func All() ([]byte, error) {
|
||||
for _, step := range []func() ([]byte, error){Codegen, Tailwind, Wasm, Shim} {
|
||||
if out, err := step(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func run(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).CombinedOutput()
|
||||
}
|
||||
@@ -13,15 +13,13 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"kjol/httputil"
|
||||
"kjol/vdom"
|
||||
"kjol/wasmdevserver"
|
||||
|
||||
"gowasmweb/app"
|
||||
"gowasmweb/buildsteps"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -34,7 +32,8 @@ func main() {
|
||||
Dir: "./wwwroot",
|
||||
Watch: *watch,
|
||||
WatchDirs: []string{"app", "wasm", "css", "../../../webui", "../../../vdom", "../../../wasmruntime", "../../../rsc"}, // example + kjol engine + kit
|
||||
Build: buildWasm,
|
||||
Build: buildsteps.All,
|
||||
BuildCSS: buildsteps.Tailwind, // a .css save skips codegen+wasm and hot-swaps the stylesheet
|
||||
Render: render,
|
||||
Document: document,
|
||||
Handle: apiRoutes,
|
||||
@@ -89,29 +88,3 @@ func document(inner string) string {
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
// buildWasm runs the directive codegen (kjol/cmd/wasmgen), compiles the Tailwind
|
||||
// CSS (kjol/cmd/twcss, scanning the Go markup + webui kit), then compiles ./wasm
|
||||
// to wwwroot/app.wasm. Returned combined output is shown in the browser overlay
|
||||
// on failure.
|
||||
func buildWasm() ([]byte, error) {
|
||||
if out, err := exec.Command("go", "run", "kjol/cmd/wasmgen", "./app").CombinedOutput(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
// Compile Tailwind from the kjol module root (so the engine's deps resolve),
|
||||
// scanning the webui kit + this example's Go markup for utility candidates.
|
||||
tw := exec.Command("go", "run", "./cmd/twcss",
|
||||
"-entry", "cmd/examples/go-wasm-web/css/app.css",
|
||||
"-out", "cmd/examples/go-wasm-web/wwwroot/app.css",
|
||||
"-base", ".",
|
||||
"webui/**/*.go",
|
||||
"cmd/examples/go-wasm-web/app/**/*.go",
|
||||
"cmd/examples/go-wasm-web/server/**/*.go")
|
||||
tw.Dir = "../../.." // kjol/go
|
||||
if out, err := tw.CombinedOutput(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
cmd := exec.Command("go", "build", "-o", filepath.Join("wwwroot", "app.wasm"), "./wasm")
|
||||
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
// The wasm client entry point (main.go) builds only under GOOS=js GOARCH=wasm.
|
||||
// This native placeholder keeps the package buildable on the host so a plain
|
||||
// `go build ./...` succeeds; the real client is built by build.sh / the dev
|
||||
// server with GOOS=js GOARCH=wasm.
|
||||
// `go build ./...` succeeds; the real client is built by ./build (or the dev
|
||||
// server) with GOOS=js GOARCH=wasm.
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"kjol/bundler"
|
||||
"kjol/tw"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, "twcss:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
css, err := bundler.CompileTailwind(string(src), *base, flag.Args())
|
||||
css, err := tw.CompileFiles(string(src), *base, flag.Args())
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "twcss:", err)
|
||||
os.Exit(1)
|
||||
|
||||
Reference in New Issue
Block a user