Created
March 8, 2023 06:19
-
-
Save hasenj/1b06ca1d5749e2045dff57b2f0fa67a0 to your computer and use it in GitHub Desktop.
A little js/ts bundler based on esbuild (assumes preact, change to suit your needs). CC0/mit-0/unlicense
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package esbuilder | |
import "os" | |
import "path" | |
import "fmt" | |
import "log" | |
import "time" | |
// the main hero! | |
import "github.com/evanw/esbuild/pkg/api" | |
// so we can support watch mode | |
import "github.com/fsnotify/fsnotify" | |
// for copying files and directories as-is | |
import "github.com/otiai10/copy" | |
import "github.com/fatih/color" | |
type Options struct { | |
Minify bool | |
EntryPoints []string | |
Define map[string]string | |
Outdir string | |
CopyRoot string // the root directory that the items to copy are relative to | |
CopyItems []string // files and directories to copy as-is to the output directory | |
} | |
func Build(options Options) bool { | |
t0 := time.Now() | |
esbOptions := api.BuildOptions{ | |
EntryPoints: options.EntryPoints, | |
Charset: api.CharsetUTF8, | |
Bundle: true, | |
Outdir: options.Outdir, | |
MinifyWhitespace: false, | |
MinifyIdentifiers: false, | |
MinifySyntax: false, | |
Sourcemap: api.SourceMapLinked, | |
LogLimit: 3, | |
JSXFactory: "preact.h", | |
JSXFragment: "preact.Fragment", | |
Define: options.Define, | |
Write: true, | |
} | |
if options.Minify { | |
esbOptions.MinifyWhitespace = true | |
esbOptions.MinifyIdentifiers = true | |
esbOptions.MinifySyntax = true | |
esbOptions.Sourcemap = api.SourceMapNone | |
} | |
os.RemoveAll(options.Outdir) | |
cleanTime := time.Since(t0).Round(time.Millisecond) | |
t1 := time.Now() | |
result := api.Build(esbOptions) | |
buildTime := time.Since(t1).Round(time.Millisecond) | |
if len(result.Errors) > 0 { | |
msgColor := color.New(color.FgRed, color.Bold) | |
locColor := color.New(color.FgMagenta) | |
contentColor := color.New(color.FgHiCyan) | |
contentErrorColor := color.New(color.FgHiRed) | |
errorCursorColor := color.New(color.FgHiRed) | |
for _, err := range result.Errors { | |
if err.Location != nil { | |
locColor.Printf("%s:%d:%d: ", err.Location.File, err.Location.Line, err.Location.Column) | |
} | |
msgColor.Println(err.Text) | |
if err.Location != nil { | |
fmt.Println() | |
fmt.Print(" ") | |
textBefore := err.Location.LineText[:err.Location.Column] | |
textError := err.Location.LineText[err.Location.Column : err.Location.Column+err.Location.Length] | |
textAfter := err.Location.LineText[err.Location.Column+err.Location.Length:] | |
contentColor.Print(textBefore) | |
contentErrorColor.Print(textError) | |
contentColor.Print(textAfter) | |
fmt.Println() | |
fmt.Print(" ") | |
for _ = range textBefore { | |
fmt.Print(" ") | |
} | |
for _ = range textError { | |
errorCursorColor.Print("^") | |
} | |
fmt.Println() | |
} | |
} | |
return false | |
} | |
t2 := time.Now() | |
// copy some files as-is | |
var copyErrors []error | |
for _, item := range options.CopyItems { | |
err := copy.Copy(path.Join(options.CopyRoot, item), path.Join(options.Outdir, item)) | |
if err != nil { | |
copyErrors = append(copyErrors, err) | |
} | |
} | |
if len(copyErrors) > 0 { | |
fmt.Println("Copying Errors:") | |
for _, err := range copyErrors { | |
fmt.Println(err) | |
} | |
return false | |
} | |
copyTime := time.Since(t2).Round(time.Millisecond) | |
fmt.Println("Frontend Build Time:", time.Since(t0).Round(time.Millisecond)) | |
return true | |
} | |
func WatchFrontend(options Options, watchDirs ...string) { | |
callback := func() { | |
log.Println("Building frontend") | |
Build(options) | |
} | |
watcher, err := fsnotify.NewWatcher() | |
if err != nil { | |
fmt.Println("Watching is not supported:", err) | |
return | |
} | |
for _, dir := range watchDirs { | |
err = watcher.Add(dir) | |
if err != nil { | |
fmt.Println("Error watching directory", dir) | |
fmt.Println("\t", err) | |
return | |
} | |
} | |
// build once at first | |
callback() | |
var next time.Time | |
var waitTime = 10 * time.Millisecond | |
for event := range watcher.Events { | |
if !(event.Op == fsnotify.Write || event.Op == fsnotify.Create) { | |
// we don't care about these other events | |
continue | |
} | |
var now = time.Now() | |
if event.Op == fsnotify.Create { | |
watcher.Add(event.Name) | |
} | |
if now.Before(next) { | |
// fmt.Println("skipping file event handler; next is scheduled at", next) | |
continue | |
} | |
next = now.Add(waitTime) | |
time.AfterFunc(waitTime, callback) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment