{
args := []string{"build"}
var tags []string
if opts.Devtools {
tags = append(tags, "devtools")
}
if opts.DevBuild {
tags = append(tags, "rfwdev")
}
if len(tags) > 0 {
args = append(args, "-tags="+strings.Join(tags, ","))
}
if !opts.SkipOptimize {
args = append(args, "-trimpath", "-ldflags=-s -w")
}
return args
}
{
var manifest struct {
Build struct {
Type string `json:"type"`
} `json:"build"`
Plugins map[string]json.RawMessage `json:"plugins"`
}
if data, err := os.ReadFile("rfw.json"); err == nil {
_ = json.Unmarshal(data, &manifest)
}
if os.Getenv("RFW_DEVTOOLS") == "1" {
if manifest.Plugins == nil {
manifest.Plugins = map[string]json.RawMessage{}
}
manifest.Plugins["devtools"] = nil
}
if err := plugins.Configure(manifest.Plugins); err != nil {
return fmt.Errorf("failed to configure plugins: %w", err)
}
if err := plugins.PreBuild(); err != nil {
return fmt.Errorf("pre build failed: %w", err)
}
clientDir := filepath.Join("build", "client")
hostDir := filepath.Join("build", "host")
staticDir := filepath.Join("build", "static")
if err := os.MkdirAll(clientDir, 0o755); err != nil {
return fmt.Errorf("failed to create client build directory: %w", err)
}
if err := os.MkdirAll(staticDir, 0o755); err != nil {
return fmt.Errorf("failed to create static build directory: %w", err)
}
goroot, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
return fmt.Errorf("failed to get GOROOT: %w", err)
}
wasmExec := filepath.Join(strings.TrimSpace(string(goroot)), "lib", "wasm", "wasm_exec.js")
if err := copyFile(wasmExec, filepath.Join(clientDir, "wasm_exec.js")); err != nil {
return fmt.Errorf("failed to copy wasm_exec.js: %w", err)
}
args := goBuildArgs(buildOptions{
Devtools: os.Getenv("RFW_DEVTOOLS") == "1",
DevBuild: os.Getenv("RFW_DEV_BUILD") == "1",
SkipOptimize: utils.IsDebug() || os.Getenv("RFW_SKIP_STRIP") == "1",
})
wasmPath := filepath.Join(clientDir, "app.wasm")
args = append(args, "-o", wasmPath, ".")
cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), "GOARCH=wasm", "GOOS=js")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to build project: %s: %w", output, err)
}
if err := compressWasmBrotli(wasmPath); err != nil {
return fmt.Errorf("failed to brotli-compress wasm: %w", err)
}
if err := os.MkdirAll(hostDir, 0o755); err != nil {
return fmt.Errorf("failed to create host build directory: %w", err)
}
hostCmd := exec.Command("go", "build", "-o", filepath.Join(hostDir, "host"), "./host")
if hostOutput, err := hostCmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to build host components: %s: %w", hostOutput, err)
}
if err := plugins.Build(); err != nil {
return fmt.Errorf("failed to run plugins: %w", err)
}
if _, err := os.Stat("index.html"); err == nil {
if err := copyFile("index.html", filepath.Join(clientDir, "index.html")); err != nil {
return fmt.Errorf("failed to copy index.html: %w", err)
}
}
if _, err := os.Stat("wasm_loader.js"); err == nil {
if err := copyFile("wasm_loader.js", filepath.Join(clientDir, "wasm_loader.js")); err != nil {
return fmt.Errorf("failed to copy wasm_loader.js: %w", err)
}
}
if _, err := os.Stat("static"); err == nil {
if err := filepath.Walk("static", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
rel, err := filepath.Rel("static", path)
if err != nil {
return err
}
dst := filepath.Join(staticDir, rel)
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
return copyFile(path, dst)
}); err != nil {
return fmt.Errorf("failed to copy static assets: %w", err)
}
}
if err := plugins.PostBuild(); err != nil {
return fmt.Errorf("post build failed: %w", err)
}
return nil
}
{
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
if _, err = io.Copy(out, in); err != nil {
return err
}
return out.Close()
}
{
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
dst := src + ".br"
tmp, err := os.CreateTemp(filepath.Dir(dst), filepath.Base(dst)+".*")
if err != nil {
return err
}
tmpName := tmp.Name()
defer func() {
if tmp != nil {
tmp.Close()
}
if err != nil {
_ = os.Remove(tmpName)
}
}()
writer := brotli.NewWriterLevel(tmp, brotli.BestCompression)
if _, err := io.Copy(writer, in); err != nil {
writer.Close()
return err
}
if err := writer.Close(); err != nil {
return err
}
if err := tmp.Close(); err != nil {
return err
}
tmp = nil
if err := os.Rename(tmpName, dst); err != nil {
return err
}
return nil
}
TestCopyFile ensures copyFile replicates the source file's contents at the
destination path.
{
dir := t.TempDir()
src := filepath.Join(dir, "src.txt")
dst := filepath.Join(dir, "dst.txt")
content := []byte("hello world")
if err := os.WriteFile(src, content, 0o644); err != nil {
t.Fatalf("write src: %v", err)
}
if err := copyFile(src, dst); err != nil {
t.Fatalf("copyFile error: %v", err)
}
got, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("read dst: %v", err)
}
if string(got) != string(content) {
t.Fatalf("expected %q, got %q", content, got)
}
}
{
dir := t.TempDir()
src := filepath.Join(dir, "app.wasm")
content := []byte(strings.Repeat("rfw wasm", 32))
if err := os.WriteFile(src, content, 0o644); err != nil {
t.Fatalf("write wasm: %v", err)
}
if err := compressWasmBrotli(src); err != nil {
t.Fatalf("compressWasmBrotli: %v", err)
}
brPath := src + ".br"
f, err := os.Open(brPath)
if err != nil {
t.Fatalf("open brotli file: %v", err)
}
defer f.Close()
reader := brotli.NewReader(f)
decompressed, err := io.ReadAll(reader)
if err != nil {
t.Fatalf("read brotli: %v", err)
}
if string(decompressed) != string(content) {
t.Fatalf("unexpected decompressed content")
}
}
import "encoding/json"
import "fmt"
import "io"
import "os"
import "os/exec"
import "path/filepath"
import "strings"
import "github.com/andybalholm/brotli"
import "github.com/rfwlab/rfw/cmd/rfw/plugins"
import "github.com/rfwlab/rfw/cmd/rfw/plugins/assets"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/bundler"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/copy"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/devtools"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/docs"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/env"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/pages"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/seo"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/tailwind"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/plugins/test"
Anonymous Import
import "github.com/rfwlab/rfw/cmd/rfw/utils"
import "io"
import "os"
import "path/filepath"
import "strings"
import "testing"
import "github.com/andybalholm/brotli"