2022-06-07 21:24:22 +00:00
|
|
|
// Copyright (c) 2022 Tailscale Inc & 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 main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
2022-07-26 00:51:06 +00:00
|
|
|
"time"
|
2022-06-07 21:24:22 +00:00
|
|
|
|
|
|
|
esbuild "github.com/evanw/esbuild/pkg/api"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
devMode = true
|
|
|
|
prodMode = false
|
|
|
|
)
|
|
|
|
|
|
|
|
// commonSetup performs setup that is common to both dev and build modes.
|
|
|
|
func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
|
|
|
|
// Change cwd to to where this file lives -- that's where all inputs for
|
|
|
|
// esbuild and other build steps live.
|
|
|
|
if _, filename, _, ok := runtime.Caller(0); ok {
|
|
|
|
if err := os.Chdir(path.Dir(filename)); err != nil {
|
|
|
|
return nil, fmt.Errorf("Cannot change cwd: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := buildDeps(dev); err != nil {
|
|
|
|
return nil, fmt.Errorf("Cannot build deps: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &esbuild.BuildOptions{
|
2022-07-26 22:45:52 +00:00
|
|
|
EntryPoints: []string{"src/index.ts", "src/index.css"},
|
2022-06-07 21:24:22 +00:00
|
|
|
Loader: map[string]esbuild.Loader{".wasm": esbuild.LoaderFile},
|
2022-07-20 01:04:09 +00:00
|
|
|
Outdir: *distDir,
|
2022-06-07 21:24:22 +00:00
|
|
|
Bundle: true,
|
|
|
|
Sourcemap: esbuild.SourceMapLinked,
|
|
|
|
LogLevel: esbuild.LogLevelInfo,
|
|
|
|
Define: map[string]string{"DEBUG": strconv.FormatBool(dev)},
|
|
|
|
Target: esbuild.ES2017,
|
2022-07-26 00:51:06 +00:00
|
|
|
Plugins: []esbuild.Plugin{{
|
|
|
|
Name: "tailscale-tailwind",
|
|
|
|
Setup: func(build esbuild.PluginBuild) {
|
|
|
|
setupEsbuildTailwind(build, dev)
|
|
|
|
},
|
|
|
|
}},
|
2022-06-07 21:24:22 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildDeps builds the static assets that are needed for the server (except for
|
|
|
|
// JS/CSS bundling, which is handled by esbuild).
|
|
|
|
func buildDeps(dev bool) error {
|
|
|
|
if err := copyWasmExec(); err != nil {
|
|
|
|
return fmt.Errorf("Cannot copy wasm_exec.js: %w", err)
|
|
|
|
}
|
|
|
|
if err := buildWasm(dev); err != nil {
|
|
|
|
return fmt.Errorf("Cannot build main.wasm: %w", err)
|
|
|
|
}
|
|
|
|
if err := installJSDeps(); err != nil {
|
|
|
|
return fmt.Errorf("Cannot install JS deps: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyWasmExec grabs the current wasm_exec.js runtime helper library from the
|
|
|
|
// Go toolchain.
|
|
|
|
func copyWasmExec() error {
|
|
|
|
log.Printf("Copying wasm_exec.js...\n")
|
|
|
|
wasmExecSrcPath := filepath.Join(runtime.GOROOT(), "misc", "wasm", "wasm_exec.js")
|
|
|
|
wasmExecDstPath := filepath.Join("src", "wasm_exec.js")
|
|
|
|
contents, err := os.ReadFile(wasmExecSrcPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return os.WriteFile(wasmExecDstPath, contents, 0600)
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildWasm builds the Tailscale wasm binary and places it where the JS can
|
|
|
|
// load it.
|
|
|
|
func buildWasm(dev bool) error {
|
|
|
|
log.Printf("Building wasm...\n")
|
|
|
|
args := []string{"build", "-tags", "tailscale_go,osusergo,netgo,nethttpomithttp2,omitidna,omitpemdecrypt"}
|
|
|
|
if !dev {
|
|
|
|
// Omit long paths and debug symbols in release builds, to reduce the
|
|
|
|
// generated WASM binary size.
|
|
|
|
args = append(args, "-trimpath", "-ldflags", "-s -w")
|
|
|
|
}
|
|
|
|
args = append(args, "-o", "src/main.wasm", "./wasm")
|
|
|
|
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
|
|
|
|
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// installJSDeps installs the JavaScript dependencies specified by package.json
|
|
|
|
func installJSDeps() error {
|
|
|
|
log.Printf("Installing JS deps...\n")
|
2022-07-22 00:58:50 +00:00
|
|
|
cmd := exec.Command(*yarnPath)
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd.Run()
|
2022-06-07 21:24:22 +00:00
|
|
|
}
|
2022-07-20 01:04:09 +00:00
|
|
|
|
|
|
|
// EsbuildMetadata is the subset of metadata struct (described by
|
|
|
|
// https://esbuild.github.io/api/#metafile) that we care about for mapping
|
|
|
|
// from entry points to hashed file names.
|
|
|
|
type EsbuildMetadata struct {
|
|
|
|
Outputs map[string]struct {
|
|
|
|
EntryPoint string `json:"entryPoint,omitempty"`
|
|
|
|
} `json:"outputs,omitempty"`
|
|
|
|
}
|
2022-07-26 00:51:06 +00:00
|
|
|
|
|
|
|
func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) {
|
|
|
|
build.OnLoad(esbuild.OnLoadOptions{
|
|
|
|
Filter: "./src/index.css$",
|
|
|
|
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
|
|
|
start := time.Now()
|
|
|
|
yarnArgs := []string{"--silent", "tailwind", "-i", args.Path}
|
|
|
|
if !dev {
|
|
|
|
yarnArgs = append(yarnArgs, "--minify")
|
|
|
|
}
|
|
|
|
cmd := exec.Command(*yarnPath, yarnArgs...)
|
|
|
|
tailwindOutput, err := cmd.Output()
|
|
|
|
log.Printf("Ran tailwind in %v\n", time.Since(start))
|
|
|
|
if err != nil {
|
|
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
|
|
log.Printf("Tailwind stderr: %s", exitErr.Stderr)
|
|
|
|
}
|
|
|
|
return esbuild.OnLoadResult{}, fmt.Errorf("Cannot run tailwind: %w", err)
|
|
|
|
}
|
|
|
|
tailwindOutputStr := string(tailwindOutput)
|
|
|
|
return esbuild.OnLoadResult{
|
|
|
|
Contents: &tailwindOutputStr,
|
|
|
|
Loader: esbuild.LoaderCSS,
|
|
|
|
}, nil
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|