1package main
2
3import (
4	"fmt"
5	"io"
6	"os"
7	"runtime/debug"
8	"strings"
9	"time"
10
11	"github.com/evanw/esbuild/internal/api_helpers"
12	"github.com/evanw/esbuild/internal/logger"
13	"github.com/evanw/esbuild/pkg/cli"
14)
15
16var helpText = func(colors logger.Colors) string {
17	// Read "NO_COLOR" from the environment. This is a convention that some
18	// software follows. See https://no-color.org/ for more information.
19	for _, key := range os.Environ() {
20		if strings.HasPrefix(key, "NO_COLOR=") {
21			colors = logger.Colors{}
22			break
23		}
24	}
25
26	return `
27` + colors.Bold + `Usage:` + colors.Reset + `
28  esbuild [options] [entry points]
29
30` + colors.Bold + `Documentation:` + colors.Reset + `
31  ` + colors.Underline + `https://esbuild.github.io/` + colors.Reset + `
32
33` + colors.Bold + `Repository:` + colors.Reset + `
34  ` + colors.Underline + `https://github.com/evanw/esbuild` + colors.Reset + `
35
36` + colors.Bold + `Simple options:` + colors.Reset + `
37  --bundle              Bundle all dependencies into the output files
38  --define:K=V          Substitute K with V while parsing
39  --external:M          Exclude module M from the bundle (can use * wildcards)
40  --format=...          Output format (iife | cjs | esm, no default when not
41                        bundling, otherwise default is iife when platform
42                        is browser and cjs when platform is node)
43  --loader:X=L          Use loader L to load file extension X, where L is
44                        one of: js | jsx | ts | tsx | json | text | base64 |
45                        file | dataurl | binary
46  --minify              Minify the output (sets all --minify-* flags)
47  --outdir=...          The output directory (for multiple entry points)
48  --outfile=...         The output file (for one entry point)
49  --platform=...        Platform target (browser | node | neutral,
50                        default browser)
51  --serve=...           Start a local HTTP server on this host:port for outputs
52  --sourcemap           Emit a source map
53  --splitting           Enable code splitting (currently only for esm)
54  --target=...          Environment target (e.g. es2017, chrome58, firefox57,
55                        safari11, edge16, node10, default esnext)
56  --watch               Watch mode: rebuild on file system changes
57
58` + colors.Bold + `Advanced options:` + colors.Reset + `
59  --allow-overwrite         Allow output files to overwrite input files
60  --asset-names=...         Path template to use for "file" loader files
61                            (default "[name]-[hash]")
62  --banner:T=...            Text to be prepended to each output file of type T
63                            where T is one of: css | js
64  --charset=utf8            Do not escape UTF-8 code points
65  --chunk-names=...         Path template to use for code splitting chunks
66                            (default "[name]-[hash]")
67  --color=...               Force use of color terminal escapes (true | false)
68  --entry-names=...         Path template to use for entry point output paths
69                            (default "[dir]/[name]", can also use "[hash]")
70  --footer:T=...            Text to be appended to each output file of type T
71                            where T is one of: css | js
72  --global-name=...         The name of the global for the IIFE format
73  --inject:F                Import the file F into all input files and
74                            automatically replace matching globals with imports
75  --jsx-factory=...         What to use for JSX instead of React.createElement
76  --jsx-fragment=...        What to use for JSX instead of React.Fragment
77  --jsx=...                 Set to "preserve" to disable transforming JSX to JS
78  --keep-names              Preserve "name" on functions and classes
79  --legal-comments=...      Where to place license comments (none | inline |
80                            eof | linked | external, default eof when bundling
81                            and inline otherwise)
82  --log-level=...           Disable logging (verbose | debug | info | warning |
83                            error | silent, default info)
84  --log-limit=...           Maximum message count or 0 to disable (default 10)
85  --main-fields=...         Override the main file order in package.json
86                            (default "browser,module,main" when platform is
87                            browser and "main,module" when platform is node)
88  --metafile=...            Write metadata about the build to a JSON file
89  --minify-whitespace       Remove whitespace in output files
90  --minify-identifiers      Shorten identifiers in output files
91  --minify-syntax           Use equivalent but shorter syntax in output files
92  --out-extension:.js=.mjs  Use a custom output extension instead of ".js"
93  --outbase=...             The base path used to determine entry point output
94                            paths (for multiple entry points)
95  --preserve-symlinks       Disable symlink resolution for module lookup
96  --public-path=...         Set the base URL for the "file" loader
97  --pure:N                  Mark the name N as a pure function for tree shaking
98  --resolve-extensions=...  A comma-separated list of implicit extensions
99                            (default ".tsx,.ts,.jsx,.js,.css,.json")
100  --servedir=...            What to serve in addition to generated output files
101  --source-root=...         Sets the "sourceRoot" field in generated source maps
102  --sourcefile=...          Set the source file for the source map (for stdin)
103  --sourcemap=external      Do not link to the source map with a comment
104  --sourcemap=inline        Emit the source map with an inline data URL
105  --sources-content=false   Omit "sourcesContent" in generated source maps
106  --tree-shaking=...        Set to "ignore-annotations" to work with packages
107                            that have incorrect tree-shaking annotations
108  --tsconfig=...            Use this tsconfig.json file instead of other ones
109  --version                 Print the current version (` + esbuildVersion + `) and exit
110
111` + colors.Bold + `Examples:` + colors.Reset + `
112  ` + colors.Dim + `# Produces dist/entry_point.js and dist/entry_point.js.map` + colors.Reset + `
113  esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap
114
115  ` + colors.Dim + `# Allow JSX syntax in .js files` + colors.Reset + `
116  esbuild --bundle entry_point.js --outfile=out.js --loader:.js=jsx
117
118  ` + colors.Dim + `# Substitute the identifier RELEASE for the literal true` + colors.Reset + `
119  esbuild example.js --outfile=out.js --define:RELEASE=true
120
121  ` + colors.Dim + `# Provide input via stdin, get output via stdout` + colors.Reset + `
122  esbuild --minify --loader=ts < input.ts > output.js
123
124  ` + colors.Dim + `# Automatically rebuild when input files are changed` + colors.Reset + `
125  esbuild app.ts --bundle --watch
126
127  ` + colors.Dim + `# Start a local HTTP server for everything in "www"` + colors.Reset + `
128  esbuild app.ts --bundle --servedir=www --outdir=www/js
129
130`
131}
132
133func main() {
134	logger.API = logger.CLIAPI
135
136	osArgs := os.Args[1:]
137	heapFile := ""
138	traceFile := ""
139	cpuprofileFile := ""
140	isRunningService := false
141	sendPings := false
142
143	// Do an initial scan over the argument list
144	argsEnd := 0
145	for _, arg := range osArgs {
146		switch {
147		// Show help if a common help flag is provided
148		case arg == "-h", arg == "-help", arg == "--help", arg == "/?":
149			logger.PrintText(os.Stdout, logger.LevelSilent, os.Args, helpText)
150			os.Exit(0)
151
152		// Special-case the version flag here
153		case arg == "--version":
154			fmt.Printf("%s\n", esbuildVersion)
155			os.Exit(0)
156
157		case strings.HasPrefix(arg, "--heap="):
158			heapFile = arg[len("--heap="):]
159
160		case strings.HasPrefix(arg, "--trace="):
161			traceFile = arg[len("--trace="):]
162
163		case strings.HasPrefix(arg, "--timing"):
164			// This is a hidden flag because it's only intended for debugging esbuild
165			// itself. The output is not documented and not stable.
166			api_helpers.UseTimer = true
167
168		case strings.HasPrefix(arg, "--cpuprofile="):
169			cpuprofileFile = arg[len("--cpuprofile="):]
170
171		// This flag turns the process into a long-running service that uses
172		// message passing with the host process over stdin/stdout
173		case strings.HasPrefix(arg, "--service="):
174			hostVersion := arg[len("--service="):]
175			isRunningService = true
176
177			// Validate the host's version number to make sure esbuild was installed
178			// correctly. This check was added because some people have reported
179			// errors that appear to indicate an incorrect installation.
180			if hostVersion != esbuildVersion {
181				logger.PrintErrorToStderr(osArgs,
182					fmt.Sprintf("Cannot start service: Host version %q does not match binary version %q",
183						hostVersion, esbuildVersion))
184				os.Exit(1)
185			}
186
187		case strings.HasPrefix(arg, "--ping"):
188			sendPings = true
189
190		default:
191			// Strip any arguments that were handled above
192			osArgs[argsEnd] = arg
193			argsEnd++
194		}
195	}
196	osArgs = osArgs[:argsEnd]
197
198	// Run in service mode if requested
199	if isRunningService {
200		runService(sendPings)
201		return
202	}
203
204	// Print help text when there are no arguments
205	isStdinTTY := logger.GetTerminalInfo(os.Stdin).IsTTY
206	if len(osArgs) == 0 && isStdinTTY {
207		logger.PrintText(os.Stdout, logger.LevelSilent, osArgs, helpText)
208		os.Exit(0)
209	}
210
211	// Capture the defer statements below so the "done" message comes last
212	exitCode := 1
213	func() {
214		// To view a CPU trace, use "go tool trace [file]". Note that the trace
215		// viewer doesn't work under Windows Subsystem for Linux for some reason.
216		if traceFile != "" {
217			if done := createTraceFile(osArgs, traceFile); done == nil {
218				return
219			} else {
220				defer done()
221			}
222		}
223
224		// To view a heap trace, use "go tool pprof [file]" and type "top". You can
225		// also drop it into https://speedscope.app and use the "left heavy" or
226		// "sandwich" view modes.
227		if heapFile != "" {
228			if done := createHeapFile(osArgs, heapFile); done == nil {
229				return
230			} else {
231				defer done()
232			}
233		}
234
235		// To view a CPU profile, drop the file into https://speedscope.app.
236		// Note: Running the CPU profiler doesn't work under Windows subsystem for
237		// Linux. The profiler has to be built for native Windows and run using the
238		// command prompt instead.
239		if cpuprofileFile != "" {
240			if done := createCpuprofileFile(osArgs, cpuprofileFile); done == nil {
241				return
242			} else {
243				defer done()
244			}
245		}
246
247		if cpuprofileFile != "" {
248			// The CPU profiler in Go only runs at 100 Hz, which is far too slow to
249			// return useful information for esbuild, since it's so fast. Let's keep
250			// running for 30 seconds straight, which should give us 3,000 samples.
251			seconds := 30.0
252			start := time.Now()
253			for time.Since(start).Seconds() < seconds {
254				exitCode = cli.Run(osArgs)
255			}
256		} else {
257			// Don't disable the GC if this is a long-running process
258			isServeOrWatch := false
259			for _, arg := range osArgs {
260				if arg == "--serve" || arg == "--watch" || strings.HasPrefix(arg, "--serve=") {
261					isServeOrWatch = true
262					break
263				}
264			}
265
266			if !isServeOrWatch {
267				// Disable the GC since we're just going to allocate a bunch of memory
268				// and then exit anyway. This speedup is not insignificant. Make sure to
269				// only do this here once we know that we're not going to be a long-lived
270				// process though.
271				debug.SetGCPercent(-1)
272			} else if !isStdinTTY {
273				// If stdin isn't a TTY, watch stdin and abort in case it is closed.
274				// This is necessary when the esbuild binary executable is invoked via
275				// the Erlang VM, which doesn't provide a way to exit a child process.
276				// See: https://github.com/brunch/brunch/issues/920.
277				//
278				// We don't do this when stdin is a TTY because that interferes with
279				// the Unix background job system. If we read from stdin then Ctrl+Z
280				// to move the process to the background will incorrectly cause the
281				// job to stop. See: https://github.com/brunch/brunch/issues/998.
282				go func() {
283					// This just discards information from stdin because we don't use
284					// it and we can avoid unnecessarily allocating space for it
285					buffer := make([]byte, 512)
286					for {
287						_, err := os.Stdin.Read(buffer)
288						if err != nil {
289							// Only exit cleanly if stdin was closed cleanly
290							if err == io.EOF {
291								os.Exit(0)
292							} else {
293								os.Exit(1)
294							}
295						}
296					}
297				}()
298			}
299
300			exitCode = cli.Run(osArgs)
301		}
302	}()
303
304	os.Exit(exitCode)
305}
306