bundler API

bundler

package

API reference for the bundler package.

S
struct

plugin

cmd/rfw/plugins/bundler/bundler.go:23-23
type plugin struct

Methods

Name
Method

Returns

string
func (*plugin) Name() string
{ return "bundler" }
Priority
Method

Returns

int
func (*plugin) Priority() int
{ return 10 }
PostBuild
Method

Parameters

Returns

err error
func (*plugin) PostBuild(_ json.RawMessage) (err error)
{
	if utils.IsDebug() {
		logging.Log.Info("skipped in debug mode", logging.F("plugin", "bundler"))
		return nil
	}

	m := minify.New()
	m.AddFunc("text/javascript", js.Minify)
	m.AddFunc("text/css", css.Minify)
	m.AddFunc("text/html", html.Minify)

	buildDir := "build"
	root, err := os.OpenRoot(buildDir)
	if err != nil {
		return err
	}
	defer func() {
		if closeErr := root.Close(); err == nil {
			err = closeErr
		}
	}()
	if err := filepath.Walk(buildDir, func(path string, info os.FileInfo, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if info.IsDir() {
			return nil
		}
		rel, err := filepath.Rel(buildDir, path)
		if err != nil {
			return err
		}
		ext := filepath.Ext(path)
		var media string
		var data []byte
		switch ext {
		case ".js":
			media = "text/javascript"
		case ".css":
			data, err = readRootFile(root, rel)
			if err != nil {
				return err
			}
			if isTailwindCSSData(data) {
				logging.Log.Info("skipping tailwind css", logging.F("plugin", "bundler"), logging.F("path", path))
				return nil
			}
			media = "text/css"
		case ".html":
			media = "text/html"
		default:
			return nil
		}
		if data == nil {
			data, err = readRootFile(root, rel)
			if err != nil {
				return err
			}
		}
		out, err := m.Bytes(media, data)
		if err != nil {
			return err
		}
		file, err := root.OpenFile(rel, os.O_TRUNC|os.O_WRONLY, 0o600)
		if err != nil {
			return err
		}
		_, writeErr := file.Write(out)
		return errors.Join(writeErr, file.Close())
	}); err != nil {
		return err
	}
	logging.Log.Info("build complete", logging.F("plugin", "bundler"))
	return nil
}
ShouldRebuild
Method

Parameters

path string

Returns

bool
func (*plugin) ShouldRebuild(path string) bool
{
	path = filepath.ToSlash(path)
	if strings.HasPrefix(path, "build/") {
		return false
	}
	return strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".html")
}
F
function

init

cmd/rfw/plugins/bundler/bundler.go:25-25
func init()

{ plugins.Register(&plugin{}) }
F
function

readRootFile

Parameters

root
path
string

Returns

[]byte
error
cmd/rfw/plugins/bundler/bundler.go:115-122
func readRootFile(root *os.Root, path string) ([]byte, error)

{
	file, err := root.Open(path)
	if err != nil {
		return nil, err
	}
	data, readErr := io.ReadAll(file)
	return data, errors.Join(readErr, file.Close())
}
F
function

isTailwindCSSData

Parameters

data
[]byte

Returns

bool
cmd/rfw/plugins/bundler/bundler.go:124-127
func isTailwindCSSData(data []byte) bool

{
	src := string(data)
	return strings.Contains(src, "@tailwind") || strings.Contains(src, "tailwindcss")
}
F
function

TestShouldRebuild

Parameters

cmd/rfw/plugins/bundler/bundler_test.go:13-30
func TestShouldRebuild(t *testing.T)

{
	p := &plugin{}
	if !p.ShouldRebuild("main.js") {
		t.Fatalf("expected js change to trigger rebuild")
	}
	if !p.ShouldRebuild("styles.css") {
		t.Fatalf("expected css change to trigger rebuild")
	}
	if !p.ShouldRebuild("index.html") {
		t.Fatalf("expected html change to trigger rebuild")
	}
	if p.ShouldRebuild(filepath.Join("build", "app.js")) {
		t.Fatalf("output directory should not trigger rebuild")
	}
	if p.ShouldRebuild("image.png") {
		t.Fatalf("unrelated files should not trigger rebuild")
	}
}
F
function

TestIsTailwindCSS

Parameters

cmd/rfw/plugins/bundler/bundler_test.go:32-39
func TestIsTailwindCSS(t *testing.T)

{
	if !isTailwindCSSData([]byte("@import \"tailwindcss\";")) {
		t.Fatalf("expected tailwind directives to be detected")
	}
	if isTailwindCSSData([]byte("body{}")) {
		t.Fatalf("unexpected tailwind detection in normal css")
	}
}
F
function

TestPostBuildMinifiesFiles

Parameters

cmd/rfw/plugins/bundler/bundler_test.go:41-92
func TestPostBuildMinifiesFiles(t *testing.T)

{
	dir := t.TempDir()
	buildDir := filepath.Join(dir, "build", "static")
	if err := os.MkdirAll(buildDir, 0o750); err != nil {
		t.Fatalf("mkdir build: %v", err)
	}

	jsFile := filepath.Join(buildDir, "app.js")
	if err := os.WriteFile(jsFile, []byte("function add ( a , b ){ return a + b ; }"), 0o600); err != nil {
		t.Fatalf("write js: %v", err)
	}
	cssFile := filepath.Join(buildDir, "app.css")
	if err := os.WriteFile(cssFile, []byte("body { color: red; }"), 0o600); err != nil {
		t.Fatalf("write css: %v", err)
	}
	htmlFile := filepath.Join(buildDir, "index.html")
	html := "<html><head><title> hi </title></head><body> <h1> hi </h1> </body></html>"
	if err := os.WriteFile(htmlFile, []byte(html), 0o600); err != nil {
		t.Fatalf("write html: %v", err)
	}

	t.Chdir(dir)

	utils.EnableDebug(false)

	p := &plugin{}
	if err := p.PostBuild(nil); err != nil {
		t.Fatalf("postbuild: %v", err)
	}

	outJS, err := os.ReadFile("build/static/app.js")
	if err != nil {
		t.Fatal(err)
	}
	if len(outJS) >= len("function add ( a , b ){ return a + b ; }") {
		t.Fatalf("js not minified: %s", outJS)
	}
	outCSS, err := os.ReadFile("build/static/app.css")
	if err != nil {
		t.Fatal(err)
	}
	if len(outCSS) >= len("body { color: red; }") {
		t.Fatalf("css not minified: %s", outCSS)
	}
	outHTML, err := os.ReadFile("build/static/index.html")
	if err != nil {
		t.Fatal(err)
	}
	if len(outHTML) >= len(html) {
		t.Fatalf("html not minified: %s", outHTML)
	}
}
F
function

TestPostBuildSkippedInDebug

Parameters

cmd/rfw/plugins/bundler/bundler_test.go:94-124
func TestPostBuildSkippedInDebug(t *testing.T)

{
	dir := t.TempDir()
	buildDir := filepath.Join(dir, "build", "static")
	if err := os.MkdirAll(buildDir, 0o750); err != nil {
		t.Fatalf("mkdir build: %v", err)
	}

	jsFile := filepath.Join(buildDir, "app.js")
	content := "function add ( a , b ){ return a + b ; }"
	if err := os.WriteFile(jsFile, []byte(content), 0o600); err != nil {
		t.Fatalf("write js: %v", err)
	}

	t.Chdir(dir)

	utils.EnableDebug(true)
	defer utils.EnableDebug(false)

	p := &plugin{}
	if err := p.PostBuild(nil); err != nil {
		t.Fatalf("postbuild: %v", err)
	}

	out, err := os.ReadFile("build/static/app.js")
	if err != nil {
		t.Fatal(err)
	}
	if string(out) != content {
		t.Fatalf("file should remain unminified, got %s", out)
	}
}