userland: add archive tools and pinned GoAWK, esbuild, and QuickJS

This commit is contained in:
2026-11-07 10:56:29 +00:00
parent 7e7e49bfa2
commit d79b2bc2e1
1798 changed files with 562969 additions and 1 deletions
+737
View File
@@ -0,0 +1,737 @@
// This API exposes esbuild's two main operations: building and transforming.
// It's intended for integrating esbuild into other tools as a library.
//
// If you are just trying to run esbuild from Go without the overhead of
// creating a child process, there is also an API for the command-line
// interface itself: https://pkg.go.dev/github.com/evanw/esbuild/pkg/cli.
//
// # Build API
//
// This function runs an end-to-end build operation. It takes an array of file
// paths as entry points, parses them and all of their dependencies, and
// returns the output files to write to the file system. The available options
// roughly correspond to esbuild's command-line flags.
//
// Example usage:
//
// package main
//
// import (
// "os"
//
// "github.com/evanw/esbuild/pkg/api"
// )
//
// func main() {
// result := api.Build(api.BuildOptions{
// EntryPoints: []string{"input.js"},
// Outfile: "output.js",
// Bundle: true,
// Write: true,
// LogLevel: api.LogLevelInfo,
// })
//
// if len(result.Errors) > 0 {
// os.Exit(1)
// }
// }
//
// # Transform API
//
// This function transforms a string of source code into JavaScript. It can be
// used to minify JavaScript, convert TypeScript/JSX to JavaScript, or convert
// newer JavaScript to older JavaScript. The available options roughly
// correspond to esbuild's command-line flags.
//
// Example usage:
//
// package main
//
// import (
// "fmt"
// "os"
//
// "github.com/evanw/esbuild/pkg/api"
// )
//
// func main() {
// jsx := `
// import * as React from 'react'
// import * as ReactDOM from 'react-dom'
//
// ReactDOM.render(
// <h1>Hello, world!</h1>,
// document.getElementById('root')
// );
// `
//
// result := api.Transform(jsx, api.TransformOptions{
// Loader: api.LoaderJSX,
// })
//
// fmt.Printf("%d errors and %d warnings\n",
// len(result.Errors), len(result.Warnings))
//
// os.Stdout.Write(result.Code)
// }
package api
import (
"time"
"github.com/evanw/esbuild/internal/logger"
)
type SourceMap uint8
const (
SourceMapNone SourceMap = iota
SourceMapInline
SourceMapLinked
SourceMapExternal
SourceMapInlineAndExternal
)
type SourcesContent uint8
const (
SourcesContentInclude SourcesContent = iota
SourcesContentExclude
)
type LegalComments uint8
const (
LegalCommentsDefault LegalComments = iota
LegalCommentsNone
LegalCommentsInline
LegalCommentsEndOfFile
LegalCommentsLinked
LegalCommentsExternal
)
type JSX uint8
const (
JSXTransform JSX = iota
JSXPreserve
JSXAutomatic
)
type Target uint8
const (
DefaultTarget Target = iota
ESNext
ES5
ES2015
ES2016
ES2017
ES2018
ES2019
ES2020
ES2021
ES2022
ES2023
ES2024
ES2025
)
type Loader uint16
const (
LoaderNone Loader = iota
LoaderBase64
LoaderBinary
LoaderCopy
LoaderCSS
LoaderDataURL
LoaderDefault
LoaderEmpty
LoaderFile
LoaderGlobalCSS
LoaderJS
LoaderJSON
LoaderJSX
LoaderLocalCSS
LoaderText
LoaderTS
LoaderTSX
)
type Platform uint8
const (
PlatformDefault Platform = iota
PlatformBrowser
PlatformNode
PlatformNeutral
)
type Format uint8
const (
FormatDefault Format = iota
FormatIIFE
FormatCommonJS
FormatESModule
)
type Packages uint8
const (
PackagesDefault Packages = iota
PackagesBundle
PackagesExternal
)
type Engine struct {
Name EngineName
Version string
}
type Location struct {
File string
Namespace string
Line int // 1-based
Column int // 0-based, in bytes
Length int // in bytes
LineText string
Suggestion string
}
type Message struct {
ID string
PluginName string
Text string
Location *Location
Notes []Note
// Optional user-specified data that is passed through unmodified. You can
// use this to stash the original error, for example.
Detail interface{}
}
type Note struct {
Text string
Location *Location
}
type StderrColor uint8
const (
ColorIfTerminal StderrColor = iota
ColorNever
ColorAlways
)
type LogLevel uint8
const (
LogLevelSilent LogLevel = iota
LogLevelVerbose
LogLevelDebug
LogLevelInfo
LogLevelWarning
LogLevelError
)
type Charset uint8
const (
CharsetDefault Charset = iota
CharsetASCII
CharsetUTF8
)
type TreeShaking uint8
const (
TreeShakingDefault TreeShaking = iota
TreeShakingFalse
TreeShakingTrue
)
type Drop uint8
const (
DropConsole Drop = 1 << iota
DropDebugger
)
type MangleQuoted uint8
const (
MangleQuotedFalse MangleQuoted = iota
MangleQuotedTrue
)
type AbsPaths uint8
const (
CodeAbsPath AbsPaths = 1 << iota
LogAbsPath
MetafileAbsPath
)
////////////////////////////////////////////////////////////////////////////////
// Build API
type BuildOptions struct {
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
LogOverride map[string]LogLevel // Documentation: https://esbuild.github.io/api/#log-override
AbsPaths AbsPaths // Documentation: https://esbuild.github.io/api/#abs-path
Sourcemap SourceMap // Documentation: https://esbuild.github.io/api/#sourcemap
SourceRoot string // Documentation: https://esbuild.github.io/api/#source-root
SourcesContent SourcesContent // Documentation: https://esbuild.github.io/api/#sources-content
Target Target // Documentation: https://esbuild.github.io/api/#target
Engines []Engine // Documentation: https://esbuild.github.io/api/#target
Supported map[string]bool // Documentation: https://esbuild.github.io/api/#supported
MangleProps string // Documentation: https://esbuild.github.io/api/#mangle-props
ReserveProps string // Documentation: https://esbuild.github.io/api/#mangle-props
MangleQuoted MangleQuoted // Documentation: https://esbuild.github.io/api/#mangle-props
MangleCache map[string]interface{} // Documentation: https://esbuild.github.io/api/#mangle-props
Drop Drop // Documentation: https://esbuild.github.io/api/#drop
DropLabels []string // Documentation: https://esbuild.github.io/api/#drop-labels
MinifyWhitespace bool // Documentation: https://esbuild.github.io/api/#minify
MinifyIdentifiers bool // Documentation: https://esbuild.github.io/api/#minify
MinifySyntax bool // Documentation: https://esbuild.github.io/api/#minify
LineLimit int // Documentation: https://esbuild.github.io/api/#line-limit
Charset Charset // Documentation: https://esbuild.github.io/api/#charset
TreeShaking TreeShaking // Documentation: https://esbuild.github.io/api/#tree-shaking
IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations
LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments
JSX JSX // Documentation: https://esbuild.github.io/api/#jsx-mode
JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory
JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment
JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source
JSXDev bool // Documentation: https://esbuild.github.io/api/#jsx-dev
JSXSideEffects bool // Documentation: https://esbuild.github.io/api/#jsx-side-effects
Define map[string]string // Documentation: https://esbuild.github.io/api/#define
Pure []string // Documentation: https://esbuild.github.io/api/#pure
KeepNames bool // Documentation: https://esbuild.github.io/api/#keep-names
GlobalName string // Documentation: https://esbuild.github.io/api/#global-name
Bundle bool // Documentation: https://esbuild.github.io/api/#bundle
PreserveSymlinks bool // Documentation: https://esbuild.github.io/api/#preserve-symlinks
Splitting bool // Documentation: https://esbuild.github.io/api/#splitting
Outfile string // Documentation: https://esbuild.github.io/api/#outfile
Metafile bool // Documentation: https://esbuild.github.io/api/#metafile
Outdir string // Documentation: https://esbuild.github.io/api/#outdir
Outbase string // Documentation: https://esbuild.github.io/api/#outbase
AbsWorkingDir string // Documentation: https://esbuild.github.io/api/#working-directory
Platform Platform // Documentation: https://esbuild.github.io/api/#platform
Format Format // Documentation: https://esbuild.github.io/api/#format
External []string // Documentation: https://esbuild.github.io/api/#external
Packages Packages // Documentation: https://esbuild.github.io/api/#packages
Alias map[string]string // Documentation: https://esbuild.github.io/api/#alias
MainFields []string // Documentation: https://esbuild.github.io/api/#main-fields
Conditions []string // Documentation: https://esbuild.github.io/api/#conditions
Loader map[string]Loader // Documentation: https://esbuild.github.io/api/#loader
ResolveExtensions []string // Documentation: https://esbuild.github.io/api/#resolve-extensions
Tsconfig string // Documentation: https://esbuild.github.io/api/#tsconfig
TsconfigRaw string // Documentation: https://esbuild.github.io/api/#tsconfig-raw
OutExtension map[string]string // Documentation: https://esbuild.github.io/api/#out-extension
PublicPath string // Documentation: https://esbuild.github.io/api/#public-path
Inject []string // Documentation: https://esbuild.github.io/api/#inject
Banner map[string]string // Documentation: https://esbuild.github.io/api/#banner
Footer map[string]string // Documentation: https://esbuild.github.io/api/#footer
NodePaths []string // Documentation: https://esbuild.github.io/api/#node-paths
EntryNames string // Documentation: https://esbuild.github.io/api/#entry-names
ChunkNames string // Documentation: https://esbuild.github.io/api/#chunk-names
AssetNames string // Documentation: https://esbuild.github.io/api/#asset-names
EntryPoints []string // Documentation: https://esbuild.github.io/api/#entry-points
EntryPointsAdvanced []EntryPoint // Documentation: https://esbuild.github.io/api/#entry-points
Stdin *StdinOptions // Documentation: https://esbuild.github.io/api/#stdin
Write bool // Documentation: https://esbuild.github.io/api/#write
AllowOverwrite bool // Documentation: https://esbuild.github.io/api/#allow-overwrite
Plugins []Plugin // Documentation: https://esbuild.github.io/plugins/
}
type EntryPoint struct {
InputPath string
OutputPath string
}
type StdinOptions struct {
Contents string
ResolveDir string
Sourcefile string
Loader Loader
}
type BuildResult struct {
Errors []Message
Warnings []Message
OutputFiles []OutputFile
Metafile string
MangleCache map[string]interface{}
}
type OutputFile struct {
Path string
Contents []byte
Hash string
}
// Documentation: https://esbuild.github.io/api/#build
func Build(options BuildOptions) BuildResult {
start := time.Now()
ctx, errors := contextImpl(options)
if ctx == nil {
return BuildResult{Errors: errors}
}
result := ctx.Rebuild()
// Print a summary of the generated files to stderr. Except don't do
// this if the terminal is already being used for something else.
if ctx.args.logOptions.LogLevel <= logger.LevelInfo && !ctx.args.options.WriteToStdout {
printSummary(ctx.args.logOptions.Color, result.OutputFiles, start)
}
ctx.Dispose()
return result
}
////////////////////////////////////////////////////////////////////////////////
// Transform API
type TransformOptions struct {
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
LogOverride map[string]LogLevel // Documentation: https://esbuild.github.io/api/#log-override
AbsPaths AbsPaths // Documentation: https://esbuild.github.io/api/#abs-path
Sourcemap SourceMap // Documentation: https://esbuild.github.io/api/#sourcemap
SourceRoot string // Documentation: https://esbuild.github.io/api/#source-root
SourcesContent SourcesContent // Documentation: https://esbuild.github.io/api/#sources-content
Target Target // Documentation: https://esbuild.github.io/api/#target
Engines []Engine // Documentation: https://esbuild.github.io/api/#target
Supported map[string]bool // Documentation: https://esbuild.github.io/api/#supported
Platform Platform // Documentation: https://esbuild.github.io/api/#platform
Format Format // Documentation: https://esbuild.github.io/api/#format
GlobalName string // Documentation: https://esbuild.github.io/api/#global-name
MangleProps string // Documentation: https://esbuild.github.io/api/#mangle-props
ReserveProps string // Documentation: https://esbuild.github.io/api/#mangle-props
MangleQuoted MangleQuoted // Documentation: https://esbuild.github.io/api/#mangle-props
MangleCache map[string]interface{} // Documentation: https://esbuild.github.io/api/#mangle-props
Drop Drop // Documentation: https://esbuild.github.io/api/#drop
DropLabels []string // Documentation: https://esbuild.github.io/api/#drop-labels
MinifyWhitespace bool // Documentation: https://esbuild.github.io/api/#minify
MinifyIdentifiers bool // Documentation: https://esbuild.github.io/api/#minify
MinifySyntax bool // Documentation: https://esbuild.github.io/api/#minify
LineLimit int // Documentation: https://esbuild.github.io/api/#line-limit
Charset Charset // Documentation: https://esbuild.github.io/api/#charset
TreeShaking TreeShaking // Documentation: https://esbuild.github.io/api/#tree-shaking
IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations
LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments
JSX JSX // Documentation: https://esbuild.github.io/api/#jsx
JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory
JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment
JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source
JSXDev bool // Documentation: https://esbuild.github.io/api/#jsx-dev
JSXSideEffects bool // Documentation: https://esbuild.github.io/api/#jsx-side-effects
TsconfigRaw string // Documentation: https://esbuild.github.io/api/#tsconfig-raw
Banner string // Documentation: https://esbuild.github.io/api/#banner
Footer string // Documentation: https://esbuild.github.io/api/#footer
Define map[string]string // Documentation: https://esbuild.github.io/api/#define
Pure []string // Documentation: https://esbuild.github.io/api/#pure
KeepNames bool // Documentation: https://esbuild.github.io/api/#keep-names
Sourcefile string // Documentation: https://esbuild.github.io/api/#sourcefile
Loader Loader // Documentation: https://esbuild.github.io/api/#loader
}
type TransformResult struct {
Errors []Message
Warnings []Message
Code []byte
Map []byte
LegalComments []byte
MangleCache map[string]interface{}
}
// Documentation: https://esbuild.github.io/api/#transform
func Transform(input string, options TransformOptions) TransformResult {
return transformImpl(input, options)
}
////////////////////////////////////////////////////////////////////////////////
// Context API
// Documentation: https://esbuild.github.io/api/#serve-arguments
type ServeOptions struct {
Port int
Host string
Servedir string
Keyfile string
Certfile string
Fallback string
CORS CORSOptions
OnRequest func(ServeOnRequestArgs)
}
// Documentation: https://esbuild.github.io/api/#cors
type CORSOptions struct {
Origin []string
}
type ServeOnRequestArgs struct {
RemoteAddress string
Method string
Path string
Status int
TimeInMS int // The time to generate the response, not to send it
}
// Documentation: https://esbuild.github.io/api/#serve-return-values
type ServeResult struct {
Port uint16
Hosts []string
}
// Documentation: https://esbuild.github.io/api/#watch-arguments
type WatchOptions struct {
Delay int // In milliseconds
}
type BuildContext interface {
// Documentation: https://esbuild.github.io/api/#rebuild
Rebuild() BuildResult
// Documentation: https://esbuild.github.io/api/#watch
Watch(options WatchOptions) error
// Documentation: https://esbuild.github.io/api/#serve
Serve(options ServeOptions) (ServeResult, error)
Cancel()
Dispose()
}
type ContextError struct {
Errors []Message // Option validation errors are returned here
}
func (err *ContextError) Error() string {
if len(err.Errors) > 0 {
return err.Errors[0].Text
}
return "Context creation failed"
}
// Documentation: https://esbuild.github.io/api/#build
func Context(buildOptions BuildOptions) (BuildContext, *ContextError) {
ctx, errors := contextImpl(buildOptions)
if ctx == nil {
return nil, &ContextError{Errors: errors}
}
return ctx, nil
}
////////////////////////////////////////////////////////////////////////////////
// Plugin API
type SideEffects uint8
const (
SideEffectsTrue SideEffects = iota
SideEffectsFalse
)
type Plugin struct {
Name string
Setup func(PluginBuild)
}
type PluginBuild struct {
// Documentation: https://esbuild.github.io/plugins/#build-options
InitialOptions *BuildOptions
// Documentation: https://esbuild.github.io/plugins/#resolve
Resolve func(path string, options ResolveOptions) ResolveResult
// Documentation: https://esbuild.github.io/plugins/#on-start
OnStart func(callback func() (OnStartResult, error))
// Documentation: https://esbuild.github.io/plugins/#on-end
OnEnd func(callback func(result *BuildResult) (OnEndResult, error))
// Documentation: https://esbuild.github.io/plugins/#on-resolve
OnResolve func(options OnResolveOptions, callback func(OnResolveArgs) (OnResolveResult, error))
// Documentation: https://esbuild.github.io/plugins/#on-load
OnLoad func(options OnLoadOptions, callback func(OnLoadArgs) (OnLoadResult, error))
// Documentation: https://esbuild.github.io/plugins/#on-dispose
OnDispose func(callback func())
}
// Documentation: https://esbuild.github.io/plugins/#resolve-options
type ResolveOptions struct {
PluginName string
Importer string
Namespace string
ResolveDir string
Kind ResolveKind
PluginData interface{}
With map[string]string
}
// Documentation: https://esbuild.github.io/plugins/#resolve-results
type ResolveResult struct {
Errors []Message
Warnings []Message
Path string
External bool
SideEffects bool
Namespace string
Suffix string
PluginData interface{}
}
type OnStartResult struct {
Errors []Message
Warnings []Message
}
type OnEndResult struct {
Errors []Message
Warnings []Message
}
// Documentation: https://esbuild.github.io/plugins/#on-resolve-options
type OnResolveOptions struct {
Filter string
Namespace string
}
// Documentation: https://esbuild.github.io/plugins/#on-resolve-arguments
type OnResolveArgs struct {
Path string
Importer string
Namespace string
ResolveDir string
Kind ResolveKind
PluginData interface{}
With map[string]string
}
// Documentation: https://esbuild.github.io/plugins/#on-resolve-results
type OnResolveResult struct {
PluginName string
Errors []Message
Warnings []Message
Path string
External bool
SideEffects SideEffects
Namespace string
Suffix string
PluginData interface{}
WatchFiles []string
WatchDirs []string
}
// Documentation: https://esbuild.github.io/plugins/#on-load-options
type OnLoadOptions struct {
Filter string
Namespace string
}
// Documentation: https://esbuild.github.io/plugins/#on-load-arguments
type OnLoadArgs struct {
Path string
Namespace string
Suffix string
PluginData interface{}
With map[string]string
}
// Documentation: https://esbuild.github.io/plugins/#on-load-results
type OnLoadResult struct {
PluginName string
Errors []Message
Warnings []Message
Contents *string
ResolveDir string
Loader Loader
PluginData interface{}
WatchFiles []string
WatchDirs []string
}
type ResolveKind uint8
const (
ResolveNone ResolveKind = iota
ResolveEntryPoint
ResolveJSImportStatement
ResolveJSRequireCall
ResolveJSDynamicImport
ResolveJSRequireResolve
ResolveCSSImportRule
ResolveCSSComposesFrom
ResolveCSSURLToken
)
////////////////////////////////////////////////////////////////////////////////
// FormatMessages API
type MessageKind uint8
const (
ErrorMessage MessageKind = iota
WarningMessage
)
type FormatMessagesOptions struct {
TerminalWidth int
Kind MessageKind
Color bool
}
func FormatMessages(msgs []Message, opts FormatMessagesOptions) []string {
return formatMsgsImpl(msgs, opts)
}
////////////////////////////////////////////////////////////////////////////////
// AnalyzeMetafile API
type AnalyzeMetafileOptions struct {
Color bool
Verbose bool
}
// Documentation: https://esbuild.github.io/api/#analyze
func AnalyzeMetafile(metafile string, opts AnalyzeMetafileOptions) string {
return analyzeMetafileImpl(metafile, opts)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
package api
import (
"fmt"
"testing"
"github.com/evanw/esbuild/internal/test"
)
func TestStripDirPrefix(t *testing.T) {
expectSuccess := func(path string, prefix string, allowedSlashes string, expected string) {
t.Helper()
t.Run(fmt.Sprintf("path=%s prefix=%s slashes=%s", path, prefix, allowedSlashes), func(t *testing.T) {
t.Helper()
observed, ok := stripDirPrefix(path, prefix, allowedSlashes)
if !ok {
t.Fatalf("Unexpected failure")
}
test.AssertEqualWithDiff(t, observed, expected)
})
}
expectFailure := func(path string, prefix string, allowedSlashes string) {
t.Helper()
t.Run(fmt.Sprintf("path=%s prefix=%s slashes=%s", path, prefix, allowedSlashes), func(t *testing.T) {
t.Helper()
_, ok := stripDirPrefix(path, prefix, allowedSlashes)
if ok {
t.Fatalf("Unexpected success")
}
})
}
// Note: People sometimes set "outdir" to "/" and expect that to work:
// https://github.com/evanw/esbuild/issues/3027
expectSuccess(`/foo/bar/baz`, ``, `/`, `/foo/bar/baz`)
expectSuccess(`/foo/bar/baz`, `/`, `/`, `foo/bar/baz`)
expectSuccess(`/foo/bar/baz`, `/foo`, `/`, `bar/baz`)
expectSuccess(`/foo/bar/baz`, `/foo/bar`, `/`, `baz`)
expectSuccess(`/foo/bar/baz`, `/foo/bar/baz`, `/`, ``)
expectSuccess(`/foo/bar//baz`, `/foo/bar`, `/`, `/baz`)
expectSuccess(`C:\foo\bar\baz`, ``, `\/`, `C:\foo\bar\baz`)
expectSuccess(`C:\foo\bar\baz`, `C:`, `\/`, `foo\bar\baz`)
expectSuccess(`C:\foo\bar\baz`, `C:\`, `\/`, `foo\bar\baz`)
expectSuccess(`C:\foo\bar\baz`, `C:\foo`, `\/`, `bar\baz`)
expectSuccess(`C:\foo\bar\baz`, `C:\foo\bar`, `\/`, `baz`)
expectSuccess(`C:\foo\bar\baz`, `C:\foo\bar\baz`, `\/`, ``)
expectSuccess(`C:\foo\bar\\baz`, `C:\foo\bar`, `\/`, `\baz`)
expectSuccess(`C:\foo\bar/baz`, `C:\foo\bar`, `\/`, `baz`)
expectFailure(`/foo/bar`, `/foo/ba`, `/`)
expectFailure(`/foo/bar`, `/fo`, `/`)
expectFailure(`C:\foo\bar`, `C:\foo\ba`, `\/`)
expectFailure(`C:\foo\bar`, `C:\fo`, `\/`)
expectFailure(`C:/foo/bar`, `C:\foo`, `\/`)
}
@@ -0,0 +1,50 @@
// This file was automatically generated by "js_table.ts"
package api
import "github.com/evanw/esbuild/internal/compat"
type EngineName uint8
const (
EngineChrome EngineName = iota
EngineDeno
EngineEdge
EngineFirefox
EngineHermes
EngineIE
EngineIOS
EngineNode
EngineOpera
EngineRhino
EngineSafari
)
func convertEngineName(engine EngineName) compat.Engine {
switch engine {
case EngineChrome:
return compat.Chrome
case EngineDeno:
return compat.Deno
case EngineEdge:
return compat.Edge
case EngineFirefox:
return compat.Firefox
case EngineHermes:
return compat.Hermes
case EngineIE:
return compat.IE
case EngineIOS:
return compat.IOS
case EngineNode:
return compat.Node
case EngineOpera:
return compat.Opera
case EngineRhino:
return compat.Rhino
case EngineSafari:
return compat.Safari
default:
panic("Invalid engine name")
}
}
+321
View File
@@ -0,0 +1,321 @@
package api_test
import (
"testing"
"github.com/evanw/esbuild/internal/test"
"github.com/evanw/esbuild/pkg/api"
)
func TestFormatMessages(t *testing.T) {
check := func(name string, opts api.FormatMessagesOptions, msg api.Message, expected string) {
t.Helper()
t.Run(name, func(t *testing.T) {
test.AssertEqualWithDiff(t, api.FormatMessages([]api.Message{msg}, opts)[0], expected)
})
}
check("Error", api.FormatMessagesOptions{Kind: api.ErrorMessage}, api.Message{Text: "This is a test"}, "✘ [ERROR] This is a test\n\n")
check("Warning", api.FormatMessagesOptions{Kind: api.WarningMessage}, api.Message{Text: "This is a test"}, "▲ [WARNING] This is a test\n\n")
check("Basic location",
api.FormatMessagesOptions{},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 5, // 0-based
Length: 3,
LineText: "this.foo();",
Suggestion: "bar",
}},
`✘ [ERROR] This is a test
some file.js:100:5:
100 │ this.foo();
│ ~~~
╵ bar
`,
)
check("Unicode location",
api.FormatMessagesOptions{
Kind: api.WarningMessage,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 17, // In UTF-8 bytes
Length: 10, // In UTF-8 bytes
LineText: "𝓉𝒽𝒾𝓈.𝒻ℴℴ();",
Suggestion: "𝒷𝒶𝓇",
}},
`▲ [WARNING] This is a test
some file.js:100:17:
100 │ 𝓉𝒽𝒾𝓈.𝒻ℴℴ();
│ ~~~
╵ 𝒷𝒶𝓇
`,
)
check("Tab stop rendering",
api.FormatMessagesOptions{
Kind: api.WarningMessage,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 6,
Length: 4,
LineText: "0\t1\t23\t45\t678",
}},
`▲ [WARNING] This is a test
some file.js:100:6:
100 │ 0 1 23 45 678
╵ ~~~~~~
`,
)
check("Truncated location tail, zero length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 3,
Length: 0,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:3:
100 │ 012345678 abcdefg...
╵ ^
`,
)
check("Truncated location tail, nonzero length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 3,
Length: 6,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:3:
100 │ 012345678 abcdefg...
╵ ~~~~~~
`,
)
check("Truncated location tail, truncated length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 3,
Length: 100,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:3:
100 │ 012345678 abcdefg...
╵ ~~~~~~~~~~~~~~
`,
)
check("Truncated location head, zero length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 200,
Length: 0,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:59:
100 │ ...defghi ABCDEFGHI
╵ ^
`,
)
check("Truncated location head, nonzero length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 50,
Length: 200,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:50:
100 │ ...cdefghi ABCDEFGHI
╵ ~~~~~~~~~
`,
)
check("Truncated location head and tail, truncated length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 30,
Length: 30,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:30:
100 │ ... 012345678 abc...
╵ ~~~~~~~~~~~~~
`,
)
check("Truncated location head and tail, non-truncated length",
api.FormatMessagesOptions{
TerminalWidth: 32,
},
api.Message{Text: "This is a test", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 30,
Length: 9,
LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI",
}},
`✘ [ERROR] This is a test
some file.js:100:30:
100 │ ...HI 012345678 a...
╵ ~~~~~~~~~
`,
)
check("Multi-line line text",
api.FormatMessagesOptions{},
api.Message{Text: "ReferenceError: Cannot access 'foo' before initialization", Location: &api.Location{
File: "some file.js",
Line: 100,
Column: 2,
LineText: ` foo();
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)`,
}},
`✘ [ERROR] ReferenceError: Cannot access 'foo' before initialization
some file.js:100:2:
100 │ foo();
╵ ^
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
`,
)
check("Note formatting",
api.FormatMessagesOptions{
TerminalWidth: 40,
},
api.Message{
Text: "Why would you do this?",
Location: &api.Location{
File: "some file.js",
Line: 1,
Column: 10,
Length: 16,
LineText: "let ten = +([+!+[]]+[+[]]);",
},
Notes: []api.Note{{
Text: "This is 1:",
Location: &api.Location{
File: "some file.js",
Line: 1,
Column: 12,
Length: 7,
LineText: "let ten = +([+!+[]]+[+[]]);",
Suggestion: "'1'",
},
}, {
Text: "This is 0:",
Location: &api.Location{
File: "some file.js",
Line: 1,
Column: 20,
Length: 5,
LineText: "let ten = +([+!+[]]+[+[]]);",
Suggestion: "'0'",
},
}, {
Text: "The number 0 is created by +[], where [] is the empty array and + is the unary plus, " +
"used to convert the right side to a numeric value. The number 1 is formed as +!+[], where " +
"the boolean value true is converted into the numeric value 1 by the prepended plus sign.",
}},
},
`✘ [ERROR] Why would you do this?
some file.js:1:10:
1 │ let ten = +([+!+[]]+[+[]]);
╵ ~~~~~~~~~~~~~~~~
This is 1:
some file.js:1:12:
1 │ let ten = +([+!+[]]+[+[]]);
│ ~~~~~~~
╵ '1'
This is 0:
some file.js:1:20:
1 │ let ten = +([+!+[]]+[+[]]);
│ ~~~~~
╵ '0'
The number 0 is created by +[], where
[] is the empty array and + is the
unary plus, used to convert the right
side to a numeric value. The number 1
is formed as +!+[], where the boolean
value true is converted into the
numeric value 1 by the prepended plus
sign.
`,
)
}
@@ -0,0 +1,31 @@
package api
// This is the "favicon.ico" file used by esbuild's built-in development server
var favicon_ico_gz = []byte{
0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x63, 0x60, 0x60, 0x64, 0x60, 0x62,
0x10, 0x10, 0x60, 0x00, 0xD2, 0x0A, 0x0C, 0x19, 0x2C, 0x0C, 0x0C, 0x6A, 0x0C, 0x0C, 0x0C, 0x0A,
0x0A, 0x10, 0xBE, 0x86, 0x20, 0x03, 0x43, 0x1F, 0x50, 0x4C, 0x03, 0x28, 0x26, 0x00, 0x12, 0x67,
0x80, 0x88, 0x13, 0x04, 0xE7, 0xFF, 0xFF, 0x27, 0x09, 0xD3, 0x4A, 0xFF, 0xC9, 0xEF, 0xFF, 0x59,
0x97, 0x9F, 0x81, 0xAB, 0x63, 0x5B, 0x72, 0xF2, 0x3F, 0xC3, 0x99, 0xDF, 0x44, 0xEB, 0xE7, 0x29,
0xE9, 0xF9, 0x2F, 0xAE, 0xA8, 0x02, 0xD6, 0xC7, 0x74, 0xE0, 0xCD, 0x7F, 0x09, 0x45, 0xE5, 0xFF,
0x02, 0xA1, 0xA9, 0x98, 0x66, 0xE0, 0xB1, 0x5F, 0xC8, 0x27, 0x12, 0x6E, 0x06, 0xFB, 0xEC, 0x7D,
0xFF, 0x25, 0xE4, 0x14, 0x30, 0xCD, 0xC0, 0xE7, 0x7F, 0xA0, 0x19, 0xC2, 0x0E, 0xDE, 0x60, 0x33,
0x58, 0x36, 0x5C, 0xFF, 0xCF, 0x31, 0x79, 0xF3, 0x7F, 0x49, 0x49, 0xC9, 0xFF, 0xFC, 0xB1, 0xF9,
0x44, 0xE9, 0x47, 0xB6, 0x93, 0xF1, 0xD8, 0x17, 0x14, 0xF7, 0x10, 0xD2, 0x4F, 0x94, 0x5E, 0x02,
0xFA, 0x05, 0x42, 0x53, 0xC0, 0x7E, 0x85, 0xE9, 0xC7, 0xD0, 0x4B, 0xCB, 0xF8, 0xA7, 0x85, 0xFE,
0x9A, 0x99, 0x68, 0x78, 0x56, 0x3D, 0xF9, 0xFA, 0xB1, 0xE8, 0x25, 0x5A, 0x3F, 0x0E, 0xBD, 0x44,
0xE9, 0xC7, 0xA3, 0x97, 0xA0, 0x7E, 0x02, 0x7A, 0x29, 0x00, 0x1A, 0xD0, 0x32, 0xC6, 0x81, 0xD8,
0x72, 0x86, 0xDC, 0xF8, 0xA6, 0x34, 0x7D, 0x8C, 0xDA, 0x3F, 0x6A, 0x3F, 0x01, 0xFB, 0x99, 0x77,
0x3C, 0xFA, 0x2F, 0xEC, 0x1E, 0x0C, 0xA6, 0xD1, 0xE5, 0x58, 0xD7, 0x5C, 0x06, 0xCB, 0x31, 0x1D,
0xF9, 0x48, 0x13, 0xFB, 0x41, 0x76, 0x8A, 0x19, 0x98, 0x81, 0xEB, 0x09, 0x11, 0x1B, 0x57, 0x14,
0x7B, 0x40, 0x76, 0x4B, 0xA8, 0xA8, 0x63, 0x95, 0xA3, 0x85, 0xFD, 0xE8, 0xF6, 0xE0, 0x93, 0xA3,
0x76, 0xF8, 0x53, 0xCD, 0x0D, 0x64, 0xA6, 0x3F, 0xAA, 0xB9, 0x81, 0x82, 0xF4, 0x4F, 0x15, 0x37,
0x50, 0x98, 0xFF, 0xD8, 0x16, 0x1E, 0x85, 0xDB, 0x01, 0xC2, 0x02, 0x71, 0x05, 0x70, 0x39, 0x8E,
0x69, 0xDB, 0x71, 0xCA, 0x0D, 0x75, 0xFF, 0x0F, 0x64, 0xFC, 0x0F, 0x64, 0xFA, 0x1F, 0xE8, 0xFC,
0x3F, 0xD0, 0xE5, 0xDF, 0x40, 0x97, 0xFF, 0xA3, 0xF5, 0xEF, 0xA8, 0xFD, 0x44, 0x61, 0x8C, 0xBE,
0x0C, 0x36, 0x3C, 0xA7, 0x7E, 0xE0, 0xEC, 0x9F, 0x53, 0x4F, 0xD3, 0xF6, 0x3F, 0x35, 0xEC, 0xA6,
0x89, 0xFD, 0x73, 0x48, 0xEB, 0x63, 0x51, 0xD5, 0xFE, 0x39, 0xA4, 0xF7, 0xEF, 0xA8, 0x66, 0xFF,
0x1C, 0xF2, 0xFA, 0x96, 0x54, 0xB1, 0x7F, 0x0E, 0xF9, 0xFD, 0x5A, 0x8A, 0xED, 0x9F, 0x43, 0x59,
0x9F, 0x9A, 0x22, 0xFB, 0xE7, 0x50, 0xDE, 0x9F, 0x27, 0xDB, 0xFE, 0x39, 0x34, 0x1B, 0x4B, 0x18,
0xCE, 0x00, 0x00, 0xDA, 0xEB, 0x61, 0xFD, 0xB6, 0x15, 0x00, 0x00,
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
//go:build js && wasm
// +build js,wasm
package api
import "fmt"
// Remove the serve API in the WebAssembly build. This removes 2.7mb of stuff.
func (*internalContext) Serve(ServeOptions) (ServeResult, error) {
return ServeResult{}, fmt.Errorf("The \"serve\" API is not supported when using WebAssembly")
}
type apiHandler struct {
}
func (*apiHandler) broadcastBuildResult(BuildResult, map[string]string) {
}
func (*apiHandler) stop() {
}
+199
View File
@@ -0,0 +1,199 @@
package api
// This file implements a polling file watcher for esbuild (i.e. it detects
// when files are changed by repeatedly checking their contents). Polling is
// used instead of more efficient platform-specific file system APIs because:
//
// * Go's standard library doesn't have built-in APIs for file watching
// * Using platform-specific APIs means using cgo, which I want to avoid
// * Polling is cross-platform and esbuild needs to work on 20+ platforms
// * Platform-specific APIs might be unreliable and could introduce bugs
//
// That said, this polling system is designed to use relatively little CPU vs.
// a more traditional polling system that scans the whole directory tree at
// once. The file system is still scanned regularly but each scan only checks
// a random subset of your files, which means a change to a file will be picked
// up soon after the change is made but not necessarily instantly.
//
// With the current heuristics, large projects should be completely scanned
// around every 2 seconds so in the worst case it could take up to 2 seconds
// for a change to be noticed. However, after a change has been noticed the
// change's path goes on a short list of recently changed paths which are
// checked on every scan, so further changes to recently changed files should
// be noticed almost instantly.
import (
"fmt"
"math/rand"
"os"
"sync"
"sync/atomic"
"time"
"github.com/evanw/esbuild/internal/fs"
"github.com/evanw/esbuild/internal/logger"
"github.com/evanw/esbuild/internal/resolver"
)
// The time to wait between watch intervals
const watchIntervalSleep = 100 * time.Millisecond
// The maximum number of recently-edited items to check every interval
const maxRecentItemCount = 16
// The minimum number of non-recent items to check every interval
const minItemCountPerIter = 64
// The maximum number of intervals before a change is detected
const maxIntervalsBeforeUpdate = 20
type watcher struct {
data fs.WatchData
fs fs.FS
rebuild func() fs.WatchData
delayInMS time.Duration
recentItems []string
itemsToScan []string
mutex sync.Mutex
itemsPerIteration int
shouldStop int32
shouldLog bool
useColor logger.UseColor
pathStyle logger.PathStyle
stopWaitGroup sync.WaitGroup
}
func (w *watcher) setWatchData(data fs.WatchData) {
defer w.mutex.Unlock()
w.mutex.Lock()
// Print something for the end of the first build
if w.shouldLog && w.data.Paths == nil {
logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string {
var delay string
if w.delayInMS > 0 {
delay = fmt.Sprintf(" with a %dms delay", w.delayInMS)
}
return fmt.Sprintf("%s[watch] build finished, watching for changes%s...%s\n", colors.Dim, delay, colors.Reset)
})
}
w.data = data
w.itemsToScan = w.itemsToScan[:0] // Reuse memory
// Remove any recent items that weren't a part of the latest build
end := 0
for _, path := range w.recentItems {
if data.Paths[path] != nil {
w.recentItems[end] = path
end++
}
}
w.recentItems = w.recentItems[:end]
}
func (w *watcher) start() {
w.stopWaitGroup.Add(1)
go func() {
// Note: Do not change these log messages without a breaking version change.
// People want to run regexes over esbuild's stderr stream to look for these
// messages instead of using esbuild's API.
for atomic.LoadInt32(&w.shouldStop) == 0 {
// Sleep for the watch interval
time.Sleep(watchIntervalSleep)
// Rebuild if we're dirty
if absPath := w.tryToFindDirtyPath(); absPath != "" {
// Optionally wait before rebuilding
if w.delayInMS > 0 {
time.Sleep(w.delayInMS * time.Millisecond)
}
if w.shouldLog {
logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string {
prettyPaths := resolver.MakePrettyPaths(w.fs, logger.Path{Text: absPath, Namespace: "file"})
return fmt.Sprintf("%s[watch] build started (change: %q)%s\n",
colors.Dim, prettyPaths.Select(w.pathStyle), colors.Reset)
})
}
// Run the build
w.setWatchData(w.rebuild())
if w.shouldLog {
logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string {
return fmt.Sprintf("%s[watch] build finished%s\n", colors.Dim, colors.Reset)
})
}
}
}
w.stopWaitGroup.Done()
}()
}
func (w *watcher) stop() {
atomic.StoreInt32(&w.shouldStop, 1)
w.stopWaitGroup.Wait()
}
func (w *watcher) tryToFindDirtyPath() string {
defer w.mutex.Unlock()
w.mutex.Lock()
// If we ran out of items to scan, fill the items back up in a random order
if len(w.itemsToScan) == 0 {
items := w.itemsToScan[:0] // Reuse memory
for path := range w.data.Paths {
items = append(items, path)
}
rand.Seed(time.Now().UnixNano())
for i := int32(len(items) - 1); i > 0; i-- { // Fisher-Yates shuffle
j := rand.Int31n(i + 1)
items[i], items[j] = items[j], items[i]
}
w.itemsToScan = items
// Determine how many items to check every iteration, rounded up
perIter := (len(items) + maxIntervalsBeforeUpdate - 1) / maxIntervalsBeforeUpdate
if perIter < minItemCountPerIter {
perIter = minItemCountPerIter
}
w.itemsPerIteration = perIter
}
// Always check all recent items every iteration
for i, path := range w.recentItems {
if dirtyPath := w.data.Paths[path](); dirtyPath != "" {
// Move this path to the back of the list (i.e. the "most recent" position)
copy(w.recentItems[i:], w.recentItems[i+1:])
w.recentItems[len(w.recentItems)-1] = path
return dirtyPath
}
}
// Check a constant number of items every iteration
remainingCount := len(w.itemsToScan) - w.itemsPerIteration
if remainingCount < 0 {
remainingCount = 0
}
toCheck, remaining := w.itemsToScan[remainingCount:], w.itemsToScan[:remainingCount]
w.itemsToScan = remaining
// Check if any of the entries in this iteration have been modified
for _, path := range toCheck {
if dirtyPath := w.data.Paths[path](); dirtyPath != "" {
// Mark this item as recent by adding it to the back of the list
w.recentItems = append(w.recentItems, path)
if len(w.recentItems) > maxRecentItemCount {
// Remove items from the front of the list when we hit the limit
copy(w.recentItems, w.recentItems[1:])
w.recentItems = w.recentItems[:maxRecentItemCount]
}
return dirtyPath
}
}
return ""
}