build.go
package frontend
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/evanw/esbuild/pkg/api"
)
// Build compiles all components in SourceDir to OutputDir.
// If a bundler is configured (via WithBundler), it delegates to that bundler.
func (f *Frontend) Build() error {
// Use bundler if configured
if f.bundler != nil {
return f.bundler.Build()
}
// Legacy build: discover component files
entries, err := f.discoverComponents()
if err != nil {
return fmt.Errorf("discovering components: %w", err)
}
if len(entries) == 0 {
// No components found, create empty bundle
if err := os.MkdirAll(f.OutputDir, 0755); err != nil {
return fmt.Errorf("creating output dir: %w", err)
}
return os.WriteFile(filepath.Join(f.OutputDir, "components.js"), []byte("// No components\nwindow.__render = () => {};\nwindow.__unmount = () => {};\n"), 0644)
}
// Build options with sensible defaults
opts := api.BuildOptions{
EntryPoints: entries,
Bundle: true,
Outfile: filepath.Join(f.OutputDir, "components.js"),
Format: api.FormatESModule,
Target: api.ES2020,
Platform: api.PlatformBrowser,
Sourcemap: api.SourceMapLinked,
Write: true,
LogLevel: api.LogLevelWarning,
JSX: api.JSXAutomatic,
Define: map[string]string{
"process.env.NODE_ENV": `"production"`,
},
}
// Minify in production
if !f.DevMode {
opts.MinifyWhitespace = true
opts.MinifyIdentifiers = true
opts.MinifySyntax = true
}
// Create output directory
if err := os.MkdirAll(f.OutputDir, 0755); err != nil {
return fmt.Errorf("creating output dir: %w", err)
}
// Run esbuild
result := api.Build(opts)
// Check for errors
if len(result.Errors) > 0 {
var errMsgs []string
for _, e := range result.Errors {
errMsgs = append(errMsgs, e.Text)
}
return fmt.Errorf("build errors: %s", strings.Join(errMsgs, "; "))
}
return nil
}
// discoverComponents finds all component files in SourceDir.
func (f *Frontend) discoverComponents() ([]string, error) {
var entries []string
// Check if source directory exists
if _, err := os.Stat(f.SourceDir); os.IsNotExist(err) {
return nil, nil
}
// Walk the source directory
err := filepath.Walk(f.SourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// Include .tsx, .ts, .jsx, .js files
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".tsx", ".ts", ".jsx", ".js":
entries = append(entries, path)
}
return nil
})
return entries, err
}
// BuildAndWatch builds components and starts watching for changes.
// This is a convenience method that combines Build() and Dev().
func (f *Frontend) BuildAndWatch() error {
// Initial build
if err := f.Build(); err != nil {
return err
}
// Start dev server if in dev mode
if f.DevMode {
return f.Dev()
}
return nil
}