blob: 9068561e1e7ca723339ac6ab9fb46abad30c651b [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package static builds static assets for the frontend and the worker.
package static
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/evanw/esbuild/pkg/api"
)
type Config struct {
// Entrypoint is a directory in which to to build TypeScript
// sources.
EntryPoint string
// Bundle is true if files imported by an entry file
// should be joined together in a single output file.
Bundle bool
// Watch is true in development. Sourcemaps are placed inline,
// the output is unminified, and changes to any TypeScript
// files will force a rebuild of the JavaScript output.
Watch bool
}
// Build compiles TypeScript files into minified JavaScript
// files using github.com/evanw/esbuild.
//
// This function is used in Server.staticHandler with Watch=true
// when cmd/frontend is run in dev mode and in
// devtools/cmd/static/main.go with Watch=false for building
// productionized assets.
func Build(config Config) (*api.BuildResult, error) {
files, err := getEntry(config.EntryPoint, config.Bundle)
if err != nil {
return nil, err
}
options := api.BuildOptions{
EntryPoints: files,
Bundle: config.Bundle,
Outdir: config.EntryPoint,
Write: true,
OutExtensions: map[string]string{".css": ".min.css"},
External: []string{"*.svg"},
Banner: map[string]string{"css": "/*!\n" +
" * Copyright 2021 The Go Authors. All rights reserved.\n" +
" * Use of this source code is governed by a BSD-style\n" +
" * license that can be found in the LICENSE file.\n" +
" */"},
}
if config.Watch {
options.Sourcemap = api.SourceMapInline
options.Watch = &api.WatchMode{}
} else {
options.MinifyIdentifiers = true
options.MinifySyntax = true
options.MinifyWhitespace = true
options.Sourcemap = api.SourceMapLinked
}
result := api.Build(options)
if len(result.Errors) > 0 {
return nil, fmt.Errorf("error building static files: %v", result.Errors)
}
if len(result.Warnings) > 0 {
return nil, fmt.Errorf("error building static files: %v", result.Warnings)
}
return &result, nil
}
// getEntry walks the the given directory and collects entry file paths
// for esbuild. It ignores test files and files prefixed with an underscore.
// Underscore prefixed files are assumed to be imported by and bundled together
// with the output of an entry file.
func getEntry(dir string, bundle bool) ([]string, error) {
var matches []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
basePath := filepath.Base(path)
notPartial := !strings.HasPrefix(basePath, "_")
notTest := !strings.HasSuffix(basePath, ".test.ts")
isTS := strings.HasSuffix(basePath, ".ts")
isCSS := strings.HasSuffix(basePath, ".css") && !strings.HasSuffix(basePath, ".min.css")
if notPartial && notTest && (isTS || (bundle && isCSS)) {
matches = append(matches, path)
}
return nil
})
if err != nil {
return nil, err
}
return matches, nil
}