├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── cmd └── esbuild │ ├── main.go │ ├── main_other.go │ ├── main_wasm.go │ ├── service.go │ ├── stdio_protocol.go │ └── version.go ├── docs └── architecture.md ├── go.mod ├── go.sum ├── images ├── benchmark.svg ├── build-pipeline.png ├── code-splitting-1.png ├── code-splitting-2.png ├── logo.png ├── logo.svg ├── tree-shaking.png └── wordmark.svg ├── internal ├── api_helpers │ └── use_timer.go ├── ast │ └── ast.go ├── bundler │ ├── bundler.go │ ├── bundler_css_test.go │ ├── bundler_dce_test.go │ ├── bundler_default_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 │ ├── debug.go │ ├── linker.go │ └── snapshots │ │ ├── snapshots_css.txt │ │ ├── snapshots_dce.txt │ │ ├── snapshots_default.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 ├── cli_helpers │ └── cli_helpers.go ├── compat │ ├── 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_decls.go │ ├── css_decls_border_radius.go │ ├── css_decls_box.go │ ├── css_decls_box_shadow.go │ ├── css_decls_color.go │ ├── css_decls_transform.go │ ├── css_parser.go │ ├── css_parser_selector.go │ └── css_parser_test.go ├── css_printer │ ├── css_printer.go │ └── css_printer_test.go ├── fs │ ├── filepath.go │ ├── fs.go │ ├── fs_mock.go │ ├── fs_mock_test.go │ ├── fs_real.go │ ├── iswin_other.go │ ├── iswin_wasm.go │ ├── iswin_windows.go │ ├── modkey_other.go │ └── modkey_unix.go ├── graph │ ├── graph.go │ ├── input.go │ └── meta.go ├── helpers │ ├── bitset.go │ ├── hash.go │ ├── joiner.go │ ├── mime.go │ ├── path.go │ └── timer.go ├── js_ast │ ├── js_ast.go │ └── js_ast_test.go ├── js_lexer │ ├── js_lexer.go │ ├── js_lexer_test.go │ ├── tables.go │ └── unicode.go ├── js_parser │ ├── global_name_parser.go │ ├── js_parser.go │ ├── js_parser_lower.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 ├── logger │ ├── logger.go │ ├── logger_darwin.go │ ├── logger_linux.go │ ├── logger_other.go │ └── logger_windows.go ├── renamer │ └── renamer.go ├── resolver │ ├── dataurl.go │ ├── package_json.go │ ├── resolver.go │ └── tsconfig_json.go ├── runtime │ └── runtime.go ├── sourcemap │ └── sourcemap.go ├── test │ ├── diff.go │ └── util.go └── xxhash │ ├── LICENSE.txt │ ├── README.md │ ├── xxhash.go │ └── xxhash_other.go ├── lib ├── README.md ├── deno │ ├── external.d.ts │ ├── lib.deno.ns.d.ts │ └── mod.ts ├── npm │ ├── browser.ts │ ├── exit0 │ │ ├── darwin-x64-LE.node │ │ ├── linux-x64-LE.node │ │ └── win32-x64-LE.node │ ├── install.ts │ ├── node.ts │ └── worker.ts ├── package-lock.json ├── package.json ├── shared │ ├── common.ts │ ├── stdio_protocol.ts │ └── types.ts └── tsconfig.json ├── npm ├── esbuild-android-arm64 │ ├── README.md │ └── package.json ├── esbuild-darwin-64 │ ├── README.md │ └── package.json ├── esbuild-darwin-arm64 │ ├── README.md │ └── package.json ├── esbuild-freebsd-64 │ ├── README.md │ └── package.json ├── esbuild-freebsd-arm64 │ ├── README.md │ └── package.json ├── esbuild-linux-32 │ ├── README.md │ └── package.json ├── esbuild-linux-64 │ ├── README.md │ └── package.json ├── esbuild-linux-arm │ ├── README.md │ └── package.json ├── esbuild-linux-arm64 │ ├── README.md │ └── package.json ├── esbuild-linux-mips64le │ ├── README.md │ └── package.json ├── esbuild-linux-ppc64le │ ├── README.md │ └── package.json ├── esbuild-openbsd-64 │ ├── README.md │ └── package.json ├── esbuild-sunos-64 │ ├── README.md │ └── package.json ├── esbuild-wasm │ ├── README.md │ ├── bin │ │ └── esbuild │ └── package.json ├── esbuild-windows-32 │ ├── README.md │ ├── bin │ │ └── esbuild │ └── package.json ├── esbuild-windows-64 │ ├── README.md │ ├── bin │ │ └── esbuild │ └── package.json ├── esbuild-windows-arm64 │ ├── README.md │ ├── bin │ │ └── esbuild │ └── package.json └── esbuild │ ├── README.md │ ├── bin │ └── esbuild │ └── package.json ├── pkg ├── api │ ├── api.go │ ├── api_impl.go │ ├── serve_other.go │ └── serve_wasm.go └── cli │ ├── cli.go │ └── cli_impl.go ├── require ├── parcel │ ├── package-lock.json │ └── package.json ├── parcel2 │ ├── package-lock.json │ └── package.json ├── rollup │ ├── package-lock.json │ └── package.json ├── webpack │ ├── package-lock.json │ └── package.json └── webpack5 │ ├── package-lock.json │ └── package.json ├── scripts ├── browser │ ├── browser-tests.js │ ├── package-lock.json │ └── package.json ├── compat-table.js ├── deno-tests.js ├── destructuring-fuzzer.js ├── end-to-end-tests.js ├── es6-fuzzer.js ├── esbuild.js ├── gen-unicode-table.js ├── graph-debugger.html ├── js-api-tests.js ├── node-unref-tests.js ├── package-lock.json ├── package.json ├── parse-ts-files.js ├── plugin-tests.js ├── readmin-package-lock.json ├── register-test.js ├── terser-tests.js ├── test262.js ├── try.html ├── ts-type-tests.js ├── uglify-tests.js ├── verify-source-map.js └── wasm-tests.js └── version.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 2 4 | 5 | [*.{js,ts}] 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ '*' ] 8 | 9 | jobs: 10 | 11 | esbuild: 12 | name: esbuild CI 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | 18 | steps: 19 | 20 | - name: Set up Go 1.x 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: 1.17.1 24 | id: go 25 | 26 | - name: Setup Node.js environment 27 | uses: actions/setup-node@v1.4.4 28 | with: 29 | node-version: 14 30 | 31 | - name: Setup Deno 1.x 32 | uses: denoland/setup-deno@main 33 | with: 34 | deno-version: v1.x 35 | 36 | - name: Check out code into the Go module directory 37 | uses: actions/checkout@v2 38 | 39 | - name: go test 40 | run: go test ./internal/... 41 | 42 | - name: go vet 43 | run: go vet ./cmd/... ./internal/... ./pkg/... 44 | 45 | - name: Deno Tests 46 | # Deno tests currently don't run on Windows because of "esbuild" vs. 47 | # "esbuild.exe" in the test harness. This should be fixed... 48 | if: matrix.os != 'windows-latest' 49 | run: make test-deno 50 | 51 | - name: Test for path/filepath 52 | if: matrix.os == 'ubuntu-latest' 53 | run: make no-filepath 54 | 55 | - name: Make sure "check-go-version" works 56 | if: matrix.os != 'windows-latest' 57 | run: make check-go-version 58 | 59 | - name: go fmt 60 | if: matrix.os == 'macos-latest' 61 | run: make fmt-go 62 | 63 | - name: npm ci 64 | run: cd scripts && npm ci 65 | 66 | - name: Register Test (ESBUILD_WORKER_THREADS=0) 67 | if: matrix.os != 'windows-latest' 68 | run: ESBUILD_WORKER_THREADS=0 node scripts/register-test.js 69 | 70 | - name: Register Test 71 | run: node scripts/register-test.js 72 | 73 | - name: Verify Source Map 74 | run: node scripts/verify-source-map.js 75 | 76 | - name: E2E Tests 77 | run: node scripts/end-to-end-tests.js 78 | 79 | - name: JS API Tests (ESBUILD_WORKER_THREADS=0) 80 | if: matrix.os != 'windows-latest' 81 | run: ESBUILD_WORKER_THREADS=0 node scripts/js-api-tests.js 82 | 83 | - name: JS API Tests 84 | run: node scripts/js-api-tests.js 85 | 86 | - name: NodeJS Unref Tests 87 | run: node scripts/node-unref-tests.js 88 | 89 | - name: Plugin Tests 90 | run: node scripts/plugin-tests.js 91 | 92 | - name: TypeScript Type Definition Tests 93 | if: matrix.os == 'ubuntu-latest' 94 | run: node scripts/ts-type-tests.js 95 | 96 | - name: JS API Type Check 97 | if: matrix.os == 'ubuntu-latest' 98 | run: make lib-typecheck 99 | 100 | - name: WebAssembly API Tests (browser) 101 | if: matrix.os == 'ubuntu-latest' 102 | run: make test-wasm-browser 103 | 104 | - name: WebAssembly API Tests (node) 105 | if: matrix.os == 'ubuntu-latest' 106 | run: make test-wasm-node 107 | 108 | - name: WebAssembly API Tests (node) 109 | if: matrix.os != 'ubuntu-latest' 110 | run: node scripts/wasm-tests.js 111 | 112 | - name: Sucrase Tests 113 | if: matrix.os == 'ubuntu-latest' 114 | run: make test-sucrase 115 | 116 | - name: Esprima Tests 117 | if: matrix.os == 'ubuntu-latest' 118 | run: make test-esprima 119 | 120 | - name: Preact Splitting Tests 121 | if: matrix.os == 'ubuntu-latest' 122 | run: make test-preact-splitting 123 | 124 | - name: Uglify Tests 125 | if: matrix.os == 'ubuntu-latest' 126 | run: make uglify 127 | 128 | - name: Check the unicode table generator 129 | if: matrix.os == 'ubuntu-latest' 130 | run: cd scripts && node gen-unicode-table.js 131 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Extract changelog 15 | run: | 16 | CHANGELOG=$(awk -v ver=$(cat version.txt) '/## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p' CHANGELOG.md) 17 | echo "CHANGELOG<> $GITHUB_ENV 18 | echo "$CHANGELOG" >> $GITHUB_ENV 19 | echo "EOF" >> $GITHUB_ENV 20 | - name: Create GitHub Release 21 | uses: actions/create-release@v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | tag_name: ${{ github.ref }} 26 | release_name: ${{ github.ref }} 27 | body: ${{ env.CHANGELOG }} 28 | draft: false 29 | prerelease: false 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /bench/ 3 | /demo/ 4 | /deno/ 5 | /esbuild 6 | /github/ 7 | /npm/esbuild-wasm/browser.js 8 | /npm/esbuild-wasm/esbuild.wasm 9 | /npm/esbuild-wasm/esm/ 10 | /npm/esbuild-wasm/exit0.js 11 | /npm/esbuild-wasm/lib/ 12 | /npm/esbuild-wasm/wasm_exec.js 13 | /npm/esbuild/install.js 14 | /npm/esbuild/lib/ 15 | /require/*/bench/ 16 | /require/*/demo/ 17 | /scripts/.*/ 18 | bin 19 | esbuild.exe 20 | node_modules/ 21 | -------------------------------------------------------------------------------- /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 |

2 | esbuild: An extremely fast JavaScript bundler 3 |
4 | Website | 5 | Getting started | 6 | Documentation | 7 | Plugins | 8 | FAQ 9 |

10 | 11 | ## Why? 12 | 13 | Our current build tools for the web are 10-100x slower than they could be: 14 | 15 |

16 | Bar chart with benchmark results 17 |

18 | 19 | The main goal of the esbuild bundler project is to bring about a new era of build tool performance, and create an easy-to-use modern bundler along the way. 20 | 21 | Major features: 22 | 23 | * Extreme speed without needing a cache 24 | * ES6 and CommonJS modules 25 | * Tree shaking of ES6 modules 26 | * An [API](https://esbuild.github.io/api/) for JavaScript and Go 27 | * [TypeScript](https://esbuild.github.io/content-types/#typescript) and [JSX](https://esbuild.github.io/content-types/#jsx) syntax 28 | * [Source maps](https://esbuild.github.io/api/#sourcemap) 29 | * [Minification](https://esbuild.github.io/api/#minify) 30 | * [Plugins](https://esbuild.github.io/plugins/) 31 | 32 | Check out the [getting started](https://esbuild.github.io/getting-started/) instructions if you want to give esbuild a try. 33 | -------------------------------------------------------------------------------- /cmd/esbuild/main_other.go: -------------------------------------------------------------------------------- 1 | //go:build !js || !wasm 2 | // +build !js !wasm 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "runtime/pprof" 10 | "runtime/trace" 11 | 12 | "github.com/evanw/esbuild/internal/logger" 13 | ) 14 | 15 | func createTraceFile(osArgs []string, traceFile string) func() { 16 | f, err := os.Create(traceFile) 17 | if err != nil { 18 | logger.PrintErrorToStderr(osArgs, fmt.Sprintf( 19 | "Failed to create trace file: %s", err.Error())) 20 | return nil 21 | } 22 | trace.Start(f) 23 | return func() { 24 | trace.Stop() 25 | f.Close() 26 | } 27 | } 28 | 29 | func createHeapFile(osArgs []string, heapFile string) func() { 30 | f, err := os.Create(heapFile) 31 | if err != nil { 32 | logger.PrintErrorToStderr(osArgs, fmt.Sprintf( 33 | "Failed to create heap file: %s", err.Error())) 34 | return nil 35 | } 36 | return func() { 37 | if err := pprof.WriteHeapProfile(f); err != nil { 38 | logger.PrintErrorToStderr(osArgs, fmt.Sprintf( 39 | "Failed to write heap profile: %s", err.Error())) 40 | } 41 | f.Close() 42 | } 43 | } 44 | 45 | func createCpuprofileFile(osArgs []string, cpuprofileFile string) func() { 46 | f, err := os.Create(cpuprofileFile) 47 | if err != nil { 48 | logger.PrintErrorToStderr(osArgs, fmt.Sprintf( 49 | "Failed to create cpuprofile file: %s", err.Error())) 50 | return nil 51 | } 52 | pprof.StartCPUProfile(f) 53 | return func() { 54 | pprof.StopCPUProfile() 55 | f.Close() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cmd/esbuild/main_wasm.go: -------------------------------------------------------------------------------- 1 | //go:build js && wasm 2 | // +build js,wasm 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/evanw/esbuild/internal/logger" 8 | ) 9 | 10 | // Remove this code from the WebAssembly binary to reduce size. This only removes 0.4mb of stuff. 11 | 12 | func createTraceFile(osArgs []string, traceFile string) func() { 13 | logger.PrintErrorToStderr(osArgs, "The \"--trace\" flag is not supported when using WebAssembly") 14 | return nil 15 | } 16 | 17 | func createHeapFile(osArgs []string, heapFile string) func() { 18 | logger.PrintErrorToStderr(osArgs, "The \"--heap\" flag is not supported when using WebAssembly") 19 | return nil 20 | } 21 | 22 | func createCpuprofileFile(osArgs []string, cpuprofileFile string) func() { 23 | logger.PrintErrorToStderr(osArgs, "The \"--cpuprofile\" flag is not supported when using WebAssembly") 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /cmd/esbuild/stdio_protocol.go: -------------------------------------------------------------------------------- 1 | // The JavaScript API communicates with the Go child process over stdin/stdout 2 | // using this protocol. It's a very simple binary protocol that uses primitives 3 | // and nested arrays and maps. It's basically JSON with UTF-8 encoding and an 4 | // additional byte array primitive. You must send a response after receiving a 5 | // request because the other end is blocking on the response coming back. 6 | 7 | package main 8 | 9 | import ( 10 | "encoding/binary" 11 | "sort" 12 | ) 13 | 14 | func readUint32(bytes []byte) (value uint32, leftOver []byte, ok bool) { 15 | if len(bytes) >= 4 { 16 | return binary.LittleEndian.Uint32(bytes), bytes[4:], true 17 | } 18 | 19 | return 0, bytes, false 20 | } 21 | 22 | func writeUint32(bytes []byte, value uint32) []byte { 23 | bytes = append(bytes, 0, 0, 0, 0) 24 | binary.LittleEndian.PutUint32(bytes[len(bytes)-4:], value) 25 | return bytes 26 | } 27 | 28 | func readLengthPrefixedSlice(bytes []byte) (slice []byte, leftOver []byte, ok bool) { 29 | if length, afterLength, ok := readUint32(bytes); ok && uint(len(afterLength)) >= uint(length) { 30 | return afterLength[:length], afterLength[length:], true 31 | } 32 | 33 | return []byte{}, bytes, false 34 | } 35 | 36 | type packet struct { 37 | id uint32 38 | isRequest bool 39 | value interface{} 40 | } 41 | 42 | func encodePacket(p packet) []byte { 43 | var visit func(interface{}) 44 | var bytes []byte 45 | 46 | visit = func(value interface{}) { 47 | switch v := value.(type) { 48 | case nil: 49 | bytes = append(bytes, 0) 50 | 51 | case bool: 52 | n := uint8(0) 53 | if v { 54 | n = 1 55 | } 56 | bytes = append(bytes, 1, n) 57 | 58 | case int: 59 | bytes = append(bytes, 2) 60 | bytes = writeUint32(bytes, uint32(v)) 61 | 62 | case string: 63 | bytes = append(bytes, 3) 64 | bytes = writeUint32(bytes, uint32(len(v))) 65 | bytes = append(bytes, v...) 66 | 67 | case []byte: 68 | bytes = append(bytes, 4) 69 | bytes = writeUint32(bytes, uint32(len(v))) 70 | bytes = append(bytes, v...) 71 | 72 | case []interface{}: 73 | bytes = append(bytes, 5) 74 | bytes = writeUint32(bytes, uint32(len(v))) 75 | for _, item := range v { 76 | visit(item) 77 | } 78 | 79 | case map[string]interface{}: 80 | // Sort keys for determinism 81 | keys := make([]string, 0, len(v)) 82 | for k := range v { 83 | keys = append(keys, k) 84 | } 85 | sort.Strings(keys) 86 | bytes = append(bytes, 6) 87 | bytes = writeUint32(bytes, uint32(len(keys))) 88 | for _, k := range keys { 89 | bytes = writeUint32(bytes, uint32(len(k))) 90 | bytes = append(bytes, k...) 91 | visit(v[k]) 92 | } 93 | 94 | default: 95 | panic("Invalid packet") 96 | } 97 | } 98 | 99 | bytes = writeUint32(bytes, 0) // Reserve space for the length 100 | if p.isRequest { 101 | bytes = writeUint32(bytes, p.id<<1) 102 | } else { 103 | bytes = writeUint32(bytes, (p.id<<1)|1) 104 | } 105 | visit(p.value) 106 | writeUint32(bytes[:0], uint32(len(bytes)-4)) // Patch the length in 107 | return bytes 108 | } 109 | 110 | func decodePacket(bytes []byte) (packet, bool) { 111 | var visit func() (interface{}, bool) 112 | 113 | visit = func() (interface{}, bool) { 114 | kind := bytes[0] 115 | bytes = bytes[1:] 116 | switch kind { 117 | case 0: // nil 118 | return nil, true 119 | 120 | case 1: // bool 121 | value := bytes[0] 122 | bytes = bytes[1:] 123 | return value != 0, true 124 | 125 | case 2: // int 126 | value, next, ok := readUint32(bytes) 127 | if !ok { 128 | return nil, false 129 | } 130 | bytes = next 131 | return int(value), true 132 | 133 | case 3: // string 134 | value, next, ok := readLengthPrefixedSlice(bytes) 135 | if !ok { 136 | return nil, false 137 | } 138 | bytes = next 139 | return string(value), true 140 | 141 | case 4: // []byte 142 | value, next, ok := readLengthPrefixedSlice(bytes) 143 | if !ok { 144 | return nil, false 145 | } 146 | bytes = next 147 | return value, true 148 | 149 | case 5: // []interface{} 150 | count, next, ok := readUint32(bytes) 151 | if !ok { 152 | return nil, false 153 | } 154 | bytes = next 155 | value := make([]interface{}, count) 156 | for i := 0; i < int(count); i++ { 157 | item, ok := visit() 158 | if !ok { 159 | return nil, false 160 | } 161 | value[i] = item 162 | } 163 | return value, true 164 | 165 | case 6: // map[string]interface{} 166 | count, next, ok := readUint32(bytes) 167 | if !ok { 168 | return nil, false 169 | } 170 | bytes = next 171 | value := make(map[string]interface{}, count) 172 | for i := 0; i < int(count); i++ { 173 | key, next, ok := readLengthPrefixedSlice(bytes) 174 | if !ok { 175 | return nil, false 176 | } 177 | bytes = next 178 | item, ok := visit() 179 | if !ok { 180 | return nil, false 181 | } 182 | value[string(key)] = item 183 | } 184 | return value, true 185 | 186 | default: 187 | panic("Invalid packet") 188 | } 189 | } 190 | 191 | id, bytes, ok := readUint32(bytes) 192 | if !ok { 193 | return packet{}, false 194 | } 195 | isRequest := (id & 1) == 0 196 | id >>= 1 197 | value, ok := visit() 198 | if !ok { 199 | return packet{}, false 200 | } 201 | if len(bytes) != 0 { 202 | return packet{}, false 203 | } 204 | return packet{id: id, isRequest: isRequest, value: value}, true 205 | } 206 | -------------------------------------------------------------------------------- /cmd/esbuild/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const esbuildVersion = "0.12.28" 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/evanw/esbuild 2 | 3 | go 1.13 4 | 5 | require golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4= 2 | golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 3 | -------------------------------------------------------------------------------- /images/benchmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | esbuild 18 | 0.37s 19 | 20 | rollup + terser 21 | 37.79s 22 | 23 | parcel 2 24 | 39.28s 25 | 26 | webpack 4 27 | 43.07s 28 | 29 | webpack 5 30 | 55.25s 31 | 0s 32 | 10s 33 | 20s 34 | 30s 35 | 40s 36 | 50s 37 | 60s 38 | 39 | -------------------------------------------------------------------------------- /images/build-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/esbuild/076d28dca6642f97a7df7012b9691f24a926bc45/images/build-pipeline.png -------------------------------------------------------------------------------- /images/code-splitting-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/esbuild/076d28dca6642f97a7df7012b9691f24a926bc45/images/code-splitting-1.png -------------------------------------------------------------------------------- /images/code-splitting-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/esbuild/076d28dca6642f97a7df7012b9691f24a926bc45/images/code-splitting-2.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/esbuild/076d28dca6642f97a7df7012b9691f24a926bc45/images/logo.png -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/tree-shaking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/esbuild/076d28dca6642f97a7df7012b9691f24a926bc45/images/tree-shaking.png -------------------------------------------------------------------------------- /images/wordmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | esbuild 12 | An extremely fast JavaScript bundler 13 | 14 | -------------------------------------------------------------------------------- /internal/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 | -------------------------------------------------------------------------------- /internal/ast/ast.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "github.com/evanw/esbuild/internal/logger" 4 | 5 | // This file contains data structures that are used with the AST packages for 6 | // both JavaScript and CSS. This helps the bundler treat both AST formats in 7 | // a somewhat format-agnostic manner. 8 | 9 | type ImportKind uint8 10 | 11 | const ( 12 | // An entry point provided by the user 13 | ImportEntryPoint ImportKind = iota 14 | 15 | // An ES6 import or re-export statement 16 | ImportStmt 17 | 18 | // A call to "require()" 19 | ImportRequire 20 | 21 | // An "import()" expression with a string argument 22 | ImportDynamic 23 | 24 | // A call to "require.resolve()" 25 | ImportRequireResolve 26 | 27 | // A CSS "@import" rule 28 | ImportAt 29 | 30 | // A CSS "@import" rule with import conditions 31 | ImportAtConditional 32 | 33 | // A CSS "url(...)" token 34 | ImportURL 35 | ) 36 | 37 | func (kind ImportKind) StringForMetafile() string { 38 | switch kind { 39 | case ImportStmt: 40 | return "import-statement" 41 | case ImportRequire: 42 | return "require-call" 43 | case ImportDynamic: 44 | return "dynamic-import" 45 | case ImportRequireResolve: 46 | return "require-resolve" 47 | case ImportAt, ImportAtConditional: 48 | return "import-rule" 49 | case ImportURL: 50 | return "url-token" 51 | case ImportEntryPoint: 52 | return "entry-point" 53 | default: 54 | panic("Internal error") 55 | } 56 | } 57 | 58 | func (kind ImportKind) IsFromCSS() bool { 59 | return kind == ImportAt || kind == ImportURL 60 | } 61 | 62 | type ImportRecord struct { 63 | Range logger.Range 64 | Path logger.Path 65 | Assertions *[]AssertEntry 66 | 67 | // The resolved source index for an internal import (within the bundle) or 68 | // nil for an external import (not included in the bundle) 69 | SourceIndex Index32 70 | 71 | // Sometimes the parser creates an import record and decides it isn't needed. 72 | // For example, TypeScript code may have import statements that later turn 73 | // out to be type-only imports after analyzing the whole file. 74 | IsUnused bool 75 | 76 | // If this is true, the import contains syntax like "* as ns". This is used 77 | // to determine whether modules that have no exports need to be wrapped in a 78 | // CommonJS wrapper or not. 79 | ContainsImportStar bool 80 | 81 | // If this is true, the import contains an import for the alias "default", 82 | // either via the "import x from" or "import {default as x} from" syntax. 83 | ContainsDefaultAlias bool 84 | 85 | // If true, this "export * from 'path'" statement is evaluated at run-time by 86 | // calling the "__reExport()" helper function 87 | CallsRunTimeReExportFn bool 88 | 89 | // Tell the printer to wrap this call to "require()" in "__toModule(...)" 90 | WrapWithToModule bool 91 | 92 | // Tell the printer to use the runtime "__require()" instead of "require()" 93 | CallRuntimeRequire bool 94 | 95 | // True for the following cases: 96 | // 97 | // try { require('x') } catch { handle } 98 | // try { await import('x') } catch { handle } 99 | // try { require.resolve('x') } catch { handle } 100 | // import('x').catch(handle) 101 | // import('x').then(_, handle) 102 | // 103 | // In these cases we shouldn't generate an error if the path could not be 104 | // resolved. 105 | HandlesImportErrors bool 106 | 107 | // If true, this was originally written as a bare "import 'file'" statement 108 | WasOriginallyBareImport bool 109 | 110 | Kind ImportKind 111 | } 112 | 113 | type AssertEntry struct { 114 | Key []uint16 // An identifier or a string 115 | Value []uint16 // Always a string 116 | KeyLoc logger.Loc 117 | ValueLoc logger.Loc 118 | PreferQuotedKey bool 119 | } 120 | 121 | // This stores a 32-bit index where the zero value is an invalid index. This is 122 | // a better alternative to storing the index as a pointer since that has the 123 | // same properties but takes up more space and costs an extra pointer traversal. 124 | type Index32 struct { 125 | flippedBits uint32 126 | } 127 | 128 | func MakeIndex32(index uint32) Index32 { 129 | return Index32{flippedBits: ^index} 130 | } 131 | 132 | func (i Index32) IsValid() bool { 133 | return i.flippedBits != 0 134 | } 135 | 136 | func (i Index32) GetIndex() uint32 { 137 | return ^i.flippedBits 138 | } 139 | -------------------------------------------------------------------------------- /internal/bundler/bundler_test.go: -------------------------------------------------------------------------------- 1 | package bundler 2 | 3 | // Bundling test results are stored in snapshot files, located in the 4 | // "snapshots" directory. This allows test results to be updated easily without 5 | // manually rewriting all of the expected values. To update the tests run 6 | // "UPDATE_SNAPSHOTS=1 make test" and commit the updated values. Make sure to 7 | // inspect the diff to ensure the expected values are valid. 8 | 9 | import ( 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "path" 14 | "sort" 15 | "strings" 16 | "sync" 17 | "testing" 18 | 19 | "github.com/evanw/esbuild/internal/cache" 20 | "github.com/evanw/esbuild/internal/compat" 21 | "github.com/evanw/esbuild/internal/config" 22 | "github.com/evanw/esbuild/internal/fs" 23 | "github.com/evanw/esbuild/internal/logger" 24 | "github.com/evanw/esbuild/internal/resolver" 25 | "github.com/evanw/esbuild/internal/test" 26 | ) 27 | 28 | func es(version int) compat.JSFeature { 29 | return compat.UnsupportedJSFeatures(map[compat.Engine][]int{ 30 | compat.ES: {version}, 31 | }) 32 | } 33 | 34 | func assertLog(t *testing.T, msgs []logger.Msg, expected string) { 35 | t.Helper() 36 | text := "" 37 | for _, msg := range msgs { 38 | text += msg.String(logger.OutputOptions{}, logger.TerminalInfo{}) 39 | } 40 | test.AssertEqualWithDiff(t, text, expected) 41 | } 42 | 43 | func hasErrors(msgs []logger.Msg) bool { 44 | for _, msg := range msgs { 45 | if msg.Kind == logger.Error { 46 | return true 47 | } 48 | } 49 | return false 50 | } 51 | 52 | type bundled struct { 53 | files map[string]string 54 | entryPaths []string 55 | expectedScanLog string 56 | expectedCompileLog string 57 | options config.Options 58 | } 59 | 60 | type suite struct { 61 | name string 62 | path string 63 | mutex sync.Mutex 64 | expectedSnapshots map[string]string 65 | generatedSnapshots map[string]string 66 | } 67 | 68 | func (s *suite) expectBundled(t *testing.T, args bundled) { 69 | t.Helper() 70 | testName := t.Name() 71 | t.Run("", func(t *testing.T) { 72 | t.Helper() 73 | fs := fs.MockFS(args.files) 74 | if args.options.ExtensionOrder == nil { 75 | args.options.ExtensionOrder = []string{".tsx", ".ts", ".jsx", ".js", ".css", ".json"} 76 | } 77 | if args.options.AbsOutputFile != "" { 78 | args.options.AbsOutputDir = path.Dir(args.options.AbsOutputFile) 79 | } 80 | log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug) 81 | caches := cache.MakeCacheSet() 82 | resolver := resolver.NewResolver(fs, log, caches, args.options) 83 | entryPoints := make([]EntryPoint, 0, len(args.entryPaths)) 84 | for _, path := range args.entryPaths { 85 | entryPoints = append(entryPoints, EntryPoint{InputPath: path}) 86 | } 87 | bundle := ScanBundle(log, fs, resolver, caches, entryPoints, args.options, nil) 88 | msgs := log.Done() 89 | assertLog(t, msgs, args.expectedScanLog) 90 | 91 | // Stop now if there were any errors during the scan 92 | if hasErrors(msgs) { 93 | return 94 | } 95 | 96 | log = logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug) 97 | args.options.OmitRuntimeForTests = true 98 | results, _ := bundle.Compile(log, args.options, nil) 99 | msgs = log.Done() 100 | assertLog(t, msgs, args.expectedCompileLog) 101 | 102 | // Stop now if there were any errors during the compile 103 | if hasErrors(msgs) { 104 | return 105 | } 106 | 107 | // Don't include source maps in results since they are just noise. Source 108 | // map validity is tested separately in a test that uses Mozilla's source 109 | // map parsing library. 110 | generated := "" 111 | for _, result := range results { 112 | if !strings.HasSuffix(result.AbsPath, ".map") { 113 | if generated != "" { 114 | generated += "\n" 115 | } 116 | generated += fmt.Sprintf("---------- %s ----------\n%s", result.AbsPath, string(result.Contents)) 117 | } 118 | } 119 | s.compareSnapshot(t, testName, generated) 120 | }) 121 | } 122 | 123 | const snapshotsDir = "snapshots" 124 | const snapshotSplitter = "\n================================================================================\n" 125 | 126 | var globalTestMutex sync.Mutex 127 | var globalSuites map[*suite]bool 128 | var globalUpdateSnapshots bool 129 | 130 | func (s *suite) compareSnapshot(t *testing.T, testName string, generated string) { 131 | t.Helper() 132 | // Initialize the test suite during the first test 133 | s.mutex.Lock() 134 | defer s.mutex.Unlock() 135 | if s.path == "" { 136 | s.path = snapshotsDir + "/snapshots_" + s.name + ".txt" 137 | s.generatedSnapshots = make(map[string]string) 138 | s.expectedSnapshots = make(map[string]string) 139 | if contents, err := ioutil.ReadFile(s.path); err == nil { 140 | // Replacing CRLF with LF is necessary to fix tests in GitHub actions, 141 | // which for some reason check out the source code in CLRF mode 142 | for _, part := range strings.Split(strings.ReplaceAll(string(contents), "\r\n", "\n"), snapshotSplitter) { 143 | if newline := strings.IndexByte(part, '\n'); newline != -1 { 144 | key := part[:newline] 145 | value := part[newline+1:] 146 | s.expectedSnapshots[key] = value 147 | } else { 148 | s.expectedSnapshots[part] = "" 149 | } 150 | } 151 | } 152 | globalTestMutex.Lock() 153 | defer globalTestMutex.Unlock() 154 | if globalSuites == nil { 155 | globalSuites = make(map[*suite]bool) 156 | } 157 | globalSuites[s] = true 158 | _, globalUpdateSnapshots = os.LookupEnv("UPDATE_SNAPSHOTS") 159 | } 160 | 161 | // Check against the stored snapshot if present 162 | s.generatedSnapshots[testName] = generated 163 | if !globalUpdateSnapshots { 164 | if expected, ok := s.expectedSnapshots[testName]; ok { 165 | test.AssertEqualWithDiff(t, generated, expected) 166 | } else { 167 | t.Fatalf("No snapshot saved for %s\n%s%s%s", 168 | testName, 169 | logger.TerminalColors.Green, 170 | generated, 171 | logger.TerminalColors.Reset, 172 | ) 173 | } 174 | } 175 | } 176 | 177 | func (s *suite) updateSnapshots() { 178 | os.Mkdir(snapshotsDir, 0755) 179 | keys := make([]string, 0, len(s.generatedSnapshots)) 180 | for key := range s.generatedSnapshots { 181 | keys = append(keys, key) 182 | } 183 | sort.Strings(keys) 184 | contents := "" 185 | for i, key := range keys { 186 | if i > 0 { 187 | contents += snapshotSplitter 188 | } 189 | contents += fmt.Sprintf("%s\n%s", key, s.generatedSnapshots[key]) 190 | } 191 | if err := ioutil.WriteFile(s.path, []byte(contents), 0644); err != nil { 192 | panic(err) 193 | } 194 | } 195 | 196 | func (s *suite) validateSnapshots() bool { 197 | isValid := true 198 | for key := range s.expectedSnapshots { 199 | if _, ok := s.generatedSnapshots[key]; !ok { 200 | if isValid { 201 | fmt.Printf("%s\n", s.path) 202 | } 203 | fmt.Printf(" No test found for snapshot %s\n", key) 204 | isValid = false 205 | } 206 | } 207 | return isValid 208 | } 209 | 210 | func TestMain(m *testing.M) { 211 | code := m.Run() 212 | if globalSuites != nil { 213 | if globalUpdateSnapshots { 214 | for s := range globalSuites { 215 | s.updateSnapshots() 216 | } 217 | } else { 218 | for s := range globalSuites { 219 | if !s.validateSnapshots() { 220 | code = 1 221 | } 222 | } 223 | } 224 | } 225 | os.Exit(code) 226 | } 227 | -------------------------------------------------------------------------------- /internal/bundler/debug.go: -------------------------------------------------------------------------------- 1 | package bundler 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/evanw/esbuild/internal/ast" 8 | "github.com/evanw/esbuild/internal/graph" 9 | "github.com/evanw/esbuild/internal/js_ast" 10 | "github.com/evanw/esbuild/internal/js_printer" 11 | ) 12 | 13 | // Set this to true and then load the resulting metafile in "graph-debugger.html" 14 | // to debug graph information. 15 | // 16 | // This is deliberately not exposed in the final binary. It is *very* internal 17 | // and only exists to help debug esbuild itself. Make sure this is always set 18 | // back to false before committing. 19 | const debugVerboseMetafile = false 20 | 21 | func (c *linkerContext) generateExtraDataForFileJS(sourceIndex uint32) string { 22 | if !debugVerboseMetafile { 23 | return "" 24 | } 25 | 26 | file := &c.graph.Files[sourceIndex] 27 | repr := file.InputFile.Repr.(*graph.JSRepr) 28 | sb := strings.Builder{} 29 | 30 | quoteSym := func(ref js_ast.Ref) string { 31 | name := fmt.Sprintf("%d:%d [%s]", ref.SourceIndex, ref.InnerIndex, c.graph.Symbols.Get(ref).OriginalName) 32 | return string(js_printer.QuoteForJSON(name, c.options.ASCIIOnly)) 33 | } 34 | 35 | sb.WriteString(`,"parts":[`) 36 | for partIndex, part := range repr.AST.Parts { 37 | if partIndex > 0 { 38 | sb.WriteByte(',') 39 | } 40 | var isFirst bool 41 | code := "" 42 | 43 | sb.WriteString(fmt.Sprintf(`{"isLive":%v`, part.IsLive)) 44 | sb.WriteString(fmt.Sprintf(`,"canBeRemovedIfUnused":%v`, part.CanBeRemovedIfUnused)) 45 | 46 | if partIndex == int(js_ast.NSExportPartIndex) { 47 | sb.WriteString(`,"nsExportPartIndex":true`) 48 | } else if ast.MakeIndex32(uint32(partIndex)) == repr.Meta.WrapperPartIndex { 49 | sb.WriteString(`,"wrapperPartIndex":true`) 50 | } else if len(part.Stmts) > 0 { 51 | start := part.Stmts[0].Loc.Start 52 | end := len(file.InputFile.Source.Contents) 53 | if partIndex+1 < len(repr.AST.Parts) { 54 | if nextStmts := repr.AST.Parts[partIndex+1].Stmts; len(nextStmts) > 0 { 55 | if nextStart := nextStmts[0].Loc.Start; nextStart >= start { 56 | end = int(nextStart) 57 | } 58 | } 59 | } 60 | code = file.InputFile.Source.Contents[start:end] 61 | } 62 | 63 | // importRecords 64 | sb.WriteString(`,"importRecords":[`) 65 | isFirst = true 66 | for _, importRecordIndex := range part.ImportRecordIndices { 67 | record := repr.AST.ImportRecords[importRecordIndex] 68 | if !record.SourceIndex.IsValid() { 69 | continue 70 | } 71 | if isFirst { 72 | isFirst = false 73 | } else { 74 | sb.WriteByte(',') 75 | } 76 | path := c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Source.PrettyPath 77 | sb.WriteString(fmt.Sprintf(`{"source":%s}`, js_printer.QuoteForJSON(path, c.options.ASCIIOnly))) 78 | } 79 | sb.WriteByte(']') 80 | 81 | // declaredSymbols 82 | sb.WriteString(`,"declaredSymbols":[`) 83 | isFirst = true 84 | for _, declSym := range part.DeclaredSymbols { 85 | if !declSym.IsTopLevel { 86 | continue 87 | } 88 | if isFirst { 89 | isFirst = false 90 | } else { 91 | sb.WriteByte(',') 92 | } 93 | sb.WriteString(fmt.Sprintf(`{"name":%s}`, quoteSym(declSym.Ref))) 94 | } 95 | sb.WriteByte(']') 96 | 97 | // symbolUses 98 | sb.WriteString(`,"symbolUses":[`) 99 | isFirst = true 100 | for ref, uses := range part.SymbolUses { 101 | if isFirst { 102 | isFirst = false 103 | } else { 104 | sb.WriteByte(',') 105 | } 106 | sb.WriteString(fmt.Sprintf(`{"name":%s,"countEstimate":%d}`, quoteSym(ref), uses.CountEstimate)) 107 | } 108 | sb.WriteByte(']') 109 | 110 | // dependencies 111 | sb.WriteString(`,"dependencies":[`) 112 | for i, dep := range part.Dependencies { 113 | if i > 0 { 114 | sb.WriteByte(',') 115 | } 116 | sb.WriteString(fmt.Sprintf(`{"source":%s,"partIndex":%d}`, 117 | js_printer.QuoteForJSON(c.graph.Files[dep.SourceIndex].InputFile.Source.PrettyPath, c.options.ASCIIOnly), 118 | dep.PartIndex, 119 | )) 120 | } 121 | sb.WriteByte(']') 122 | 123 | // code 124 | sb.WriteString(`,"code":`) 125 | sb.Write(js_printer.QuoteForJSON(strings.TrimRight(code, "\n"), c.options.ASCIIOnly)) 126 | 127 | sb.WriteByte('}') 128 | } 129 | sb.WriteString(`]`) 130 | 131 | return sb.String() 132 | } 133 | -------------------------------------------------------------------------------- /internal/bundler/snapshots/snapshots_css.txt: -------------------------------------------------------------------------------- 1 | TestBase64ImportURLInCSS 2 | ---------- /out/entry.css ---------- 3 | /* entry.css */ 4 | a { 5 | background: url(data:image/png;base64,iVBORw0KGgo=); 6 | } 7 | 8 | ================================================================================ 9 | TestBinaryImportURLInCSS 10 | ---------- /out/entry.css ---------- 11 | /* entry.css */ 12 | a { 13 | background: url(data:application/octet-stream;base64,iVBORw0KGgo=); 14 | } 15 | 16 | ================================================================================ 17 | TestCSSAndJavaScriptCodeSplittingIssue1064 18 | ---------- /out/a.js ---------- 19 | import { 20 | shared_default 21 | } from "./chunk-XTGNVFM6.js"; 22 | 23 | // a.js 24 | console.log(shared_default() + 1); 25 | 26 | ---------- /out/b.js ---------- 27 | import { 28 | shared_default 29 | } from "./chunk-XTGNVFM6.js"; 30 | 31 | // b.js 32 | console.log(shared_default() + 2); 33 | 34 | ---------- /out/chunk-XTGNVFM6.js ---------- 35 | // shared.js 36 | function shared_default() { 37 | return 3; 38 | } 39 | 40 | export { 41 | shared_default 42 | }; 43 | 44 | ---------- /out/c.css ---------- 45 | /* shared.css */ 46 | body { 47 | background: black; 48 | } 49 | 50 | /* c.css */ 51 | body { 52 | color: red; 53 | } 54 | 55 | ---------- /out/d.css ---------- 56 | /* shared.css */ 57 | body { 58 | background: black; 59 | } 60 | 61 | /* d.css */ 62 | body { 63 | color: blue; 64 | } 65 | 66 | ================================================================================ 67 | TestCSSAtImport 68 | ---------- /out.css ---------- 69 | /* a.css */ 70 | .a { 71 | color: green; 72 | } 73 | 74 | /* shared.css */ 75 | .shared { 76 | color: black; 77 | } 78 | 79 | /* b.css */ 80 | .b { 81 | color: blue; 82 | } 83 | 84 | /* entry.css */ 85 | .entry { 86 | color: red; 87 | } 88 | 89 | ================================================================================ 90 | TestCSSAtImportConditionsBundleExternal 91 | ---------- /out.css ---------- 92 | @import "https://example.com/print.css" print; 93 | 94 | /* entry.css */ 95 | 96 | ================================================================================ 97 | TestCSSAtImportConditionsNoBundle 98 | ---------- /out.css ---------- 99 | @import "./print.css" print; 100 | 101 | ================================================================================ 102 | TestCSSAtImportExtensionOrderCollision 103 | ---------- /out.css ---------- 104 | /* test.css */ 105 | .css { 106 | color: red; 107 | } 108 | 109 | /* entry.css */ 110 | 111 | ================================================================================ 112 | TestCSSAtImportExternal 113 | ---------- /out/entry.css ---------- 114 | @charset "UTF-8"; 115 | @import "../external1.css"; 116 | @import "../external2.css"; 117 | @import "../external4.css"; 118 | @import "../external5.css"; 119 | @import "https://www.example.com/style2.css"; 120 | @import "../external3.css"; 121 | @import "https://www.example.com/style1.css"; 122 | @import "https://www.example.com/style3.css"; 123 | @import "../external5.css" screen; 124 | 125 | /* internal.css */ 126 | .before { 127 | color: red; 128 | } 129 | 130 | /* charset1.css */ 131 | .middle { 132 | color: green; 133 | } 134 | 135 | /* charset2.css */ 136 | .after { 137 | color: blue; 138 | } 139 | 140 | /* entry.css */ 141 | 142 | ================================================================================ 143 | TestCSSEntryPoint 144 | ---------- /out.css ---------- 145 | /* entry.css */ 146 | body { 147 | background: white; 148 | color: black; 149 | } 150 | 151 | ================================================================================ 152 | TestCSSFromJSMissingStarImport 153 | ---------- /out/entry.js ---------- 154 | // entry.js 155 | console.log(void 0); 156 | 157 | ---------- /out/entry.css ---------- 158 | /* a.css */ 159 | .a { 160 | color: red; 161 | } 162 | 163 | ================================================================================ 164 | TestDataURLImportURLInCSS 165 | ---------- /out/entry.css ---------- 166 | /* entry.css */ 167 | a { 168 | background: url(data:image/png;base64,iVBORw0KGgo=); 169 | } 170 | 171 | ================================================================================ 172 | TestExternalImportURLInCSS 173 | ---------- /out/entry.css ---------- 174 | /* src/entry.css */ 175 | div:after { 176 | content: 'If this is recognized, the path should become "../src/external.png"'; 177 | background: url(../src/external.png); 178 | } 179 | a { 180 | background: url(http://example.com/images/image.png); 181 | } 182 | b { 183 | background: url(https://example.com/images/image.png); 184 | } 185 | c { 186 | background: url(//example.com/images/image.png); 187 | } 188 | d { 189 | background: url(data:image/png;base64,iVBORw0KGgo=); 190 | } 191 | path { 192 | fill: url(#filter); 193 | } 194 | 195 | ================================================================================ 196 | TestFileImportURLInCSS 197 | ---------- /out/example-GDKWWYFY.data ---------- 198 | This is some data. 199 | ---------- /out/entry.css ---------- 200 | /* one.css */ 201 | a { 202 | background: url(./example-GDKWWYFY.data); 203 | } 204 | 205 | /* two.css */ 206 | b { 207 | background: url(./example-GDKWWYFY.data); 208 | } 209 | 210 | /* entry.css */ 211 | 212 | ================================================================================ 213 | TestIgnoreURLsInAtRulePrelude 214 | ---------- /out/entry.css ---------- 215 | /* entry.css */ 216 | @supports (background: url(ignored.png)) { 217 | a { 218 | color: red; 219 | } 220 | } 221 | 222 | ================================================================================ 223 | TestImportCSSFromJS 224 | ---------- /out/entry.js ---------- 225 | // a.js 226 | console.log("a"); 227 | 228 | // b.js 229 | console.log("b"); 230 | 231 | ---------- /out/entry.css ---------- 232 | /* a.css */ 233 | .a { 234 | color: red; 235 | } 236 | 237 | /* b.css */ 238 | .b { 239 | color: blue; 240 | } 241 | 242 | ================================================================================ 243 | TestPackageURLsInCSS 244 | ---------- /out/entry.css ---------- 245 | /* test.css */ 246 | .css { 247 | color: red; 248 | } 249 | 250 | /* entry.css */ 251 | a { 252 | background: url(data:image/png;base64,YS0x); 253 | } 254 | b { 255 | background: url(data:image/png;base64,Yi0yLW5vZGVfbW9kdWxlcw==); 256 | } 257 | c { 258 | background: url(data:image/png;base64,Yy0z); 259 | } 260 | 261 | ================================================================================ 262 | TestTextImportURLInCSSText 263 | ---------- /out/entry.css ---------- 264 | /* entry.css */ 265 | a { 266 | background: url(data:text/plain;base64,VGhpcyBpcyBzb21lIHRleHQu); 267 | } 268 | -------------------------------------------------------------------------------- /internal/bundler/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(), 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 = __toModule(require_foo()); 46 | var foo = 234; 47 | console.log(ns, ns.foo, foo); 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 = __toModule(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 | -------------------------------------------------------------------------------- /internal/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/evanw/esbuild/internal/logger" 7 | "github.com/evanw/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 | // 34 | type CacheSet struct { 35 | SourceIndexCache SourceIndexCache 36 | FSCache FSCache 37 | CSSCache CSSCache 38 | JSONCache JSONCache 39 | JSCache JSCache 40 | } 41 | 42 | func MakeCacheSet() *CacheSet { 43 | return &CacheSet{ 44 | SourceIndexCache: SourceIndexCache{ 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 | mutex sync.Mutex 65 | entries map[sourceIndexKey]uint32 66 | nextSourceIndex uint32 67 | } 68 | 69 | type SourceIndexKind uint8 70 | 71 | const ( 72 | SourceIndexNormal SourceIndexKind = iota 73 | SourceIndexJSStubForCSS 74 | ) 75 | 76 | type sourceIndexKey struct { 77 | path logger.Path 78 | kind SourceIndexKind 79 | } 80 | 81 | func (c *SourceIndexCache) LenHint() uint32 { 82 | c.mutex.Lock() 83 | defer c.mutex.Unlock() 84 | 85 | // Add some extra room at the end for a new file or two without reallocating 86 | const someExtraRoom = 16 87 | return c.nextSourceIndex + someExtraRoom 88 | } 89 | 90 | func (c *SourceIndexCache) Get(path logger.Path, kind SourceIndexKind) uint32 { 91 | key := sourceIndexKey{path: path, kind: kind} 92 | c.mutex.Lock() 93 | defer c.mutex.Unlock() 94 | if sourceIndex, ok := c.entries[key]; ok { 95 | return sourceIndex 96 | } 97 | sourceIndex := c.nextSourceIndex 98 | c.nextSourceIndex++ 99 | c.entries[key] = sourceIndex 100 | return sourceIndex 101 | } 102 | -------------------------------------------------------------------------------- /internal/cache/cache_ast.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/evanw/esbuild/internal/css_ast" 7 | "github.com/evanw/esbuild/internal/css_parser" 8 | "github.com/evanw/esbuild/internal/js_ast" 9 | "github.com/evanw/esbuild/internal/js_parser" 10 | "github.com/evanw/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 | mutex sync.Mutex 31 | entries map[logger.Path]*cssCacheEntry 32 | } 33 | 34 | type cssCacheEntry struct { 35 | source logger.Source 36 | options css_parser.Options 37 | ast css_ast.AST 38 | msgs []logger.Msg 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 == 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) 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 | mutex sync.Mutex 85 | entries map[logger.Path]*jsonCacheEntry 86 | } 87 | 88 | type jsonCacheEntry struct { 89 | source logger.Source 90 | options js_parser.JSONOptions 91 | expr js_ast.Expr 92 | ok bool 93 | msgs []logger.Msg 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) 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 | mutex sync.Mutex 141 | entries map[logger.Path]*jsCacheEntry 142 | } 143 | 144 | type jsCacheEntry struct { 145 | source logger.Source 146 | options js_parser.Options 147 | ast js_ast.AST 148 | ok bool 149 | msgs []logger.Msg 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) 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 | -------------------------------------------------------------------------------- /internal/cache/cache_fs.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/evanw/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 | mutex sync.Mutex 16 | entries map[string]*fsEntry 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 | -------------------------------------------------------------------------------- /internal/cli_helpers/cli_helpers.go: -------------------------------------------------------------------------------- 1 | // This package contains internal CLI-related code that must be shared with 2 | // other internal code outside of the CLI package. 3 | 4 | package cli_helpers 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/evanw/esbuild/pkg/api" 10 | ) 11 | 12 | func ParseLoader(text string) (api.Loader, error) { 13 | switch text { 14 | case "js": 15 | return api.LoaderJS, nil 16 | case "jsx": 17 | return api.LoaderJSX, nil 18 | case "ts": 19 | return api.LoaderTS, nil 20 | case "tsx": 21 | return api.LoaderTSX, nil 22 | case "css": 23 | return api.LoaderCSS, nil 24 | case "json": 25 | return api.LoaderJSON, nil 26 | case "text": 27 | return api.LoaderText, nil 28 | case "base64": 29 | return api.LoaderBase64, nil 30 | case "dataurl": 31 | return api.LoaderDataURL, nil 32 | case "file": 33 | return api.LoaderFile, nil 34 | case "binary": 35 | return api.LoaderBinary, nil 36 | case "default": 37 | return api.LoaderDefault, nil 38 | default: 39 | return api.LoaderNone, fmt.Errorf("Invalid loader: %q (valid: "+ 40 | "js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary)", text) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/compat/css_table.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | type CSSFeature uint32 4 | 5 | const ( 6 | HexRGBA CSSFeature = 1 << iota 7 | 8 | RebeccaPurple 9 | 10 | // This feature includes all of the following: 11 | // - Allow floats in rgb() and rgba() 12 | // - hsl() can accept alpha values 13 | // - rgb() can accept alpha values 14 | // - Space-separated functional color notations 15 | Modern_RGB_HSL 16 | ) 17 | 18 | func (features CSSFeature) Has(feature CSSFeature) bool { 19 | return (features & feature) != 0 20 | } 21 | 22 | var cssTable = map[CSSFeature]map[Engine][]int{ 23 | // Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value 24 | HexRGBA: { 25 | Chrome: {62}, 26 | Edge: {79}, 27 | Firefox: {49}, 28 | IOS: {9, 3}, 29 | Safari: {9, 1}, 30 | }, 31 | RebeccaPurple: { 32 | Chrome: {38}, 33 | Edge: {12}, 34 | Firefox: {33}, 35 | IOS: {8}, 36 | Safari: {9}, 37 | }, 38 | Modern_RGB_HSL: { 39 | Chrome: {66}, 40 | Edge: {79}, 41 | Firefox: {52}, 42 | IOS: {12, 2}, 43 | Safari: {12, 1}, 44 | }, 45 | } 46 | 47 | // Return all features that are not available in at least one environment 48 | func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeature) { 49 | for feature, engines := range cssTable { 50 | for engine, version := range constraints { 51 | if engine == ES || engine == Node { 52 | // Specifying "--target=es2020" shouldn't affect CSS 53 | continue 54 | } 55 | if minVersion, ok := engines[engine]; !ok || isVersionLessThan(version, minVersion) { 56 | unsupported |= feature 57 | } 58 | } 59 | } 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /internal/css_lexer/css_lexer_test.go: -------------------------------------------------------------------------------- 1 | package css_lexer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/evanw/esbuild/internal/logger" 7 | "github.com/evanw/esbuild/internal/test" 8 | ) 9 | 10 | func lexToken(contents string) (T, string) { 11 | log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug) 12 | result := Tokenize(log, test.SourceForTest(contents)) 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) 22 | Tokenize(log, test.SourceForTest(contents)) 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 | token T 34 | text string 35 | }{ 36 | {"", TEndOfFile, "end of file"}, 37 | {"@media", TAtKeyword, "@-keyword"}, 38 | {"url(x y", TBadURL, "bad URL token"}, 39 | {"-->", TCDC, "\"-->\""}, 40 | {"