├── .editorconfig ├── .gitignore ├── CHANGELOG-2020.md ├── CHANGELOG-2021.md ├── CHANGELOG-2022.md ├── CHANGELOG-2023.md ├── CHANGELOG-2024.md ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── api ├── api.go ├── api_impl.go ├── api_js_table.go ├── favicon.go ├── serve_other.go ├── serve_wasm.go └── watcher.go ├── api_helpers └── use_timer.go ├── ast └── ast.go ├── bundler └── bundler.go ├── bundler_tests ├── bundler_css_test.go ├── bundler_dce_test.go ├── bundler_default_test.go ├── bundler_glob_test.go ├── bundler_importstar_test.go ├── bundler_importstar_ts_test.go ├── bundler_loader_test.go ├── bundler_lower_test.go ├── bundler_packagejson_test.go ├── bundler_splitting_test.go ├── bundler_test.go ├── bundler_ts_test.go ├── bundler_tsconfig_test.go └── snapshots │ ├── snapshots_css.txt │ ├── snapshots_dce.txt │ ├── snapshots_default.txt │ ├── snapshots_glob.txt │ ├── snapshots_importstar.txt │ ├── snapshots_importstar_ts.txt │ ├── snapshots_loader.txt │ ├── snapshots_lower.txt │ ├── snapshots_packagejson.txt │ ├── snapshots_splitting.txt │ ├── snapshots_ts.txt │ └── snapshots_tsconfig.txt ├── cache ├── cache.go ├── cache_ast.go └── cache_fs.go ├── compat ├── compat.go ├── compat_test.go ├── css_table.go └── js_table.go ├── config ├── config.go └── globals.go ├── css_ast ├── css_ast.go └── css_decl_table.go ├── css_lexer ├── css_lexer.go └── css_lexer_test.go ├── css_parser ├── css_color_spaces.go ├── css_decls.go ├── css_decls_animation.go ├── css_decls_border_radius.go ├── css_decls_box.go ├── css_decls_box_shadow.go ├── css_decls_color.go ├── css_decls_composes.go ├── css_decls_container.go ├── css_decls_font.go ├── css_decls_font_family.go ├── css_decls_font_weight.go ├── css_decls_gradient.go ├── css_decls_list_style.go ├── css_decls_transform.go ├── css_nesting.go ├── css_parser.go ├── css_parser_selector.go ├── css_parser_test.go └── css_reduce_calc.go ├── css_printer ├── css_printer.go └── css_printer_test.go ├── fs ├── error_other.go ├── error_wasm+windows.go ├── filepath.go ├── fs.go ├── fs_mock.go ├── fs_mock_test.go ├── fs_real.go ├── fs_zip.go ├── iswin_other.go ├── iswin_wasm.go ├── iswin_windows.go ├── modkey_other.go └── modkey_unix.go ├── go.mod ├── go.sum ├── graph ├── graph.go ├── input.go └── meta.go ├── helpers ├── bitset.go ├── comment.go ├── dataurl.go ├── dataurl_test.go ├── float.go ├── glob.go ├── hash.go ├── joiner.go ├── mime.go ├── path.go ├── quote.go ├── serializer.go ├── stack.go ├── strings.go ├── timer.go ├── typos.go ├── utf.go └── waitgroup.go ├── images ├── benchmark-dark.svg ├── benchmark-light.svg ├── wordmark-dark.svg └── wordmark-light.svg ├── js_ast ├── js_ast.go ├── js_ast_helpers.go ├── js_ast_test.go ├── js_ident.go └── unicode.go ├── js_lexer ├── js_lexer.go ├── js_lexer_test.go └── tables.go ├── js_parser ├── global_name_parser.go ├── js_parser.go ├── js_parser_lower.go ├── js_parser_lower_class.go ├── js_parser_lower_test.go ├── js_parser_test.go ├── json_parser.go ├── json_parser_test.go ├── sourcemap_parser.go ├── ts_parser.go └── ts_parser_test.go ├── js_printer ├── js_printer.go └── js_printer_test.go ├── linker ├── debug.go └── linker.go ├── logger ├── logger.go ├── logger_darwin.go ├── logger_linux.go ├── logger_other.go ├── logger_test.go ├── logger_windows.go └── msg_ids.go ├── renamer └── renamer.go ├── resolver ├── dataurl.go ├── package_json.go ├── resolver.go ├── testExpectations.json ├── tsconfig_json.go ├── yarnpnp.go └── yarnpnp_test.go ├── runtime ├── runtime.go └── runtime_test.go ├── sourcemap └── sourcemap.go ├── test ├── diff.go └── util.go ├── update.ts ├── version.txt └── xxhash ├── LICENSE.txt ├── README.md ├── xxhash.go └── xxhash_other.go /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 2 4 | 5 | [*.{js,ts}] 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Evan Wallace 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esbuild-internal 2 | 3 | Exports [esbuild](https://esbuild.github.io/) internal packages. 4 | -------------------------------------------------------------------------------- /api/api_js_table.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by "js_table.ts" 2 | 3 | package api 4 | 5 | import "github.com/ije/esbuild-internal/compat" 6 | 7 | type EngineName uint8 8 | 9 | const ( 10 | EngineChrome EngineName = iota 11 | EngineDeno 12 | EngineEdge 13 | EngineFirefox 14 | EngineHermes 15 | EngineIE 16 | EngineIOS 17 | EngineNode 18 | EngineOpera 19 | EngineRhino 20 | EngineSafari 21 | ) 22 | 23 | func convertEngineName(engine EngineName) compat.Engine { 24 | switch engine { 25 | case EngineChrome: 26 | return compat.Chrome 27 | case EngineDeno: 28 | return compat.Deno 29 | case EngineEdge: 30 | return compat.Edge 31 | case EngineFirefox: 32 | return compat.Firefox 33 | case EngineHermes: 34 | return compat.Hermes 35 | case EngineIE: 36 | return compat.IE 37 | case EngineIOS: 38 | return compat.IOS 39 | case EngineNode: 40 | return compat.Node 41 | case EngineOpera: 42 | return compat.Opera 43 | case EngineRhino: 44 | return compat.Rhino 45 | case EngineSafari: 46 | return compat.Safari 47 | default: 48 | panic("Invalid engine name") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/favicon.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // This is the "favicon.ico" file used by esbuild's built-in development server 4 | var favicon_ico_gz = []byte{ 5 | 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x63, 0x60, 0x60, 0x64, 0x60, 0x62, 6 | 0x10, 0x10, 0x60, 0x00, 0xD2, 0x0A, 0x0C, 0x19, 0x2C, 0x0C, 0x0C, 0x6A, 0x0C, 0x0C, 0x0C, 0x0A, 7 | 0x0A, 0x10, 0xBE, 0x86, 0x20, 0x03, 0x43, 0x1F, 0x50, 0x4C, 0x03, 0x28, 0x26, 0x00, 0x12, 0x67, 8 | 0x80, 0x88, 0x13, 0x04, 0xE7, 0xFF, 0xFF, 0x27, 0x09, 0xD3, 0x4A, 0xFF, 0xC9, 0xEF, 0xFF, 0x59, 9 | 0x97, 0x9F, 0x81, 0xAB, 0x63, 0x5B, 0x72, 0xF2, 0x3F, 0xC3, 0x99, 0xDF, 0x44, 0xEB, 0xE7, 0x29, 10 | 0xE9, 0xF9, 0x2F, 0xAE, 0xA8, 0x02, 0xD6, 0xC7, 0x74, 0xE0, 0xCD, 0x7F, 0x09, 0x45, 0xE5, 0xFF, 11 | 0x02, 0xA1, 0xA9, 0x98, 0x66, 0xE0, 0xB1, 0x5F, 0xC8, 0x27, 0x12, 0x6E, 0x06, 0xFB, 0xEC, 0x7D, 12 | 0xFF, 0x25, 0xE4, 0x14, 0x30, 0xCD, 0xC0, 0xE7, 0x7F, 0xA0, 0x19, 0xC2, 0x0E, 0xDE, 0x60, 0x33, 13 | 0x58, 0x36, 0x5C, 0xFF, 0xCF, 0x31, 0x79, 0xF3, 0x7F, 0x49, 0x49, 0xC9, 0xFF, 0xFC, 0xB1, 0xF9, 14 | 0x44, 0xE9, 0x47, 0xB6, 0x93, 0xF1, 0xD8, 0x17, 0x14, 0xF7, 0x10, 0xD2, 0x4F, 0x94, 0x5E, 0x02, 15 | 0xFA, 0x05, 0x42, 0x53, 0xC0, 0x7E, 0x85, 0xE9, 0xC7, 0xD0, 0x4B, 0xCB, 0xF8, 0xA7, 0x85, 0xFE, 16 | 0x9A, 0x99, 0x68, 0x78, 0x56, 0x3D, 0xF9, 0xFA, 0xB1, 0xE8, 0x25, 0x5A, 0x3F, 0x0E, 0xBD, 0x44, 17 | 0xE9, 0xC7, 0xA3, 0x97, 0xA0, 0x7E, 0x02, 0x7A, 0x29, 0x00, 0x1A, 0xD0, 0x32, 0xC6, 0x81, 0xD8, 18 | 0x72, 0x86, 0xDC, 0xF8, 0xA6, 0x34, 0x7D, 0x8C, 0xDA, 0x3F, 0x6A, 0x3F, 0x01, 0xFB, 0x99, 0x77, 19 | 0x3C, 0xFA, 0x2F, 0xEC, 0x1E, 0x0C, 0xA6, 0xD1, 0xE5, 0x58, 0xD7, 0x5C, 0x06, 0xCB, 0x31, 0x1D, 20 | 0xF9, 0x48, 0x13, 0xFB, 0x41, 0x76, 0x8A, 0x19, 0x98, 0x81, 0xEB, 0x09, 0x11, 0x1B, 0x57, 0x14, 21 | 0x7B, 0x40, 0x76, 0x4B, 0xA8, 0xA8, 0x63, 0x95, 0xA3, 0x85, 0xFD, 0xE8, 0xF6, 0xE0, 0x93, 0xA3, 22 | 0x76, 0xF8, 0x53, 0xCD, 0x0D, 0x64, 0xA6, 0x3F, 0xAA, 0xB9, 0x81, 0x82, 0xF4, 0x4F, 0x15, 0x37, 23 | 0x50, 0x98, 0xFF, 0xD8, 0x16, 0x1E, 0x85, 0xDB, 0x01, 0xC2, 0x02, 0x71, 0x05, 0x70, 0x39, 0x8E, 24 | 0x69, 0xDB, 0x71, 0xCA, 0x0D, 0x75, 0xFF, 0x0F, 0x64, 0xFC, 0x0F, 0x64, 0xFA, 0x1F, 0xE8, 0xFC, 25 | 0x3F, 0xD0, 0xE5, 0xDF, 0x40, 0x97, 0xFF, 0xA3, 0xF5, 0xEF, 0xA8, 0xFD, 0x44, 0x61, 0x8C, 0xBE, 26 | 0x0C, 0x36, 0x3C, 0xA7, 0x7E, 0xE0, 0xEC, 0x9F, 0x53, 0x4F, 0xD3, 0xF6, 0x3F, 0x35, 0xEC, 0xA6, 27 | 0x89, 0xFD, 0x73, 0x48, 0xEB, 0x63, 0x51, 0xD5, 0xFE, 0x39, 0xA4, 0xF7, 0xEF, 0xA8, 0x66, 0xFF, 28 | 0x1C, 0xF2, 0xFA, 0x96, 0x54, 0xB1, 0x7F, 0x0E, 0xF9, 0xFD, 0x5A, 0x8A, 0xED, 0x9F, 0x43, 0x59, 29 | 0x9F, 0x9A, 0x22, 0xFB, 0xE7, 0x50, 0xDE, 0x9F, 0x27, 0xDB, 0xFE, 0x39, 0x34, 0x1B, 0x4B, 0x18, 30 | 0xCE, 0x00, 0x00, 0xDA, 0xEB, 0x61, 0xFD, 0xB6, 0x15, 0x00, 0x00, 31 | } 32 | -------------------------------------------------------------------------------- /api/serve_wasm.go: -------------------------------------------------------------------------------- 1 | //go:build js && wasm 2 | // +build js,wasm 3 | 4 | package api 5 | 6 | import "fmt" 7 | 8 | // Remove the serve API in the WebAssembly build. This removes 2.7mb of stuff. 9 | 10 | func (*internalContext) Serve(ServeOptions) (ServeResult, error) { 11 | return ServeResult{}, fmt.Errorf("The \"serve\" API is not supported when using WebAssembly") 12 | } 13 | 14 | type apiHandler struct { 15 | } 16 | 17 | func (*apiHandler) broadcastBuildResult(BuildResult, map[string]string) { 18 | } 19 | 20 | func (*apiHandler) stop() { 21 | } 22 | -------------------------------------------------------------------------------- /api/watcher.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // This file implements a polling file watcher for esbuild (i.e. it detects 4 | // when files are changed by repeatedly checking their contents). Polling is 5 | // used instead of more efficient platform-specific file system APIs because: 6 | // 7 | // * Go's standard library doesn't have built-in APIs for file watching 8 | // * Using platform-specific APIs means using cgo, which I want to avoid 9 | // * Polling is cross-platform and esbuild needs to work on 20+ platforms 10 | // * Platform-specific APIs might be unreliable and could introduce bugs 11 | // 12 | // That said, this polling system is designed to use relatively little CPU vs. 13 | // a more traditional polling system that scans the whole directory tree at 14 | // once. The file system is still scanned regularly but each scan only checks 15 | // a random subset of your files, which means a change to a file will be picked 16 | // up soon after the change is made but not necessarily instantly. 17 | // 18 | // With the current heuristics, large projects should be completely scanned 19 | // around every 2 seconds so in the worst case it could take up to 2 seconds 20 | // for a change to be noticed. However, after a change has been noticed the 21 | // change's path goes on a short list of recently changed paths which are 22 | // checked on every scan, so further changes to recently changed files should 23 | // be noticed almost instantly. 24 | 25 | import ( 26 | "fmt" 27 | "math/rand" 28 | "os" 29 | "sync" 30 | "sync/atomic" 31 | "time" 32 | 33 | "github.com/ije/esbuild-internal/fs" 34 | "github.com/ije/esbuild-internal/logger" 35 | "github.com/ije/esbuild-internal/resolver" 36 | ) 37 | 38 | // The time to wait between watch intervals 39 | const watchIntervalSleep = 100 * time.Millisecond 40 | 41 | // The maximum number of recently-edited items to check every interval 42 | const maxRecentItemCount = 16 43 | 44 | // The minimum number of non-recent items to check every interval 45 | const minItemCountPerIter = 64 46 | 47 | // The maximum number of intervals before a change is detected 48 | const maxIntervalsBeforeUpdate = 20 49 | 50 | type watcher struct { 51 | data fs.WatchData 52 | fs fs.FS 53 | rebuild func() fs.WatchData 54 | recentItems []string 55 | itemsToScan []string 56 | mutex sync.Mutex 57 | itemsPerIteration int 58 | shouldStop int32 59 | shouldLog bool 60 | useColor logger.UseColor 61 | stopWaitGroup sync.WaitGroup 62 | } 63 | 64 | func (w *watcher) setWatchData(data fs.WatchData) { 65 | defer w.mutex.Unlock() 66 | w.mutex.Lock() 67 | 68 | // Print something for the end of the first build 69 | if w.shouldLog && w.data.Paths == nil { 70 | logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string { 71 | return fmt.Sprintf("%s[watch] build finished, watching for changes...%s\n", colors.Dim, colors.Reset) 72 | }) 73 | } 74 | 75 | w.data = data 76 | w.itemsToScan = w.itemsToScan[:0] // Reuse memory 77 | 78 | // Remove any recent items that weren't a part of the latest build 79 | end := 0 80 | for _, path := range w.recentItems { 81 | if data.Paths[path] != nil { 82 | w.recentItems[end] = path 83 | end++ 84 | } 85 | } 86 | w.recentItems = w.recentItems[:end] 87 | } 88 | 89 | func (w *watcher) start() { 90 | w.stopWaitGroup.Add(1) 91 | 92 | go func() { 93 | // Note: Do not change these log messages without a breaking version change. 94 | // People want to run regexes over esbuild's stderr stream to look for these 95 | // messages instead of using esbuild's API. 96 | 97 | for atomic.LoadInt32(&w.shouldStop) == 0 { 98 | // Sleep for the watch interval 99 | time.Sleep(watchIntervalSleep) 100 | 101 | // Rebuild if we're dirty 102 | if absPath := w.tryToFindDirtyPath(); absPath != "" { 103 | if w.shouldLog { 104 | logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string { 105 | prettyPath := resolver.PrettyPath(w.fs, logger.Path{Text: absPath, Namespace: "file"}) 106 | return fmt.Sprintf("%s[watch] build started (change: %q)%s\n", colors.Dim, prettyPath, colors.Reset) 107 | }) 108 | } 109 | 110 | // Run the build 111 | w.setWatchData(w.rebuild()) 112 | 113 | if w.shouldLog { 114 | logger.PrintTextWithColor(os.Stderr, w.useColor, func(colors logger.Colors) string { 115 | return fmt.Sprintf("%s[watch] build finished%s\n", colors.Dim, colors.Reset) 116 | }) 117 | } 118 | } 119 | } 120 | 121 | w.stopWaitGroup.Done() 122 | }() 123 | } 124 | 125 | func (w *watcher) stop() { 126 | atomic.StoreInt32(&w.shouldStop, 1) 127 | w.stopWaitGroup.Wait() 128 | } 129 | 130 | func (w *watcher) tryToFindDirtyPath() string { 131 | defer w.mutex.Unlock() 132 | w.mutex.Lock() 133 | 134 | // If we ran out of items to scan, fill the items back up in a random order 135 | if len(w.itemsToScan) == 0 { 136 | items := w.itemsToScan[:0] // Reuse memory 137 | for path := range w.data.Paths { 138 | items = append(items, path) 139 | } 140 | rand.Seed(time.Now().UnixNano()) 141 | for i := int32(len(items) - 1); i > 0; i-- { // Fisher-Yates shuffle 142 | j := rand.Int31n(i + 1) 143 | items[i], items[j] = items[j], items[i] 144 | } 145 | w.itemsToScan = items 146 | 147 | // Determine how many items to check every iteration, rounded up 148 | perIter := (len(items) + maxIntervalsBeforeUpdate - 1) / maxIntervalsBeforeUpdate 149 | if perIter < minItemCountPerIter { 150 | perIter = minItemCountPerIter 151 | } 152 | w.itemsPerIteration = perIter 153 | } 154 | 155 | // Always check all recent items every iteration 156 | for i, path := range w.recentItems { 157 | if dirtyPath := w.data.Paths[path](); dirtyPath != "" { 158 | // Move this path to the back of the list (i.e. the "most recent" position) 159 | copy(w.recentItems[i:], w.recentItems[i+1:]) 160 | w.recentItems[len(w.recentItems)-1] = path 161 | return dirtyPath 162 | } 163 | } 164 | 165 | // Check a constant number of items every iteration 166 | remainingCount := len(w.itemsToScan) - w.itemsPerIteration 167 | if remainingCount < 0 { 168 | remainingCount = 0 169 | } 170 | toCheck, remaining := w.itemsToScan[remainingCount:], w.itemsToScan[:remainingCount] 171 | w.itemsToScan = remaining 172 | 173 | // Check if any of the entries in this iteration have been modified 174 | for _, path := range toCheck { 175 | if dirtyPath := w.data.Paths[path](); dirtyPath != "" { 176 | // Mark this item as recent by adding it to the back of the list 177 | w.recentItems = append(w.recentItems, path) 178 | if len(w.recentItems) > maxRecentItemCount { 179 | // Remove items from the front of the list when we hit the limit 180 | copy(w.recentItems, w.recentItems[1:]) 181 | w.recentItems = w.recentItems[:maxRecentItemCount] 182 | } 183 | return dirtyPath 184 | } 185 | } 186 | return "" 187 | } 188 | -------------------------------------------------------------------------------- /api_helpers/use_timer.go: -------------------------------------------------------------------------------- 1 | package api_helpers 2 | 3 | // This flag is set by the CLI to activate the timer. It's put here instead of 4 | // by the timer to discourage code from checking this flag. Only the code that 5 | // creates the root timer should check this flag. Other code should check that 6 | // the timer is not null to detect if the timer is being used or not. 7 | var UseTimer bool 8 | -------------------------------------------------------------------------------- /bundler_tests/bundler_glob_test.go: -------------------------------------------------------------------------------- 1 | package bundler_tests 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ije/esbuild-internal/config" 7 | ) 8 | 9 | var glob_suite = suite{ 10 | name: "glob", 11 | } 12 | 13 | func TestGlobBasicNoBundle(t *testing.T) { 14 | glob_suite.expectBundled(t, bundled{ 15 | files: map[string]string{ 16 | "/entry.js": ` 17 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 18 | console.log({ 19 | concat: { 20 | require: require('./src/' + ab), 21 | import: import('./src/' + ab), 22 | }, 23 | template: { 24 | require: require(` + "`./src/${ab}`" + `), 25 | import: import(` + "`./src/${ab}`" + `), 26 | }, 27 | }) 28 | `, 29 | }, 30 | entryPaths: []string{"/entry.js"}, 31 | options: config.Options{ 32 | Mode: config.ModeConvertFormat, 33 | OutputFormat: config.FormatCommonJS, 34 | AbsOutputFile: "/out.js", 35 | }, 36 | }) 37 | } 38 | 39 | func TestGlobBasicNoSplitting(t *testing.T) { 40 | glob_suite.expectBundled(t, bundled{ 41 | files: map[string]string{ 42 | "/entry.js": ` 43 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 44 | console.log({ 45 | concat: { 46 | require: require('./src/' + ab), 47 | import: import('./src/' + ab), 48 | }, 49 | template: { 50 | require: require(` + "`./src/${ab}`" + `), 51 | import: import(` + "`./src/${ab}`" + `), 52 | }, 53 | }) 54 | `, 55 | "/src/a.js": `module.exports = 'a'`, 56 | "/src/b.js": `module.exports = 'b'`, 57 | }, 58 | entryPaths: []string{"/entry.js"}, 59 | options: config.Options{ 60 | Mode: config.ModeBundle, 61 | AbsOutputFile: "/out.js", 62 | }, 63 | }) 64 | } 65 | 66 | func TestTSGlobBasicNoSplitting(t *testing.T) { 67 | glob_suite.expectBundled(t, bundled{ 68 | files: map[string]string{ 69 | "/entry.ts": ` 70 | const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts' 71 | console.log({ 72 | concat: { 73 | require: require('./src/' + ab), 74 | import: import('./src/' + ab), 75 | }, 76 | template: { 77 | require: require(` + "`./src/${ab}`" + `), 78 | import: import(` + "`./src/${ab}`" + `), 79 | }, 80 | }) 81 | `, 82 | "/src/a.ts": `module.exports = 'a'`, 83 | "/src/b.ts": `module.exports = 'b'`, 84 | }, 85 | entryPaths: []string{"/entry.ts"}, 86 | options: config.Options{ 87 | Mode: config.ModeBundle, 88 | AbsOutputFile: "/out.js", 89 | }, 90 | }) 91 | } 92 | 93 | func TestGlobBasicSplitting(t *testing.T) { 94 | glob_suite.expectBundled(t, bundled{ 95 | files: map[string]string{ 96 | "/entry.js": ` 97 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 98 | console.log({ 99 | concat: { 100 | require: require('./src/' + ab), 101 | import: import('./src/' + ab), 102 | }, 103 | template: { 104 | require: require(` + "`./src/${ab}`" + `), 105 | import: import(` + "`./src/${ab}`" + `), 106 | }, 107 | }) 108 | `, 109 | "/src/a.js": `module.exports = 'a'`, 110 | "/src/b.js": `module.exports = 'b'`, 111 | }, 112 | entryPaths: []string{"/entry.js"}, 113 | options: config.Options{ 114 | Mode: config.ModeBundle, 115 | AbsOutputDir: "/out", 116 | CodeSplitting: true, 117 | }, 118 | }) 119 | } 120 | 121 | func TestTSGlobBasicSplitting(t *testing.T) { 122 | glob_suite.expectBundled(t, bundled{ 123 | files: map[string]string{ 124 | "/entry.ts": ` 125 | const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts' 126 | console.log({ 127 | concat: { 128 | require: require('./src/' + ab), 129 | import: import('./src/' + ab), 130 | }, 131 | template: { 132 | require: require(` + "`./src/${ab}`" + `), 133 | import: import(` + "`./src/${ab}`" + `), 134 | }, 135 | }) 136 | `, 137 | "/src/a.ts": `module.exports = 'a'`, 138 | "/src/b.ts": `module.exports = 'b'`, 139 | }, 140 | entryPaths: []string{"/entry.ts"}, 141 | options: config.Options{ 142 | Mode: config.ModeBundle, 143 | AbsOutputDir: "/out", 144 | CodeSplitting: true, 145 | }, 146 | }) 147 | } 148 | 149 | func TestGlobDirDoesNotExist(t *testing.T) { 150 | glob_suite.expectBundled(t, bundled{ 151 | files: map[string]string{ 152 | "/entry.js": ` 153 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 154 | console.log({ 155 | concat: { 156 | require: require('./src/' + ab), 157 | import: import('./src/' + ab), 158 | }, 159 | template: { 160 | require: require(` + "`./src/${ab}`" + `), 161 | import: import(` + "`./src/${ab}`" + `), 162 | }, 163 | }) 164 | `, 165 | }, 166 | entryPaths: []string{"/entry.js"}, 167 | options: config.Options{ 168 | Mode: config.ModeBundle, 169 | AbsOutputDir: "/out", 170 | CodeSplitting: true, 171 | }, 172 | expectedScanLog: `entry.js: ERROR: Could not resolve require("./src/**/*") 173 | entry.js: ERROR: Could not resolve import("./src/**/*") 174 | `, 175 | }) 176 | } 177 | 178 | func TestGlobNoMatches(t *testing.T) { 179 | glob_suite.expectBundled(t, bundled{ 180 | files: map[string]string{ 181 | "/entry.js": ` 182 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 183 | console.log({ 184 | concat: { 185 | require: require('./src/' + ab + '.json'), 186 | import: import('./src/' + ab + '.json'), 187 | }, 188 | template: { 189 | require: require(` + "`./src/${ab}.json`" + `), 190 | import: import(` + "`./src/${ab}.json`" + `), 191 | }, 192 | }) 193 | `, 194 | "/src/dummy.js": ``, 195 | }, 196 | entryPaths: []string{"/entry.js"}, 197 | options: config.Options{ 198 | Mode: config.ModeBundle, 199 | AbsOutputDir: "/out", 200 | CodeSplitting: true, 201 | }, 202 | expectedScanLog: `entry.js: WARNING: The glob pattern require("./src/**/*.json") did not match any files 203 | entry.js: WARNING: The glob pattern import("./src/**/*.json") did not match any files 204 | `, 205 | }) 206 | } 207 | 208 | func TestGlobEntryPointAbsPath(t *testing.T) { 209 | glob_suite.expectBundled(t, bundled{ 210 | files: map[string]string{ 211 | "/Users/user/project/src/entry.js": ` 212 | works = true 213 | `, 214 | }, 215 | entryPaths: []string{"/Users/user/project/**/*.js"}, 216 | options: config.Options{ 217 | Mode: config.ModeBundle, 218 | AbsOutputDir: "/out", 219 | }, 220 | }) 221 | } 222 | 223 | func TestGlobWildcardSlash(t *testing.T) { 224 | glob_suite.expectBundled(t, bundled{ 225 | files: map[string]string{ 226 | "/entry.js": ` 227 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 228 | console.log({ 229 | concat: { 230 | require: require('./src/' + ab + '.js'), 231 | import: import('./src/' + ab + '.js'), 232 | }, 233 | template: { 234 | require: require(` + "`./src/${ab}.js`" + `), 235 | import: import(` + "`./src/${ab}.js`" + `), 236 | }, 237 | }) 238 | `, 239 | 240 | "/src/file-a.js": `module.exports = 'a'`, 241 | "/src/file-b.js": `module.exports = 'b'`, 242 | "/src/file-a.js.map": `DO NOT BUNDLE`, 243 | "/src/file-b.js.map": `DO NOT BUNDLE`, 244 | 245 | "/src/nested/dir/file-a.js": `module.exports = 'a'`, 246 | "/src/nested/dir/file-b.js": `module.exports = 'b'`, 247 | "/src/nested/dir/file-a.js.map": `DO NOT BUNDLE`, 248 | "/src/nested/dir/file-b.js.map": `DO NOT BUNDLE`, 249 | }, 250 | entryPaths: []string{"/entry.js"}, 251 | options: config.Options{ 252 | Mode: config.ModeBundle, 253 | AbsOutputFile: "/out.js", 254 | }, 255 | }) 256 | } 257 | 258 | func TestGlobWildcardNoSlash(t *testing.T) { 259 | glob_suite.expectBundled(t, bundled{ 260 | files: map[string]string{ 261 | "/entry.js": ` 262 | const ab = Math.random() < 0.5 ? 'a.js' : 'b.js' 263 | console.log({ 264 | concat: { 265 | require: require('./src/file-' + ab + '.js'), 266 | import: import('./src/file-' + ab + '.js'), 267 | }, 268 | template: { 269 | require: require(` + "`./src/file-${ab}.js`" + `), 270 | import: import(` + "`./src/file-${ab}.js`" + `), 271 | }, 272 | }) 273 | `, 274 | 275 | "/src/file-a.js": `module.exports = 'a'`, 276 | "/src/file-b.js": `module.exports = 'b'`, 277 | "/src/file-a.js.map": `DO NOT BUNDLE`, 278 | "/src/file-b.js.map": `DO NOT BUNDLE`, 279 | 280 | "/src/nested/dir/file-a.js": `DO NOT BUNDLE`, 281 | "/src/nested/dir/file-b.js": `DO NOT BUNDLE`, 282 | "/src/nested/dir/file-a.js.map": `DO NOT BUNDLE`, 283 | "/src/nested/dir/file-b.js.map": `DO NOT BUNDLE`, 284 | }, 285 | entryPaths: []string{"/entry.js"}, 286 | options: config.Options{ 287 | Mode: config.ModeBundle, 288 | AbsOutputFile: "/out.js", 289 | }, 290 | }) 291 | } 292 | -------------------------------------------------------------------------------- /bundler_tests/snapshots/snapshots_importstar_ts.txt: -------------------------------------------------------------------------------- 1 | TestTSImportStarAndCommonJS 2 | ---------- /out.js ---------- 3 | // foo.ts 4 | var foo_exports = {}; 5 | __export(foo_exports, { 6 | foo: () => foo 7 | }); 8 | var foo; 9 | var init_foo = __esm({ 10 | "foo.ts"() { 11 | foo = 123; 12 | } 13 | }); 14 | 15 | // entry.js 16 | init_foo(); 17 | var ns2 = (init_foo(), __toCommonJS(foo_exports)); 18 | console.log(foo, ns2.foo); 19 | 20 | ================================================================================ 21 | TestTSImportStarCapture 22 | ---------- /out.js ---------- 23 | // foo.ts 24 | var foo_exports = {}; 25 | __export(foo_exports, { 26 | foo: () => foo 27 | }); 28 | var foo = 123; 29 | 30 | // entry.ts 31 | var foo2 = 234; 32 | console.log(foo_exports, foo, foo2); 33 | 34 | ================================================================================ 35 | TestTSImportStarCommonJSCapture 36 | ---------- /out.js ---------- 37 | // foo.ts 38 | var require_foo = __commonJS({ 39 | "foo.ts"(exports) { 40 | exports.foo = 123; 41 | } 42 | }); 43 | 44 | // entry.ts 45 | var ns = __toESM(require_foo()); 46 | var foo2 = 234; 47 | console.log(ns, ns.foo, foo2); 48 | 49 | ================================================================================ 50 | TestTSImportStarCommonJSNoCapture 51 | ---------- /out.js ---------- 52 | // foo.ts 53 | var require_foo = __commonJS({ 54 | "foo.ts"(exports) { 55 | exports.foo = 123; 56 | } 57 | }); 58 | 59 | // entry.ts 60 | var ns = __toESM(require_foo()); 61 | var foo2 = 234; 62 | console.log(ns.foo, ns.foo, foo2); 63 | 64 | ================================================================================ 65 | TestTSImportStarCommonJSUnused 66 | ---------- /out.js ---------- 67 | // entry.ts 68 | var foo = 234; 69 | console.log(foo); 70 | 71 | ================================================================================ 72 | TestTSImportStarExportImportStarCapture 73 | ---------- /out.js ---------- 74 | // foo.ts 75 | var foo_exports = {}; 76 | __export(foo_exports, { 77 | foo: () => foo 78 | }); 79 | var foo = 123; 80 | 81 | // entry.ts 82 | var foo2 = 234; 83 | console.log(foo_exports, foo_exports.foo, foo2); 84 | 85 | ================================================================================ 86 | TestTSImportStarExportImportStarNoCapture 87 | ---------- /out.js ---------- 88 | // foo.ts 89 | var foo_exports = {}; 90 | __export(foo_exports, { 91 | foo: () => foo 92 | }); 93 | var foo = 123; 94 | 95 | // entry.ts 96 | var foo2 = 234; 97 | console.log(foo_exports.foo, foo_exports.foo, foo2); 98 | 99 | ================================================================================ 100 | TestTSImportStarExportImportStarUnused 101 | ---------- /out.js ---------- 102 | // entry.ts 103 | var foo = 234; 104 | console.log(foo); 105 | 106 | ================================================================================ 107 | TestTSImportStarExportStarAsCapture 108 | ---------- /out.js ---------- 109 | // foo.ts 110 | var foo_exports = {}; 111 | __export(foo_exports, { 112 | foo: () => foo 113 | }); 114 | var foo = 123; 115 | 116 | // entry.ts 117 | var foo2 = 234; 118 | console.log(foo_exports, foo_exports.foo, foo2); 119 | 120 | ================================================================================ 121 | TestTSImportStarExportStarAsNoCapture 122 | ---------- /out.js ---------- 123 | // foo.ts 124 | var foo_exports = {}; 125 | __export(foo_exports, { 126 | foo: () => foo 127 | }); 128 | var foo = 123; 129 | 130 | // entry.ts 131 | var foo2 = 234; 132 | console.log(foo_exports.foo, foo_exports.foo, foo2); 133 | 134 | ================================================================================ 135 | TestTSImportStarExportStarAsUnused 136 | ---------- /out.js ---------- 137 | // entry.ts 138 | var foo = 234; 139 | console.log(foo); 140 | 141 | ================================================================================ 142 | TestTSImportStarExportStarCapture 143 | ---------- /out.js ---------- 144 | // bar.ts 145 | var bar_exports = {}; 146 | __export(bar_exports, { 147 | foo: () => foo 148 | }); 149 | 150 | // foo.ts 151 | var foo = 123; 152 | 153 | // entry.ts 154 | var foo2 = 234; 155 | console.log(bar_exports, foo, foo2); 156 | 157 | ================================================================================ 158 | TestTSImportStarExportStarNoCapture 159 | ---------- /out.js ---------- 160 | // foo.ts 161 | var foo = 123; 162 | 163 | // entry.ts 164 | var foo2 = 234; 165 | console.log(foo, foo, foo2); 166 | 167 | ================================================================================ 168 | TestTSImportStarExportStarUnused 169 | ---------- /out.js ---------- 170 | // entry.ts 171 | var foo = 234; 172 | console.log(foo); 173 | 174 | ================================================================================ 175 | TestTSImportStarMangleNoBundleCapture 176 | ---------- /out.js ---------- 177 | import * as ns from "./foo"; 178 | let foo = 234; 179 | console.log(ns, ns.foo, foo); 180 | 181 | ================================================================================ 182 | TestTSImportStarMangleNoBundleNoCapture 183 | ---------- /out.js ---------- 184 | import * as ns from "./foo"; 185 | let foo = 234; 186 | console.log(ns.foo, ns.foo, foo); 187 | 188 | ================================================================================ 189 | TestTSImportStarMangleNoBundleUnused 190 | ---------- /out.js ---------- 191 | let foo = 234; 192 | console.log(foo); 193 | 194 | ================================================================================ 195 | TestTSImportStarNoBundleCapture 196 | ---------- /out.js ---------- 197 | import * as ns from "./foo"; 198 | let foo = 234; 199 | console.log(ns, ns.foo, foo); 200 | 201 | ================================================================================ 202 | TestTSImportStarNoBundleNoCapture 203 | ---------- /out.js ---------- 204 | import * as ns from "./foo"; 205 | let foo = 234; 206 | console.log(ns.foo, ns.foo, foo); 207 | 208 | ================================================================================ 209 | TestTSImportStarNoBundleUnused 210 | ---------- /out.js ---------- 211 | let foo = 234; 212 | console.log(foo); 213 | 214 | ================================================================================ 215 | TestTSImportStarNoCapture 216 | ---------- /out.js ---------- 217 | // foo.ts 218 | var foo = 123; 219 | 220 | // entry.ts 221 | var foo2 = 234; 222 | console.log(foo, foo, foo2); 223 | 224 | ================================================================================ 225 | TestTSImportStarUnused 226 | ---------- /out.js ---------- 227 | // entry.ts 228 | var foo = 234; 229 | console.log(foo); 230 | 231 | ================================================================================ 232 | TestTSReExportTypeOnlyFileES6 233 | ---------- /out.js ---------- 234 | // types1.ts 235 | console.log("some code"); 236 | 237 | // types2.ts 238 | console.log("some code"); 239 | 240 | // types3.ts 241 | console.log("some code"); 242 | 243 | // values.ts 244 | var foo = 123; 245 | 246 | // entry.ts 247 | console.log(foo); 248 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ije/esbuild-internal/logger" 7 | "github.com/ije/esbuild-internal/runtime" 8 | ) 9 | 10 | // This is a cache of the parsed contents of a set of files. The idea is to be 11 | // able to reuse the results of parsing between builds and make subsequent 12 | // builds faster by avoiding redundant parsing work. This only works if: 13 | // 14 | // - The AST information in the cache must be considered immutable. There is 15 | // no way to enforce this in Go, but please be disciplined about this. The 16 | // ASTs are shared in between builds. Any information that must be mutated 17 | // in the AST during a build must be done on a shallow clone of the data if 18 | // the mutation happens after parsing (i.e. a clone that clones everything 19 | // that will be mutated and shares only the parts that won't be mutated). 20 | // 21 | // - The information in the cache must not depend at all on the contents of 22 | // any file other than the file being cached. Invalidating an entry in the 23 | // cache does not also invalidate any entries that depend on that file, so 24 | // caching information that depends on other files can result in incorrect 25 | // results due to reusing stale data. For example, do not "bake in" some 26 | // value imported from another file. 27 | // 28 | // - Cached ASTs must only be reused if the parsing options are identical 29 | // between builds. For example, it would be bad if the AST parser depended 30 | // on options inherited from a nearby "package.json" file but those options 31 | // were not part of the cache key. Then the cached AST could incorrectly be 32 | // reused even if the contents of that "package.json" file have changed. 33 | type CacheSet struct { 34 | FSCache FSCache 35 | CSSCache CSSCache 36 | JSONCache JSONCache 37 | JSCache JSCache 38 | SourceIndexCache SourceIndexCache 39 | } 40 | 41 | func MakeCacheSet() *CacheSet { 42 | return &CacheSet{ 43 | SourceIndexCache: SourceIndexCache{ 44 | globEntries: make(map[uint64]uint32), 45 | entries: make(map[sourceIndexKey]uint32), 46 | nextSourceIndex: runtime.SourceIndex + 1, 47 | }, 48 | FSCache: FSCache{ 49 | entries: make(map[string]*fsEntry), 50 | }, 51 | CSSCache: CSSCache{ 52 | entries: make(map[logger.Path]*cssCacheEntry), 53 | }, 54 | JSONCache: JSONCache{ 55 | entries: make(map[logger.Path]*jsonCacheEntry), 56 | }, 57 | JSCache: JSCache{ 58 | entries: make(map[logger.Path]*jsCacheEntry), 59 | }, 60 | } 61 | } 62 | 63 | type SourceIndexCache struct { 64 | globEntries map[uint64]uint32 65 | entries map[sourceIndexKey]uint32 66 | mutex sync.Mutex 67 | nextSourceIndex uint32 68 | } 69 | 70 | type SourceIndexKind uint8 71 | 72 | const ( 73 | SourceIndexNormal SourceIndexKind = iota 74 | SourceIndexJSStubForCSS 75 | ) 76 | 77 | type sourceIndexKey struct { 78 | path logger.Path 79 | kind SourceIndexKind 80 | } 81 | 82 | func (c *SourceIndexCache) LenHint() uint32 { 83 | c.mutex.Lock() 84 | defer c.mutex.Unlock() 85 | 86 | // Add some extra room at the end for a new file or two without reallocating 87 | const someExtraRoom = 16 88 | return c.nextSourceIndex + someExtraRoom 89 | } 90 | 91 | func (c *SourceIndexCache) Get(path logger.Path, kind SourceIndexKind) uint32 { 92 | key := sourceIndexKey{path: path, kind: kind} 93 | c.mutex.Lock() 94 | defer c.mutex.Unlock() 95 | if sourceIndex, ok := c.entries[key]; ok { 96 | return sourceIndex 97 | } 98 | sourceIndex := c.nextSourceIndex 99 | c.nextSourceIndex++ 100 | c.entries[key] = sourceIndex 101 | return sourceIndex 102 | } 103 | 104 | func (c *SourceIndexCache) GetGlob(parentSourceIndex uint32, globIndex uint32) uint32 { 105 | key := (uint64(parentSourceIndex) << 32) | uint64(globIndex) 106 | c.mutex.Lock() 107 | defer c.mutex.Unlock() 108 | if sourceIndex, ok := c.globEntries[key]; ok { 109 | return sourceIndex 110 | } 111 | sourceIndex := c.nextSourceIndex 112 | c.nextSourceIndex++ 113 | c.globEntries[key] = sourceIndex 114 | return sourceIndex 115 | } 116 | -------------------------------------------------------------------------------- /cache/cache_ast.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ije/esbuild-internal/css_ast" 7 | "github.com/ije/esbuild-internal/css_parser" 8 | "github.com/ije/esbuild-internal/js_ast" 9 | "github.com/ije/esbuild-internal/js_parser" 10 | "github.com/ije/esbuild-internal/logger" 11 | ) 12 | 13 | // This cache intends to avoid unnecessarily re-parsing files in subsequent 14 | // builds. For a given path, parsing can be avoided if the contents of the file 15 | // and the options for the parser are the same as last time. Even if the 16 | // contents of the file are the same, the options for the parser may have 17 | // changed if they depend on some other file ("package.json" for example). 18 | // 19 | // This cache checks if the file contents have changed even though we have 20 | // the ability to detect if a file has changed on the file system by reading 21 | // its metadata. First of all, if the file contents are cached then they should 22 | // be the same pointer, which makes the comparison trivial. Also we want to 23 | // cache the AST for plugins in the common case that the plugin output stays 24 | // the same. 25 | 26 | //////////////////////////////////////////////////////////////////////////////// 27 | // CSS 28 | 29 | type CSSCache struct { 30 | entries map[logger.Path]*cssCacheEntry 31 | mutex sync.Mutex 32 | } 33 | 34 | type cssCacheEntry struct { 35 | source logger.Source 36 | msgs []logger.Msg 37 | ast css_ast.AST 38 | options css_parser.Options 39 | } 40 | 41 | func (c *CSSCache) Parse(log logger.Log, source logger.Source, options css_parser.Options) css_ast.AST { 42 | // Check the cache 43 | entry := func() *cssCacheEntry { 44 | c.mutex.Lock() 45 | defer c.mutex.Unlock() 46 | return c.entries[source.KeyPath] 47 | }() 48 | 49 | // Cache hit 50 | if entry != nil && entry.source == source && entry.options.Equal(&options) { 51 | for _, msg := range entry.msgs { 52 | log.AddMsg(msg) 53 | } 54 | return entry.ast 55 | } 56 | 57 | // Cache miss 58 | tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides) 59 | ast := css_parser.Parse(tempLog, source, options) 60 | msgs := tempLog.Done() 61 | for _, msg := range msgs { 62 | log.AddMsg(msg) 63 | } 64 | 65 | // Create the cache entry 66 | entry = &cssCacheEntry{ 67 | source: source, 68 | options: options, 69 | ast: ast, 70 | msgs: msgs, 71 | } 72 | 73 | // Save for next time 74 | c.mutex.Lock() 75 | defer c.mutex.Unlock() 76 | c.entries[source.KeyPath] = entry 77 | return ast 78 | } 79 | 80 | //////////////////////////////////////////////////////////////////////////////// 81 | // JSON 82 | 83 | type JSONCache struct { 84 | entries map[logger.Path]*jsonCacheEntry 85 | mutex sync.Mutex 86 | } 87 | 88 | type jsonCacheEntry struct { 89 | expr js_ast.Expr 90 | msgs []logger.Msg 91 | source logger.Source 92 | options js_parser.JSONOptions 93 | ok bool 94 | } 95 | 96 | func (c *JSONCache) Parse(log logger.Log, source logger.Source, options js_parser.JSONOptions) (js_ast.Expr, bool) { 97 | // Check the cache 98 | entry := func() *jsonCacheEntry { 99 | c.mutex.Lock() 100 | defer c.mutex.Unlock() 101 | return c.entries[source.KeyPath] 102 | }() 103 | 104 | // Cache hit 105 | if entry != nil && entry.source == source && entry.options == options { 106 | for _, msg := range entry.msgs { 107 | log.AddMsg(msg) 108 | } 109 | return entry.expr, entry.ok 110 | } 111 | 112 | // Cache miss 113 | tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides) 114 | expr, ok := js_parser.ParseJSON(tempLog, source, options) 115 | msgs := tempLog.Done() 116 | for _, msg := range msgs { 117 | log.AddMsg(msg) 118 | } 119 | 120 | // Create the cache entry 121 | entry = &jsonCacheEntry{ 122 | source: source, 123 | options: options, 124 | expr: expr, 125 | ok: ok, 126 | msgs: msgs, 127 | } 128 | 129 | // Save for next time 130 | c.mutex.Lock() 131 | defer c.mutex.Unlock() 132 | c.entries[source.KeyPath] = entry 133 | return expr, ok 134 | } 135 | 136 | //////////////////////////////////////////////////////////////////////////////// 137 | // JS 138 | 139 | type JSCache struct { 140 | entries map[logger.Path]*jsCacheEntry 141 | mutex sync.Mutex 142 | } 143 | 144 | type jsCacheEntry struct { 145 | source logger.Source 146 | msgs []logger.Msg 147 | options js_parser.Options 148 | ast js_ast.AST 149 | ok bool 150 | } 151 | 152 | func (c *JSCache) Parse(log logger.Log, source logger.Source, options js_parser.Options) (js_ast.AST, bool) { 153 | // Check the cache 154 | entry := func() *jsCacheEntry { 155 | c.mutex.Lock() 156 | defer c.mutex.Unlock() 157 | return c.entries[source.KeyPath] 158 | }() 159 | 160 | // Cache hit 161 | if entry != nil && entry.source == source && entry.options.Equal(&options) { 162 | for _, msg := range entry.msgs { 163 | log.AddMsg(msg) 164 | } 165 | return entry.ast, entry.ok 166 | } 167 | 168 | // Cache miss 169 | tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides) 170 | ast, ok := js_parser.Parse(tempLog, source, options) 171 | msgs := tempLog.Done() 172 | for _, msg := range msgs { 173 | log.AddMsg(msg) 174 | } 175 | 176 | // Create the cache entry 177 | entry = &jsCacheEntry{ 178 | source: source, 179 | options: options, 180 | ast: ast, 181 | ok: ok, 182 | msgs: msgs, 183 | } 184 | 185 | // Save for next time 186 | c.mutex.Lock() 187 | defer c.mutex.Unlock() 188 | c.entries[source.KeyPath] = entry 189 | return ast, ok 190 | } 191 | -------------------------------------------------------------------------------- /cache/cache_fs.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ije/esbuild-internal/fs" 7 | ) 8 | 9 | // This cache uses information from the "stat" syscall to try to avoid re- 10 | // reading files from the file system during subsequent builds if the file 11 | // hasn't changed. The assumption is reading the file metadata is faster than 12 | // reading the file contents. 13 | 14 | type FSCache struct { 15 | entries map[string]*fsEntry 16 | mutex sync.Mutex 17 | } 18 | 19 | type fsEntry struct { 20 | contents string 21 | modKey fs.ModKey 22 | isModKeyUsable bool 23 | } 24 | 25 | func (c *FSCache) ReadFile(fs fs.FS, path string) (contents string, canonicalError error, originalError error) { 26 | entry := func() *fsEntry { 27 | c.mutex.Lock() 28 | defer c.mutex.Unlock() 29 | return c.entries[path] 30 | }() 31 | 32 | // If the file's modification key hasn't changed since it was cached, assume 33 | // the contents of the file are also the same and skip reading the file. 34 | modKey, modKeyErr := fs.ModKey(path) 35 | if entry != nil && entry.isModKeyUsable && modKeyErr == nil && entry.modKey == modKey { 36 | return entry.contents, nil, nil 37 | } 38 | 39 | contents, err, originalError := fs.ReadFile(path) 40 | if err != nil { 41 | return "", err, originalError 42 | } 43 | 44 | c.mutex.Lock() 45 | defer c.mutex.Unlock() 46 | c.entries[path] = &fsEntry{ 47 | contents: contents, 48 | modKey: modKey, 49 | isModKeyUsable: modKeyErr == nil, 50 | } 51 | return contents, nil, nil 52 | } 53 | -------------------------------------------------------------------------------- /compat/compat.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/ije/esbuild-internal/ast" 8 | ) 9 | 10 | type v struct { 11 | major uint16 12 | minor uint8 13 | patch uint8 14 | } 15 | 16 | type Semver struct { 17 | // "1.2.3-alpha" => { Parts: {1, 2, 3}, PreRelease: "-alpha" } 18 | Parts []int 19 | PreRelease string 20 | } 21 | 22 | func (v Semver) String() string { 23 | b := strings.Builder{} 24 | for _, part := range v.Parts { 25 | if b.Len() > 0 { 26 | b.WriteRune('.') 27 | } 28 | b.WriteString(strconv.Itoa(part)) 29 | } 30 | b.WriteString(v.PreRelease) 31 | return b.String() 32 | } 33 | 34 | // Returns <0 if "a < b" 35 | // Returns 0 if "a == b" 36 | // Returns >0 if "a > b" 37 | func compareVersions(a v, b Semver) int { 38 | diff := int(a.major) 39 | if len(b.Parts) > 0 { 40 | diff -= b.Parts[0] 41 | } 42 | if diff == 0 { 43 | diff = int(a.minor) 44 | if len(b.Parts) > 1 { 45 | diff -= b.Parts[1] 46 | } 47 | } 48 | if diff == 0 { 49 | diff = int(a.patch) 50 | if len(b.Parts) > 2 { 51 | diff -= b.Parts[2] 52 | } 53 | } 54 | if diff == 0 && len(b.PreRelease) != 0 { 55 | return 1 // "1.0.0" > "1.0.0-alpha" 56 | } 57 | return diff 58 | } 59 | 60 | // The start is inclusive and the end is exclusive 61 | type versionRange struct { 62 | start v 63 | end v // Use 0.0.0 for "no end" 64 | } 65 | 66 | func isVersionSupported(ranges []versionRange, version Semver) bool { 67 | for _, r := range ranges { 68 | if compareVersions(r.start, version) <= 0 && (r.end == (v{}) || compareVersions(r.end, version) > 0) { 69 | return true 70 | } 71 | } 72 | return false 73 | } 74 | 75 | func SymbolFeature(kind ast.SymbolKind) JSFeature { 76 | switch kind { 77 | case ast.SymbolPrivateField: 78 | return ClassPrivateField 79 | case ast.SymbolPrivateMethod: 80 | return ClassPrivateMethod 81 | case ast.SymbolPrivateGet, ast.SymbolPrivateSet, ast.SymbolPrivateGetSetPair: 82 | return ClassPrivateAccessor 83 | case ast.SymbolPrivateStaticField: 84 | return ClassPrivateStaticField 85 | case ast.SymbolPrivateStaticMethod: 86 | return ClassPrivateStaticMethod 87 | case ast.SymbolPrivateStaticGet, ast.SymbolPrivateStaticSet, ast.SymbolPrivateStaticGetSetPair: 88 | return ClassPrivateStaticAccessor 89 | default: 90 | return 0 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /compat/compat_test.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/ije/esbuild-internal/test" 8 | ) 9 | 10 | func TestCompareVersions(t *testing.T) { 11 | t.Helper() 12 | 13 | check := func(a v, b Semver, expected rune) { 14 | t.Helper() 15 | 16 | at := fmt.Sprintf("%d.%d.%d", a.major, a.minor, a.patch) 17 | bt := b.String() 18 | 19 | t.Run(fmt.Sprintf("%q ? %q", at, bt), func(t *testing.T) { 20 | observed := '=' 21 | if result := compareVersions(a, b); result < 0 { 22 | observed = '<' 23 | } else if result > 0 { 24 | observed = '>' 25 | } 26 | if observed != expected { 27 | test.AssertEqual(t, fmt.Sprintf("%c", observed), fmt.Sprintf("%c", expected)) 28 | } 29 | }) 30 | } 31 | 32 | check(v{0, 0, 0}, Semver{}, '=') 33 | 34 | check(v{1, 0, 0}, Semver{}, '>') 35 | check(v{0, 1, 0}, Semver{}, '>') 36 | check(v{0, 0, 1}, Semver{}, '>') 37 | 38 | check(v{0, 0, 0}, Semver{Parts: []int{1}}, '<') 39 | check(v{0, 0, 0}, Semver{Parts: []int{0, 1}}, '<') 40 | check(v{0, 0, 0}, Semver{Parts: []int{0, 0, 1}}, '<') 41 | 42 | check(v{0, 4, 0}, Semver{Parts: []int{0, 5, 0}}, '<') 43 | check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=') 44 | check(v{0, 6, 0}, Semver{Parts: []int{0, 5, 0}}, '>') 45 | 46 | check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 1}}, '<') 47 | check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=') 48 | check(v{0, 5, 1}, Semver{Parts: []int{0, 5, 0}}, '>') 49 | 50 | check(v{0, 5, 0}, Semver{Parts: []int{0, 5}}, '=') 51 | check(v{0, 5, 1}, Semver{Parts: []int{0, 5}}, '>') 52 | 53 | check(v{1, 0, 0}, Semver{Parts: []int{1}}, '=') 54 | check(v{1, 1, 0}, Semver{Parts: []int{1}}, '>') 55 | check(v{1, 0, 1}, Semver{Parts: []int{1}}, '>') 56 | 57 | check(v{1, 2, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>') 58 | check(v{1, 2, 1}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>') 59 | check(v{1, 1, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '<') 60 | 61 | check(v{1, 2, 3}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '>') 62 | check(v{1, 2, 2}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '<') 63 | } 64 | -------------------------------------------------------------------------------- /css_lexer/css_lexer_test.go: -------------------------------------------------------------------------------- 1 | package css_lexer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ije/esbuild-internal/logger" 7 | "github.com/ije/esbuild-internal/test" 8 | ) 9 | 10 | func lexToken(contents string) (T, string) { 11 | log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug, nil) 12 | result := Tokenize(log, test.SourceForTest(contents), Options{}) 13 | if len(result.Tokens) > 0 { 14 | t := result.Tokens[0] 15 | return t.Kind, t.DecodedText(contents) 16 | } 17 | return TEndOfFile, "" 18 | } 19 | 20 | func lexerError(contents string) string { 21 | log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug, nil) 22 | Tokenize(log, test.SourceForTest(contents), Options{}) 23 | text := "" 24 | for _, msg := range log.Done() { 25 | text += msg.String(logger.OutputOptions{}, logger.TerminalInfo{}) 26 | } 27 | return text 28 | } 29 | 30 | func TestTokens(t *testing.T) { 31 | expected := []struct { 32 | contents string 33 | text string 34 | token T 35 | }{ 36 | {"", "end of file", TEndOfFile}, 37 | {"@media", "@-keyword", TAtKeyword}, 38 | {"url(x y", "bad URL token", TBadURL}, 39 | {"-->", "\"-->\"", TCDC}, 40 | {"