584 lines
12 KiB
Go
584 lines
12 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/evanw/esbuild/internal/ast"
|
|
"github.com/evanw/esbuild/internal/compat"
|
|
"github.com/evanw/esbuild/internal/js_ast"
|
|
"github.com/evanw/esbuild/internal/logger"
|
|
)
|
|
|
|
type JSXOptions struct {
|
|
Factory JSXExpr
|
|
Fragment JSXExpr
|
|
Parse bool
|
|
Preserve bool
|
|
}
|
|
|
|
type JSXExpr struct {
|
|
Parts []string
|
|
Constant js_ast.E
|
|
}
|
|
|
|
type TSOptions struct {
|
|
Parse bool
|
|
NoAmbiguousLessThan bool
|
|
}
|
|
|
|
type Platform uint8
|
|
|
|
const (
|
|
PlatformBrowser Platform = iota
|
|
PlatformNode
|
|
PlatformNeutral
|
|
)
|
|
|
|
type StrictOptions struct {
|
|
// Loose: "class Foo { foo = 1 }" => "class Foo { constructor() { this.foo = 1; } }"
|
|
// Strict: "class Foo { foo = 1 }" => "class Foo { constructor() { __publicField(this, 'foo', 1); } }"
|
|
//
|
|
// The disadvantage of strictness here is code bloat and performance. The
|
|
// advantage is following the class field specification accurately. For
|
|
// example, loose mode will incorrectly trigger setter methods while strict
|
|
// mode won't.
|
|
ClassFields bool
|
|
}
|
|
|
|
type SourceMap uint8
|
|
|
|
const (
|
|
SourceMapNone SourceMap = iota
|
|
SourceMapInline
|
|
SourceMapLinkedWithComment
|
|
SourceMapExternalWithoutComment
|
|
SourceMapInlineAndExternal
|
|
)
|
|
|
|
type LegalComments uint8
|
|
|
|
const (
|
|
LegalCommentsInline LegalComments = iota
|
|
LegalCommentsNone
|
|
LegalCommentsEndOfFile
|
|
LegalCommentsLinkedWithComment
|
|
LegalCommentsExternalWithoutComment
|
|
)
|
|
|
|
func (lc LegalComments) HasExternalFile() bool {
|
|
return lc == LegalCommentsLinkedWithComment || lc == LegalCommentsExternalWithoutComment
|
|
}
|
|
|
|
type Loader int
|
|
|
|
const (
|
|
LoaderNone Loader = iota
|
|
LoaderJS
|
|
LoaderJSX
|
|
LoaderTS
|
|
LoaderTSNoAmbiguousLessThan // Used with ".mts" and ".cts"
|
|
LoaderTSX
|
|
LoaderJSON
|
|
LoaderText
|
|
LoaderBase64
|
|
LoaderDataURL
|
|
LoaderFile
|
|
LoaderBinary
|
|
LoaderCSS
|
|
LoaderDefault
|
|
)
|
|
|
|
func (loader Loader) IsTypeScript() bool {
|
|
switch loader {
|
|
case LoaderTS, LoaderTSNoAmbiguousLessThan, LoaderTSX:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (loader Loader) CanHaveSourceMap() bool {
|
|
switch loader {
|
|
case LoaderJS, LoaderJSX, LoaderTS, LoaderTSNoAmbiguousLessThan, LoaderTSX, LoaderCSS:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
type Format uint8
|
|
|
|
const (
|
|
// This is used when not bundling. It means to preserve whatever form the
|
|
// import or export was originally in. ES6 syntax stays ES6 syntax and
|
|
// CommonJS syntax stays CommonJS syntax.
|
|
FormatPreserve Format = iota
|
|
|
|
// IIFE stands for immediately-invoked function expression. That looks like
|
|
// this:
|
|
//
|
|
// (() => {
|
|
// ... bundled code ...
|
|
// })();
|
|
//
|
|
// If the optional GlobalName is configured, then we'll write out this:
|
|
//
|
|
// let globalName = (() => {
|
|
// ... bundled code ...
|
|
// return exports;
|
|
// })();
|
|
//
|
|
FormatIIFE
|
|
|
|
// The CommonJS format looks like this:
|
|
//
|
|
// ... bundled code ...
|
|
// module.exports = exports;
|
|
//
|
|
FormatCommonJS
|
|
|
|
// The ES module format looks like this:
|
|
//
|
|
// ... bundled code ...
|
|
// export {...};
|
|
//
|
|
FormatESModule
|
|
)
|
|
|
|
func (f Format) KeepES6ImportExportSyntax() bool {
|
|
return f == FormatPreserve || f == FormatESModule
|
|
}
|
|
|
|
func (f Format) String() string {
|
|
switch f {
|
|
case FormatIIFE:
|
|
return "iife"
|
|
case FormatCommonJS:
|
|
return "cjs"
|
|
case FormatESModule:
|
|
return "esm"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type StdinInfo struct {
|
|
Loader Loader
|
|
Contents string
|
|
SourceFile string
|
|
AbsResolveDir string
|
|
}
|
|
|
|
type WildcardPattern struct {
|
|
Prefix string
|
|
Suffix string
|
|
}
|
|
|
|
type ExternalModules struct {
|
|
NodeModules map[string]bool
|
|
AbsPaths map[string]bool
|
|
Patterns []WildcardPattern
|
|
}
|
|
|
|
type Mode uint8
|
|
|
|
const (
|
|
ModePassThrough Mode = iota
|
|
ModeConvertFormat
|
|
ModeBundle
|
|
)
|
|
|
|
type MaybeBool uint8
|
|
|
|
const (
|
|
Unspecified MaybeBool = iota
|
|
True
|
|
False
|
|
)
|
|
|
|
type Options struct {
|
|
Mode Mode
|
|
ModuleType js_ast.ModuleType
|
|
PreserveSymlinks bool
|
|
RemoveWhitespace bool
|
|
MinifyIdentifiers bool
|
|
MangleSyntax bool
|
|
ProfilerNames bool
|
|
CodeSplitting bool
|
|
WatchMode bool
|
|
AllowOverwrite bool
|
|
LegalComments LegalComments
|
|
|
|
// If true, make sure to generate a single file that can be written to stdout
|
|
WriteToStdout bool
|
|
|
|
OmitRuntimeForTests bool
|
|
UnusedImportsTS UnusedImportsTS
|
|
UseDefineForClassFields MaybeBool
|
|
ASCIIOnly bool
|
|
KeepNames bool
|
|
IgnoreDCEAnnotations bool
|
|
TreeShaking bool
|
|
|
|
Defines *ProcessedDefines
|
|
TS TSOptions
|
|
JSX JSXOptions
|
|
Platform Platform
|
|
|
|
TargetFromAPI TargetFromAPI
|
|
UnsupportedJSFeatures compat.JSFeature
|
|
UnsupportedCSSFeatures compat.CSSFeature
|
|
TSTarget *TSTarget
|
|
|
|
// This is the original information that was used to generate the
|
|
// unsupported feature sets above. It's used for error messages.
|
|
OriginalTargetEnv string
|
|
|
|
ExtensionOrder []string
|
|
MainFields []string
|
|
Conditions []string
|
|
AbsNodePaths []string // The "NODE_PATH" variable from Node.js
|
|
ExternalModules ExternalModules
|
|
|
|
AbsOutputFile string
|
|
AbsOutputDir string
|
|
AbsOutputBase string
|
|
OutputExtensionJS string
|
|
OutputExtensionCSS string
|
|
GlobalName []string
|
|
TsConfigOverride string
|
|
ExtensionToLoader map[string]Loader
|
|
OutputFormat Format
|
|
PublicPath string
|
|
InjectAbsPaths []string
|
|
InjectedDefines []InjectedDefine
|
|
InjectedFiles []InjectedFile
|
|
|
|
JSBanner string
|
|
JSFooter string
|
|
CSSBanner string
|
|
CSSFooter string
|
|
|
|
EntryPathTemplate []PathTemplate
|
|
ChunkPathTemplate []PathTemplate
|
|
AssetPathTemplate []PathTemplate
|
|
|
|
Plugins []Plugin
|
|
|
|
NeedsMetafile bool
|
|
|
|
SourceMap SourceMap
|
|
SourceRoot string
|
|
ExcludeSourcesContent bool
|
|
|
|
Stdin *StdinInfo
|
|
}
|
|
|
|
type TargetFromAPI uint8
|
|
|
|
const (
|
|
// In this state, the "target" field in "tsconfig.json" is respected
|
|
TargetWasUnconfigured TargetFromAPI = iota
|
|
|
|
// In this state, the "target" field in "tsconfig.json" is overridden
|
|
TargetWasConfigured
|
|
|
|
// In this state, "useDefineForClassFields" is true unless overridden
|
|
TargetWasConfiguredIncludingESNext
|
|
)
|
|
|
|
type UnusedImportsTS uint8
|
|
|
|
const (
|
|
// "import { unused } from 'foo'" => "" (TypeScript's default behavior)
|
|
UnusedImportsRemoveStmt UnusedImportsTS = iota
|
|
|
|
// "import { unused } from 'foo'" => "import 'foo'" ("importsNotUsedAsValues" != "remove")
|
|
UnusedImportsKeepStmtRemoveValues
|
|
|
|
// "import { unused } from 'foo'" => "import { unused } from 'foo'" ("preserveValueImports" == true)
|
|
UnusedImportsKeepValues
|
|
)
|
|
|
|
func UnusedImportsFromTsconfigValues(preserveImportsNotUsedAsValues bool, preserveValueImports bool) UnusedImportsTS {
|
|
if preserveValueImports {
|
|
return UnusedImportsKeepValues
|
|
}
|
|
if preserveImportsNotUsedAsValues {
|
|
return UnusedImportsKeepStmtRemoveValues
|
|
}
|
|
return UnusedImportsRemoveStmt
|
|
}
|
|
|
|
type TSTarget struct {
|
|
Source logger.Source
|
|
Range logger.Range
|
|
Target string
|
|
UnsupportedJSFeatures compat.JSFeature
|
|
}
|
|
|
|
type PathPlaceholder uint8
|
|
|
|
const (
|
|
NoPlaceholder PathPlaceholder = iota
|
|
|
|
// The relative path from the original parent directory to the configured
|
|
// "outbase" directory, or to the lowest common ancestor directory
|
|
DirPlaceholder
|
|
|
|
// The original name of the file, or the manual chunk name, or the name of
|
|
// the type of output file ("entry" or "chunk" or "asset")
|
|
NamePlaceholder
|
|
|
|
// A hash of the contents of this file, and the contents and output paths of
|
|
// all dependencies (except for their hash placeholders)
|
|
HashPlaceholder
|
|
|
|
// The original extension of the file, or the name of the output file
|
|
// (e.g. "css", "svg", "png")
|
|
ExtPlaceholder
|
|
)
|
|
|
|
type PathTemplate struct {
|
|
Data string
|
|
Placeholder PathPlaceholder
|
|
}
|
|
|
|
type PathPlaceholders struct {
|
|
Dir *string
|
|
Name *string
|
|
Hash *string
|
|
Ext *string
|
|
}
|
|
|
|
func (placeholders PathPlaceholders) Get(placeholder PathPlaceholder) *string {
|
|
switch placeholder {
|
|
case DirPlaceholder:
|
|
return placeholders.Dir
|
|
case NamePlaceholder:
|
|
return placeholders.Name
|
|
case HashPlaceholder:
|
|
return placeholders.Hash
|
|
case ExtPlaceholder:
|
|
return placeholders.Ext
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TemplateToString(template []PathTemplate) string {
|
|
if len(template) == 1 && template[0].Placeholder == NoPlaceholder {
|
|
// Avoid allocations in this case
|
|
return template[0].Data
|
|
}
|
|
sb := strings.Builder{}
|
|
for _, part := range template {
|
|
sb.WriteString(part.Data)
|
|
switch part.Placeholder {
|
|
case DirPlaceholder:
|
|
sb.WriteString("[dir]")
|
|
case NamePlaceholder:
|
|
sb.WriteString("[name]")
|
|
case HashPlaceholder:
|
|
sb.WriteString("[hash]")
|
|
case ExtPlaceholder:
|
|
sb.WriteString("[ext]")
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func HasPlaceholder(template []PathTemplate, placeholder PathPlaceholder) bool {
|
|
for _, part := range template {
|
|
if part.Placeholder == placeholder {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func SubstituteTemplate(template []PathTemplate, placeholders PathPlaceholders) []PathTemplate {
|
|
// Don't allocate if no substitution is possible and the template is already minimal
|
|
shouldSubstitute := false
|
|
for i, part := range template {
|
|
if placeholders.Get(part.Placeholder) != nil || (part.Placeholder == NoPlaceholder && i+1 < len(template)) {
|
|
shouldSubstitute = true
|
|
break
|
|
}
|
|
}
|
|
if !shouldSubstitute {
|
|
return template
|
|
}
|
|
|
|
// Otherwise, substitute and merge as appropriate
|
|
result := make([]PathTemplate, 0, len(template))
|
|
for _, part := range template {
|
|
if sub := placeholders.Get(part.Placeholder); sub != nil {
|
|
part.Data += *sub
|
|
part.Placeholder = NoPlaceholder
|
|
}
|
|
if last := len(result) - 1; last >= 0 && result[last].Placeholder == NoPlaceholder {
|
|
last := &result[last]
|
|
last.Data += part.Data
|
|
last.Placeholder = part.Placeholder
|
|
} else {
|
|
result = append(result, part)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func ShouldCallRuntimeRequire(mode Mode, outputFormat Format) bool {
|
|
return mode == ModeBundle && outputFormat != FormatCommonJS
|
|
}
|
|
|
|
type InjectedDefine struct {
|
|
Source logger.Source
|
|
Data js_ast.E
|
|
Name string
|
|
}
|
|
|
|
type InjectedFile struct {
|
|
Source logger.Source
|
|
Exports []InjectableExport
|
|
DefineName string
|
|
}
|
|
|
|
type InjectableExport struct {
|
|
Alias string
|
|
Loc logger.Loc
|
|
}
|
|
|
|
var filterMutex sync.Mutex
|
|
var filterCache map[string]*regexp.Regexp
|
|
|
|
func compileFilter(filter string) (result *regexp.Regexp) {
|
|
if filter == "" {
|
|
// Must provide a filter
|
|
return nil
|
|
}
|
|
ok := false
|
|
|
|
// Cache hit?
|
|
(func() {
|
|
filterMutex.Lock()
|
|
defer filterMutex.Unlock()
|
|
if filterCache != nil {
|
|
result, ok = filterCache[filter]
|
|
}
|
|
})()
|
|
if ok {
|
|
return
|
|
}
|
|
|
|
// Cache miss
|
|
result, err := regexp.Compile(filter)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Cache for next time
|
|
filterMutex.Lock()
|
|
defer filterMutex.Unlock()
|
|
if filterCache == nil {
|
|
filterCache = make(map[string]*regexp.Regexp)
|
|
}
|
|
filterCache[filter] = result
|
|
return
|
|
}
|
|
|
|
func CompileFilterForPlugin(pluginName string, kind string, filter string) (*regexp.Regexp, error) {
|
|
if filter == "" {
|
|
return nil, fmt.Errorf("[%s] %q is missing a filter", pluginName, kind)
|
|
}
|
|
|
|
result := compileFilter(filter)
|
|
if result == nil {
|
|
return nil, fmt.Errorf("[%s] %q filter is not a valid Go regular expression: %q", pluginName, kind, filter)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func PluginAppliesToPath(path logger.Path, filter *regexp.Regexp, namespace string) bool {
|
|
return (namespace == "" || path.Namespace == namespace) && filter.MatchString(path.Text)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Plugin API
|
|
|
|
type Plugin struct {
|
|
Name string
|
|
OnStart []OnStart
|
|
OnResolve []OnResolve
|
|
OnLoad []OnLoad
|
|
}
|
|
|
|
type OnStart struct {
|
|
Name string
|
|
Callback func() OnStartResult
|
|
}
|
|
|
|
type OnStartResult struct {
|
|
Msgs []logger.Msg
|
|
ThrownError error
|
|
}
|
|
|
|
type OnResolve struct {
|
|
Name string
|
|
Filter *regexp.Regexp
|
|
Namespace string
|
|
Callback func(OnResolveArgs) OnResolveResult
|
|
}
|
|
|
|
type OnResolveArgs struct {
|
|
Path string
|
|
Importer logger.Path
|
|
ResolveDir string
|
|
Kind ast.ImportKind
|
|
PluginData interface{}
|
|
}
|
|
|
|
type OnResolveResult struct {
|
|
PluginName string
|
|
|
|
Path logger.Path
|
|
External bool
|
|
IsSideEffectFree bool
|
|
PluginData interface{}
|
|
|
|
Msgs []logger.Msg
|
|
ThrownError error
|
|
|
|
AbsWatchFiles []string
|
|
AbsWatchDirs []string
|
|
}
|
|
|
|
type OnLoad struct {
|
|
Name string
|
|
Filter *regexp.Regexp
|
|
Namespace string
|
|
Callback func(OnLoadArgs) OnLoadResult
|
|
}
|
|
|
|
type OnLoadArgs struct {
|
|
Path logger.Path
|
|
PluginData interface{}
|
|
}
|
|
|
|
type OnLoadResult struct {
|
|
PluginName string
|
|
|
|
Contents *string
|
|
AbsResolveDir string
|
|
Loader Loader
|
|
PluginData interface{}
|
|
|
|
Msgs []logger.Msg
|
|
ThrownError error
|
|
|
|
AbsWatchFiles []string
|
|
AbsWatchDirs []string
|
|
}
|