Add support for copying files and folders.
This commit is contained in:
566
vendor/github.com/evanw/esbuild/pkg/api/api.go
generated
vendored
Normal file
566
vendor/github.com/evanw/esbuild/pkg/api/api.go
generated
vendored
Normal file
@ -0,0 +1,566 @@
|
||||
// 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://godoc.org/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
|
||||
|
||||
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 JSXMode uint8
|
||||
|
||||
const (
|
||||
JSXModeTransform JSXMode = iota
|
||||
JSXModePreserve
|
||||
)
|
||||
|
||||
type Target uint8
|
||||
|
||||
const (
|
||||
DefaultTarget Target = iota
|
||||
ESNext
|
||||
ES5
|
||||
ES2015
|
||||
ES2016
|
||||
ES2017
|
||||
ES2018
|
||||
ES2019
|
||||
ES2020
|
||||
ES2021
|
||||
)
|
||||
|
||||
type Loader uint8
|
||||
|
||||
const (
|
||||
LoaderNone Loader = iota
|
||||
LoaderJS
|
||||
LoaderJSX
|
||||
LoaderTS
|
||||
LoaderTSX
|
||||
LoaderJSON
|
||||
LoaderText
|
||||
LoaderBase64
|
||||
LoaderDataURL
|
||||
LoaderFile
|
||||
LoaderBinary
|
||||
LoaderCSS
|
||||
LoaderDefault
|
||||
)
|
||||
|
||||
type Platform uint8
|
||||
|
||||
const (
|
||||
PlatformBrowser Platform = iota
|
||||
PlatformNode
|
||||
PlatformNeutral
|
||||
)
|
||||
|
||||
type Format uint8
|
||||
|
||||
const (
|
||||
FormatDefault Format = iota
|
||||
FormatIIFE
|
||||
FormatCommonJS
|
||||
FormatESModule
|
||||
)
|
||||
|
||||
type EngineName uint8
|
||||
|
||||
const (
|
||||
EngineChrome EngineName = iota
|
||||
EngineEdge
|
||||
EngineFirefox
|
||||
EngineIOS
|
||||
EngineNode
|
||||
EngineSafari
|
||||
)
|
||||
|
||||
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 {
|
||||
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
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Build API
|
||||
|
||||
type BuildOptions struct {
|
||||
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
|
||||
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
|
||||
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
JSXMode JSXMode // 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
|
||||
|
||||
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
|
||||
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
|
||||
OutExtensions 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
|
||||
Incremental bool // Documentation: https://esbuild.github.io/api/#incremental
|
||||
Plugins []Plugin // Documentation: https://esbuild.github.io/plugins/
|
||||
|
||||
Watch *WatchMode // Documentation: https://esbuild.github.io/api/#watch
|
||||
}
|
||||
|
||||
type EntryPoint struct {
|
||||
InputPath string
|
||||
OutputPath string
|
||||
}
|
||||
|
||||
type WatchMode struct {
|
||||
OnRebuild func(BuildResult)
|
||||
}
|
||||
|
||||
type StdinOptions struct {
|
||||
Contents string
|
||||
ResolveDir string
|
||||
Sourcefile string
|
||||
Loader Loader
|
||||
}
|
||||
|
||||
type BuildResult struct {
|
||||
Errors []Message
|
||||
Warnings []Message
|
||||
|
||||
OutputFiles []OutputFile
|
||||
Metafile string
|
||||
|
||||
Rebuild func() BuildResult // Only when "Incremental: true"
|
||||
Stop func() // Only when "Watch: true"
|
||||
}
|
||||
|
||||
type OutputFile struct {
|
||||
Path string
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
// Documentation: https://esbuild.github.io/api/#build-api
|
||||
func Build(options BuildOptions) BuildResult {
|
||||
return buildImpl(options).result
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Transform API
|
||||
|
||||
type TransformOptions struct {
|
||||
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
|
||||
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
|
||||
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
|
||||
|
||||
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
|
||||
|
||||
Format Format // Documentation: https://esbuild.github.io/api/#format
|
||||
GlobalName string // Documentation: https://esbuild.github.io/api/#global-name
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
JSXMode JSXMode // 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Documentation: https://esbuild.github.io/api/#transform-api
|
||||
func Transform(input string, options TransformOptions) TransformResult {
|
||||
return transformImpl(input, options)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Serve API
|
||||
|
||||
// Documentation: https://esbuild.github.io/api/#serve-arguments
|
||||
type ServeOptions struct {
|
||||
Port uint16
|
||||
Host string
|
||||
Servedir string
|
||||
OnRequest func(ServeOnRequestArgs)
|
||||
}
|
||||
|
||||
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
|
||||
Host string
|
||||
Wait func() error
|
||||
Stop func()
|
||||
}
|
||||
|
||||
// Documentation: https://esbuild.github.io/api/#serve
|
||||
func Serve(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
|
||||
return serveImpl(serveOptions, buildOptions)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Plugin API
|
||||
|
||||
type SideEffects uint8
|
||||
|
||||
const (
|
||||
SideEffectsTrue SideEffects = iota
|
||||
SideEffectsFalse
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
Name string
|
||||
Setup func(PluginBuild)
|
||||
}
|
||||
|
||||
type PluginBuild struct {
|
||||
InitialOptions *BuildOptions
|
||||
OnStart func(callback func() (OnStartResult, error))
|
||||
OnEnd func(callback func(result *BuildResult))
|
||||
OnResolve func(options OnResolveOptions, callback func(OnResolveArgs) (OnResolveResult, error))
|
||||
OnLoad func(options OnLoadOptions, callback func(OnLoadArgs) (OnLoadResult, error))
|
||||
}
|
||||
|
||||
type OnStartResult struct {
|
||||
Errors []Message
|
||||
Warnings []Message
|
||||
}
|
||||
|
||||
type OnResolveOptions struct {
|
||||
Filter string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnResolveArgs struct {
|
||||
Path string
|
||||
Importer string
|
||||
Namespace string
|
||||
ResolveDir string
|
||||
Kind ResolveKind
|
||||
PluginData interface{}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type OnLoadOptions struct {
|
||||
Filter string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnLoadArgs struct {
|
||||
Path string
|
||||
Namespace string
|
||||
Suffix string
|
||||
PluginData interface{}
|
||||
}
|
||||
|
||||
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 (
|
||||
ResolveEntryPoint ResolveKind = iota
|
||||
ResolveJSImportStatement
|
||||
ResolveJSRequireCall
|
||||
ResolveJSDynamicImport
|
||||
ResolveJSRequireResolve
|
||||
ResolveCSSImportRule
|
||||
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)
|
||||
}
|
1949
vendor/github.com/evanw/esbuild/pkg/api/api_impl.go
generated
vendored
Normal file
1949
vendor/github.com/evanw/esbuild/pkg/api/api_impl.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
620
vendor/github.com/evanw/esbuild/pkg/api/serve_other.go
generated
vendored
Normal file
620
vendor/github.com/evanw/esbuild/pkg/api/serve_other.go
generated
vendored
Normal file
@ -0,0 +1,620 @@
|
||||
//go:build !js || !wasm
|
||||
// +build !js !wasm
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/evanw/esbuild/internal/config"
|
||||
"github.com/evanw/esbuild/internal/fs"
|
||||
"github.com/evanw/esbuild/internal/helpers"
|
||||
"github.com/evanw/esbuild/internal/logger"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Serve API
|
||||
|
||||
type apiHandler struct {
|
||||
mutex sync.Mutex
|
||||
outdirPathPrefix string
|
||||
servedir string
|
||||
options *config.Options
|
||||
onRequest func(ServeOnRequestArgs)
|
||||
rebuild func() BuildResult
|
||||
currentBuild *runningBuild
|
||||
fs fs.FS
|
||||
serveWaitGroup sync.WaitGroup
|
||||
serveError error
|
||||
}
|
||||
|
||||
type runningBuild struct {
|
||||
waitGroup sync.WaitGroup
|
||||
result BuildResult
|
||||
}
|
||||
|
||||
func (h *apiHandler) build() BuildResult {
|
||||
build := func() *runningBuild {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
if h.currentBuild == nil {
|
||||
build := &runningBuild{}
|
||||
build.waitGroup.Add(1)
|
||||
h.currentBuild = build
|
||||
|
||||
// Build on another thread
|
||||
go func() {
|
||||
result := h.rebuild()
|
||||
h.rebuild = result.Rebuild
|
||||
build.result = result
|
||||
build.waitGroup.Done()
|
||||
|
||||
// Build results stay valid for a little bit afterward since a page
|
||||
// load may involve multiple requests and don't want to rebuild
|
||||
// separately for each of those requests.
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
h.currentBuild = nil
|
||||
}()
|
||||
}
|
||||
return h.currentBuild
|
||||
}()
|
||||
build.waitGroup.Wait()
|
||||
return build.result
|
||||
}
|
||||
|
||||
func escapeForHTML(text string) string {
|
||||
text = strings.ReplaceAll(text, "&", "&")
|
||||
text = strings.ReplaceAll(text, "<", "<")
|
||||
text = strings.ReplaceAll(text, ">", ">")
|
||||
return text
|
||||
}
|
||||
|
||||
func escapeForAttribute(text string) string {
|
||||
text = escapeForHTML(text)
|
||||
text = strings.ReplaceAll(text, "\"", """)
|
||||
text = strings.ReplaceAll(text, "'", "'")
|
||||
return text
|
||||
}
|
||||
|
||||
func (h *apiHandler) notifyRequest(duration time.Duration, req *http.Request, status int) {
|
||||
if h.onRequest != nil {
|
||||
h.onRequest(ServeOnRequestArgs{
|
||||
RemoteAddress: req.RemoteAddr,
|
||||
Method: req.Method,
|
||||
Path: req.URL.Path,
|
||||
Status: status,
|
||||
TimeInMS: int(duration.Milliseconds()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func errorsToString(errors []Message) string {
|
||||
stderrOptions := logger.OutputOptions{IncludeSource: true}
|
||||
terminalOptions := logger.TerminalInfo{}
|
||||
sb := strings.Builder{}
|
||||
limit := 5
|
||||
for i, msg := range convertMessagesToInternal(nil, logger.Error, errors) {
|
||||
if i == limit {
|
||||
sb.WriteString(fmt.Sprintf("%d out of %d errors shown\n", limit, len(errors)))
|
||||
break
|
||||
}
|
||||
sb.WriteString(msg.String(stderrOptions, terminalOptions))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (h *apiHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// Handle get requests
|
||||
if req.Method == "GET" && strings.HasPrefix(req.URL.Path, "/") {
|
||||
res.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
queryPath := path.Clean(req.URL.Path)[1:]
|
||||
result := h.build()
|
||||
|
||||
// Requests fail if the build had errors
|
||||
if len(result.Errors) > 0 {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusServiceUnavailable)
|
||||
res.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
res.WriteHeader(http.StatusServiceUnavailable)
|
||||
res.Write([]byte(errorsToString(result.Errors)))
|
||||
return
|
||||
}
|
||||
|
||||
var kind fs.EntryKind
|
||||
var fileContents fs.OpenedFile
|
||||
dirEntries := make(map[string]bool)
|
||||
fileEntries := make(map[string]bool)
|
||||
|
||||
// Check for a match with the results if we're within the output directory
|
||||
if strings.HasPrefix(queryPath, h.outdirPathPrefix) {
|
||||
outdirQueryPath := queryPath[len(h.outdirPathPrefix):]
|
||||
if strings.HasPrefix(outdirQueryPath, "/") {
|
||||
outdirQueryPath = outdirQueryPath[1:]
|
||||
}
|
||||
resultKind, inMemoryBytes := h.matchQueryPathToResult(outdirQueryPath, &result, dirEntries, fileEntries)
|
||||
kind = resultKind
|
||||
fileContents = &fs.InMemoryOpenedFile{Contents: inMemoryBytes}
|
||||
} else {
|
||||
// Create a fake directory entry for the output path so that it appears to be a real directory
|
||||
p := h.outdirPathPrefix
|
||||
for p != "" {
|
||||
var dir string
|
||||
var base string
|
||||
if slash := strings.IndexByte(p, '/'); slash == -1 {
|
||||
base = p
|
||||
} else {
|
||||
dir = p[:slash]
|
||||
base = p[slash+1:]
|
||||
}
|
||||
if dir == queryPath {
|
||||
kind = fs.DirEntry
|
||||
dirEntries[base] = true
|
||||
break
|
||||
}
|
||||
p = dir
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a file in the fallback directory
|
||||
if h.servedir != "" && kind != fs.FileEntry {
|
||||
absPath := h.fs.Join(h.servedir, queryPath)
|
||||
if absDir := h.fs.Dir(absPath); absDir != absPath {
|
||||
if entries, err, _ := h.fs.ReadDirectory(absDir); err == nil {
|
||||
if entry, _ := entries.Get(h.fs.Base(absPath)); entry != nil && entry.Kind(h.fs) == fs.FileEntry {
|
||||
if contents, err, _ := h.fs.OpenFile(absPath); err == nil {
|
||||
defer contents.Close()
|
||||
fileContents = contents
|
||||
kind = fs.FileEntry
|
||||
} else if err != syscall.ENOENT {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a directory in the fallback directory
|
||||
var fallbackIndexName string
|
||||
if h.servedir != "" && kind != fs.FileEntry {
|
||||
if entries, err, _ := h.fs.ReadDirectory(h.fs.Join(h.servedir, queryPath)); err == nil {
|
||||
kind = fs.DirEntry
|
||||
for _, name := range entries.SortedKeys() {
|
||||
entry, _ := entries.Get(name)
|
||||
switch entry.Kind(h.fs) {
|
||||
case fs.DirEntry:
|
||||
dirEntries[name] = true
|
||||
case fs.FileEntry:
|
||||
fileEntries[name] = true
|
||||
if name == "index.html" {
|
||||
fallbackIndexName = name
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err != syscall.ENOENT {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to a trailing slash for directories
|
||||
if kind == fs.DirEntry && !strings.HasSuffix(req.URL.Path, "/") {
|
||||
res.Header().Set("Location", req.URL.Path+"/")
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusFound)
|
||||
res.WriteHeader(http.StatusFound)
|
||||
res.Write(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve a "index.html" file if present
|
||||
if kind == fs.DirEntry && fallbackIndexName != "" {
|
||||
queryPath += "/" + fallbackIndexName
|
||||
if contents, err, _ := h.fs.OpenFile(h.fs.Join(h.servedir, queryPath)); err == nil {
|
||||
defer contents.Close()
|
||||
fileContents = contents
|
||||
kind = fs.FileEntry
|
||||
} else if err != syscall.ENOENT {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Serve a file
|
||||
if kind == fs.FileEntry {
|
||||
// Default to serving the whole file
|
||||
status := http.StatusOK
|
||||
fileContentsLen := fileContents.Len()
|
||||
begin := 0
|
||||
end := fileContentsLen
|
||||
isRange := false
|
||||
|
||||
// Handle range requests so that video playback works in Safari
|
||||
if rangeBegin, rangeEnd, ok := parseRangeHeader(req.Header.Get("Range"), fileContentsLen); ok && rangeBegin < rangeEnd {
|
||||
// Note: The content range is inclusive so subtract 1 from the end
|
||||
isRange = true
|
||||
begin = rangeBegin
|
||||
end = rangeEnd
|
||||
status = http.StatusPartialContent
|
||||
}
|
||||
|
||||
// Try to read the range from the file, which may fail
|
||||
fileBytes, err := fileContents.Read(begin, end)
|
||||
if err != nil {
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
if contentType := helpers.MimeTypeByExtension(path.Ext(queryPath)); contentType != "" {
|
||||
res.Header().Set("Content-Type", contentType)
|
||||
} else {
|
||||
res.Header().Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
if isRange {
|
||||
res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", begin, end-1, fileContentsLen))
|
||||
}
|
||||
res.Header().Set("Content-Length", fmt.Sprintf("%d", len(fileBytes)))
|
||||
go h.notifyRequest(time.Since(start), req, status)
|
||||
res.WriteHeader(status)
|
||||
res.Write(fileBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve a directory listing
|
||||
if kind == fs.DirEntry {
|
||||
html := respondWithDirList(queryPath, dirEntries, fileEntries)
|
||||
res.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
res.Header().Set("Content-Length", fmt.Sprintf("%d", len(html)))
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusOK)
|
||||
res.Write(html)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Default to a 404
|
||||
res.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusNotFound)
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
res.Write([]byte("404 - Not Found"))
|
||||
}
|
||||
|
||||
// Handle enough of the range specification so that video playback works in Safari
|
||||
func parseRangeHeader(r string, contentLength int) (int, int, bool) {
|
||||
if strings.HasPrefix(r, "bytes=") {
|
||||
r = r[len("bytes="):]
|
||||
if dash := strings.IndexByte(r, '-'); dash != -1 {
|
||||
// Note: The range is inclusive so the limit is deliberately "length - 1"
|
||||
if begin, ok := parseRangeInt(r[:dash], contentLength-1); ok {
|
||||
if end, ok := parseRangeInt(r[dash+1:], contentLength-1); ok {
|
||||
// Note: The range is inclusive so a range of "0-1" is two bytes long
|
||||
return begin, end + 1, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
func parseRangeInt(text string, maxValue int) (int, bool) {
|
||||
if text == "" {
|
||||
return 0, false
|
||||
}
|
||||
value := 0
|
||||
for _, c := range text {
|
||||
if c < '0' || c > '9' {
|
||||
return 0, false
|
||||
}
|
||||
value = value*10 + int(c-'0')
|
||||
if value > maxValue {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (h *apiHandler) matchQueryPathToResult(
|
||||
queryPath string,
|
||||
result *BuildResult,
|
||||
dirEntries map[string]bool,
|
||||
fileEntries map[string]bool,
|
||||
) (fs.EntryKind, []byte) {
|
||||
queryIsDir := false
|
||||
queryDir := queryPath
|
||||
if queryDir != "" {
|
||||
queryDir += "/"
|
||||
}
|
||||
|
||||
// Check the output files for a match
|
||||
for _, file := range result.OutputFiles {
|
||||
if relPath, ok := h.fs.Rel(h.options.AbsOutputDir, file.Path); ok {
|
||||
relPath = strings.ReplaceAll(relPath, "\\", "/")
|
||||
|
||||
// An exact match
|
||||
if relPath == queryPath {
|
||||
return fs.FileEntry, file.Contents
|
||||
}
|
||||
|
||||
// A match inside this directory
|
||||
if strings.HasPrefix(relPath, queryDir) {
|
||||
entry := relPath[len(queryDir):]
|
||||
queryIsDir = true
|
||||
if slash := strings.IndexByte(entry, '/'); slash == -1 {
|
||||
fileEntries[entry] = true
|
||||
} else if dir := entry[:slash]; !dirEntries[dir] {
|
||||
dirEntries[dir] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Treat this as a directory if it's non-empty
|
||||
if queryIsDir {
|
||||
return fs.DirEntry, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func respondWithDirList(queryPath string, dirEntries map[string]bool, fileEntries map[string]bool) []byte {
|
||||
queryPath = "/" + queryPath
|
||||
queryDir := queryPath
|
||||
if queryDir != "/" {
|
||||
queryDir += "/"
|
||||
}
|
||||
html := strings.Builder{}
|
||||
html.WriteString(`<!doctype html>`)
|
||||
html.WriteString(`<meta charset="utf8">`)
|
||||
html.WriteString(`<title>Directory: `)
|
||||
html.WriteString(escapeForHTML(queryDir))
|
||||
html.WriteString(`</title>`)
|
||||
html.WriteString(`<h1>Directory: `)
|
||||
html.WriteString(escapeForHTML(queryDir))
|
||||
html.WriteString(`</h1>`)
|
||||
html.WriteString(`<ul>`)
|
||||
|
||||
// Link to the parent directory
|
||||
if queryPath != "/" {
|
||||
parentDir := path.Dir(queryPath)
|
||||
if parentDir != "/" {
|
||||
parentDir += "/"
|
||||
}
|
||||
html.WriteString(fmt.Sprintf(`<li><a href="%s">../</a></li>`, escapeForAttribute(parentDir)))
|
||||
}
|
||||
|
||||
// Link to child directories
|
||||
strings := make([]string, 0, len(dirEntries)+len(fileEntries))
|
||||
for entry := range dirEntries {
|
||||
strings = append(strings, entry)
|
||||
}
|
||||
sort.Strings(strings)
|
||||
for _, entry := range strings {
|
||||
html.WriteString(fmt.Sprintf(`<li><a href="%s/">%s/</a></li>`, escapeForAttribute(path.Join(queryPath, entry)), escapeForHTML(entry)))
|
||||
}
|
||||
|
||||
// Link to files in the directory
|
||||
strings = strings[:0]
|
||||
for entry := range fileEntries {
|
||||
strings = append(strings, entry)
|
||||
}
|
||||
sort.Strings(strings)
|
||||
for _, entry := range strings {
|
||||
html.WriteString(fmt.Sprintf(`<li><a href="%s">%s</a></li>`, escapeForAttribute(path.Join(queryPath, entry)), escapeForHTML(entry)))
|
||||
}
|
||||
|
||||
html.WriteString(`</ul>`)
|
||||
return []byte(html.String())
|
||||
}
|
||||
|
||||
// This is used to make error messages platform-independent
|
||||
func prettyPrintPath(fs fs.FS, path string) string {
|
||||
if relPath, ok := fs.Rel(fs.Cwd(), path); ok {
|
||||
return strings.ReplaceAll(relPath, "\\", "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func serveImpl(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
|
||||
realFS, err := fs.RealFS(fs.RealFSOptions{
|
||||
AbsWorkingDir: buildOptions.AbsWorkingDir,
|
||||
|
||||
// This is a long-lived file system object so do not cache calls to
|
||||
// ReadDirectory() (they are normally cached for the duration of a build
|
||||
// for performance).
|
||||
DoNotCache: true,
|
||||
})
|
||||
if err != nil {
|
||||
return ServeResult{}, err
|
||||
}
|
||||
buildOptions.Incremental = true
|
||||
buildOptions.Write = false
|
||||
|
||||
// Watch and serve are both different ways of rebuilding, and cannot be combined
|
||||
if buildOptions.Watch != nil {
|
||||
return ServeResult{}, fmt.Errorf("Cannot use \"watch\" with \"serve\"")
|
||||
}
|
||||
|
||||
// Validate the fallback path
|
||||
if serveOptions.Servedir != "" {
|
||||
if absPath, ok := realFS.Abs(serveOptions.Servedir); ok {
|
||||
serveOptions.Servedir = absPath
|
||||
} else {
|
||||
return ServeResult{}, fmt.Errorf("Invalid serve path: %s", serveOptions.Servedir)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no output directory, set the output directory to something so
|
||||
// the build doesn't try to write to stdout. Make sure not to set this to a
|
||||
// path that may contain the user's files in it since we don't want to get
|
||||
// errors about overwriting input files.
|
||||
outdirPathPrefix := ""
|
||||
if buildOptions.Outdir == "" && buildOptions.Outfile == "" {
|
||||
buildOptions.Outdir = realFS.Join(realFS.Cwd(), "...")
|
||||
} else if serveOptions.Servedir != "" {
|
||||
// Compute the output directory
|
||||
var outdir string
|
||||
if buildOptions.Outdir != "" {
|
||||
if absPath, ok := realFS.Abs(buildOptions.Outdir); ok {
|
||||
outdir = absPath
|
||||
} else {
|
||||
return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", buildOptions.Outdir)
|
||||
}
|
||||
} else {
|
||||
if absPath, ok := realFS.Abs(buildOptions.Outfile); ok {
|
||||
outdir = realFS.Dir(absPath)
|
||||
} else {
|
||||
return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", buildOptions.Outfile)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the output directory is contained in the fallback directory
|
||||
relPath, ok := realFS.Rel(serveOptions.Servedir, outdir)
|
||||
if !ok {
|
||||
return ServeResult{}, fmt.Errorf(
|
||||
"Cannot compute relative path from %q to %q\n", serveOptions.Servedir, outdir)
|
||||
}
|
||||
relPath = strings.ReplaceAll(relPath, "\\", "/") // Fix paths on Windows
|
||||
if relPath == ".." || strings.HasPrefix(relPath, "../") {
|
||||
return ServeResult{}, fmt.Errorf(
|
||||
"Output directory %q must be contained in serve directory %q",
|
||||
prettyPrintPath(realFS, outdir),
|
||||
prettyPrintPath(realFS, serveOptions.Servedir),
|
||||
)
|
||||
}
|
||||
if relPath != "." {
|
||||
outdirPathPrefix = relPath
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the host
|
||||
var listener net.Listener
|
||||
network := "tcp4"
|
||||
host := "0.0.0.0"
|
||||
if serveOptions.Host != "" {
|
||||
host = serveOptions.Host
|
||||
|
||||
// Only use "tcp4" if this is an IPv4 address, otherwise use "tcp"
|
||||
if ip := net.ParseIP(host); ip == nil || ip.To4() == nil {
|
||||
network = "tcp"
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the port
|
||||
if serveOptions.Port == 0 {
|
||||
// Default to picking a "800X" port
|
||||
for port := 8000; port <= 8009; port++ {
|
||||
if result, err := net.Listen(network, net.JoinHostPort(host, fmt.Sprintf("%d", port))); err == nil {
|
||||
listener = result
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if listener == nil {
|
||||
// Otherwise pick the provided port
|
||||
if result, err := net.Listen(network, net.JoinHostPort(host, fmt.Sprintf("%d", serveOptions.Port))); err != nil {
|
||||
return ServeResult{}, err
|
||||
} else {
|
||||
listener = result
|
||||
}
|
||||
}
|
||||
|
||||
// Try listening on the provided port
|
||||
addr := listener.Addr().String()
|
||||
|
||||
// Extract the real port in case we passed a port of "0"
|
||||
var result ServeResult
|
||||
if host, text, err := net.SplitHostPort(addr); err == nil {
|
||||
if port, err := strconv.ParseInt(text, 10, 32); err == nil {
|
||||
result.Port = uint16(port)
|
||||
result.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
var stoppingMutex sync.Mutex
|
||||
isStopping := false
|
||||
|
||||
// The first build will just build normally
|
||||
var handler *apiHandler
|
||||
handler = &apiHandler{
|
||||
onRequest: serveOptions.OnRequest,
|
||||
outdirPathPrefix: outdirPathPrefix,
|
||||
servedir: serveOptions.Servedir,
|
||||
rebuild: func() BuildResult {
|
||||
stoppingMutex.Lock()
|
||||
defer stoppingMutex.Unlock()
|
||||
|
||||
// Don't start more rebuilds if we were told to stop
|
||||
if isStopping {
|
||||
return BuildResult{}
|
||||
}
|
||||
|
||||
build := buildImpl(buildOptions)
|
||||
if handler.options == nil {
|
||||
handler.options = &build.options
|
||||
}
|
||||
return build.result
|
||||
},
|
||||
fs: realFS,
|
||||
}
|
||||
|
||||
// When wait is called, block until the server's call to "Serve()" returns
|
||||
result.Wait = func() error {
|
||||
handler.serveWaitGroup.Wait()
|
||||
return handler.serveError
|
||||
}
|
||||
|
||||
// Create the server
|
||||
server := &http.Server{Addr: addr, Handler: handler}
|
||||
|
||||
// When stop is called, block further rebuilds and then close the server
|
||||
result.Stop = func() {
|
||||
stoppingMutex.Lock()
|
||||
defer stoppingMutex.Unlock()
|
||||
|
||||
// Only try to close the server once
|
||||
if isStopping {
|
||||
return
|
||||
}
|
||||
isStopping = true
|
||||
|
||||
// Close the server and wait for it to close
|
||||
server.Close()
|
||||
handler.serveWaitGroup.Wait()
|
||||
}
|
||||
|
||||
// Start the server and signal on "serveWaitGroup" when it stops
|
||||
handler.serveWaitGroup.Add(1)
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != http.ErrServerClosed {
|
||||
handler.serveError = err
|
||||
}
|
||||
handler.serveWaitGroup.Done()
|
||||
}()
|
||||
|
||||
// Start the first build shortly after this function returns (but not
|
||||
// immediately so that stuff we print right after this will come first)
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
handler.build()
|
||||
}()
|
||||
return result, nil
|
||||
}
|
12
vendor/github.com/evanw/esbuild/pkg/api/serve_wasm.go
generated
vendored
Normal file
12
vendor/github.com/evanw/esbuild/pkg/api/serve_wasm.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
//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 serveImpl(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
|
||||
return ServeResult{}, fmt.Errorf("The \"serve\" API is not supported when using WebAssembly")
|
||||
}
|
Reference in New Issue
Block a user