userland: add archive tools and pinned GoAWK, esbuild, and QuickJS

This commit is contained in:
2026-11-07 10:56:29 +00:00
parent 7e7e49bfa2
commit d79b2bc2e1
1798 changed files with 562969 additions and 1 deletions
+8
View File
@@ -4340,6 +4340,14 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "sausage-archive-tools"
version = "0.1.0"
dependencies = [
"flate2",
"tar",
]
[[package]]
name = "sausage-coreutils"
version = "0.1.0"
+1 -1
View File
@@ -1,7 +1,7 @@
# The default CLIs of a world, built for wasm32-wasip2. Its own workspace, outside the host's.
[workspace]
resolver = "3"
members = ["git", "coreutils", "procps", "cwd-init/probe", "ripgrep", "diffutils", "jaq", "sed", "grep", "fd", "spawn", "findutils", "exec-tools", "ssh-client", "ssh", "curl", "init", "box"]
members = ["git", "coreutils", "procps", "cwd-init/probe", "ripgrep", "diffutils", "jaq", "sed", "grep", "fd", "spawn", "findutils", "exec-tools", "ssh-client", "ssh", "curl", "init", "box", "archive-tools"]
# WASI compatibility patches: git/vendor/PATCHES.md.
[patch.crates-io]
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "sausage-archive-tools"
version = "0.1.0"
edition = "2024"
[dependencies]
tar = { version = "0.4", default-features = false }
flate2 = "1"
[[bin]]
name = "tar"
path = "src/tar.rs"
[[bin]]
name = "gzip"
path = "src/gzip.rs"
[[bin]]
name = "gunzip"
path = "src/gzip.rs"
[[bin]]
name = "zcat"
path = "src/gzip.rs"
+36
View File
@@ -0,0 +1,36 @@
# Archive tools
These Rust commands use standard WASI files and streams.
```sh
tar -czf archive.tgz -C /repos project
tar -xzf archive.tgz -C /work --strip-components 1
tar -tf archive.tar
tar -cf - project | gzip -c > project.tgz
gzip -k file
gunzip -fk file.gz
zcat file.gz
```
The tar parser accepts short combined flags, `-C`, `-f -`, and `--strip-components`.
Extraction supports regular files and directories. It rejects special files and paths outside the destination.
Creation uses portable modes because WASI does not expose Unix mode bits.
It includes symbolic links in archives, but world extraction does not create symbolic links or hardlinks.
The selected `-C` applies to all input paths.
Gzip supports `-d`, `-c`, `-k`, and `-f`.
Gunzip defaults to decompression. Zcat defaults to decompression to stdout.
Files use the `.gz` suffix. Decompression accepts concatenated gzip members.
Existing output files require `-f`. A successful file operation removes its input unless `-k` is set.
From `userland/`, with the executor's `CARGO_TARGET_DIR`:
```sh
nix develop -c cargo build -p sausage-archive-tools --release --locked
nix develop -c nu archive-tools/test.nu
nix develop -c cargo test -p sausage-archive-tools --target aarch64-unknown-linux-gnu --locked
```
The test script compares cross-format tar and gzip transfers with native tools.
It also compares common GoAWK one-liners and checks the pinned QuickJS command.
The full T42 acceptance suite remains incomplete.
+106
View File
@@ -0,0 +1,106 @@
//! Compress or decompress files and streams with the gzip, gunzip, and zcat names.
use flate2::{Compression, read::MultiGzDecoder, write::GzEncoder};
use std::{
fs::File,
io::{self, Read, Write},
};
fn transfer(input: &mut dyn Read, output: &mut dyn Write, decompress: bool) -> io::Result<()> {
if decompress {
io::copy(&mut MultiGzDecoder::new(input), output)?;
} else {
let mut encoder = GzEncoder::new(output, Compression::default());
io::copy(input, &mut encoder)?;
encoder.finish()?;
}
Ok(())
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args();
let program = args.next().unwrap_or_default();
let name = std::path::Path::new(&program)
.file_stem()
.unwrap_or_default()
.to_string_lossy();
let mut decompress = name == "gunzip" || name == "zcat";
let mut stdout = name == "zcat";
let mut keep = false;
let mut force = false;
let mut paths = Vec::new();
while let Some(arg) = args.next() {
if arg == "--" {
paths.extend(args);
break;
}
if arg.starts_with('-') && arg != "-" {
for c in arg[1..].chars() {
match c {
'd' => decompress = true,
'c' => stdout = true,
'k' => keep = true,
'f' => force = true,
_ => return Err(format!("unsupported option: {c}").into()),
}
}
} else {
paths.push(arg);
}
}
if paths.is_empty() {
paths.push("-".into());
}
for path in paths {
let mut input: Box<dyn Read> = if path == "-" {
Box::new(io::stdin())
} else {
Box::new(File::open(&path)?)
};
if stdout || path == "-" {
transfer(&mut input, &mut io::stdout(), decompress)?;
} else {
let dest = if decompress {
path.strip_suffix(".gz")
.ok_or("unknown suffix (expected .gz)")?
.to_owned()
} else {
format!("{path}.gz")
};
let mut output = File::options()
.write(true)
.create(force)
.truncate(force)
.create_new(!force)
.open(&dest)?;
let result =
transfer(&mut input, &mut output, decompress).and_then(|()| output.flush());
if result.is_err() {
drop(output);
let _ = std::fs::remove_file(&dest);
}
result?;
if !keep {
std::fs::remove_file(path)?;
}
}
}
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("gzip: {e}");
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binary_round_trip_and_corrupt_input() {
let source = [0, 255, 1, 42];
let mut encoded = Vec::new();
transfer(&mut source.as_slice(), &mut encoded, false).unwrap();
let mut decoded = Vec::new();
transfer(&mut encoded.as_slice(), &mut decoded, true).unwrap();
assert_eq!(decoded, source);
assert!(transfer(&mut &b"bad gzip"[..], &mut Vec::new(), true).is_err());
}
}
+210
View File
@@ -0,0 +1,210 @@
//! Create, list, and extract portable tar archives through standard WASI files.
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
use std::{
fs::File,
io::{self, Read, Write},
path::{Path, PathBuf},
};
fn append<W: Write>(
builder: &mut tar::Builder<W>,
source: &Path,
name: &Path,
verbose: bool,
) -> io::Result<()> {
let meta = std::fs::symlink_metadata(source)?;
let mut header = tar::Header::new_gnu();
header.set_uid(0);
header.set_gid(0);
header.set_mtime(
meta.modified()?
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
);
if verbose {
eprintln!("{}", name.display());
}
if meta.is_dir() {
header.set_entry_type(tar::EntryType::Directory);
header.set_size(0);
header.set_mode(0o755);
header.set_cksum();
builder.append_data(&mut header, name, io::empty())?;
let mut entries = std::fs::read_dir(source)?.collect::<io::Result<Vec<_>>>()?;
entries.sort_by_key(|e| e.file_name());
for entry in entries {
append(
builder,
&entry.path(),
&name.join(entry.file_name()),
verbose,
)?;
}
} else if meta.is_file() {
header.set_size(meta.len());
header.set_mode(0o644);
header.set_cksum();
builder.append_data(&mut header, name, File::open(source)?)?;
} else if meta.file_type().is_symlink() {
header.set_entry_type(tar::EntryType::Symlink);
header.set_size(0);
header.set_mode(0o777);
header.set_cksum();
builder.append_link(&mut header, name, std::fs::read_link(source)?)?;
} else {
return Err(io::Error::other("unsupported special file"));
}
Ok(())
}
fn create<W: Write>(writer: W, cwd: &Path, paths: &[String], verbose: bool) -> io::Result<W> {
let mut builder = tar::Builder::new(writer);
for path in paths {
let name = Path::new(path).strip_prefix("/").unwrap_or(Path::new(path));
append(&mut builder, &cwd.join(path), name, verbose)?;
}
builder.finish()?;
builder.into_inner()
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let mut mode = None;
let mut compressed = false;
let mut verbose = false;
let mut archive = None;
let mut cwd = PathBuf::from(".");
let mut strip = 0usize;
let mut paths = Vec::new();
while let Some(arg) = args.next() {
if arg == "--" {
paths.extend(args);
break;
}
if arg == "--strip-components" {
strip = args.next().ok_or("missing strip count")?.parse()?;
continue;
}
if let Some(n) = arg.strip_prefix("--strip-components=") {
strip = n.parse()?;
continue;
}
if arg.starts_with('-') || (mode.is_none() && arg.chars().all(|c| "cxtzvf".contains(c))) {
let flags = arg.trim_start_matches('-');
let mut chars = flags.chars();
while let Some(flag) = chars.next() {
match flag {
'c' | 'x' | 't' => {
if mode.replace(flag).is_some() {
return Err("choose one archive operation".into());
}
}
'z' => compressed = true,
'v' => verbose = true,
'f' | 'C' => {
let tail = chars.as_str();
let value = if tail.is_empty() {
args.next().ok_or("missing option value")?
} else {
tail.to_owned()
};
if flag == 'f' {
archive = Some(value);
} else {
cwd = PathBuf::from(value);
}
break;
}
_ => return Err(format!("unsupported option: {flag}").into()),
}
}
} else {
paths.push(arg);
}
}
let archive = archive.ok_or("-f is required")?;
match mode.ok_or("an operation is required")? {
'c' => {
if paths.is_empty() {
return Err("refusing to create an empty archive".into());
}
let writer: Box<dyn Write> = if archive == "-" {
Box::new(io::stdout())
} else {
Box::new(File::create(archive)?)
};
if compressed {
create(
GzEncoder::new(writer, Compression::default()),
&cwd,
&paths,
verbose,
)?
.finish()?
.flush()?;
} else {
create(writer, &cwd, &paths, verbose)?.flush()?;
}
}
operation => {
let reader: Box<dyn Read> = if archive == "-" {
Box::new(io::stdin())
} else {
Box::new(File::open(archive)?)
};
let reader: Box<dyn Read> = if compressed {
Box::new(GzDecoder::new(reader))
} else {
reader
};
let mut archive = tar::Archive::new(reader);
for entry in archive.entries()? {
let mut entry = entry?;
let name = entry.path()?.into_owned();
if !paths.is_empty() && !paths.iter().any(|p| name.starts_with(p)) {
continue;
}
if operation == 't' {
println!("{}", name.display());
continue;
}
let name: PathBuf = name
.components()
.filter(|c| !matches!(c, std::path::Component::CurDir))
.skip(strip)
.collect();
if name.as_os_str().is_empty() {
continue;
}
if name
.components()
.any(|c| !matches!(c, std::path::Component::Normal(_)))
{
return Err("archive path escapes destination".into());
}
if verbose {
println!("{}", name.display());
}
let dest = cwd.join(name);
if entry.header().entry_type().is_dir() {
std::fs::create_dir_all(dest)?;
} else if entry.header().entry_type().is_file() {
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
io::copy(&mut entry, &mut File::create(dest)?)?;
} else {
return Err(
"world extraction supports regular files and directories only".into(),
);
}
}
}
}
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("tar: {e}");
std::process::exit(2);
}
}
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env nu
# Compare archive tools and pinned preview1 commands against native tools.
def check [ok: bool, message: string] { if not $ok { error make {msg: $message} }; print $'ok - ($message)' }
def call [target: path, root: path, tool: string, args: list<string>, input?: any] {
let input = ($input | default '')
$input | ^wasmtime run -S cli-exit-with-code=y --dir $'($root)::/' ($target | path join wasm32-wasip2 release $'($tool).wasm') ...$args | complete
}
def success [] { let r = $in; if $r.exit_code != 0 { error make {msg: $r.stderr} }; $r }
def main [] {
let target = ($env.CARGO_TARGET_DIR | path expand)
let root = (^mktemp -d /tmp/t42-tools-XXXXXXXX | str trim)
mkdir ($root | path join input sub)
'hello from a nested file' | save ($root | path join input sub file)
let created = (call $target $root tar [-czf /ours.tgz -C / input] | success)
check ($created.stdout == '') 'tar create stdout'
^tar -xzf ($root | path join ours.tgz) -C $root
check ($env.LAST_EXIT_CODE == 0) 'native tar reads world gzip archive'
^tar -czf ($root | path join native.tgz) -C $root input
mkdir ($root | path join extracted)
call $target $root tar [-xzf /native.tgz -C /extracted --strip-components 1] | success | ignore
check ((open --raw ($root | path join extracted sub file)) == 'hello from a nested file') 'world extracts native archive with strip-components'
let listed = (call $target $root tar [-tf /missing.tar])
let native_error = (^tar -tf ($root | path join missing.tar) | complete)
check ($listed.exit_code == $native_error.exit_code) 'tar missing archive exit code'
let compressed = (call $target $root gzip [-c] 'binary payload' | success)
let decoded = ($compressed.stdout | ^gzip -dc | complete)
check ($decoded.exit_code == 0 and $decoded.stdout == 'binary payload') 'native gzip decodes world stream'
let native_compressed = ('reverse payload' | ^gzip -c | complete)
let decoded = (call $target $root gunzip [-c] $native_compressed.stdout | success)
check ($decoded.stdout == 'reverse payload') 'world gunzip decodes native stream'
call $target $root gzip [-k /input/sub/file] | success | ignore
check (($root | path join input sub file.gz | path exists) and ($root | path join input sub file | path exists)) 'gzip -k keeps input'
let conflict = (call $target $root gzip [/input/sub/file])
check ($conflict.exit_code == 1) 'gzip refuses overwrite'
call $target $root gunzip [-fk /input/sub/file.gz] | success | ignore
for program in ['{sum += $1} END {print sum}' '{print NR, NF, $2}' 'BEGIN {for (i=1;i<=4;i++) print i*i}'] {
let native = ("2 a\n3 b\n" | ^awk $program | complete)
let wasm = ("2 a\n3 b\n" | ^wasmtime run --dir $'($root)::/' ($target | path join awk.wasm) $program | complete)
check ($native.stdout == $wasm.stdout and $native.exit_code == $wasm.exit_code) $'awk ($program)'
}
let qjs = (^wasmtime run ($env.FILE_PWD | path join .. qjs qjs.wasm) -e 'console.log(6*7)' | complete)
check ($qjs.exit_code == 0 and ($qjs.stdout | str trim) == '42') 'qjs -e'
print $'evidence: ($root)'
}
+2
View File
@@ -16,6 +16,8 @@ def main [destination?: path] {
^nu ($here | path join brush build.nu)
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush build failed'} }
mkdir $bin
^nu ($here | path join preview1.nu) $bin
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'preview1 build failed'} }
for package in ($metadata.packages | where {|p| $p.id in $metadata.workspace_members }) {
let tools = ($package.manifest_path | path dirname | path join tools.txt)
for binary in ($package.targets | where {|t| 'bin' in $t.kind }) {
+14
View File
@@ -0,0 +1,14 @@
# esbuild
`source/` contains esbuild v0.27.7 from https://github.com/evanw/esbuild.
The pinned commit is `2025c9ff6ab15ba6b0f9d074fd732250cc46e4a3`.
The source includes its MIT license.
The `vendor/` directory contains the Go dependency selected by the upstream `go.mod` and `go.sum`.
`userland/preview1.nu` builds this source for `GOOS=wasip1 GOARCH=wasm` with the native Go compiler.
It disables the module proxy and uses the vendored dependency.
It copies the result to `bin/esbuild.wasm` without a guest adapter, as D41 requires.
The bundle source snapshot includes this source under `/src/sausage/userland/esbuild/source`.
The build follows the accepted S6 source recipe.
It does not install npm dependencies in the world.
+7
View File
@@ -0,0 +1,7 @@
[*]
indent_style = tab
indent_size = 2
[*.{js,json,ts}]
indent_style = space
indent_size = 2
@@ -0,0 +1,16 @@
---
name: New issue
about: This is the template for new issues.
title: ''
labels: ''
assignees: ''
---
<!--
When reporting a bug or requesting a feature, please do the following:
* Describe what esbuild is doing incorrectly and what it should be doing instead.
* Provide a way to reproduce the issue. The best way to do this is to demonstrate the issue on the playground (https://esbuild.github.io/try/) and paste the URL here. A link to a minimal code sample with instructions for how to reproduce the issue may also work. Issues without a way to reproduce them may be closed.
-->
+331
View File
@@ -0,0 +1,331 @@
name: CI
on:
push:
branches: ['*']
pull_request:
branches: ['*']
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
esbuild-platforms:
# Split this out into its own runner because it's slow
name: esbuild CI (All Platforms)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 18
- name: Ensure all platforms can be built
run: |
git status
make platform-all
# Plan 9 is not a supported platform, but someone wanted esbuild to be able to build for it anyway...
- name: Ensure esbuild can be built for Plan 9
run: |
GOOS=plan9 GOARCH=386 go build ./cmd/esbuild
GOOS=plan9 GOARCH=amd64 go build ./cmd/esbuild
GOOS=plan9 GOARCH=arm go build ./cmd/esbuild
- name: Test an old TypeScript version
run: make test-old-ts
esbuild-slow:
# Split these out into their own runner because they're very slow
name: esbuild CI (Slow Tests)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 16
# Note: These tests break with node version 18. Something about WebAssembly.
- name: Rollup Tests
run: make test-rollup
- name: Uglify Tests
run: CI=1 make uglify
- name: Type check tsc using tsc
run: make test-tsc
esbuild:
name: esbuild CI
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version (non-Windows)
if: matrix.os != 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Read go.version (Windows)
if: matrix.os == 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $Env:GITHUB_ENV
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 18
- name: Setup Deno 1.40.0
uses: denoland/setup-deno@main
with:
deno-version: v1.40.0
- name: go test
run: go test -race ./internal/...
- name: go vet
run: go vet ./cmd/... ./internal/... ./pkg/...
- name: Deno Tests (non-Windows)
if: matrix.os != 'windows-latest'
run: make test-deno
- name: Deno Tests (Windows)
if: matrix.os == 'windows-latest'
run: make test-deno-windows
- name: Test for path/filepath
if: matrix.os == 'ubuntu-latest'
run: make no-filepath
- name: Make sure "check-go-version" works (non-Windows)
if: matrix.os != 'windows-latest'
run: make check-go-version
- name: go fmt
if: matrix.os == 'macos-latest'
run: make fmt-go
- name: npm ci
run: cd scripts && npm ci
- name: Register Test (ESBUILD_WORKER_THREADS=0, non-Windows)
if: matrix.os != 'windows-latest'
run: ESBUILD_WORKER_THREADS=0 node scripts/register-test.js
- name: Register Test
run: node scripts/register-test.js
- name: Verify Source Map
run: node scripts/verify-source-map.js
- name: E2E Tests
run: node scripts/end-to-end-tests.js
- name: JS API Tests (ESBUILD_WORKER_THREADS=0, non-Windows)
if: matrix.os != 'windows-latest'
run: ESBUILD_WORKER_THREADS=0 node scripts/js-api-tests.js
- name: JS API Tests
run: node scripts/js-api-tests.js
- name: NodeJS Unref Tests
run: node scripts/node-unref-tests.js
- name: Plugin Tests
run: node scripts/plugin-tests.js
- name: TypeScript Type Definition Tests
if: matrix.os == 'ubuntu-latest'
run: node scripts/ts-type-tests.js
- name: JS API Type Check
if: matrix.os == 'ubuntu-latest'
run: make lib-typecheck
- name: Decorator Tests
if: matrix.os == 'ubuntu-latest'
run: make decorator-tests
- name: WebAssembly API Tests (browser)
if: matrix.os == 'ubuntu-latest'
run: make test-wasm-browser
- name: WebAssembly API Tests (node, Linux)
if: matrix.os == 'ubuntu-latest'
run: make test-wasm-node
- name: WebAssembly API Tests (node, non-Linux)
if: matrix.os != 'ubuntu-latest'
run: node scripts/wasm-tests.js
- name: Sucrase Tests
if: matrix.os == 'ubuntu-latest'
run: make test-sucrase
- name: Esprima Tests
if: matrix.os == 'ubuntu-latest'
run: make test-esprima
- name: Preact Splitting Tests
if: matrix.os == 'ubuntu-latest'
run: make test-preact-splitting
- name: Check the unicode table generator
if: matrix.os == 'ubuntu-latest'
run: cd scripts && node gen-unicode-table.js
- name: Yarn PnP tests
run: |
# Note that Yarn recently deliberately broke "npm install -g yarn".
# They say you now have to run "corepack enable" to fix it. They have
# written about this here: https://yarnpkg.com/corepack
corepack enable
make test-yarnpnp
esbuild-old-go-version:
name: esbuild CI (old Go version)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Go 1.13 (the minimum required Go version for esbuild)
uses: actions/setup-go@v3
with:
go-version: 1.13
id: go
- name: go build
run: go build ./cmd/esbuild
- name: go test
run: go test ./internal/...
esbuild-old-deno-version:
name: esbuild CI (old Deno version)
runs-on: ${{ matrix.os }}
strategy:
matrix:
# Note: I'm excluding "macos-latest" here because GitHub recently
# changed their macOS CI VMs from x86_64 to aarch64 (i.e. from Intel
# to ARM) and it looks like old Deno versions have WASM bugs on ARM.
# Specifically, this test now crashes like this when run on macOS:
#
# #
# # Fatal error in , line 0
# # Check failed: RwxMemoryWriteScope::IsAllowed().
# #
# #
# #
# #FailureMessage Object: 0x16f282368
# ==== C stack trace ===============================
#
# 0 deno 0x0000000101bb8e78 v8::base::debug::StackTrace::StackTrace() + 24
# 1 deno 0x0000000101bbda84 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
# 2 deno 0x0000000101bb6230 V8_Fatal(char const*, ...) + 268
# 3 deno 0x000000010227e468 v8::internal::wasm::WasmCodeManager::MemoryProtectionKeysEnabled() const + 0
# 4 deno 0x0000000102299994 v8::internal::wasm::WasmEngine::InitializeOncePerProcess() + 44
# 5 deno 0x0000000101e78fd0 v8::internal::V8::Initialize() + 1576
# 6 deno 0x0000000101c3b7d8 v8::V8::Initialize(int) + 32
# 7 deno 0x00000001011833dc _ZN3std4sync4once4Once9call_once28_$u7b$$u7b$closure$u7d$$u7d$17h2bbe74d315ab3e84E + 488
# 8 deno 0x00000001017f8854 std::sync::once::Once::call_inner::h70fbdd48fe002a01 + 724
# 9 deno 0x000000010115ca80 deno_core::runtime::JsRuntime::new::h9c5f1a9c910f1eed + 192
# 10 deno 0x00000001014d3b50 deno_runtime::worker::MainWorker::bootstrap_from_options::h91a0eaac48dfc18e + 4260
# 11 deno 0x0000000100ee692c deno::create_main_worker::h0d1622755821ae7f + 1608
# 12 deno 0x0000000100f6c688 _ZN97_$LT$core..future..from_generator..GenFuture$LT$T$GT$$u20$as$u20$core..future..future..Future$GT$4poll17h87ddfac9566887c8E + 492
# 13 deno 0x0000000100f6ba18 tokio::runtime::task::raw::poll::h7d51f1a7d5a61c15 + 1396
# 14 deno 0x0000000101917b98 std::sys_common::backtrace::__rust_begin_short_backtrace::hd384935dcffe6f2d + 332
# 15 deno 0x0000000101917954 _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17he2755732d5d29cf0E + 124
# 16 deno 0x0000000101829684 std::sys::unix::thread::Thread::new::thread_start::h432bc30153e41f60 + 48
# 17 libsystem_pthread.dylib 0x000000018a436f94 _pthread_start + 136
# 18 libsystem_pthread.dylib 0x000000018a431d34 thread_start + 8
#
# Hopefully running this old Deno version on Linux is sufficiently
# close to running it on macOS. For reference, I believe this is the
# change that GitHub made which broke this test:
# https://github.blog/changelog/2023-10-02-github-actions-apple-silicon-m1-macos-runners-are-now-available-in-public-beta/
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version (non-Windows)
if: matrix.os != 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Read go.version (Windows)
if: matrix.os == 'windows-latest'
run: |
echo "GO_VERSION=$(cat go.version)" >> $Env:GITHUB_ENV
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
id: go
# Make sure esbuild works with old versions of Deno. Note: It's important
# to test a version before 1.31.0, which introduced the "Deno.Command" API.
- name: Setup Deno 1.24.0
uses: denoland/setup-deno@main
with:
deno-version: v1.24.0
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 18
- name: Deno Tests (non-Windows)
if: matrix.os != 'windows-latest'
run: make test-deno
- name: Deno Tests (Windows)
if: matrix.os == 'windows-latest'
run: make test-deno-windows
+52
View File
@@ -0,0 +1,52 @@
name: End-to-end install tests
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 18
# The version of Deno is pinned because version 1.25.1 was causing test
# flakes due to random segfaults.
- name: Setup Deno 1.24.0
uses: denoland/setup-deno@main
with:
deno-version: v1.24.0
- name: Test npm
run: |
npm i -g npm@next-7
time make test-e2e-npm
- name: Test pnpm
run: |
npm i -g pnpm@next-7
time make test-e2e-pnpm
- name: Test yarn (classic)
run: |
npm i -g yarn@latest
time make test-e2e-yarn
- name: Test yarn (berry)
run: |
npm i -g yarn@latest
time make test-e2e-yarn-berry
- name: Test deno
run: |
time make test-e2e-deno
+89
View File
@@ -0,0 +1,89 @@
name: Publish
permissions:
id-token: write
contents: write
on:
push:
branches:
- main
paths:
- version.txt
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read version info
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
echo "ESBUILD_VERSION=$(cat version.txt)" >> $GITHUB_ENV
# This is here to fail quickly if the release already exists
- name: Try to create the "v${{ env.ESBUILD_VERSION }}" tag
run: |
git fetch --tags
git tag "v$ESBUILD_VERSION"
- name: Extract the release notes
run: |
CHANGELOG=$(awk -v "ver=$ESBUILD_VERSION" '/^## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p' CHANGELOG.md)
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
echo "$CHANGELOG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
# Make sure we'll be able to generate release notes later on below
- name: Release notes must not be empty
run: |
test -n "$CHANGELOG"
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 24
# This updates the version in all "package.json" files
- name: Build for all platforms
run: |
git status
make platform-all
# All "package.json" files should have been updated already by running "make platform-all" and committing the results
- name: Reject uncommitted/untracked changes
run: |
git status --porcelain
test -z "$(git status --porcelain)"
# Trusted publishing requires this specific version of npm
- name: Install npm
run: |
npm install -g npm@11.5.1
- name: Publish packages
run: |
make publish-all
- name: Push the tag to GitHub
run: |
git push origin tag "v$ESBUILD_VERSION"
# Only do this after publishing was successful
- name: Create a GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ env.ESBUILD_VERSION }}
release_name: v${{ env.ESBUILD_VERSION }}
body: ${{ env.CHANGELOG }}
draft: false
prerelease: false
+30
View File
@@ -0,0 +1,30 @@
name: Validate release builds
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Read go.version
run: |
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
- name: Set up Go 1.x
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Validation checks
run: |
make validate-builds
+36
View File
@@ -0,0 +1,36 @@
.DS_Store
.idea/
.vscode/
/bench/
/demo/
/deno/
/esbuild
/github/
/go/
/lib/deno/lib.deno.d.ts
/npm/@esbuild/android-arm/esbuild.wasm
/npm/@esbuild/android-arm/wasm_exec_node.js
/npm/@esbuild/android-arm/wasm_exec.js
/npm/@esbuild/android-x64/esbuild.wasm
/npm/@esbuild/android-x64/wasm_exec_node.js
/npm/@esbuild/android-x64/wasm_exec.js
/npm/@esbuild/openharmony-arm64/esbuild.wasm
/npm/@esbuild/openharmony-arm64/wasm_exec_node.js
/npm/@esbuild/openharmony-arm64/wasm_exec.js
/npm/@esbuild/wasi-preview1/esbuild.wasm
/npm/esbuild-wasm/browser.js
/npm/esbuild-wasm/esbuild.wasm
/npm/esbuild-wasm/esm/
/npm/esbuild-wasm/lib/
/npm/esbuild-wasm/wasm_exec_node.js
/npm/esbuild-wasm/wasm_exec.js
/npm/esbuild/install.js
/npm/esbuild/lib/
/require/*/bench/
/require/*/demo/
/scripts/.*/
/validate/
/www
bin
esbuild.exe
node_modules/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+251
View File
@@ -0,0 +1,251 @@
# Changelog
## 0.27.7
* Fix lowering of define semantics for TypeScript parameter properties ([#4421](https://github.com/evanw/esbuild/issues/4421))
The previous release incorrectly generated class fields for TypeScript parameter properties even when the configured target environment does not support class fields. With this release, the generated class fields will now be correctly lowered in this case:
```ts
// Original code
class Foo {
constructor(public x = 1) {}
y = 2
}
// Old output (with --loader=ts --target=es2021)
class Foo {
constructor(x = 1) {
this.x = x;
__publicField(this, "y", 2);
}
x;
}
// New output (with --loader=ts --target=es2021)
class Foo {
constructor(x = 1) {
__publicField(this, "x", x);
__publicField(this, "y", 2);
}
}
```
## 0.27.5
* Fix for an async generator edge case ([#4401](https://github.com/evanw/esbuild/issues/4401), [#4417](https://github.com/evanw/esbuild/pull/4417))
Support for transforming async generators into the equivalent state machine was added in version 0.19.0. However, the generated state machine didn't work correctly when polling async generators concurrently, such as in the following code:
```js
async function* inner() { yield 1; yield 2 }
async function* outer() { yield* inner() }
let gen = outer()
for await (let x of [gen.next(), gen.next()]) console.log(x)
```
Previously esbuild's output of the above code behaved incorrectly when async generators were transformed (such as with `--supported:async-generator=false`). The transformation should be fixed starting with this release.
This fix was contributed by [@2767mr](https://github.com/2767mr).
* Fix a regression when `metafile` is enabled ([#4420](https://github.com/evanw/esbuild/issues/4420), [#4418](https://github.com/evanw/esbuild/pull/4418))
This release fixes a regression introduced by the previous release. When `metafile: true` was enabled in esbuild's JavaScript API, builds with build errors were incorrectly throwing an error about an empty JSON string instead of an object containing the build errors.
* Use define semantics for TypeScript parameter properties ([#4421](https://github.com/evanw/esbuild/issues/4421))
Parameter properties are a TypeScript-specific code generation feature that converts constructor parameters into class fields when they are prefixed by certain keywords. When `"useDefineForClassFields": true` is present in `tsconfig.json`, the TypeScript compiler automatically generates class field declarations for parameter properties. Previously esbuild didn't do this, but esbuild will now do this starting with this release:
```ts
// Original code
class Foo {
constructor(public x: number) {}
}
// Old output (with --loader=ts)
class Foo {
constructor(x) {
this.x = x;
}
}
// New output (with --loader=ts)
class Foo {
constructor(x) {
this.x = x;
}
x;
}
```
* Allow `es2025` as a target in `tsconfig.json` ([#4432](https://github.com/evanw/esbuild/issues/4432))
TypeScript recently [added `es2025`](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#es2025-option-for-target-and-lib) as a compilation target, so esbuild now supports this in the `target` field of `tsconfig.json` files, such as in the following configuration file:
```json
{
"compilerOptions": {
"target": "ES2025"
}
}
```
As a reminder, the only thing that esbuild uses this field for is determining whether or not to use legacy TypeScript behavior for class fields. You can read more in [the documentation](https://esbuild.github.io/content-types/#tsconfig-json).
## 0.27.4
* Fix a regression with CSS media queries ([#4395](https://github.com/evanw/esbuild/issues/4395), [#4405](https://github.com/evanw/esbuild/issues/4405), [#4406](https://github.com/evanw/esbuild/issues/4406))
Version 0.25.11 of esbuild introduced support for parsing media queries. This unintentionally introduced a regression with printing media queries that use the `<media-type> and <media-condition-without-or>` grammar. Specifically, esbuild was failing to wrap an `or` clause with parentheses when inside `<media-condition-without-or>`. This release fixes the regression.
Here is an example:
```css
/* Original code */
@media only screen and ((min-width: 10px) or (min-height: 10px)) {
a { color: red }
}
/* Old output (incorrect) */
@media only screen and (min-width: 10px) or (min-height: 10px) {
a {
color: red;
}
}
/* New output (correct) */
@media only screen and ((min-width: 10px) or (min-height: 10px)) {
a {
color: red;
}
}
```
* Fix an edge case with the `inject` feature ([#4407](https://github.com/evanw/esbuild/issues/4407))
This release fixes an edge case where esbuild's `inject` feature could not be used with arbitrary module namespace names exported using an `export {} from` statement with bundling disabled and a target environment where arbitrary module namespace names is unsupported.
With the fix, the following `inject` file:
```js
import jquery from 'jquery';
export { jquery as 'window.jQuery' };
```
Can now always be rewritten as this without esbuild sometimes incorrectly generating an error:
```js
export { default as 'window.jQuery' } from 'jquery';
```
* Attempt to improve API handling of huge metafiles ([#4329](https://github.com/evanw/esbuild/issues/4329), [#4415](https://github.com/evanw/esbuild/issues/4415))
This release contains a few changes that attempt to improve the behavior of esbuild's JavaScript API with huge metafiles (esbuild's name for the build metadata, formatted as a JSON object). The JavaScript API is designed to return the metafile JSON as a JavaScript object in memory, which makes it easy to access from within a JavaScript-based plugin. Multiple people have encountered issues where this API breaks down with a pathologically-large metafile.
The primary issue is that V8 has an implementation-specific maximum string length, so using the `JSON.parse` API with large enough strings is impossible. This release will now attempt to use a fallback JavaScript-based JSON parser that operates directly on the UTF8-encoded JSON bytes instead of using `JSON.parse` when the JSON metafile is too big to fit in a JavaScript string. The new fallback path has not yet been heavily-tested. The metafile will also now be generated with whitespace removed if the bundle is significantly large, which will reduce the size of the metafile JSON slightly.
However, hitting this case is potentially a sign that something else is wrong. Ideally you wouldn't be building something so enormous that the build metadata can't even fit inside a JavaScript string. You may want to consider optimizing your project, or breaking up your project into multiple parts that are built independently. Another option could potentially be to use esbuild's command-line API instead of its JavaScript API, which is more efficient (although of course then you can't use JavaScript plugins, so it may not be an option).
## 0.27.3
* Preserve URL fragments in data URLs ([#4370](https://github.com/evanw/esbuild/issues/4370))
Consider the following HTML, CSS, and SVG:
* `index.html`:
```html
<!DOCTYPE html>
<html>
<head><link rel="stylesheet" href="icons.css"></head>
<body><div class="triangle"></div></body>
</html>
```
* `icons.css`:
```css
.triangle {
width: 10px;
height: 10px;
background: currentColor;
clip-path: url(./triangle.svg#x);
}
```
* `triangle.svg`:
```xml
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="x">
<path d="M0 0H10V10Z"/>
</clipPath>
</defs>
</svg>
```
The CSS uses a URL fragment (the `#x`) to reference the `clipPath` element in the SVG file. Previously esbuild's CSS bundler didn't preserve the URL fragment when bundling the SVG using the `dataurl` loader, which broke the bundled CSS. With this release, esbuild will now preserve the URL fragment in the bundled CSS:
```css
/* icons.css */
.triangle {
width: 10px;
height: 10px;
background: currentColor;
clip-path: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="x"><path d="M0 0H10V10Z"/></clipPath></defs></svg>#x');
}
```
* Parse and print CSS `@scope` rules ([#4322](https://github.com/evanw/esbuild/issues/4322))
This release includes dedicated support for parsing `@scope` rules in CSS. These rules include optional "start" and "end" selector lists. One important consequence of this is that the local/global status of names in selector lists is now respected, which improves the correctness of esbuild's support for [CSS modules](https://esbuild.github.io/content-types/#local-css). Minification of selectors inside `@scope` rules has also improved slightly.
Here's an example:
```css
/* Original code */
@scope (:global(.foo)) to (:local(.bar)) {
.bar {
color: red;
}
}
/* Old output (with --loader=local-css --minify) */
@scope (:global(.foo)) to (:local(.bar)){.o{color:red}}
/* New output (with --loader=local-css --minify) */
@scope(.foo)to (.o){.o{color:red}}
```
* Fix a minification bug with lowering of `for await` ([#4378](https://github.com/evanw/esbuild/pull/4378), [#4385](https://github.com/evanw/esbuild/pull/4385))
This release fixes a bug where the minifier would incorrectly strip the variable in the automatically-generated `catch` clause of lowered `for await` loops. The code that generated the loop previously failed to mark the internal variable references as used.
* Update the Go compiler from v1.25.5 to v1.25.7 ([#4383](https://github.com/evanw/esbuild/issues/4383), [#4388](https://github.com/evanw/esbuild/pull/4388))
This PR was contributed by [@MikeWillCook](https://github.com/MikeWillCook).
## 2025
All esbuild versions published in the year 2025 (versions 0.25.0 through 0.27.2) can be found in [CHANGELOG-2025.md](./CHANGELOG-2025.md).
## 2024
All esbuild versions published in the year 2024 (versions 0.19.12 through 0.24.2) can be found in [CHANGELOG-2024.md](./CHANGELOG-2024.md).
## 2023
All esbuild versions published in the year 2023 (versions 0.16.13 through 0.19.11) can be found in [CHANGELOG-2023.md](./CHANGELOG-2023.md).
## 2022
All esbuild versions published in the year 2022 (versions 0.14.11 through 0.16.12) can be found in [CHANGELOG-2022.md](./CHANGELOG-2022.md).
## 2021
All esbuild versions published in the year 2021 (versions 0.8.29 through 0.14.10) can be found in [CHANGELOG-2021.md](./CHANGELOG-2021.md).
## 2020
All esbuild versions published in the year 2020 (versions 0.3.0 through 0.8.28) can be found in [CHANGELOG-2020.md](./CHANGELOG-2020.md).
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Evan Wallace
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/wordmark-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="./images/wordmark-light.svg">
<img alt="esbuild: An extremely fast JavaScript bundler" src="./images/wordmark-light.svg">
</picture>
<br>
<a href="https://esbuild.github.io/">Website</a> |
<a href="https://esbuild.github.io/getting-started/">Getting started</a> |
<a href="https://esbuild.github.io/api/">Documentation</a> |
<a href="https://esbuild.github.io/plugins/">Plugins</a> |
<a href="https://esbuild.github.io/faq/">FAQ</a>
</p>
## Why?
Our current build tools for the web are 10-100x slower than they could be:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/benchmark-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="./images/benchmark-light.svg">
<img alt="Bar chart with benchmark results" src="./images/benchmark-light.svg">
</picture>
</p>
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.
Major features:
- Extreme speed without needing a cache
- [JavaScript](https://esbuild.github.io/content-types/#javascript), [CSS](https://esbuild.github.io/content-types/#css), [TypeScript](https://esbuild.github.io/content-types/#typescript), and [JSX](https://esbuild.github.io/content-types/#jsx) built-in
- A straightforward [API](https://esbuild.github.io/api/) for CLI, JS, and Go
- Bundles ESM and CommonJS modules
- Bundles CSS including [CSS modules](https://github.com/css-modules/css-modules)
- Tree shaking, [minification](https://esbuild.github.io/api/#minify), and [source maps](https://esbuild.github.io/api/#sourcemap)
- [Local server](https://esbuild.github.io/api/#serve), [watch mode](https://esbuild.github.io/api/#watch), and [plugins](https://esbuild.github.io/plugins/)
Check out the [getting started](https://esbuild.github.io/getting-started/) instructions if you want to give esbuild a try.
+73
View File
@@ -0,0 +1,73 @@
# Runbook
This documents some maintenance tasks for esbuild so I don't forget how they
work. There are a lot of moving parts now that esbuild uses trusted publishing.
## Publishing a release
Publishing a release is now done by using GitHub Actions as a
[trusted publisher](https://docs.npmjs.com/trusted-publishers).
To publish a new release:
1. Update the version in [`version.txt`](./version.txt). Only include the number. Do not include a leading `v`.
2. Copy that version verbatim (without the leading `v`) to a `##` header in [`CHANGELOG.md`](./CHANGELOG.md). This usually replaces the `## Unreleased` header used for unreleased changes.
3. Run `make platform-all` to update the version number in all `package.json` files. The publishing workflow will fail without this step.
4. Commit and push using a message such as `publish 0.X.Y to npm`. This should trigger the publishing workflow described below.
Pushing a change to [`version.txt`](./version.txt) causes the following:
- The [`publish.yml`](./.github/workflows/publish.yml) workflow in this repo
will be triggered, which will:
1. Build and publish all npm packages to npm using trusted publishing
2. Create a tag for the release that looks like `v0.X.Y`
3. Publish a [GitHub Release](https://github.com/evanw/esbuild/releases) containing the release notes in [`CHANGELOG.md`](./CHANGELOG.md)
- The [`release.yml`](https://github.com/esbuild/deno-esbuild/blob/main/.github/workflows/release.yml)
workflow in the https://github.com/esbuild/deno-esbuild repo runs
occasionally. On the next run, it will notice the version change and:
1. Clone this repo
2. Run `make platform-deno`
3. Commit and push the new contents of the `deno` folder to the `deno-esbuild` repo
4. Create a tag for the release that looks like `v0.X.Y`
5. Post an event to the https://api.deno.land/webhook/gh/esbuild webhook
6. Deno will then add a new version to https://deno.land/x/esbuild
You can also manually trigger this workflow if you want it to happen immediately.
- The [`release.yml`](https://github.com/esbuild/esbuild.github.io/blob/main/.github/workflows/release.yml)
workflow in the https://github.com/esbuild/esbuild.github.io repo runs
occasionally. On the next run, it will notice the version change and:
1. Create a new `dl/v0.X.Y` script for the new version number
2. Update the `dl/latest` script with the new version number
3. Commit and push these new scripts to the `gh-pages` branch of the `esbuild.github.io` repo
4. GitHub Pages will then deploy these updates to https://esbuild.github.io/
You can also manually trigger this workflow if you want it to happen immediately.
## Adding a new package
Each platform (operating system + architecture) needs a separate optional npm
package due to how esbuild's installer works. New packages should be created
under the `@esbuild/` scope so it's obvious that they are official.
Create a directory for the new package inside the [`npm/@esbuild`](./npm/@esbuild/)
directory. Then modify the rest of the repo to reference the new package. The
specifics for what to modify depends on the platform, but a good place to
start is to search for the name of a similar existing package and see where
it's used.
In addition, you'll need to prepare that package for the next release. To do
that:
1. Create an empty package with the expected name and a version of 0.0.1
2. Publish it with `npm publish --access public` (note that scoped packages are private by default)
3. Log in to the npm website and go to the package settings
4. Ensure that the only maintainer is the [esbuild](https://www.npmjs.com/~esbuild) user
5. Add the GitHub repo as the trusted publisher:
- **Organization or user:** `evanw`
- **Repository:** `esbuild`
- **Workflow filename:** `publish.yml`
6. Ensure publishing access is set to **Require two-factor authentication and disallow tokens (recommended)**
+386
View File
@@ -0,0 +1,386 @@
package main
import (
"fmt"
"io"
"os"
"runtime/debug"
"strings"
"time"
"github.com/evanw/esbuild/internal/api_helpers"
"github.com/evanw/esbuild/internal/logger"
"github.com/evanw/esbuild/pkg/cli"
)
var helpText = func(colors logger.Colors) string {
// Read "NO_COLOR" from the environment. This is a convention that some
// software follows. See https://no-color.org/ for more information.
if _, ok := os.LookupEnv("NO_COLOR"); ok {
colors = logger.Colors{}
}
return `
` + colors.Bold + `Usage:` + colors.Reset + `
esbuild [options] [entry points]
` + colors.Bold + `Documentation:` + colors.Reset + `
` + colors.Underline + `https://esbuild.github.io/` + colors.Reset + `
` + colors.Bold + `Repository:` + colors.Reset + `
` + colors.Underline + `https://github.com/evanw/esbuild` + colors.Reset + `
` + colors.Bold + `Simple options:` + colors.Reset + `
--bundle Bundle all dependencies into the output files
--define:K=V Substitute K with V while parsing
--external:M Exclude module M from the bundle (can use * wildcards)
--format=... Output format (iife | cjs | esm, no default when not
bundling, otherwise default is iife when platform
is browser and cjs when platform is node)
--loader:X=L Use loader L to load file extension X, where L is
one of: base64 | binary | copy | css | dataurl |
empty | file | global-css | js | json | jsx |
local-css | text | ts | tsx
--minify Minify the output (sets all --minify-* flags)
--outdir=... The output directory (for multiple entry points)
--outfile=... The output file (for one entry point)
--packages=... Set to "external" to avoid bundling any package
--platform=... Platform target (browser | node | neutral,
default browser)
--serve=... Start a local HTTP server on this host:port for outputs
--sourcemap Emit a source map
--splitting Enable code splitting (currently only for esm)
--target=... Environment target (e.g. es2017, chrome58, firefox57,
safari11, edge16, node10, ie9, opera45, default esnext)
--watch Watch mode: rebuild on file system changes (stops when
stdin is closed, use "--watch=forever" to ignore stdin)
` + colors.Bold + `Advanced options:` + colors.Reset + `
--abs-paths=... Emit absolute instead of relative paths in these
situations (code | log | metafile)
--allow-overwrite Allow output files to overwrite input files
--analyze Print a report about the contents of the bundle
(use "--analyze=verbose" for a detailed report)
--asset-names=... Path template to use for "file" loader files
(default "[name]-[hash]")
--banner:T=... Text to be prepended to each output file of type T
where T is one of: css | js
--certfile=... Certificate for serving HTTPS (see also "--keyfile")
--charset=utf8 Do not escape UTF-8 code points
--chunk-names=... Path template to use for code splitting chunks
(default "[name]-[hash]")
--color=... Force use of color terminal escapes (true | false)
--cors-origin=... Allow cross-origin requests from this origin
--drop:... Remove certain constructs (console | debugger)
--drop-labels=... Remove labeled statements with these label names
--entry-names=... Path template to use for entry point output paths
(default "[dir]/[name]", can also use "[hash]")
--footer:T=... Text to be appended to each output file of type T
where T is one of: css | js
--global-name=... The name of the global for the IIFE format
--ignore-annotations Enable this to work with packages that have
incorrect tree-shaking annotations
--inject:F Import the file F into all input files and
automatically replace matching globals with imports
--jsx-dev Use React's automatic runtime in development mode
--jsx-factory=... What to use for JSX instead of React.createElement
--jsx-fragment=... What to use for JSX instead of React.Fragment
--jsx-import-source=... Override the package name for the automatic runtime
(default "react")
--jsx-side-effects Do not remove unused JSX expressions
--jsx=... Set to "automatic" to use React's automatic runtime
or to "preserve" to disable transforming JSX to JS
--keep-names Preserve "name" on functions and classes
--keyfile=... Key for serving HTTPS (see also "--certfile")
--legal-comments=... Where to place legal comments (none | inline |
eof | linked | external, default eof when bundling
and inline otherwise)
--line-limit=... Lines longer than this will be wrap onto a new line
--log-level=... Disable logging (verbose | debug | info | warning |
error | silent, default info)
--log-limit=... Maximum message count or 0 to disable (default 6)
--log-override:X=Y Use log level Y for log messages with identifier X
--main-fields=... Override the main file order in package.json
(default "browser,module,main" when platform is
browser and "main,module" when platform is node)
--mangle-cache=... Save "mangle props" decisions to a JSON file
--mangle-props=... Rename all properties matching a regular expression
--mangle-quoted=... Enable renaming of quoted properties (true | false)
--metafile=... Write metadata about the build to a JSON file
(see also: ` + colors.Underline + `https://esbuild.github.io/analyze/` + colors.Reset + `)
--minify-whitespace Remove whitespace in output files
--minify-identifiers Shorten identifiers in output files
--minify-syntax Use equivalent but shorter syntax in output files
--out-extension:.js=.mjs Use a custom output extension instead of ".js"
--outbase=... The base path used to determine entry point output
paths (for multiple entry points)
--preserve-symlinks Disable symlink resolution for module lookup
--public-path=... Set the base URL for the "file" loader
--pure:N Mark the name N as a pure function for tree shaking
--reserve-props=... Do not mangle these properties
--resolve-extensions=... A comma-separated list of implicit extensions
(default ".tsx,.ts,.jsx,.js,.css,.json")
--serve-fallback=... Serve this HTML page when the request doesn't match
--servedir=... What to serve in addition to generated output files
--source-root=... Sets the "sourceRoot" field in generated source maps
--sourcefile=... Set the source file for the source map (for stdin)
--sourcemap=external Do not link to the source map with a comment
--sourcemap=inline Emit the source map with an inline data URL
--sources-content=false Omit "sourcesContent" in generated source maps
--supported:F=... Consider syntax F to be supported (true | false)
--tree-shaking=... Force tree shaking on or off (false | true)
--tsconfig=... Use this tsconfig.json file instead of other ones
--tsconfig-raw=... Override all tsconfig.json files with this string
--version Print the current version (` + esbuildVersion + `) and exit
--watch-delay=... Wait before watch mode rebuilds (in milliseconds)
` + colors.Bold + `Examples:` + colors.Reset + `
` + colors.Dim + `# Produces dist/entry_point.js and dist/entry_point.js.map` + colors.Reset + `
esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap
` + colors.Dim + `# Allow JSX syntax in .js files` + colors.Reset + `
esbuild --bundle entry_point.js --outfile=out.js --loader:.js=jsx
` + colors.Dim + `# Substitute the identifier RELEASE for the literal true` + colors.Reset + `
esbuild example.js --outfile=out.js --define:RELEASE=true
` + colors.Dim + `# Provide input via stdin, get output via stdout` + colors.Reset + `
esbuild --minify --loader=ts < input.ts > output.js
` + colors.Dim + `# Automatically rebuild when input files are changed` + colors.Reset + `
esbuild app.ts --bundle --watch
` + colors.Dim + `# Start a local HTTP server for everything in "www"` + colors.Reset + `
esbuild app.ts --bundle --servedir=www --outdir=www/js
`
}
func main() {
logger.API = logger.CLIAPI
osArgs := os.Args[1:]
heapFile := ""
traceFile := ""
cpuprofileFile := ""
isRunningService := false
sendPings := false
isWatch := false
isWatchForever := false
isServe := false
// Do an initial scan over the argument list
argsEnd := 0
for _, arg := range osArgs {
switch {
// Show help if a common help flag is provided
case arg == "-h", arg == "-help", arg == "--help", arg == "/?":
logger.PrintText(os.Stdout, logger.LevelSilent, os.Args, helpText)
os.Exit(0)
// Special-case the version flag here
case arg == "--version":
fmt.Printf("%s\n", esbuildVersion)
os.Exit(0)
case strings.HasPrefix(arg, "--heap="):
heapFile = arg[len("--heap="):]
case strings.HasPrefix(arg, "--trace="):
traceFile = arg[len("--trace="):]
case strings.HasPrefix(arg, "--timing"):
// This is a hidden flag because it's only intended for debugging esbuild
// itself. The output is not documented and not stable.
api_helpers.UseTimer = true
case strings.HasPrefix(arg, "--cpuprofile="):
cpuprofileFile = arg[len("--cpuprofile="):]
// This flag turns the process into a long-running service that uses
// message passing with the host process over stdin/stdout
case strings.HasPrefix(arg, "--service="):
hostVersion := arg[len("--service="):]
isRunningService = true
// Validate the host's version number to make sure esbuild was installed
// correctly. This check was added because some people have reported
// errors that appear to indicate an incorrect installation.
if hostVersion != esbuildVersion {
logger.PrintErrorToStderr(osArgs,
fmt.Sprintf("Cannot start service: Host version %q does not match binary version %q",
hostVersion, esbuildVersion))
os.Exit(1)
}
case strings.HasPrefix(arg, "--ping"):
sendPings = true
default:
// Some people want to be able to run esbuild's watch mode such that it
// never exits. However, esbuild ends watch mode when stdin is closed
// because stdin is always closed when the parent process terminates, so
// ending watch mode when stdin is closed is a good way to avoid
// accidentally creating esbuild processes that live forever.
//
// Explicitly allow processes that live forever with "--watch=forever".
// This may be a reasonable thing to do in a short-lived VM where all
// processes in the VM are only started once and then the VM is killed
// when the processes are no longer needed.
if arg == "--watch" || arg == "--watch=true" {
isWatch = true
} else if arg == "--watch=forever" {
arg = "--watch"
isWatch = true
isWatchForever = true
} else if arg == "--serve" ||
strings.HasPrefix(arg, "--serve=") ||
strings.HasPrefix(arg, "--servedir=") ||
strings.HasPrefix(arg, "--serve-fallback=") {
isServe = true
}
// Strip any arguments that were handled above
osArgs[argsEnd] = arg
argsEnd++
}
}
osArgs = osArgs[:argsEnd]
// Run in service mode if requested
if isRunningService {
runService(sendPings)
return
}
// Print help text when there are no arguments
isStdinTTY := logger.GetTerminalInfo(os.Stdin).IsTTY
if len(osArgs) == 0 && isStdinTTY {
logger.PrintText(os.Stdout, logger.LevelSilent, osArgs, helpText)
os.Exit(0)
}
// Capture the defer statements below so the "done" message comes last
exitCode := 1
func() {
// To view a CPU trace, use "go tool trace [file]". Note that the trace
// viewer doesn't work under Windows Subsystem for Linux for some reason.
if traceFile != "" {
if done := createTraceFile(osArgs, traceFile); done == nil {
return
} else {
defer done()
}
}
// To view a heap trace, use "go tool pprof [file]" and type "top". You can
// also drop it into https://speedscope.app and use the "left heavy" or
// "sandwich" view modes.
if heapFile != "" {
if done := createHeapFile(osArgs, heapFile); done == nil {
return
} else {
defer done()
}
}
// To view a CPU profile, drop the file into https://speedscope.app.
// Note: Running the CPU profiler doesn't work under Windows subsystem for
// Linux. The profiler has to be built for native Windows and run using the
// command prompt instead.
if cpuprofileFile != "" {
if done := createCpuprofileFile(osArgs, cpuprofileFile); done == nil {
return
} else {
defer done()
}
}
if cpuprofileFile != "" {
// The CPU profiler in Go only runs at 100 Hz, which is far too slow to
// return useful information for esbuild, since it's so fast. Let's keep
// running for 30 seconds straight, which should give us 3,000 samples.
seconds := 30.0
start := time.Now()
for time.Since(start).Seconds() < seconds {
exitCode = cli.Run(osArgs)
}
} else {
if !isWatch && !isServe {
// If this is not a long-running process and there is at most a single
// entry point, then disable the GC since we're just going to allocate
// a bunch of memory and then exit anyway. This speedup is not
// insignificant. We don't do this when there are multiple entry points
// since otherwise esbuild could unnecessarily use much more memory
// than it might otherwise need to process many entry points.
nonFlagCount := 0
for _, arg := range osArgs {
if !strings.HasPrefix(arg, "-") {
nonFlagCount++
}
}
if nonFlagCount <= 1 {
debug.SetGCPercent(-1)
}
} else if isServe && isServeUnsupported() {
// The development server isn't supported on WebAssembly, so we will
// immediately call "os.Exit(1)" below, which will call "process.exit(1)"
// in node. However, node has a bug/feature where any pending calls to
// "fs.read(process.stdin.fd)" hold up "process.exit()" without seemingly
// any way to stop this from happening. So to avoid this bug/feature,
// we explicitly avoid listening to stdin in this case (when we know
// that we are about to exit due to an invalid flag).
} else if !isStdinTTY && !isWatchForever {
// If stdin isn't a TTY, watch stdin and abort in case it is closed.
// This is necessary when the esbuild binary executable is invoked via
// the Erlang VM, which doesn't provide a way to exit a child process.
// See: https://github.com/brunch/brunch/issues/920.
//
// We don't do this when stdin is a TTY because that interferes with
// the Unix background job system. If we read from stdin then Ctrl+Z
// to move the process to the background will incorrectly cause the
// job to stop. See: https://github.com/brunch/brunch/issues/998.
go func() {
// This just discards information from stdin because we don't use
// it and we can avoid unnecessarily allocating space for it
buffer := make([]byte, 512)
for {
_, err := os.Stdin.Read(buffer)
if err != nil {
if options := logger.OutputOptionsForArgs(osArgs); options.LogLevel <= logger.LevelInfo {
if isWatch {
// Mention why watch mode was stopped to reduce confusion, and
// call out "--watch=forever" to get the alternative behavior
logger.PrintTextWithColor(os.Stderr, options.Color, func(colors logger.Colors) string {
return fmt.Sprintf("%s[watch] stopped automatically because stdin was closed (use \"--watch=forever\" to keep watching even after stdin is closed)%s\n", colors.Dim, colors.Reset)
})
} else if isServe {
logger.PrintTextWithColor(os.Stderr, options.Color, func(colors logger.Colors) string {
return fmt.Sprintf("%s[serve] stopped automatically because stdin was closed (keep stdin open to continue serving)%s\n", colors.Dim, colors.Reset)
})
}
}
// Only exit cleanly if stdin was closed cleanly
if err == io.EOF {
os.Exit(0)
} else {
os.Exit(1)
}
}
// Some people attempt to keep esbuild's watch mode open by piping
// an infinite stream of data to stdin such as with "< /dev/zero".
// This will make esbuild spin at 100% CPU. To avoid this, put a
// small delay after we read some data from stdin.
time.Sleep(4 * time.Millisecond)
}
}()
}
exitCode = cli.Run(osArgs)
}
}()
os.Exit(exitCode)
}
@@ -0,0 +1,61 @@
//go:build !js || !wasm
// +build !js !wasm
package main
import (
"fmt"
"os"
"runtime/pprof"
"runtime/trace"
"github.com/evanw/esbuild/internal/logger"
)
func createTraceFile(osArgs []string, traceFile string) func() {
f, err := os.Create(traceFile)
if err != nil {
logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
"Failed to create trace file: %s", err.Error()))
return nil
}
trace.Start(f)
return func() {
trace.Stop()
f.Close()
}
}
func createHeapFile(osArgs []string, heapFile string) func() {
f, err := os.Create(heapFile)
if err != nil {
logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
"Failed to create heap file: %s", err.Error()))
return nil
}
return func() {
if err := pprof.WriteHeapProfile(f); err != nil {
logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
"Failed to write heap profile: %s", err.Error()))
}
f.Close()
}
}
func createCpuprofileFile(osArgs []string, cpuprofileFile string) func() {
f, err := os.Create(cpuprofileFile)
if err != nil {
logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
"Failed to create cpuprofile file: %s", err.Error()))
return nil
}
pprof.StartCPUProfile(f)
return func() {
pprof.StopCPUProfile()
f.Close()
}
}
func isServeUnsupported() bool {
return false
}
@@ -0,0 +1,29 @@
//go:build js && wasm
// +build js,wasm
package main
import (
"github.com/evanw/esbuild/internal/logger"
)
// Remove this code from the WebAssembly binary to reduce size. This only removes 0.4mb of stuff.
func createTraceFile(osArgs []string, traceFile string) func() {
logger.PrintErrorToStderr(osArgs, "The \"--trace\" flag is not supported when using WebAssembly")
return nil
}
func createHeapFile(osArgs []string, heapFile string) func() {
logger.PrintErrorToStderr(osArgs, "The \"--heap\" flag is not supported when using WebAssembly")
return nil
}
func createCpuprofileFile(osArgs []string, cpuprofileFile string) func() {
logger.PrintErrorToStderr(osArgs, "The \"--cpuprofile\" flag is not supported when using WebAssembly")
return nil
}
func isServeUnsupported() bool {
return true
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,205 @@
// The JavaScript API communicates with the Go child process over stdin/stdout
// using this protocol. It's a very simple binary protocol that uses primitives
// and nested arrays and maps. It's basically JSON with UTF-8 encoding and an
// additional byte array primitive. You must send a response after receiving a
// request because the other end is blocking on the response coming back.
package main
import (
"encoding/binary"
"sort"
)
func readUint32(bytes []byte) (value uint32, leftOver []byte, ok bool) {
if len(bytes) >= 4 {
return binary.LittleEndian.Uint32(bytes), bytes[4:], true
}
return 0, bytes, false
}
func writeUint32(bytes []byte, value uint32) []byte {
bytes = append(bytes, 0, 0, 0, 0)
binary.LittleEndian.PutUint32(bytes[len(bytes)-4:], value)
return bytes
}
func readLengthPrefixedSlice(bytes []byte) (slice []byte, leftOver []byte, ok bool) {
if length, afterLength, ok := readUint32(bytes); ok && uint(len(afterLength)) >= uint(length) {
return afterLength[:length], afterLength[length:], true
}
return []byte{}, bytes, false
}
type packet struct {
value interface{}
id uint32
isRequest bool
}
func encodePacket(p packet) []byte {
var visit func(interface{})
var bytes []byte
visit = func(value interface{}) {
switch v := value.(type) {
case nil:
bytes = append(bytes, 0)
case bool:
n := uint8(0)
if v {
n = 1
}
bytes = append(bytes, 1, n)
case int:
bytes = append(bytes, 2)
bytes = writeUint32(bytes, uint32(v))
case string:
bytes = append(bytes, 3)
bytes = writeUint32(bytes, uint32(len(v)))
bytes = append(bytes, v...)
case []byte:
bytes = append(bytes, 4)
bytes = writeUint32(bytes, uint32(len(v)))
bytes = append(bytes, v...)
case []interface{}:
bytes = append(bytes, 5)
bytes = writeUint32(bytes, uint32(len(v)))
for _, item := range v {
visit(item)
}
case map[string]interface{}:
// Sort keys for determinism
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
bytes = append(bytes, 6)
bytes = writeUint32(bytes, uint32(len(keys)))
for _, k := range keys {
bytes = writeUint32(bytes, uint32(len(k)))
bytes = append(bytes, k...)
visit(v[k])
}
default:
panic("Invalid packet")
}
}
bytes = writeUint32(bytes, 0) // Reserve space for the length
if p.isRequest {
bytes = writeUint32(bytes, p.id<<1)
} else {
bytes = writeUint32(bytes, (p.id<<1)|1)
}
visit(p.value)
writeUint32(bytes[:0], uint32(len(bytes)-4)) // Patch the length in
return bytes
}
func decodePacket(bytes []byte) (packet, bool) {
var visit func() (interface{}, bool)
visit = func() (interface{}, bool) {
kind := bytes[0]
bytes = bytes[1:]
switch kind {
case 0: // nil
return nil, true
case 1: // bool
value := bytes[0]
bytes = bytes[1:]
return value != 0, true
case 2: // int
value, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
return int(value), true
case 3: // string
value, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
return string(value), true
case 4: // []byte
value, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
return value, true
case 5: // []interface{}
count, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
value := make([]interface{}, count)
for i := 0; i < int(count); i++ {
item, ok := visit()
if !ok {
return nil, false
}
value[i] = item
}
return value, true
case 6: // map[string]interface{}
count, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
value := make(map[string]interface{}, count)
for i := 0; i < int(count); i++ {
key, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
item, ok := visit()
if !ok {
return nil, false
}
value[string(key)] = item
}
return value, true
default:
panic("Invalid packet")
}
}
id, bytes, ok := readUint32(bytes)
if !ok {
return packet{}, false
}
isRequest := (id & 1) == 0
id >>= 1
value, ok := visit()
if !ok {
return packet{}, false
}
if len(bytes) != 0 {
return packet{}, false
}
return packet{id: id, isRequest: isRequest, value: value}, true
}
@@ -0,0 +1,3 @@
package main
const esbuildVersion = "0.27.7"
@@ -0,0 +1,3 @@
/out.js
/out.js.map
/repos/
@@ -0,0 +1,3 @@
# compat-table
This code generates esbuild's internal browser compatibility tables from 3rd-party browser compatibility data. Run this code with `make compat-table` in the top-level repository directory. Update the data sources behind these tables using `make update-compat-table`.
+59
View File
@@ -0,0 +1,59 @@
{
"name": "compat-table",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@mdn/browser-compat-data": "7.3.9",
"@types/caniuse-lite": "1.0.1",
"@types/node": "25.0.2",
"caniuse-lite": "1.0.30001784"
}
},
"node_modules/@mdn/browser-compat-data": {
"version": "7.3.9",
"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-7.3.9.tgz",
"integrity": "sha512-ARxwDFfq4uhRSd9Wr1FMaxeKKIFDPyMnSoLe/8WX+nNXbKDILf1H8KDJW9nzu2QTR0hQ37jDCzAgyaNYdLzACQ==",
"license": "CC0-1.0"
},
"node_modules/@types/caniuse-lite": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/caniuse-lite/-/caniuse-lite-1.0.1.tgz",
"integrity": "sha512-enW8d/vdojHAhr1C/0BqsNkhz/0ik2WCuAJIp9EClP+mHkR8xXBJ2OGeXHZ6cPqDjVKvWBRe9tY/Nr9r4yQSuA=="
},
"node_modules/@types/node": {
"version": "25.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz",
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001784",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz",
"integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
}
}
}
@@ -0,0 +1,11 @@
{
"githubDependencies": {
"compat-table/compat-table": "8f4179ffb5e7a06a12739400fcd31b5a34ef7964"
},
"dependencies": {
"@mdn/browser-compat-data": "7.3.9",
"@types/caniuse-lite": "1.0.1",
"@types/node": "25.0.2",
"caniuse-lite": "1.0.30001784"
}
}
@@ -0,0 +1,149 @@
// This file processes data from https://caniuse.com
import lite = require('caniuse-lite')
import { CSSFeature, CSSPrefixMap, CSSProperty, Engine, JSFeature, PrefixData, Support, SupportMap } from './index'
const enum StatusCode {
Almost = 'a',
Disabled = 'd',
No = 'n',
Polyfill = 'p',
Prefix = 'x',
Unknown = 'u',
Yes = 'y',
}
const supportedAgents: Record<string, Engine> = {
chrome: 'Chrome',
edge: 'Edge',
firefox: 'Firefox',
ie: 'IE',
ios_saf: 'IOS',
opera: 'Opera',
safari: 'Safari',
}
const jsFeatures: Record<string, JSFeature> = {
'es6-module-dynamic-import': 'DynamicImport',
}
const cssFeatures: Record<string, CSSFeature> = {
'css-matches-pseudo': 'IsPseudoClass',
'css-media-range-syntax': 'MediaRange',
}
const cssPrefixFeatures: Record<string, CSSProperty> = {
'css-appearance': 'DAppearance',
'css-backdrop-filter': 'DBackdropFilter',
'background-clip-text': 'DBackgroundClip',
'css-boxdecorationbreak': 'DBoxDecorationBreak',
'css-clip-path': 'DClipPath',
'font-kerning': 'DFontKerning',
'css-hyphens': 'DHyphens',
'css-initial-letter': 'DInitialLetter',
'css-sticky': 'DPosition',
'css-color-adjust': 'DPrintColorAdjust',
'css3-tabsize': 'DTabSize',
'css-text-orientation': 'DTextOrientation',
'text-size-adjust': 'DTextSizeAdjust',
}
export const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
export const css: SupportMap<CSSFeature> = {} as SupportMap<CSSFeature>
export const cssPrefix: CSSPrefixMap = {}
const compareVersions = (aStr: string, bStr: string): number => {
const a = aStr.split('.')
const b = bStr.split('.')
let diff = +a[0] - +b[0]
if (diff === 0) {
diff = +(a[1] || '0') - +(b[1] || '0')
if (diff === 0) {
diff = +(a[2] || '0') - +(b[2] || '0')
}
}
return diff
}
const addFeatures = <F extends string>(map: SupportMap<F>, features: Record<string, F>): void => {
for (const feature in features) {
const engines: Partial<Record<Engine, Record<string, Support>>> = {}
const entry = lite.feature(lite.features[feature])
for (const agent in entry.stats) {
const engine = supportedAgents[agent]
if (!engine) continue
const versionRanges = entry.stats[agent]
const versions: Record<string, Support> = {}
for (const versionRange in versionRanges) {
const statusCodes = versionRanges[versionRange].split(' ')
const isSupported = statusCodes.includes(StatusCode.Yes)
for (const version of versionRange.split('-')) {
if (/^\d+(?:\.\d+(?:\.\d+)?)?$/.test(version)) {
versions[version] = { force: isSupported }
}
}
}
engines[engine] = versions
}
map[features[feature]] = engines
}
}
addFeatures(js, jsFeatures)
addFeatures(css, cssFeatures)
for (const feature in cssPrefixFeatures) {
const prefixData: PrefixData[] = []
const entry = lite.feature(lite.features[feature])
for (const agent in entry.stats) {
const engine = supportedAgents[agent]
if (!engine) continue
const model = lite.agents[agent]!
const versionRanges = entry.stats[agent]
const sortedVersions: { version: string, prefix: string | null }[] = []
const prefixes = new Set<string>()
for (const versionRange in versionRanges) {
const statusCodes = versionRanges[versionRange].split(' ')
const prefix = statusCodes.includes(StatusCode.Prefix)
? (model.prefix_exceptions && model.prefix_exceptions[versionRange]) || model.prefix
: null
for (const version of versionRange.split('-')) {
// Filter out bogus versions such as "TP" for Safari
if (/^\d+(\.\d+)*$/.test(version)) {
sortedVersions.push({ version, prefix })
}
}
if (prefix !== null) {
prefixes.add(prefix)
}
}
sortedVersions.sort((a, b) => compareVersions(a.version, b.version))
for (const prefix of prefixes) {
// Find the version after the latest version that requires the prefix (if there even is one)
let i = sortedVersions.length
while (i > 0 && sortedVersions[i - 1].prefix !== prefix) {
i--
}
// Add an entry for this prefix combination
const result: PrefixData = { engine, prefix }
if (i < sortedVersions.length) {
result.withoutPrefix = sortedVersions[i].version.split('.').map(x => +x)
}
prefixData.push(result)
}
}
cssPrefix[cssPrefixFeatures[feature]] = prefixData
}
@@ -0,0 +1,197 @@
// This file processes the data contained in https://github.com/kangax/compat-table
import fs = require('fs')
import path = require('path')
import es5 = require('../repos/compat-table/compat-table/data-es5.js')
import es6 = require('../repos/compat-table/compat-table/data-es6.js')
import stage1to3 = require('../repos/compat-table/compat-table/data-esnext.js')
import stage4 = require('../repos/compat-table/compat-table/data-es2016plus.js')
import environments = require('../repos/compat-table/compat-table/environments.json')
import parseEnvsVersions = require('../repos/compat-table/compat-table/build-utils/parse-envs-versions.js')
import interpolateAllResults = require('../repos/compat-table/compat-table/build-utils/interpolate-all-results.js')
import { Engine, JSFeature, SupportMap } from './index'
interpolateAllResults(es5.tests, environments)
interpolateAllResults(es6.tests, environments)
interpolateAllResults(stage1to3.tests, environments)
interpolateAllResults(stage4.tests, environments)
const features: Record<string, JSFeature> = {
// ES5 features
'Object/array literal extensions: Getter accessors': 'ObjectAccessors',
'Object/array literal extensions: Setter accessors': 'ObjectAccessors',
// ES6 features
'arrow functions': 'Arrow',
'class': 'Class',
'const': 'ConstAndLet',
'default function parameters': 'DefaultArgument',
'destructuring, assignment': 'Destructuring',
'destructuring, declarations': 'Destructuring',
'destructuring, parameters': 'Destructuring',
'for..of loops': 'ForOf',
'function "name" property: isn\'t writable, is configurable': 'FunctionNameConfigurable',
'generators': 'Generator',
'let': 'ConstAndLet',
'new.target': 'NewTarget',
'object literal extensions': 'ObjectExtensions',
'RegExp "y" and "u" flags': 'RegexpStickyAndUnicodeFlags',
'rest parameters': 'RestArgument',
'spread syntax for iterable objects': 'ArraySpread',
'template literals': 'TemplateLiteral',
'Unicode code point escapes': 'UnicodeEscapes',
// >ES6 features
'async functions': 'AsyncAwait',
'Asynchronous Iterators: async generators': 'AsyncGenerator',
'Asynchronous Iterators: for-await-of loops': 'ForAwait',
'BigInt: basic functionality': 'Bigint',
'exponentiation (**) operator': 'ExponentOperator',
'Hashbang Grammar': 'Hashbang',
'Logical Assignment': 'LogicalAssignment',
'nested rest destructuring, declarations': 'NestedRestBinding',
'nested rest destructuring, parameters': 'NestedRestBinding',
'nullish coalescing operator (??)': 'NullishCoalescing',
'object rest/spread properties': 'ObjectRestSpread',
'optional catch binding': 'OptionalCatchBinding',
'optional chaining operator (?.)': 'OptionalChain',
'RegExp Lookbehind Assertions': 'RegexpLookbehindAssertions',
'RegExp named capture groups': 'RegexpNamedCaptureGroups',
'RegExp Unicode Property Escapes': 'RegexpUnicodePropertyEscapes',
's (dotAll) flag for regular expressions': 'RegexpDotAllFlag',
'Explicit Resource Management': 'Using',
// Public fields
'instance class fields: computed instance class fields': 'ClassField',
'instance class fields: public instance class fields': 'ClassField',
'static class fields: computed static class fields': 'ClassStaticField',
'static class fields: public static class fields': 'ClassStaticField',
// Private fields
'instance class fields: optional deep private instance class fields access': 'ClassPrivateField',
'instance class fields: optional private instance class fields access': 'ClassPrivateField',
'instance class fields: private instance class fields basic support': 'ClassPrivateField',
'instance class fields: private instance class fields initializers': 'ClassPrivateField',
'static class fields: private static class fields': 'ClassPrivateStaticField',
// Private methods
'private class methods: private accessor properties': 'ClassPrivateAccessor',
'private class methods: private instance methods': 'ClassPrivateMethod',
'private class methods: private static accessor properties': 'ClassPrivateStaticAccessor',
'private class methods: private static methods': 'ClassPrivateStaticMethod',
// Private "in"
'Ergonomic brand checks for private fields': 'ClassPrivateBrandCheck',
}
const environmentToEngine: Record<string, Engine> = {
// The JavaScript standard
'es': 'ES',
// Common JavaScript runtimes
'chrome': 'Chrome',
'edge': 'Edge',
'firefox': 'Firefox',
'ie': 'IE',
'ios': 'IOS',
'node': 'Node',
'opera': 'Opera',
'safari': 'Safari',
// Uncommon JavaScript runtimes
'deno': 'Deno',
'hermes': 'Hermes',
'rhino': 'Rhino',
}
const subtestsToSkip: Record<string, boolean> = {
// Safari supposedly doesn't throw an error for duplicate identifiers in
// a function parameter list. The failing test case looks like this:
//
// var f = function f([id, id]) { return id }
//
// However, this code will cause a compile error with esbuild so it's not
// possible to encounter this issue when running esbuild-generated code in
// Safari. I'm ignoring this test since Safari's destructuring otherwise
// works fine so destructuring shouldn't be forbidden when building for
// Safari.
'destructuring, parameters: duplicate identifier': true,
}
const getValueOfTest = (value: boolean | { val: boolean }): boolean => {
// Handle values like this:
//
// {
// val: true,
// note_id: "ff-shorthand-methods",
// ...
// }
//
if (typeof value === 'object' && value !== null) {
return value.val === true
}
// String values such as "flagged" are considered to be false
return value === true
}
interface Test {
name: string
res: Record<string, boolean | { val: boolean }>
subtests?: { name: string, res: Record<string, boolean | { val: boolean }> }[]
}
const updateMap = (map: SupportMap<JSFeature>, feature: JSFeature, engine: Engine, version: string, testName: string, passed: boolean): void => {
const engines = map[feature] || (map[feature] = {})
const versions = engines[engine] || (engines[engine] = {})
const support = versions[version] || (versions[version] = {})
if (passed) {
support.passed = (support.passed || 0) + 1
} else {
support.failed ||= new Set
support.failed.add(testName)
}
}
const mergeIndividualTestResults = (map: SupportMap<JSFeature>, feature: JSFeature, testName: string, res: Record<string, boolean | { val: boolean }>): void => {
const environments = parseEnvsVersions(res)
for (const environment in environments) {
const engine = environmentToEngine[environment]
if (engine) {
for (const parsed of environments[environment]) {
const version = parsed.version.join('.')
if (/^\d+(?:\.\d+(?:\.\d+)?)?$/.test(version)) {
updateMap(map, feature, engine, version, testName, getValueOfTest(res[parsed.id]))
}
}
}
}
}
const mergeAllTestResults = (map: SupportMap<JSFeature>, tests: Test[]): void => {
for (const test of tests) {
const feature = features[test.name]
if (feature) {
if (test.subtests) {
for (const subtest of test.subtests) {
const fullName = `${test.name}: ${subtest.name}`
if (subtestsToSkip[fullName]) continue
mergeIndividualTestResults(map, feature, fullName, subtest.res)
}
} else {
mergeIndividualTestResults(map, feature, test.name, test.res)
}
} else if (test.subtests) {
for (const subtest of test.subtests) {
const fullName = `${test.name}: ${subtest.name}`
if (subtestsToSkip[fullName]) continue
const feature = features[fullName]
if (feature) mergeIndividualTestResults(map, feature, fullName, subtest.res)
}
}
}
}
export const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
mergeAllTestResults(js, [...es5.tests, ...es6.tests, ...stage4.tests, ...stage1to3.tests])
@@ -0,0 +1,158 @@
// This file generates "internal/compat/css_table.go"
import fs = require('fs')
import { Engine, CSSFeature, VersionRange, VersionRangeMap, CSSPrefixMap, PrefixData, CSSProperty } from './index'
const cssFeatureString = (feature: string): string => {
return feature.replace(/([A-Z]+)/g, '-$1').slice(1).toLowerCase().replace(/[-_]+/g, '-')
}
const simpleMap = (entries: [string, string][]) => {
let maxLength = 0
for (const [key] of entries) {
maxLength = Math.max(maxLength, key.length + 1)
}
return entries.map(([key, value]) => `\t${(key + ':').padEnd(maxLength)} ${value},`).join('\n')
}
const compareEngines = (a: Engine, b: Engine): number => {
const lowerA = a.toLowerCase()
const lowerB = b.toLowerCase()
return lowerA < lowerB ? -1 : lowerA > lowerB ? 1 : 0
}
const cssTableMap = (map: Partial<Record<Engine, VersionRange[]>>) => {
const engineKeys = (Object.keys(map) as Engine[]).sort(compareEngines)
const maxLength = engineKeys.reduce((a, b) => Math.max(a, b.length + 1), 0)
if (engineKeys.length === 0) return '{}'
return `{\n${engineKeys.map(engine => {
const items = map[engine]!.map(range => {
return `{start: v{${range.start.concat(0, 0).slice(0, 3).join(', ')
}}${range.end ? `, end: v{${range.end.concat(0, 0).slice(0, 3).join(', ')}}` : ''}}`
})
return `\t\t${(engine + ':').padEnd(maxLength)} {${items.join(', ')}},`
}).join('\n')}\n\t}`
}
const cssPrefixName = (prefix: string): string => {
return prefix[0].toUpperCase() + prefix.slice(1) + 'Prefix'
}
const cssPrefixMap = (entries: PrefixData[]) => {
if (entries.length === 0) return '{}'
entries.sort((a, b) => compareEngines(a.engine, b.engine))
return `{\n${entries.map(({ engine, prefix, withoutPrefix }) => {
const version = withoutPrefix && withoutPrefix.concat(0, 0).slice(0, 3).join(', ')
return `\t\t{engine: ${engine}, prefix: ${cssPrefixName(prefix)}${version ? `, withoutPrefix: v{${version}}` : ''}},`
}).join('\n')}\n\t}`
}
const generatedByComment = `// This file was automatically generated by "css_table.ts"`
export const generateTableForCSS = (map: VersionRangeMap<CSSFeature>, prefixes: CSSPrefixMap): void => {
const prefixNames = new Set<string>()
for (const property in prefixes) {
for (const { prefix } of prefixes[property as CSSProperty]!) {
prefixNames.add(cssPrefixName(prefix))
}
}
fs.writeFileSync(__dirname + '/../internal/compat/css_table.go',
`${generatedByComment}
package compat
import (
\t"github.com/evanw/esbuild/internal/css_ast"
)
type CSSFeature uint16
const (
${Object.keys(map).sort().map((feature, i) => `\t${feature}${i ? '' : ' CSSFeature = 1 << iota'}`).join('\n')}
)
var StringToCSSFeature = map[string]CSSFeature{
${simpleMap(Object.keys(map).sort().map(feature => [`"${cssFeatureString(feature)}"`, feature]))}
}
func (features CSSFeature) Has(feature CSSFeature) bool {
\treturn (features & feature) != 0
}
func (features CSSFeature) ApplyOverrides(overrides CSSFeature, mask CSSFeature) CSSFeature {
\treturn (features & ^mask) | (overrides & mask)
}
var cssTable = map[CSSFeature]map[Engine][]versionRange{
${Object.keys(map).sort().map(feature => `\t${feature}: ${cssTableMap(map[feature as CSSFeature]!)},`).join('\n')}
}
// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine]Semver) (unsupported CSSFeature) {
\tfor feature, engines := range cssTable {
\t\tif feature == InlineStyle {
\t\t\tcontinue // This is purely user-specified
\t\t}
\t\tfor engine, version := range constraints {
\t\t\tif !engine.IsBrowser() {
\t\t\t\t// Specifying "--target=es2020" shouldn't affect CSS
\t\t\t\tcontinue
\t\t\t}
\t\t\tif versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
\t\t\t\tunsupported |= feature
\t\t\t}
\t\t}
\t}
\treturn
}
type CSSPrefix uint8
const (
${[...prefixNames].sort().map((name, i) => `\t${name}${i ? '' : ' CSSPrefix = 1 << iota'}`).join('\n')}
\tNoPrefix CSSPrefix = 0
)
type prefixData struct {
\t// Note: In some cases, earlier versions did not require a prefix but later
\t// ones do. This is the case for Microsoft Edge for example, which switched
\t// the underlying browser engine from a custom one to the one from Chrome.
\t// However, we assume that users specifying a browser version for CSS mean
\t// "works in this version or newer", so we still add a prefix when a target
\t// is an old Edge version.
\tengine Engine
\twithoutPrefix v
\tprefix CSSPrefix
}
var cssPrefixTable = map[css_ast.D][]prefixData{
${Object.keys(prefixes).sort().map(property => `\tcss_ast.${property}: ${cssPrefixMap(prefixes[property as CSSProperty]!)},`).join('\n')}
}
func CSSPrefixData(constraints map[Engine]Semver) (entries map[css_ast.D]CSSPrefix) {
\tfor property, items := range cssPrefixTable {
\t\tprefixes := NoPrefix
\t\tfor engine, version := range constraints {
\t\t\tif !engine.IsBrowser() {
\t\t\t\t// Specifying "--target=es2020" shouldn't affect CSS
\t\t\t\tcontinue
\t\t\t}
\t\t\tfor _, item := range items {
\t\t\t\tif item.engine == engine && (item.withoutPrefix == v{} || compareVersions(item.withoutPrefix, version) > 0) {
\t\t\t\t\tprefixes |= item.prefix
\t\t\t\t}
\t\t\t}
\t\t}
\t\tif prefixes != NoPrefix {
\t\t\tif entries == nil {
\t\t\t\tentries = make(map[css_ast.D]CSSPrefix)
\t\t\t}
\t\t\tentries[property] = prefixes
\t\t}
\t}
\treturn
}
`)
}
@@ -0,0 +1,572 @@
// Run "make compat-table" to run this code
// Run "make update-compat-table" to update the data sources
import child_process = require('child_process')
import fs = require('fs')
import path = require('path')
import { generateTableForJS } from './js_table'
import { generateTableForCSS } from './css_table'
import * as caniuse from './caniuse'
import * as mdn from './mdn'
export type Engine = keyof typeof engines
export const engines = {
Chrome: true,
Deno: true,
Edge: true,
ES: true,
Firefox: true,
Hermes: true,
IE: true,
IOS: true,
Node: true,
Opera: true,
Rhino: true,
Safari: true,
}
export type JSFeature = keyof typeof jsFeatures
export const jsFeatures = {
ArbitraryModuleNamespaceNames: true,
ArraySpread: true,
Arrow: true,
AsyncAwait: true,
AsyncGenerator: true,
Bigint: true,
Class: true,
ClassField: true,
ClassPrivateAccessor: true,
ClassPrivateBrandCheck: true,
ClassPrivateField: true,
ClassPrivateMethod: true,
ClassPrivateStaticAccessor: true,
ClassPrivateStaticField: true,
ClassPrivateStaticMethod: true,
ClassStaticBlocks: true,
ClassStaticField: true,
ConstAndLet: true,
Decorators: true,
DefaultArgument: true,
Destructuring: true,
DynamicImport: true,
ExponentOperator: true,
ExportStarAs: true,
ForAwait: true,
ForOf: true,
FromBase64: true,
FunctionNameConfigurable: true,
FunctionOrClassPropertyAccess: true,
Generator: true,
Hashbang: true,
ImportAssertions: true,
ImportAttributes: true,
ImportDefer: true,
ImportMeta: true,
ImportSource: true,
InlineScript: true,
LogicalAssignment: true,
NestedRestBinding: true,
NewTarget: true,
NodeColonPrefixImport: true,
NodeColonPrefixRequire: true,
NullishCoalescing: true,
ObjectAccessors: true,
ObjectExtensions: true,
ObjectRestSpread: true,
OptionalCatchBinding: true,
OptionalChain: true,
RegexpDotAllFlag: true,
RegexpLookbehindAssertions: true,
RegexpMatchIndices: true,
RegexpNamedCaptureGroups: true,
RegexpSetNotation: true,
RegexpStickyAndUnicodeFlags: true,
RegexpUnicodePropertyEscapes: true,
RestArgument: true,
TemplateLiteral: true,
TopLevelAwait: true,
TypeofExoticObjectIsObject: true,
UnicodeEscapes: true,
Using: true,
}
export type CSSFeature = keyof typeof cssFeatures
export const cssFeatures = {
ColorFunctions: true,
GradientDoublePosition: true,
GradientInterpolation: true,
GradientMidpoints: true,
HexRGBA: true,
HWB: true,
InlineStyle: true,
InsetProperty: true,
IsPseudoClass: true,
MediaRange: true,
Modern_RGB_HSL: true,
Nesting: true,
RebeccaPurple: true,
}
export type CSSProperty = keyof typeof cssProperties
export const cssProperties = {
DAppearance: true,
DBackdropFilter: true,
DBackgroundClip: true,
DBoxDecorationBreak: true,
DClipPath: true,
DFontKerning: true,
DHeight: true,
DHyphens: true,
DInitialLetter: true,
DMask: true,
DMaskComposite: true,
DMaskImage: true,
DMaskOrigin: true,
DMaskPosition: true,
DMaskRepeat: true,
DMaskSize: true,
DMaxHeight: true,
DMaxWidth: true,
DMinHeight: true,
DMinWidth: true,
DPosition: true,
DPrintColorAdjust: true,
DTabSize: true,
DTextDecorationColor: true,
DTextDecorationLine: true,
DTextDecorationSkip: true,
DTextEmphasisColor: true,
DTextEmphasisPosition: true,
DTextEmphasisStyle: true,
DTextOrientation: true,
DTextSizeAdjust: true,
DUserSelect: true,
DWidth: true,
}
export interface Support {
force?: boolean
passed?: number
failed?: Set<string>
}
export interface VersionRange {
start: number[]
end?: number[]
}
export interface PrefixData {
engine: Engine
prefix: string
withoutPrefix?: number[]
}
export type SupportMap<F extends string> = Record<F, Partial<Record<Engine, Record<string, Support>>>>
export type VersionRangeMap<F extends string> = Partial<Record<F, Partial<Record<Engine, VersionRange[]>>>>
export type WhyNotMap<F extends string> = Partial<Record<F, Partial<Record<Engine, string[]>>>>
export type CSSPrefixMap = Partial<Record<CSSProperty, PrefixData[]>>
const compareVersions = (a: number[], b: number[]): number => {
let diff = a[0] - b[0]
if (!diff) {
diff = (a[1] || 0) - (b[1] || 0)
if (!diff) {
diff = (a[2] || 0) - (b[2] || 0)
}
}
return diff
}
const mergeSupportMaps = <F extends string>(to: SupportMap<F>, from: SupportMap<F>): void => {
for (const feature in from) {
const fromEngines = from[feature as F]
const toEngines = to[feature as F] || (to[feature as F] = {})
for (const engine in fromEngines) {
const fromVersions = fromEngines[engine as Engine]
const toVersions = toEngines[engine as Engine] || (toEngines[engine as Engine] = {})
for (const version in fromVersions) {
if (version in toVersions) {
throw new Error(`Merge conflict with feature=${feature} engine=${engine} version=${version}`)
}
toVersions[version] = fromVersions[version]
}
}
}
}
const mergePrefixMaps = (to: CSSPrefixMap, from: CSSPrefixMap): void => {
for (const property in from) {
if (property in to) {
throw new Error(`Merge conflict with property=${property}`)
}
to[property as CSSProperty] = from[property as CSSProperty]
}
}
const supportMapToVersionRanges = <F extends string>(supportMap: SupportMap<F>): [VersionRangeMap<F>, WhyNotMap<F>] => {
const versionRangeMap: VersionRangeMap<F> = {}
const whyNotMap: WhyNotMap<F> = {}
for (const feature in supportMap) {
const engines = supportMap[feature as F]
const featureMap: Partial<Record<Engine, VersionRange[]>> = {}
const whyNotByEngine: Partial<Record<Engine, string[]>> = {}
// Compute the maximum number of tests that any one engine has passed
let maxPassed = 0
for (const engine in engines) {
const versions = engines[engine as Engine]
for (const version in versions) {
const { passed } = versions[version]
if (passed && passed > maxPassed) maxPassed = passed
}
}
for (const engine in engines) {
const versions = engines[engine as Engine]
const sortedVersions: { version: number[], supported: boolean, failed?: Set<string> }[] = []
for (const version in versions) {
const { force, passed, failed } = versions[version]
const parsed = version.split('.').map(x => +x)
sortedVersions.push({
version: parsed,
supported: force !== void 0 ? force :
// If no test failed but less than the maximum number of tests passed,
// that means we have partial data (some tests have never been run for
// those versions). This happens for really old browser versions that
// people can't even run anymore. We conservatively consider this
// feature to be unsupported if not all tests were run, since it could
// be dangerous to assume otherwise.
!failed && passed === maxPassed,
failed,
})
}
sortedVersions.sort((a, b) => compareVersions(a.version, b.version))
if (sortedVersions.length) {
const last = sortedVersions[sortedVersions.length - 1]
if (last.failed) whyNotByEngine[engine as Engine] = [...last.failed].sort()
}
const versionRanges: VersionRange[] = []
let i = 0
while (i < sortedVersions.length) {
const { version, supported } = sortedVersions[i++]
if (supported) {
while (i < sortedVersions.length && sortedVersions[i].supported) {
i++
}
const range: VersionRange = { start: version }
if (i < sortedVersions.length) range.end = sortedVersions[i].version
versionRanges.push(range)
}
}
// The target is typically used to mean "make sure it works in this
// version and later". So we just take the last version range here.
//
// However, we make an exception for node since people sometimes use
// the target to build for the version of node that they currently
// have. Node has a discontiguous version range for the support of
// several features that people want to use.
if (versionRanges.length && engine as Engine !== 'Node') {
if (versionRanges[versionRanges.length - 1].end) {
// We say this engine doesn't support this feature at all if
// the feature is broken in the latest version of this engine.
// This sometimes happens when engines deliberately decide to
// not implement a feature of the language (e.g. Hermes).
continue
}
// Otherwise, only consider this feature to be supported for the
// last version range (i.e. the earliest version for which all
// later versions support this feature). Delete all version ranges
// before the last version range.
versionRanges.splice(0, versionRanges.length - 1)
}
if (versionRanges.length) {
featureMap[engine as Engine] = versionRanges
}
}
versionRangeMap[feature as F] = featureMap
whyNotMap[feature as F] = whyNotByEngine
}
return [versionRangeMap, whyNotMap]
}
const installGitHubDependencies = (): void => {
const updateCommits = process.argv.includes('--update')
const jsonPath = path.join(__dirname, 'package.json')
const jsonData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
Object.keys(jsonData.githubDependencies).forEach(repo => {
const fullPath = path.join(__dirname, 'repos', repo)
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true })
child_process.execFileSync('git', ['clone', '-b', 'gh-pages', `https://github.com/${repo}.git`, fullPath], { cwd: fullPath, stdio: 'inherit' })
}
child_process.execFileSync('git', ['fetch'], { cwd: fullPath, stdio: 'inherit' })
if (updateCommits) {
child_process.execFileSync('git', ['reset', '--hard', '--quiet', 'origin/gh-pages'], { cwd: fullPath, stdio: 'inherit' })
const commit = child_process.execFileSync('git', ['rev-parse', 'HEAD'], { cwd: fullPath }).toString().trim()
jsonData.githubDependencies[repo] = commit
} else {
const commit = jsonData.githubDependencies[repo]
child_process.execFileSync('git', ['reset', '--hard', '--quiet', commit], { cwd: fullPath, stdio: 'inherit' })
}
})
if (updateCommits) {
fs.writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2) + '\n')
}
}
// These dependencies are not published on npm and are instead pulled from GitHub (but pinned to a specific commit)
installGitHubDependencies()
import('./compat-table').then(compatTable => {
const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
for (const feature in jsFeatures) js[feature as JSFeature] = {}
mergeSupportMaps(js, compatTable.js)
mergeSupportMaps(js, caniuse.js)
mergeSupportMaps(js, mdn.js)
// ES5 features
js.ObjectAccessors.ES = { 5: { force: true } }
js.ObjectAccessors.Node = { '0.4': { force: true } } // "node-compat-table" doesn't appear to cover ES5 features...
// ES6/ES2015 features
js.ArraySpread.ES = { 2015: { force: true } }
js.Arrow.ES = { 2015: { force: true } }
js.Class.ES = { 2015: { force: true } }
js.ConstAndLet.ES = { 2015: { force: true } }
js.DefaultArgument.ES = { 2015: { force: true } }
js.Destructuring.ES = { 2015: { force: true } }
js.DynamicImport.ES = { 2015: { force: true } }
js.ForOf.ES = { 2015: { force: true } }
js.FunctionNameConfigurable.ES = { 2015: { force: true } }
js.Generator.ES = { 2015: { force: true } }
js.NewTarget.ES = { 2015: { force: true } }
js.ObjectExtensions.ES = { 2015: { force: true } }
js.RegexpStickyAndUnicodeFlags.ES = { 2015: { force: true } }
js.RestArgument.ES = { 2015: { force: true } }
js.TemplateLiteral.ES = { 2015: { force: true } }
js.UnicodeEscapes.ES = { 2015: { force: true } }
// ES2016 features
js.ExponentOperator.ES = { 2016: { force: true } }
js.NestedRestBinding.ES = { 2016: { force: true } }
// ES2017 features
js.AsyncAwait.ES = { 2017: { force: true } }
// ES2018 features
js.AsyncGenerator.ES = { 2018: { force: true } }
js.ForAwait.ES = { 2018: { force: true } }
js.ObjectRestSpread.ES = { 2018: { force: true } }
js.RegexpDotAllFlag.ES = { 2018: { force: true } }
js.RegexpLookbehindAssertions.ES = { 2018: { force: true } }
js.RegexpNamedCaptureGroups.ES = { 2018: { force: true } }
js.RegexpUnicodePropertyEscapes.ES = { 2018: { force: true } }
// ES2019 features
js.OptionalCatchBinding.ES = { 2019: { force: true } }
// ES2020 features
js.Bigint.ES = { 2020: { force: true } }
js.ExportStarAs.ES = { 2020: { force: true } }
js.ImportMeta.ES = { 2020: { force: true } }
js.NullishCoalescing.ES = { 2020: { force: true } }
js.OptionalChain.ES = { 2020: { force: true } }
js.TypeofExoticObjectIsObject.ES = { 2020: { force: true } } // https://github.com/tc39/ecma262/pull/1441
// ES2021 features
js.LogicalAssignment.ES = { 2021: { force: true } }
// ES2022 features
js.ClassField.ES = { 2022: { force: true } }
js.ClassPrivateAccessor.ES = { 2022: { force: true } }
js.ClassPrivateBrandCheck.ES = { 2022: { force: true } }
js.ClassPrivateField.ES = { 2022: { force: true } }
js.ClassPrivateMethod.ES = { 2022: { force: true } }
js.ClassPrivateStaticAccessor.ES = { 2022: { force: true } }
js.ClassPrivateStaticField.ES = { 2022: { force: true } }
js.ClassPrivateStaticMethod.ES = { 2022: { force: true } }
js.ClassStaticBlocks.ES = { 2022: { force: true } }
js.ClassStaticField.ES = { 2022: { force: true } }
js.TopLevelAwait.ES = { 2022: { force: true } }
js.ArbitraryModuleNamespaceNames.ES = { 2022: { force: true } }
js.RegexpMatchIndices.ES = { 2022: { force: true } }
// ES2023 features
js.Hashbang.ES = { 2023: { force: true } }
// ES2024 features
js.RegexpSetNotation.ES = { 2024: { force: true } }
// This is a problem specific to Internet Explorer. See https://github.com/tc39/ecma262/issues/1440
for (const engine in engines) {
if (engine as Engine !== 'ES' && engine as Engine !== 'IE') {
js.TypeofExoticObjectIsObject[engine as Engine] = { 0: { force: true } }
}
}
// This is a problem specific to JavaScriptCore. Some examples of when the
// problematic case happens (checked in Safari 12.1):
//
// ❱ x(function(y=-1){}.z=2)
// SyntaxError: Left hand side of operator '=' must be a reference.
//
// ❱ x(class{f(y=-1){}}.z=2)
// SyntaxError: Left hand side of operator '=' must be a reference.
//
// Some examples of cases that aren't problematic (checked in Safari 12.1):
//
// // Adding parentheses makes it ok
// x((function(y=-1){}).z=2)
// x((class{f(y=-1){}}).z=2)
//
// // Not using a unary operator in the default argument makes it ok
// x(function(y=1){}.z=2)
// x(class{f(y=1){}}.z=2)
//
// // Methods in object literals are not affected
// x({f(y=-1){}}.z=2)
//
// We don't attempt to reverse-engineer the specific conditions that cause JSC
// to exhibit the bug. Instead we just always wrap function and class literals
// when they are nested inside of a property access. This workaround is overly
// conservative but is the same thing that UglifyJS does to handle this case.
//
// See https://github.com/mishoo/UglifyJS/pull/2056 and https://github.com/evanw/esbuild/issues/3072
for (const engine in engines) {
if (engine as Engine !== 'Safari') {
js.FunctionOrClassPropertyAccess[engine as Engine] = { 0: { force: true } }
} else {
// These bugs are known to be fixed in Safari 16.3+
js.FunctionOrClassPropertyAccess.Safari = { '16.3': { force: true } }
}
}
// This is a special case. Node added support for it to both v12.20+ and v13.2+
// so the range is inconveniently discontiguous. Sources:
//
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
// - https://github.com/nodejs/node/pull/35950
// - https://github.com/nodejs/node/pull/31974
//
js.DynamicImport.Node = {
'12.20': { force: true },
'13': { force: false },
'13.2': { force: true },
}
// Manually copied from https://nodejs.org/api/esm.html#node-imports
js.NodeColonPrefixImport.Node = {
'12.20': { force: true },
'13': { force: false },
'14.13.1': { force: true },
}
js.NodeColonPrefixRequire.Node = {
'14.18': { force: true },
'15': { force: false },
'16': { force: true },
}
js.NodeColonPrefixImport.ES = { 0: { force: true } }
js.NodeColonPrefixRequire.ES = { 0: { force: true } }
// Arbitrary Module Namespace Names
{
// From https://github.com/tc39/ecma262/pull/2154#issuecomment-825201030
js.ArbitraryModuleNamespaceNames.Chrome = { 90: { force: true } }
js.ArbitraryModuleNamespaceNames.Node = { 16: { force: true } }
// From https://bugzilla.mozilla.org/show_bug.cgi?id=1670044
js.ArbitraryModuleNamespaceNames.Firefox = { 87: { force: true } }
// From https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes
js.ArbitraryModuleNamespaceNames.Safari = { '14.1': { force: true } }
js.ArbitraryModuleNamespaceNames.IOS = { '14.5': { force: true } }
}
// Import assertions (note: these were removed from the JavaScript specification and never standardized)
{
js.ImportAssertions.Node = {
// From https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#16.14.0
'16.14': { force: true },
// Manually tested using a binary from https://nodejs.org/en/download/prebuilt-binaries
'22': { force: false },
}
// MDN data is wrong here: https://bugs.webkit.org/show_bug.cgi?id=251600
delete js.ImportAssertions.IOS
delete js.ImportAssertions.Safari
}
// Import attributes (the replacement for import assertions)
{
// Manually tested using binaries from https://nodejs.org/en/download/prebuilt-binaries
js.ImportAttributes.Node = {
'18.20': { force: true },
'19': { force: false },
'20.10': { force: true },
}
}
// MDN data is wrong here: https://www.chromestatus.com/feature/6482797915013120
js.ClassStaticBlocks.Chrome = { 91: { force: true } }
// WebKit has now been marked as not supporting top-level await because it
// turns out they have a pretty severe bug: Importing a module that uses
// top-level await twice causes the second import to fail. For more info see:
// https://bugs.webkit.org/show_bug.cgi?id=242740
//
// However, we're going to override this to say it's still supported because:
// - It always fails loudly when this happens (the import fails)
// - People would otherwise be blocked from using this feature at all in
// WebKit-based browsers
// - From what I understand, most use of top-level await is in the entry-point
// module, where this isn't an issue
// - When you bundle your code, nested modules become entry points so this
// issue also wouldn't come up
// - The top-level await implementation in esbuild isn't even correct for
// nested modules as they become linearized in the bundle (like Rollup, but
// not like Webpack)
js.TopLevelAwait.IOS = { 15: { force: true } }
js.TopLevelAwait.Safari = { 15: { force: true } }
// MDN data is incomplete here: https://github.com/mdn/browser-compat-data/issues/28228
js.FromBase64.Node = { 25: { force: true } }
const [jsVersionRanges, jsWhyNot] = supportMapToVersionRanges(js)
generateTableForJS(jsVersionRanges, jsWhyNot)
})
const css: SupportMap<CSSFeature> = {} as SupportMap<CSSFeature>
const cssPrefix: CSSPrefixMap = {}
for (const feature in cssFeatures) css[feature as CSSFeature] = {}
mergeSupportMaps(css, caniuse.css)
mergeSupportMaps(css, mdn.css)
mergePrefixMaps(cssPrefix, caniuse.cssPrefix)
mergePrefixMaps(cssPrefix, mdn.cssPrefix)
// MDN data is wrong here, Firefox still had gradient interpolation rendering
// bugs until version 137: https://bugzilla.mozilla.org/show_bug.cgi?id=1904106
css.GradientInterpolation.Firefox = { 137: { force: true } }
const [cssVersionRanges] = supportMapToVersionRanges(css)
generateTableForCSS(cssVersionRanges, cssPrefix)
@@ -0,0 +1,157 @@
// This file generates "internal/compat/js_table.go"
import fs = require('fs')
import { Engine, JSFeature, VersionRange, VersionRangeMap, WhyNotMap, engines } from './index'
const jsFeatureString = (feature: string): string => {
return feature.replace(/([A-Z])/g, '-$1').slice(1).toLowerCase()
}
const simpleMap = (entries: [string, string][]) => {
let maxLength = 0
for (const [key] of entries) {
maxLength = Math.max(maxLength, key.length + 1)
}
return entries.map(([key, value]) => `\t${(key + ':').padEnd(maxLength)} ${value},`).join('\n')
}
const compareEngines = (a: Engine, b: Engine): number => {
const lowerA = a.toLowerCase()
const lowerB = b.toLowerCase()
return lowerA < lowerB ? -1 : lowerA > lowerB ? 1 : 0
}
const jsTableMap = (map: Partial<Record<Engine, VersionRange[]>>, whyNot: Partial<Record<Engine, string[]>>) => {
const lines: string[] = []
const whyNotKeys = (Object.keys(whyNot) as Engine[]).sort(compareEngines)
for (const engine of whyNotKeys) {
const failedTests = whyNot[engine]!
lines.push(`\t\t// Note: The latest version of ${JSON.stringify(engine)} failed ` + (failedTests.length === 1
? `this test: ${failedTests[0]}` : `${failedTests.length} tests including: ${failedTests[0]}`))
}
const engineKeys = (Object.keys(map) as Engine[]).sort(compareEngines)
const maxLength = engineKeys.reduce((a, b) => Math.max(a, b.length + 1), 0)
for (const engine of engineKeys) {
const items = map[engine]!.map(range => {
return `{start: v{${range.start.concat(0, 0).slice(0, 3).join(', ')
}}${range.end ? `, end: v{${range.end.concat(0, 0).slice(0, 3).join(', ')}}` : ''}}`
})
lines.push(`\t\t${(engine + ':').padEnd(maxLength)} {${items.join(', ')}},`)
}
if (lines.length === 0) return '{}'
return `{\n${lines.join('\n')}\n\t}`
}
const jsTableValidEnginesMap = (engines: Engine[]) => {
const maxLength = engines.reduce((a, b) => Math.max(a, b.length + 4), 0)
if (engines.length === 0) return '{}'
return engines.map(engine => {
return `\t${`"${engine.toLowerCase()}": `.padEnd(maxLength)}api.Engine${engine},`
}).join('\n')
}
const generatedByComment = `// This file was automatically generated by "js_table.ts"`
export const generateTableForJS = (map: VersionRangeMap<JSFeature>, whyNot: WhyNotMap<JSFeature>): void => {
const enginesKeys = (Object.keys(engines) as Engine[]).sort(compareEngines)
fs.writeFileSync(__dirname + '/../internal/compat/js_table.go',
`${generatedByComment}
package compat
type Engine uint8
const (
${enginesKeys.map((engine, i) => `\t${engine}${i ? '' : ' Engine = iota'}`).join('\n')}
)
func (e Engine) String() string {
\tswitch e {
${enginesKeys.map(engine => `\tcase ${engine}:\n\t\treturn "${engine.toLowerCase()}"`).join('\n')}
\t}
\treturn ""
}
func (e Engine) IsBrowser() bool {
\tswitch e {
\tcase Chrome, Edge, Firefox, IE, IOS, Opera, Safari:
\t\treturn true
\t}
\treturn false
}
type JSFeature uint64
const (
${Object.keys(map).sort().map((feature, i) => `\t${feature}${i ? '' : ' JSFeature = 1 << iota'}`).join('\n')}
)
var StringToJSFeature = map[string]JSFeature{
${simpleMap(Object.keys(map).sort().map(feature => [`"${jsFeatureString(feature)}"`, feature]))}
}
func (features JSFeature) Has(feature JSFeature) bool {
\treturn (features & feature) != 0
}
func (features JSFeature) ApplyOverrides(overrides JSFeature, mask JSFeature) JSFeature {
\treturn (features & ^mask) | (overrides & mask)
}
var jsTable = map[JSFeature]map[Engine][]versionRange{
${Object.keys(map).sort().map(feature => `\t${feature}: ${jsTableMap(map[feature as JSFeature]!, whyNot[feature as JSFeature]!)},`).join('\n')}
}
// Return all features that are not available in at least one environment
func UnsupportedJSFeatures(constraints map[Engine]Semver) (unsupported JSFeature) {
\tfor feature, engines := range jsTable {
\t\tif feature == InlineScript {
\t\t\tcontinue // This is purely user-specified
\t\t}
\t\tfor engine, version := range constraints {
\t\t\tif versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
\t\t\t\tunsupported |= feature
\t\t\t}
\t\t}
\t}
\treturn
}
`)
fs.writeFileSync(__dirname + '/../pkg/api/api_js_table.go',
`${generatedByComment}
package api
import "github.com/evanw/esbuild/internal/compat"
type EngineName uint8
const (
${enginesKeys.filter(engine => engine !== 'ES').map((engine, i) => `\tEngine${engine}${i ? '' : ' EngineName = iota'}`).join('\n')}
)
func convertEngineName(engine EngineName) compat.Engine {
\tswitch engine {
${enginesKeys.filter(engine => engine !== 'ES').map(engine => `\tcase Engine${engine}:\n\t\treturn compat.${engine}`).join('\n')}
\tdefault:
\t\tpanic("Invalid engine name")
\t}
}
`)
fs.writeFileSync(__dirname + '/../pkg/cli/cli_js_table.go',
`${generatedByComment}
package cli
import "github.com/evanw/esbuild/pkg/api"
var validEngines = map[string]api.EngineName{
${jsTableValidEnginesMap(enginesKeys.filter(engine => engine !== 'ES'))}
}
`)
}
@@ -0,0 +1,284 @@
// This file processes data from https://developer.mozilla.org/en-US/docs/Web
import bcd, { BrowserName, SimpleSupportStatement, SupportBlock } from '@mdn/browser-compat-data'
import { CSSFeature, CSSPrefixMap, CSSProperty, Engine, JSFeature, PrefixData, Support, SupportMap } from './index'
const supportedEnvironments: Record<string, Engine> = {
chrome: 'Chrome',
deno: 'Deno',
edge: 'Edge',
firefox: 'Firefox',
ie: 'IE',
nodejs: 'Node',
opera: 'Opera',
safari: 'Safari',
safari_ios: 'IOS',
}
const jsFeatures: Partial<Record<JSFeature, string>> = {
ClassStaticBlocks: 'javascript.classes.static.initialization_blocks',
ExportStarAs: 'javascript.statements.export.namespace',
FromBase64: 'javascript.builtins.Uint8Array.fromBase64',
ImportAssertions: 'javascript.statements.import.import_assertions',
ImportAttributes: 'javascript.statements.import.import_attributes',
ImportMeta: 'javascript.operators.import_meta',
RegexpMatchIndices: 'javascript.builtins.RegExp.hasIndices',
TopLevelAwait: 'javascript.operators.await.top_level',
}
const cssFeatures: Partial<Record<CSSFeature, string | string[]>> = {
ColorFunctions: [
'css.types.color.color',
'css.types.color.lab',
'css.types.color.lch',
'css.types.color.oklab',
'css.types.color.oklch',
],
GradientDoublePosition: [
'css.types.gradient.conic-gradient.doubleposition',
'css.types.gradient.linear-gradient.doubleposition',
'css.types.gradient.radial-gradient.doubleposition',
'css.types.gradient.repeating-linear-gradient.doubleposition',
'css.types.gradient.repeating-radial-gradient.doubleposition',
],
GradientInterpolation: [
'css.types.gradient.conic-gradient.hue_interpolation_method',
'css.types.gradient.conic-gradient.interpolation_color_space',
'css.types.gradient.linear-gradient.hue_interpolation_method',
'css.types.gradient.linear-gradient.interpolation_color_space',
'css.types.gradient.radial-gradient.hue_interpolation_method',
'css.types.gradient.radial-gradient.interpolation_color_space',
'css.types.gradient.repeating-conic-gradient.hue_interpolation_method',
'css.types.gradient.repeating-conic-gradient.interpolation_color_space',
'css.types.gradient.repeating-linear-gradient.hue_interpolation_method',
'css.types.gradient.repeating-linear-gradient.interpolation_color_space',
'css.types.gradient.repeating-radial-gradient.hue_interpolation_method',
'css.types.gradient.repeating-radial-gradient.interpolation_color_space',
],
GradientMidpoints: [
'css.types.gradient.linear-gradient.interpolation_hints',
'css.types.gradient.radial-gradient.interpolation_hints',
'css.types.gradient.repeating-linear-gradient.interpolation_hints',
'css.types.gradient.repeating-radial-gradient.interpolation_hints',
],
HexRGBA: 'css.types.color.rgb_hexadecimal_notation.alpha_hexadecimal_notation',
HWB: 'css.types.color.hwb',
InsetProperty: 'css.properties.inset',
Modern_RGB_HSL: [
'css.types.color.hsl.alpha_parameter',
'css.types.color.hsl.space_separated_parameters',
'css.types.color.rgb.alpha_parameter',
'css.types.color.rgb.float_values',
'css.types.color.rgb.space_separated_parameters',
],
Nesting: 'css.selectors.nesting',
RebeccaPurple: 'css.types.color.named-color.rebeccapurple',
}
const similarPrefixedProperty: Record<string, { prefix: string, property: string }> = {
'css.properties.mask-composite': {
prefix: '-webkit-',
property: 'css.properties.-webkit-mask-composite',
},
}
const cssPrefixFeatures: Record<string, CSSProperty> = {
'css.properties.height.stretch': 'DHeight',
'css.properties.mask': 'DMask',
'css.properties.mask-composite': 'DMaskComposite',
'css.properties.mask-image': 'DMaskImage',
'css.properties.mask-origin': 'DMaskOrigin',
'css.properties.mask-position': 'DMaskPosition',
'css.properties.mask-repeat': 'DMaskRepeat',
'css.properties.mask-size': 'DMaskSize',
'css.properties.max-height.stretch': 'DMaxHeight',
'css.properties.max-width.stretch': 'DMaxWidth',
'css.properties.min-height.stretch': 'DMinHeight',
'css.properties.min-width.stretch': 'DMinWidth',
'css.properties.text-decoration-color': 'DTextDecorationColor',
'css.properties.text-decoration-line': 'DTextDecorationLine',
'css.properties.text-decoration-skip': 'DTextDecorationSkip',
'css.properties.text-emphasis-color': 'DTextEmphasisColor',
'css.properties.text-emphasis-position': 'DTextEmphasisPosition',
'css.properties.text-emphasis-style': 'DTextEmphasisStyle',
'css.properties.user-select': 'DUserSelect',
'css.properties.width.stretch': 'DWidth',
}
const alternativeNameToPrefix: Record<string, string> = {
'-webkit-fill-available': '-webkit-',
'-moz-available': '-moz-',
}
export const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
export const css: SupportMap<CSSFeature> = {} as SupportMap<CSSFeature>
export const cssPrefix: CSSPrefixMap = {}
const isSemver = /^\d+(?:\.\d+(?:\.\d+)?)?$/
const compareVersions = (aStr: string, bStr: string): number => {
const a = aStr.split('.')
const b = bStr.split('.')
let diff = +a[0] - +b[0]
if (diff === 0) {
diff = +(a[1] || '0') - +(b[1] || '0')
if (diff === 0) {
diff = +(a[2] || '0') - +(b[2] || '0')
}
}
return diff
}
const extractProperty = (object: any, fullKey: string): any => {
for (const key of fullKey.split('.')) {
object = object[key]
}
if (!object) throw new Error(`Failed to find "${fullKey}"`)
return object
}
const addFeatures = <F extends string>(map: SupportMap<F>, features: Partial<Record<F, string | string[]>>): void => {
for (const feature in features) {
const keys = features[feature]
const maxVersions: Partial<Record<Engine, { version: string, isSupported: boolean }>> = {}
for (const fullKey of Array.isArray(keys) ? keys : [keys]) {
const support: SupportBlock = extractProperty(bcd, fullKey).__compat.support
for (const env in support) {
const engine = supportedEnvironments[env]
if (engine) {
const entries = support[env as BrowserName]!
for (const { flags, version_added, version_removed, partial_implementation } of Array.isArray(entries) ? entries : [entries]) {
if (typeof version_added === 'string' && isSemver.test(version_added)) {
// The feature isn't considered to be supported if it was removed,
// if it requires a flag, or if it's only partially-implemented
const isSupported = (!version_removed || !flags) && !partial_implementation
const maxVersion = maxVersions[engine]
if (
!maxVersion ||
compareVersions(version_added, maxVersion.version) > 0 ||
(compareVersions(version_added, maxVersion.version) === 0 && !isSupported)
) {
maxVersions[engine] = { version: version_added, isSupported }
}
}
}
}
}
}
const engines: Partial<Record<Engine, Record<string, Support>>> = {}
for (const engine in maxVersions) {
const { version, isSupported } = maxVersions[engine as Engine]!
engines[engine as Engine] = { [version]: { force: isSupported } }
}
map[feature] = engines
}
}
addFeatures(js, jsFeatures)
addFeatures(css, cssFeatures)
for (const fullKey in cssPrefixFeatures) {
const prefixData: PrefixData[] = []
const support: SupportBlock = extractProperty(bcd, fullKey).__compat.support
for (const env in support) {
const engine = supportedEnvironments[env]
if (engine) {
const initialEntries = support[env as BrowserName]!
let entries: SimpleSupportStatement[] = Array.isArray(initialEntries) ? initialEntries : [initialEntries]
// Figure out which version this property can be used unprefixed, if any.
// This assumes that support for these CSS properties is never removed.
// This assumption is wrong (Edge removed many features when it changed
// its engine from EdgeHTML to Blink, basically becoming another browser)
// but we ignore those cases for now.
let version_unprefixed: string | undefined
for (const { prefix, flags, version_added, version_removed, alternative_name } of entries) {
if (!prefix && !alternative_name && !flags && typeof version_added === 'string' && !version_removed && isSemver.test(version_added)) {
version_unprefixed = version_added
}
}
type PrefixRange = { prefix: string, start: string, end?: string }
const ranges: PrefixRange[] = []
// The MDN dataset sometimes doesn't list prefixes if the values for the
// prefixed property are sufficiently different. In that case, we may need
// to search for the prefix information within another property instead.
const similar = similarPrefixedProperty[fullKey]
if (similar) {
const similarSupport: SupportBlock = extractProperty(bcd, similar.property).__compat.support
const similarEntries = similarSupport[env as BrowserName]
if (!similarEntries) continue
entries = Array.isArray(similarEntries) ? similarEntries : [similarEntries]
}
// Find all version ranges where a given prefix is supported
for (let i = 0; i < entries.length; i++) {
let { prefix, flags, version_added, version_removed, alternative_name } = entries[i]
if (similar) {
if (prefix) throw new Error(`Unexpected prefix "${prefix}" for similar property "${similar.property}"`)
prefix = similar.prefix
}
if ((prefix || alternative_name) && !flags && typeof version_added === 'string' && isSemver.test(version_added)) {
const range: PrefixRange = { prefix: prefix || alternativeNameToPrefix[alternative_name!], start: version_added }
let withoutPrefix: string | undefined
// The prefix is no longer needed if support for the feature was removed
if (typeof version_removed === 'string' && isSemver.test(version_removed)) {
withoutPrefix = version_removed
}
// The prefix is no longer needed if it can be used unprefixed
if (version_unprefixed && (!withoutPrefix || compareVersions(version_unprefixed, withoutPrefix) < 0)) {
withoutPrefix = version_unprefixed
}
if (withoutPrefix) {
if (compareVersions(version_added, withoutPrefix) === 0) {
// No prefix is needed if support for the property with and without the prefix was added simultaneously
continue
}
range.end = withoutPrefix
}
ranges.push(range)
}
}
// Sort earlier versions first, then sort prefixes for equal versions lexicographically
ranges.sort((a, b) => compareVersions(a.start, b.start) || +(a.prefix > b.prefix) - +(a.prefix < b.prefix))
for (let i = 0; i < ranges.length; i++) {
const { prefix, start, end } = ranges[i]
// Skip this prefix if it's entirely covered by the previous prefix.
// Sometimes engines add support for multiple prefixes at a time. For
// example, in version 12 Edge added support for both "-ms-user-select"
// and "-webkit-user-select", so we don't need to generate both.
if (i > 0) {
const prev = ranges[i - 1]
if (compareVersions(start, prev.start) >= 0 && (!prev.end || (end && compareVersions(end, prev.end) <= 0))) {
continue
}
}
const data: PrefixData = { engine, prefix: prefix.replace(/^-|-$/g, '') }
if (end) {
data.withoutPrefix = end.split('.').map((x: string) => +x)
}
prefixData.push(data)
}
}
}
cssPrefix[cssPrefixFeatures[fullKey]] = prefixData
}
+5
View File
@@ -0,0 +1,5 @@
// Allow TypeScript to import untyped ".js" files
declare module '*.js' {
let any: any
export = any
}
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "CommonJS",
"resolveJsonModule": true,
"strict": true,
"target": "ESNext",
}
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
set -eu
dir=$(mktemp -d)
platform=$(uname -ms)
tgz="$dir/esbuild-$ESBUILD_VERSION.tgz"
# Download the binary executable for the current platform
case $platform in
'Darwin arm64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-$ESBUILD_VERSION.tgz";;
'Darwin x86_64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-$ESBUILD_VERSION.tgz";;
'Linux arm64' | 'Linux aarch64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-$ESBUILD_VERSION.tgz";;
'Linux x86_64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-$ESBUILD_VERSION.tgz";;
'NetBSD amd64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-$ESBUILD_VERSION.tgz";;
'OpenBSD arm64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-$ESBUILD_VERSION.tgz";;
'OpenBSD amd64') curl -fo "$tgz" "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-$ESBUILD_VERSION.tgz";;
*) echo "error: Unsupported platform: $platform"; exit 1
esac
# Extract the binary executable to the current directory
tar -xzf "$tgz" -C "$dir" package/bin/esbuild
mv "$dir/package/bin/esbuild" .
rm "$tgz"
@@ -0,0 +1,579 @@
* [Architecture](#architecture)
* [Design principles](#design-principles)
* [Overview](#overview)
* [Scan phase](#scan-phase)
* [Compile phase](#compile-phase)
* [Notes about parsing](#notes-about-parsing)
* [Symbols and scopes](#symbols-and-scopes)
* [Constant folding](#constant-folding)
* [TypeScript parsing](#typescript-parsing)
* [Notes about linking](#notes-about-linking)
* [CommonJS linking](#commonjs-linking)
* [ES6 linking](#es6-linking)
* [Hybrid CommonJS and ES6 modules](#hybrid-commonjs-and-es6-modules)
* [Scope hoisting](#scope-hoisting)
* [Converting ES6 imports to CommonJS imports](#converting-es6-imports-to-commonjs-imports)
* [The runtime library](#the-runtime-library)
* [Tree shaking](#tree-shaking)
* [Code splitting](#code-splitting)
* [Notes about printing](#notes-about-printing)
* [Symbol minification](#symbol-minification)
# Architecture Documentation
This document covers how esbuild's bundler works. It's intended to aid in understanding the code, in understanding what tricks esbuild uses to improve performance, and hopefully to enable people to modify the code.
Note that there are some design decisions that have been made differently than other bundlers for performance reasons. These decisions may make the code harder to work with. Keep in mind that this project is an experiment in progress, and is not the result of a comprehensive survey of implementation techniques. The way things work now is not necessarily the best way of doing things.
### Design principles
* **Maximize parallelism**
Most of the time should be spent doing fully parallelizable work. This can be observed by taking a CPU trace using the `--trace=[file]` flag and viewing it using `go tool trace [file]`.
* **Avoid doing unnecessary work**
For example, many bundlers have intermediate stages where they write out JavaScript code and read it back in using another tool. This work is unnecessary because if the tools used the same data structures, no conversion would be needed.
* **Transparently support both ES6 and CommonJS module syntax**
The parser in esbuild processes a superset of both ES6 and CommonJS modules. It doesn't distinguish between ES6 modules and other modules so you can use both ES6 and CommonJS syntax in the same file if you'd like.
* **Try to do as few full-AST passes as possible for better cache locality**
Compilers usually have many more passes because separate passes makes code easier to understand and maintain. There are currently only three full-AST passes in esbuild because individual passes have been merged together as much as possible:
1. Lexing + parsing + scope setup + symbol declaration
2. Symbol binding + constant folding + syntax lowering + syntax mangling
3. Printing + source map generation
* **Structure things to permit a "watch mode" where compilation can happen incrementally**
Incremental builds mean only rebuilding changed files to the greatest extent possible. This means not re-running any of the full-AST passes on unchanged files. Data structures that live across builds must be immutable to allow sharing. Unfortunately the Go type system can't enforce this, so care must be taken to uphold this as the code evolves.
## Overview
<p align="center"><img src="../images/build-pipeline.png" alt="Diagram of build pipeline" width="752"></p>
The build pipeline has two main phases: scan and compile. These both reside in [bundler.go](../internal/bundler/bundler.go).
### Scan phase
This phase starts with a set of entry points and traverses the dependency graph to find all modules that need to be in the bundle. This is implemented in `bundler.ScanBundle()` as a parallel worklist algorithm. The worklist starts off being the list of entry points. Each file in the list is parsed into an AST on a separate goroutine and may add more files to the worklist if it has any dependencies (either ES6 `import` statements, ES6 `import()` expressions, or CommonJS `require()` expressions). Scanning continues until the worklist is empty.
### Compile phase
This phase creates a bundle for each entry point, which involves first "linking" imports with exports, then converting the parsed ASTs back into JavaScript, then concatenating them together to form the final bundled file. This happens in `(*Bundle).Compile()`.
## Notes about parsing
The parser is separate from the lexer. The lexer is called on the fly as the file is parsed instead of lexing the entire input ahead of time. This is necessary due to certain syntactical features such as regular expressions vs. the division operator and JSX elements vs. the less-than operator, where which token is parsed depends on the semantic context.
Lexer lookahead has been kept to one token in almost all cases with the notable exception of TypeScript, which requires arbitrary lookahead to parse correctly. All such cases are in methods called `trySkipTypeScript*WithBacktracking()` in the parser.
The parser includes a lot of transformations, all of which have been condensed into just two passes for performance:
1. The first pass does lexing and parsing, sets up the scope tree, and declares all symbols in their respective scopes.
2. The second pass binds all identifiers to their respective symbols using the scope tree, substitutes compile-time definitions for their values, performs constant folding, does lowering of syntax if we're targeting an older version of JavaScript, and performs syntax mangling/compression if we're doing a production build.
Note that, from experience, the overhead of syscalls in import path resolution is appears to be very high. Caching syscall results in the resolver and the file system implementation is a very sizable speedup.
### Symbols and scopes
A symbol is a way to refer to an identifier in a precise way. Symbols are referenced using a 64-bit identifier instead of using the name, which makes them easy to refer to without worrying about scope. For example, the parser can generate new symbols without worrying about name collisions. All identifiers reference a symbol, even "unbound" ones that don't have a matching declaration. Symbols have to be declared in a separate pass from the pass that binds identifiers to symbols because JavaScript has "variable hoisting" where a child scope can declare a hoisted symbol that can become bound to identifiers in parent and sibling scopes.
Symbols for the whole file are stored in a flat top-level array. That way you can easily traverse over all symbols in the file without traversing the AST. That also lets us easily create a modified AST where the symbols have been changed without affecting the original immutable AST. Because symbols are identified by their index into the top-level symbol array, we can just clone the array to clone the symbols and we don't need to worry about rewiring all of the symbol references.
The scope tree is not attached to the AST because it's really only needed to pass information from the first pass to the second pass. The scope tree is instead temporarily mapped onto the AST within the parser. This is done by having the first and second passes both call `pushScope*()` and `popScope()` the same number of times in the same order. Specifically the first pass calls `pushScopeForParsePass()` which appends the pushed scope to `scopesInOrder`, and the second pass calls `pushScopeForVisitPass()` which reads off the scope to push from `scopesInOrder`.
This is mostly pretty straightforward except for a few places where the parser has pushed a scope and is in the middle of parsing a declaration only to discover that it's not a declaration after all. This happens in TypeScript when a function is forward-declared without a body, and in JavaScript when it's ambiguous whether a parenthesized expression is an arrow function or not until we reach the `=>` token afterwards. This would be solved by doing three passes instead of two so we finish parsing before starting to set up scopes and declare symbols, but we're trying to do this in just two passes. So instead we call `popAndDiscardScope()` or `popAndFlattenScope()` instead of `popScope()` to modify the scope tree later if our assumptions turn out to be incorrect.
### Constant folding
The constant folding and compile-time definition substitution is pretty minimal but is enough to handle libraries such as React which contain code like this:
```js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
```
Using `--define:process.env.NODE_ENV="production"` on the command line will cause `process.env.NODE_ENV === 'production'` to become `"production" === 'production'` which will then become `true`. The parser then treats the `else` branch as dead code, which means it ignores calls to `require()` and `import()` inside that branch. The `react.development.js` module is never included in the dependency graph.
### TypeScript parsing
TypeScript parsing has been implemented by augmenting the existing JavaScript parser. Most of it just involves skipping over type declarations as if they are whitespace. Enums, namespaces, and TypeScript-only class features such as parameter properties must all be converted to JavaScript syntax, which happens in the second parser pass. I've attempted to match what the TypeScript compiler does as close as is reasonably possible.
One TypeScript subtlety is that unused imports in TypeScript code must be removed, since they may be type-only imports. And if all imports in an import statement are removed, the whole import statement itself must also be removed. This has semantic consequences because the import may have side effects. However, it's important for correctness because this is how the TypeScript compiler itself works. The imported package itself may not actually even exist on disk since it may only come from a `declare` statement. Tracking used imports is handled by the `tsUseCounts` field in the parser.
## Notes about linking
The main goal of linking is to merge multiple modules into a single file so that imports from one module can reference exports from another module. This is accomplished in several different ways depending on the import and export features used.
Linking performs an optimization called "tree shaking". This is also known as "dead code elimination" and removes unreferenced code from the bundle to reduce bundle size. Tree shaking is always active and cannot be disabled.
Finally, linking may also involve dividing the input code among multiple chunks. This is known as "code splitting" and both allows lazy loading of code and sharing code between multiple entry points. It's disabled by default in esbuild but can be enabled with the `--splitting` flag.
This will all be described in more detail below.
### CommonJS linking
If a module uses any CommonJS features (e.g. references `exports`, references `module`, or uses a top-level `return` statement) then it's considered a CommonJS module. This means it's represented as a separate closure within the bundle. This is similar to how Webpack normally works.
Here's a simplified example to explain what this looks like:
<table>
<tr><th>foo.js</th><th>bar.js</th><th>bundle.js</th></tr>
<tr><td>
```js
exports.fn = () => 123
```
</td><td>
```js
const foo = require('./foo')
console.log(foo.fn())
```
</td><td>
```js
let __commonJS = (callback, module) => () => {
if (!module) {
module = {exports: {}};
callback(module.exports, module);
}
return module.exports;
};
// foo.js
var require_foo = __commonJS((exports) => {
exports.fn = () => 123;
});
// bar.js
const foo = require_foo();
console.log(foo.fn());
```
</td></tr>
</table>
The benefit of bundling modules this way is for compatibility. This emulates exactly [how node itself will run your module](https://nodejs.org/api/modules.html#modules_the_module_wrapper).
### ES6 linking
If a module doesn't use any CommonJS features, then it's considered an ES6 module. This means it's represented as part of a cross-module scope that may contain many other ES6 modules. This is often known as "scope hoisting" and is how Rollup normally works.
Here's a simplified example to explain what this looks like:
<table>
<tr><th>foo.js</th><th>bar.js</th><th>bundle.js</th></tr>
<tr><td>
```js
export const fn = () => 123
```
</td><td>
```js
import {fn} from './foo'
console.log(fn())
```
</td><td>
```js
// foo.js
const fn = () => 123;
// bar.js
console.log(fn());
```
</td></tr>
</table>
The benefit of distinguishing between CommonJS and ES6 modules is that bundling ES6 modules is more efficient, both because the generated code is smaller and because symbols are statically bound instead of dynamically bound, which has less overhead at run time.
ES6 modules also allow for "tree shaking" optimizations which remove unreferenced code from the bundle. For example, if the call to `fn()` is commented out in the above example, the variable `fn` will be omitted from the bundle since it's not used and its definition doesn't have any side effects. This is possible with ES6 modules but not with CommonJS because ES6 imports are bound at compile time while CommonJS imports are bound at run time.
### Hybrid CommonJS and ES6 modules
These two syntaxes are supported side-by-side as transparently as possible. This means you can use both CommonJS syntax (`exports` and `module` assignments and `require()` calls) and ES6 syntax (`import` and `export` statements and `import()` expressions) in the same module. The ES6 imports will be converted to `require()` calls and the ES6 exports will be converted to getters on that module's `exports` object.
### Scope hoisting
Scope hoisting (the merging of all scopes in a module group into a single scope) is implemented using symbol merging. Each imported symbol is merged with the corresponding exported symbol so that they become the same symbol in the output, which means they both get the same name. Symbol merging is possible because each symbol has a `Link` field that, when used, forwards to another symbol. The implementation of `MergeSymbols()` essentially just links one symbol to the other one. Whenever the printer sees a symbol reference it must call `FollowSymbols()` to get to the symbol at the end of the link chain, which represents the final merged symbol. This is similar to the [union-find data structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) if you're familiar with it.
During bundling, the symbol maps from all files are merged into a single giant symbol map, which allows symbols to be merged across files. The symbol map is represented as an array-of-arrays and a symbol reference is represented as two indices, one for the outer array and one for the inner array. The array-of-arrays representation is convenient because the parser produces a single symbol array for each file. Merging them all into a single map is as simple as making an array of the symbol arrays for each file. Each source file is identified using an incrementing index allocated during the scanning phase, so the index of the outer array is just the index of the source file.
### Converting ES6 imports to CommonJS imports
One complexity around scope hoisting is that references to ES6 imports may either be a bare identifier (i.e. statically bound) or a property access off of a `require()` call (i.e. dynamically bound) depending on whether the imported module is a CommonJS-style module or not. This information isn't known yet when we're still parsing the file so we are unable to determine whether to create `EIdentifier` or `EDot` AST nodes for these imports.
To handle this, references to ES6 imports use the special `EImportIdentifier` AST node. Later during linking we can decide if these references to a symbol need to be turned into a property access and, if so, fill in the `NamespaceAlias` field on the symbol. The printer checks that field for `EImportIdentifier` expressions and, if present, prints a property access instead of an identifier. This avoids having to do another full-AST traversal just to replace identifiers with property accesses before printing.
### The runtime library
This library contains support code that is needed to implement various aspects of JavaScript transformation and bundling. For example, it contains the `__commonJS()` helper function for wrapping CommonJS modules and the `__decorate()` helper function for implementing TypeScript decorators. The code lives in a single string in [runtime.go](../internal/runtime/runtime.go). It's automatically included in every build and esbuild's tree shaking feature automatically strips out unused code. If you need to add a helper function for esbuild to call, it should be added to this library.
### Tree shaking
The goal of tree shaking is to remove code that will never be used from the final bundle, which reduces download and parse time. Tree shaking treats the input files as a graph. Each node in the graph is a top-level statement, which is called a "part" in the code. Tree shaking is a graph traversal that starts from the entry point and marks all traversed parts for inclusion.
Each part may declare symbols, reference symbols, and depend on other files. Parts are also marked as either having side effects or not. For example, the statement `let foo = 123` does not have side effects because, if nothing needs `foo`, the statement can be removed without any observable difference. But the statement `let foo = bar()` does have side effects because even if nothing needs `foo`, the call to `bar()` cannot be removed without changing the meaning of the code.
If part A references a symbol declared in part B, the graph has an edge from A to B. References can span across files due to ES6 imports and exports. And if part A depends on file C, the graph has an edge from A to every part in C with side effects. A part depends on a file if it contains an ES6 `import` statement, a CommonJS `require()` call, or an ES6 `import()` expression.
Tree shaking begins by visiting all parts in the entry point file with side effects, and continues traversing along graph edges until no more new parts are reached. Once the traversal has finished, only parts that were reached during the traversal are included in the bundle. All other parts are excluded.
Here's an example to make this easier to visualize:
<p align="center"><img src="../images/tree-shaking.png" alt="Diagram of tree shaking" width="793"></p>
There are three input files: `index.js`, `config.js`, and `net.js`. Tree shaking traverses along all graph edges from `index.js` (the entry point). The two types of edges are shown with different arrows. Solid arrows are edges due to parts with side effects. These parts must be included regardless of whether the symbols they declare are used or not. Dashed arrows are edges from symbol references to the parts that declare those symbols. These parts don't have side effects and are only included if symbol they declare is referenced.
The final bundle only includes the code visited during the tree shaking traversal. That looks like this:
```js
// net.js
function get(url) {
return fetch(url).then((r) => r.text());
}
// config.js
let session = Math.random();
let api = "/api?session=";
function load() {
return get(api + session);
}
// index.js
let el = document.getElementById("el");
load().then((x) => el.textContent = x);
```
### Code splitting
Code splitting analyzes bundles with multiple entry points and divides code into chunks such that a) a given piece of code is only ever in one chunk and b) each entry point doesn't download code that it will never use. Note that the target of each dynamic `import()` expression is considered an additional entry point.
Splitting shared code into separate chunks means that downloading the code for two entry points only downloads the shared code once. It also allows code that's only needed for an asynchronous `import()` dependency to be lazily loaded.
Code splitting is implemented as an advanced form of tree shaking. The tree shaking traversal described above is run once for each entry point. Every part (i.e. node in the graph) stores all of the entry points that reached it during the traversal for that entry point. Then the combination of entry points for a given part determines what chunk that part ends up in.
To continue the tree shaking example above, let's add a second entry point called `settings.js` that uses a different but overlapping set of parts. Tree shaking is run again starting from this new entry point:
<p align="center"><img src="../images/code-splitting-1.png" alt="Diagram of code splitting" width="793"></p>
These two tree shaking passes result in three chunks: all parts only reachable from `index.js`, all parts only reachable from `settings.js`, and all parts reachable from both `index.js` and `settings.js`. Parts belonging to the three chunks are colored red, blue, and purple in the visualization below:
<p align="center"><img src="../images/code-splitting-2.png" alt="Diagram of code splitting" width="793"></p>
After all chunks are identified, the chunks are linked together by automatically generating import and export statements for references to symbols that are declared in another chunk. Import statements must also be inserted for chunks that don't have any exported symbols. This represents shared code with side effects, and code with side effects must be retained.
Here are the final code splitting chunks for this example after linking:
<table>
<tr><th>Chunk for index.js</th><th>Chunk for settings.js</th><th>Chunk for shared code</th></tr>
<tr><td>
```js
import {
api,
session
} from "./chunk.js";
// net.js
function get(url) {
return fetch(url).then((r) => r.text());
}
// config.js
function load() {
return get(api + session);
}
// index.js
let el = document.getElementById("el");
load().then((x) => el.textContent = x);
```
</td><td>
```js
import {
api,
session
} from "./chunk.js";
// net.js
function put(url, body) {
fetch(url, {method: "PUT", body});
}
// config.js
function save(value) {
return put(api + session, value);
}
// settings.js
let it = document.getElementById("it");
it.oninput = () => save(it.value);
```
</td><td>
```js
// config.js
let session = Math.random();
let api = "/api?session=";
export {
api,
session
};
```
</td></tr>
</table>
There is one additional complexity to code splitting due to how ES6 module boundaries work. Code splitting must not be allowed to move an assignment to a module-local variable into a separate chunk from the declaration of that variable. ES6 imports are read-only and cannot be assigned to, so doing this will cause the assignment to crash at run time.
To illustrate the problem, consider these three files:
<table>
<tr><th>entry1.js</th><th>entry2.js</th><th>data.js</th></tr>
<tr><td>
```js
import {data} from './data'
console.log(data)
```
</td><td>
```js
import {setData} from './data'
setData(123)
```
</td><td>
```js
export let data
export function setData(value) {
data = value
}
```
</td></tr>
</table>
If the two entry points `entry1.js` and `entry2.js` are bundled with the code splitting algorithm described above, the result will be this invalid code:
<table>
<tr><th>Chunk for entry1.js</th><th>Chunk for entry2.js</th><th>Chunk for shared code</th></tr>
<tr><td>
```js
import {
data
} from "./chunk.js";
// entry1.js
console.log(data);
```
</td><td>
```js
import {
data
} from "./chunk.js";
// data.js
function setData(value) {
data = value;
}
// entry2.js
setData(123);
```
</td><td>
```js
// data.js
let data;
export {
data
};
```
</td></tr>
</table>
The assignment `data = value` will crash at run time with `TypeError: Assignment to constant variable`. To fix this, we must make sure that assignment ends up in the same chunk as the declaration `let data`.
This is done by linking the parts with the assignments and the parts with the symbol declarations together such that their entry point sets are the same. That way all of those parts are marked as reachable from all entry points that can reach any of those parts. Note that linking a group of parts together also involves marking all dependencies of all parts in that group as reachable from those entry points too, including propagathing through any part groups those dependencies are already linked with, recursively.
The grouping of parts can be non-trivial because there may be many parts involved and many assignments to different variables. Grouping is done by finding connected components on the graph where nodes are parts and edges are cross-part assignments.
With this algorithm, the function `setData` in our example moves into the chunk of shared code after being bundled with code splitting:
<table>
<tr><th>Chunk for entry1.js</th><th>Chunk for entry2.js</th><th>Chunk for shared code</th></tr>
<tr><td>
```js
import {
data
} from "./chunk.js";
// entry1.js
console.log(data);
```
</td><td>
```js
import {
setData
} from "./chunk.js";
// entry2.js
setData(123);
```
</td><td>
```js
// data.js
let data;
function setData(value) {
data = value;
}
export {
data,
setData
};
```
</td></tr>
</table>
This code no longer contains assignments to cross-chunk variables.
## Notes about printing
The printer converts JavaScript ASTs back into JavaScript source code. This is mainly intended to be consumed by the JavaScript VM for execution, with a secondary goal of being readable enough to debug when minification is disabled. It's not intended to be used as a code formatting tool and does not make complex formatting decisions. It handles the insertion of parentheses to preserve operator precedence as appropriate.
Each file is printed independently from other files, so files can be printed in parallel. This extends to source map generation. As each file is printed, the printer builds up a "source map chunk" which is a [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity)-encoded sequence of source map offsets assuming the output file starts with the AST currently being printed. That source map chunk will later be "rebased" to start at the correct offset when all source map chunks are joined together. This is done by rewriting the first item in the sequence, which happens in `AppendSourceMapChunk()`.
The current AST representation uses a single integer offset per AST node to store the location information. This is the index of the starting byte for that syntax construct in the original source file. Using this representation means that it's not possible to merge ASTs from two separate files and still have source maps work. That's not a problem since AST printing is fully parallelized in esbuild, but is something to keep in mind when modifying the code.
### Symbol minification
An important part of JavaScript minification is symbol renaming. Internal symbols (i.e. those not externally visible) can be renamed to shorter names without changing the meaning of the code. That looks something like this:
<table>
<tr><th>Original code</th><th>Code with symbol minification</th></tr>
<tr><td>
```js
function useReducer(reducer, initialState) {
let [state, setState] = useState(initialState);
function dispatch(action) {
let nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
```
</td><td>
```js
function useReducer(b, c) {
let [a, d] = useState(c);
function e(f) {
let g = b(a, f);
d(g);
}
return [a, e];
}
```
</td></tr>
</table>
It may initially seem like we can easily rename all symbols to a single character by assigning a unicode character to each one. There are over 100,000 unicode characters that are valid JavaScript identifiers after all. However, the goal is actually to use as few bytes as possible, and most unicode characters use multiple bytes when encoded as UTF-8.
For this reason most JavaScript minifiers (including esbuild) restrict generated symbols to ASCII-only. With ASCII, JavaScript has only 54 possible one-character identifiers and 3453 possible two-character identifiers, so we should use the shortest names to rename the most frequently used symbols so that we can save the most bytes.
Another consideration is that symbols in sibling scopes can share the same name without conflicting due to scoping rules. We can use this to our advantage by deliberately merging unrelated symbols in independent scopes, and then adding the frequency statistics of all of these symbols together before generating the final names.
Something to keep in mind when renaming symbols is that the resulting JavaScript will likely be compressed before being downloaded, usually with gzip compression. Repeated sequences of characters will compress better than unique sequences of characters. A trick esbuild borrows from [Google Closure Compiler](https://github.com/google/closure-compiler) is to merge the symbols for arguments of sibling functions together:
```js
// Before renaming
function readFile(path, encoding, callback) { ... }
function writeFile(path, contents, mode, callback) { ... }
```
```js
// After renaming
function x(a, b, c) { ... }
function y(a, b, c, d) { ... }
```
Because the symbols for the function arguments have been merged together in order, the character sequence `a, b, c` is present in both functions which should help with gzip compression. This is handled in esbuild by assigning each symbol in a nested (i.e. not top-level) scope a "slot", which is an incrementing index into an array of frequency counters. The symbols `a`, `b`, `c`, and `d` would be assigned slots `0`, `1`, `2`, and `3` in the example above. This approach is extra helpful for esbuild because slot assignments can be computed in parallel.
The algorithm:
1. For each input source file (in parallel)
1. Track information for each top-level statement while parsing
Each top-level statement tracks the which symbols are used, how many times each symbol is used, which symbols it defines, and which nested scopes that top-level statement contains. Code splitting operates on top-level statement boundaries, so this information will tell us exactly what symbols to include in the frequency analysis after code splitting is complete. This information is collected during parsing.
2. Assign slots to symbols in nested scopes
Traverse the scope tree and assign slots to all symbols declared within each scope (skipping top-level scopes). Each scope in the scope tree starts assigning slots immediately after the maximum slot of the parent scope.
2. For each output chunk file (in parallel)
1. Create an array of frequency counters
Each counter is indexed by slot and all counts are initially 0. The array length starts off being large enough to handle the maximum slot value of all files included in the chunk.
2. Assign slots for top-level symbols
For each top-level statement included in the chunk, iterate over all top-level symbols declared in that statement and assign each symbol to a new slot by appending to the counter array. This slot will not overlap with any slots from nested scopes. The count starts off at 1 to represent the declaration.
3. Accumulate symbol usage counts into slots
For each top-level statement included in the chunk, iterate over all symbol use records and increment the counter in that symbol's slot by the stored count from the symbol use record.
4. Sort slots by decreasing count
This should tell us which symbol slots should be assigned the shortest names to minimize file size.
5. Assign names to slots in order
Names are assigned from a fixed sequence of identifiers that starts off using one-character names, then two-character names, and so on (i.e. `a b c ... aa ba ca ... aaa baa caa ...`). We must be careful to avoid any JavaScript keywords such as `do` and `if` that would cause syntax errors. We must also avoid the names of any "unbound" symbols, which are symbols that are used without being declared such as `$` for jQuery.
6. When printing, use the name in that symbol's slot
The printer only needs access to the array containing the names (indexed by slot) and a way of mapping a symbol reference to a slot index. This is a compact representation that doesn't need a lot of memory per chunk because the slot array length is O(unique symbol names) instead of O(number of symbols). The mapping of symbols from nested scopes (which is usually the majority of symbols) to their slot index is static and can be shared between all chunks.
It's worth mentioning that this algorithm must be performed three separate times because there are three separate symbol namespaces in JavaScript that need minification: normal symbols, [label symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label), and [private symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields). The same name in separate namespaces can't conflict with each other, so it's ok to reuse the same name across namespaces.
@@ -0,0 +1,33 @@
# Development
Here are some quick notes about how I develop esbuild.
## Primary workflow
My development workflow revolves around the top-level [`Makefile`](../Makefile), which I use as a script runner.
1. **Build**
Assuming you have [Go](https://go.dev/) installed, you can compile esbuild by running `make` in the top-level directory (or `go build ./cmd/esbuild` if you don't have `make` installed). This creates an executable called `esbuild` (or `esbuild.exe` on Windows).
2. **Test**
You can run the tests written in Go by running `make test-go` in the top-level directory (or `go test ./internal/... ./pkg/...` if you don't have `make` installed).
If you want to run more kinds of tests, you can run `make test` instead. This requires installing [node](https://nodejs.org/). And it's possible to run even more tests than that with additional `make` commands (read the [`Makefile`](../Makefile) for details).
3. **Publish**
Here's what I do to publish a new release:
1. Bump the version in [`version.txt`](../version.txt)
2. Copy that version into [`CHANGELOG.md`](../CHANGELOG.md)
3. Run `make publish-all` and follow the prompts
## Running in the browser
If you want to test esbuild in the browser (lets you try out lots of things rapidly), you can:
1. Run `make platform-wasm` to build the WebAssembly version of esbuild
2. Serve the repo directory over HTTP (such as with `./esbuild --servedir=.`)
3. Visit [`/scripts/try.html`](../scripts/try.html) in your browser
+10
View File
@@ -0,0 +1,10 @@
module github.com/evanw/esbuild
// Support for Go 1.13 is deliberate so people can build esbuild
// themselves for old OS versions. Please do not change this.
go 1.13
// This dependency cannot be upgraded or esbuild would no longer
// compile with Go 1.13. Please do not change this. For more info,
// please read this: https://esbuild.github.io/faq/#old-go-version
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
+2
View File
@@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+1
View File
@@ -0,0 +1 @@
1.25.7
@@ -0,0 +1,24 @@
<svg width="800" height="130" fill="black" font-family="sans-serif" font-size="13px" xmlns="http://www.w3.org/2000/svg">
<rect x="120" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="260.7425382188789" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="401.4850764377578" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="542.2276146566368" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="682.9701528755156" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="120" y="23" width="5.488958990536278" height="14" fill="#FFCF00"/>
<text x="112" y="30" text-anchor="end" dominant-baseline="middle" font-weight="bold" fill="#C9D1D9">esbuild</text>
<text x="133.48895899053628" y="30" dominant-baseline="middle" font-weight="bold" fill="#C9D1D9">0.39s</text>
<rect x="120" y="43" width="209.84712448434846" height="14" fill="#FFCF00"/>
<text x="112" y="50" text-anchor="end" dominant-baseline="middle" fill="#C9D1D9">parcel 2</text>
<text x="337.84712448434846" y="50" dominant-baseline="middle" fill="#C9D1D9">14.91s</text>
<rect x="120" y="63" width="479.9320553263771" height="14" fill="#FFCF00"/>
<text x="112" y="70" text-anchor="end" dominant-baseline="middle" fill="#C9D1D9">rollup 4 + terser</text>
<text x="607.9320553263772" y="70" dominant-baseline="middle" fill="#C9D1D9">34.10s</text>
<rect x="120" y="83" width="580" height="14" fill="#FFCF00"/>
<text x="112" y="90" text-anchor="end" dominant-baseline="middle" fill="#C9D1D9">webpack 5</text>
<text x="708" y="90" dominant-baseline="middle" fill="#C9D1D9">41.21s</text>
<text x="120" y="104" text-anchor="middle" dominant-baseline="hanging" fill="#C9D1D9">0s</text>
<text x="260.7425382188789" y="104" text-anchor="middle" dominant-baseline="hanging" fill="#C9D1D9">10s</text>
<text x="401.4850764377578" y="104" text-anchor="middle" dominant-baseline="hanging" fill="#C9D1D9">20s</text>
<text x="542.2276146566368" y="104" text-anchor="middle" dominant-baseline="hanging" fill="#C9D1D9">30s</text>
<text x="682.9701528755156" y="104" text-anchor="middle" dominant-baseline="hanging" fill="#C9D1D9">40s</text>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,24 @@
<svg width="800" height="130" fill="black" font-family="sans-serif" font-size="13px" xmlns="http://www.w3.org/2000/svg">
<rect x="120" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="260.7425382188789" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="401.4850764377578" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="542.2276146566368" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="682.9701528755156" y="20" width="1" height="80" fill="#7F7F7F" fill-opacity="0.25"/>
<rect x="120" y="23" width="5.488958990536278" height="14" fill="#FFCF00"/>
<text x="112" y="30" text-anchor="end" dominant-baseline="middle" font-weight="bold">esbuild</text>
<text x="133.48895899053628" y="30" dominant-baseline="middle" font-weight="bold">0.39s</text>
<rect x="120" y="43" width="209.84712448434846" height="14" fill="#FFCF00"/>
<text x="112" y="50" text-anchor="end" dominant-baseline="middle">parcel 2</text>
<text x="337.84712448434846" y="50" dominant-baseline="middle">14.91s</text>
<rect x="120" y="63" width="479.9320553263771" height="14" fill="#FFCF00"/>
<text x="112" y="70" text-anchor="end" dominant-baseline="middle">rollup 4 + terser</text>
<text x="607.9320553263772" y="70" dominant-baseline="middle">34.10s</text>
<rect x="120" y="83" width="580" height="14" fill="#FFCF00"/>
<text x="112" y="90" text-anchor="end" dominant-baseline="middle">webpack 5</text>
<text x="708" y="90" dominant-baseline="middle">41.21s</text>
<text x="120" y="104" text-anchor="middle" dominant-baseline="hanging">0s</text>
<text x="260.7425382188789" y="104" text-anchor="middle" dominant-baseline="hanging">10s</text>
<text x="401.4850764377578" y="104" text-anchor="middle" dominant-baseline="hanging">20s</text>
<text x="542.2276146566368" y="104" text-anchor="middle" dominant-baseline="hanging">30s</text>
<text x="682.9701528755156" y="104" text-anchor="middle" dominant-baseline="hanging">40s</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#FFCF00"/>
<path d="M47.5 52.5L95 100l-47.5 47.5m60-95L155 100l-47.5 47.5" fill="none" stroke="#191919" stroke-width="24"/>
</svg>

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,6 @@
<svg width="640" height="250" fill="black" font-family="sans-serif" xmlns="http://www.w3.org/2000/svg">
<circle cx="125" cy="125" r="75" fill="#FFCF00"/>
<path d="M85.625 89.375L121.25 125L85.625 160.625M130.625 89.375L166.25 125L130.625 160.625" fill="none" stroke="#191919" stroke-width="18"/>
<text x="241" y="152" font-size="100" font-weight="bold" fill="#C9D1D9">esbuild</text>
<text x="241" y="184" font-size="21" font-style="italic" fill="#C9D1D9">An extremely fast bundler for the web</text>
</svg>

After

Width:  |  Height:  |  Size: 515 B

@@ -0,0 +1,6 @@
<svg width="640" height="250" fill="black" font-family="sans-serif" xmlns="http://www.w3.org/2000/svg">
<circle cx="125" cy="125" r="75" fill="#FFCF00"/>
<path d="M85.625 89.375L121.25 125L85.625 160.625M130.625 89.375L166.25 125L130.625 160.625" fill="none" stroke="#191919" stroke-width="18"/>
<text x="241" y="152" font-size="100" font-weight="bold">esbuild</text>
<text x="241" y="184" font-size="21" font-style="italic">An extremely fast bundler for the web</text>
</svg>

After

Width:  |  Height:  |  Size: 485 B

@@ -0,0 +1,7 @@
package api_helpers
// This flag is set by the CLI to activate the timer. It's put here instead of
// by the timer to discourage code from checking this flag. Only the code that
// creates the root timer should check this flag. Other code should check that
// the timer is not null to detect if the timer is being used or not.
var UseTimer bool
+827
View File
@@ -0,0 +1,827 @@
package ast
// This file contains data structures that are used with the AST packages for
// both JavaScript and CSS. This helps the bundler treat both AST formats in
// a somewhat format-agnostic manner.
import (
"sort"
"strings"
"github.com/evanw/esbuild/internal/helpers"
"github.com/evanw/esbuild/internal/logger"
)
type ImportKind uint8
const (
// An entry point provided by the user
ImportEntryPoint ImportKind = iota
// An ES6 import or re-export statement
ImportStmt
// A call to "require()"
ImportRequire
// An "import()" expression with a string argument
ImportDynamic
// A call to "require.resolve()"
ImportRequireResolve
// A CSS "@import" rule
ImportAt
// A CSS "composes" declaration
ImportComposesFrom
// A CSS "url(...)" token
ImportURL
)
func (kind ImportKind) StringForMetafile() string {
switch kind {
case ImportStmt:
return "import-statement"
case ImportRequire:
return "require-call"
case ImportDynamic:
return "dynamic-import"
case ImportRequireResolve:
return "require-resolve"
case ImportAt:
return "import-rule"
case ImportComposesFrom:
return "composes-from"
case ImportURL:
return "url-token"
case ImportEntryPoint:
return "entry-point"
default:
panic("Internal error")
}
}
func (kind ImportKind) IsFromCSS() bool {
switch kind {
case ImportAt, ImportComposesFrom, ImportURL:
return true
}
return false
}
func (kind ImportKind) MustResolveToCSS() bool {
switch kind {
case ImportAt, ImportComposesFrom:
return true
}
return false
}
type ImportPhase uint8
const (
EvaluationPhase ImportPhase = iota
// See: https://github.com/tc39/proposal-defer-import-eval
DeferPhase
// See: https://github.com/tc39/proposal-source-phase-imports
SourcePhase
)
type ImportRecordFlags uint16
const (
// Sometimes the parser creates an import record and decides it isn't needed.
// For example, TypeScript code may have import statements that later turn
// out to be type-only imports after analyzing the whole file.
IsUnused ImportRecordFlags = 1 << iota
// If this is true, the import contains syntax like "* as ns". This is used
// to determine whether modules that have no exports need to be wrapped in a
// CommonJS wrapper or not.
ContainsImportStar
// If this is true, the import contains an import for the alias "default",
// either via the "import x from" or "import {default as x} from" syntax.
ContainsDefaultAlias
// If this is true, the import contains an import for the alias "__esModule",
// via the "import {__esModule} from" syntax.
ContainsESModuleAlias
// If true, this "export * from 'path'" statement is evaluated at run-time by
// calling the "__reExport()" helper function
CallsRunTimeReExportFn
// Tell the printer to wrap this call to "require()" in "__toESM(...)"
WrapWithToESM
// Tell the printer to wrap this ESM exports object in "__toCJS(...)"
WrapWithToCJS
// Tell the printer to use the runtime "__require()" instead of "require()"
CallRuntimeRequire
// True for the following cases:
//
// try { require('x') } catch { handle }
// try { await import('x') } catch { handle }
// try { require.resolve('x') } catch { handle }
// import('x').catch(handle)
// import('x').then(_, handle)
//
// In these cases we shouldn't generate an error if the path could not be
// resolved.
HandlesImportErrors
// If true, this was originally written as a bare "import 'file'" statement
WasOriginallyBareImport
// If true, this import can be removed if it's unused
IsExternalWithoutSideEffects
// If true, "assert { type: 'json' }" was present
AssertTypeJSON
// If true, do not generate "external": true in the metafile
ShouldNotBeExternalInMetafile
// CSS "@import" of an empty file should be removed
WasLoadedWithEmptyLoader
// Unique keys are randomly-generated strings that are used to replace paths
// in the source code after it's printed. These must not ever be split apart.
ContainsUniqueKey
)
func (flags ImportRecordFlags) Has(flag ImportRecordFlags) bool {
return (flags & flag) != 0
}
type ImportRecord struct {
AssertOrWith *ImportAssertOrWith
GlobPattern *GlobPattern
Path logger.Path
Range logger.Range
// If the "HandlesImportErrors" flag is present, then this is the location
// of the error handler. This is used for error reporting.
ErrorHandlerLoc logger.Loc
// The resolved source index for an internal import (within the bundle) or
// invalid for an external import (not included in the bundle)
SourceIndex Index32
// Files imported via the "copy" loader use this instead of "SourceIndex"
// because they are sort of like external imports, and are not bundled.
CopySourceIndex Index32
Flags ImportRecordFlags
Phase ImportPhase
Kind ImportKind
}
type AssertOrWithKeyword uint8
const (
AssertKeyword AssertOrWithKeyword = iota
WithKeyword
)
func (kw AssertOrWithKeyword) String() string {
if kw == AssertKeyword {
return "assert"
}
return "with"
}
type ImportAssertOrWith struct {
Entries []AssertOrWithEntry
KeywordLoc logger.Loc
InnerOpenBraceLoc logger.Loc
InnerCloseBraceLoc logger.Loc
OuterOpenBraceLoc logger.Loc
OuterCloseBraceLoc logger.Loc
Keyword AssertOrWithKeyword
}
type AssertOrWithEntry struct {
Key []uint16 // An identifier or a string
Value []uint16 // Always a string
KeyLoc logger.Loc
ValueLoc logger.Loc
PreferQuotedKey bool
}
func FindAssertOrWithEntry(assertions []AssertOrWithEntry, name string) *AssertOrWithEntry {
for _, assertion := range assertions {
if helpers.UTF16EqualsString(assertion.Key, name) {
return &assertion
}
}
return nil
}
type GlobPattern struct {
Parts []helpers.GlobPart
ExportAlias string
Kind ImportKind
}
// This stores a 32-bit index where the zero value is an invalid index. This is
// a better alternative to storing the index as a pointer since that has the
// same properties but takes up more space and costs an extra pointer traversal.
type Index32 struct {
flippedBits uint32
}
func MakeIndex32(index uint32) Index32 {
return Index32{flippedBits: ^index}
}
func (i Index32) IsValid() bool {
return i.flippedBits != 0
}
func (i Index32) GetIndex() uint32 {
return ^i.flippedBits
}
type SymbolKind uint8
const (
// An unbound symbol is one that isn't declared in the file it's referenced
// in. For example, using "window" without declaring it will be unbound.
SymbolUnbound SymbolKind = iota
// This has special merging behavior. You're allowed to re-declare these
// symbols more than once in the same scope. These symbols are also hoisted
// out of the scope they are declared in to the closest containing function
// or module scope. These are the symbols with this kind:
//
// - Function arguments
// - Function statements
// - Variables declared using "var"
//
SymbolHoisted
SymbolHoistedFunction
// There's a weird special case where catch variables declared using a simple
// identifier (i.e. not a binding pattern) block hoisted variables instead of
// becoming an error:
//
// var e = 0;
// try { throw 1 } catch (e) {
// print(e) // 1
// var e = 2
// print(e) // 2
// }
// print(e) // 0 (since the hoisting stops at the catch block boundary)
//
// However, other forms are still a syntax error:
//
// try {} catch (e) { let e }
// try {} catch ({e}) { var e }
//
// This symbol is for handling this weird special case.
SymbolCatchIdentifier
// Generator and async functions are not hoisted, but still have special
// properties such as being able to overwrite previous functions with the
// same name
SymbolGeneratorOrAsyncFunction
// This is the special "arguments" variable inside functions
SymbolArguments
// Classes can merge with TypeScript namespaces.
SymbolClass
// Class names are not allowed to be referenced by computed property keys
SymbolClassInComputedPropertyKey
// A class-private identifier (i.e. "#foo").
SymbolPrivateField
SymbolPrivateMethod
SymbolPrivateGet
SymbolPrivateSet
SymbolPrivateGetSetPair
SymbolPrivateStaticField
SymbolPrivateStaticMethod
SymbolPrivateStaticGet
SymbolPrivateStaticSet
SymbolPrivateStaticGetSetPair
// Labels are in their own namespace
SymbolLabel
// TypeScript enums can merge with TypeScript namespaces and other TypeScript
// enums.
SymbolTSEnum
// TypeScript namespaces can merge with classes, functions, TypeScript enums,
// and other TypeScript namespaces.
SymbolTSNamespace
// In TypeScript, imports are allowed to silently collide with symbols within
// the module. Presumably this is because the imports may be type-only.
SymbolImport
// Assigning to a "const" symbol will throw a TypeError at runtime
SymbolConst
// Injected symbols can be overridden by provided defines
SymbolInjected
// Properties can optionally be renamed to shorter names
SymbolMangledProp
// CSS identifiers that are never renamed
SymbolGlobalCSS
// CSS identifiers that are renamed to be unique to the file they are in
SymbolLocalCSS
// This annotates all other symbols that don't have special behavior
SymbolOther
)
func (kind SymbolKind) IsPrivate() bool {
return kind >= SymbolPrivateField && kind <= SymbolPrivateStaticGetSetPair
}
func (kind SymbolKind) IsHoisted() bool {
return kind == SymbolHoisted || kind == SymbolHoistedFunction
}
func (kind SymbolKind) IsHoistedOrFunction() bool {
return kind.IsHoisted() || kind == SymbolGeneratorOrAsyncFunction
}
func (kind SymbolKind) IsFunction() bool {
return kind == SymbolHoistedFunction || kind == SymbolGeneratorOrAsyncFunction
}
func (kind SymbolKind) IsUnboundOrInjected() bool {
return kind == SymbolUnbound || kind == SymbolInjected
}
var InvalidRef Ref = Ref{^uint32(0), ^uint32(0)}
// Files are parsed in parallel for speed. We want to allow each parser to
// generate symbol IDs that won't conflict with each other. We also want to be
// able to quickly merge symbol tables from all files into one giant symbol
// table.
//
// We can accomplish both goals by giving each symbol ID two parts: a source
// index that is unique to the parser goroutine, and an inner index that
// increments as the parser generates new symbol IDs. Then a symbol map can
// be an array of arrays indexed first by source index, then by inner index.
// The maps can be merged quickly by creating a single outer array containing
// all inner arrays from all parsed files.
type Ref struct {
SourceIndex uint32
InnerIndex uint32
}
type LocRef struct {
Loc logger.Loc
Ref Ref
}
type ImportItemStatus uint8
const (
ImportItemNone ImportItemStatus = iota
// The linker doesn't report import/export mismatch errors
ImportItemGenerated
// The printer will replace this import with "undefined"
ImportItemMissing
)
type SymbolFlags uint16
const (
// Certain symbols must not be renamed or minified. For example, the
// "arguments" variable is declared by the runtime for every function.
// Renaming can also break any identifier used inside a "with" statement.
MustNotBeRenamed SymbolFlags = 1 << iota
// In React's version of JSX, lower-case names are strings while upper-case
// names are identifiers. If we are preserving JSX syntax (i.e. not
// transforming it), then we need to be careful to name the identifiers
// something with a capital letter so further JSX processing doesn't treat
// them as strings instead.
MustStartWithCapitalLetterForJSX
// If true, this symbol is the target of a "__name" helper function call.
// This call is special because it deliberately doesn't count as a use
// of the symbol (otherwise keeping names would disable tree shaking)
// so "UseCountEstimate" is not incremented. This flag helps us know to
// avoid optimizing this symbol when "UseCountEstimate" is 1 in this case.
DidKeepName
// Sometimes we lower private symbols even if they are supported. For example,
// consider the following TypeScript code:
//
// class Foo {
// #foo = 123
// bar = this.#foo
// }
//
// If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar"
// must use assignment semantics instead of define semantics. We can compile
// that to this code:
//
// class Foo {
// constructor() {
// this.#foo = 123;
// this.bar = this.#foo;
// }
// #foo;
// }
//
// However, we can't do the same for static fields:
//
// class Foo {
// static #foo = 123
// static bar = this.#foo
// }
//
// Compiling these static fields to something like this would be invalid:
//
// class Foo {
// static #foo;
// }
// Foo.#foo = 123;
// Foo.bar = Foo.#foo;
//
// Thus "#foo" must be lowered even though it's supported. Another case is
// when we're converting top-level class declarations to class expressions
// to avoid the TDZ and the class shadowing symbol is referenced within the
// class body:
//
// class Foo {
// static #foo = Foo
// }
//
// This cannot be converted into something like this:
//
// var Foo = class {
// static #foo;
// };
// Foo.#foo = Foo;
//
PrivateSymbolMustBeLowered
// This is used to remove the all but the last function re-declaration if a
// function is re-declared multiple times like this:
//
// function foo() { console.log(1) }
// function foo() { console.log(2) }
//
RemoveOverwrittenFunctionDeclaration
// This flag is to avoid warning about this symbol more than once. It only
// applies to the "module" and "exports" unbound symbols.
DidWarnAboutCommonJSInESM
// If this is present, the symbol could potentially be overwritten. This means
// it's not safe to make assumptions about this symbol from the initializer.
CouldPotentiallyBeMutated
// This flags all symbols that were exported from the module using the ES6
// "export" keyword, either directly on the declaration or using "export {}".
WasExported
// This means the symbol is a normal function that has no body statements.
IsEmptyFunction
// This means the symbol is a normal function that takes a single argument
// and returns that argument.
IsIdentityFunction
// If true, calls to this symbol can be unwrapped (i.e. removed except for
// argument side effects) if the result is unused.
CallCanBeUnwrappedIfUnused
)
func (flags SymbolFlags) Has(flag SymbolFlags) bool {
return (flags & flag) != 0
}
// Note: the order of values in this struct matters to reduce struct size.
type Symbol struct {
// This is used for symbols that represent items in the import clause of an
// ES6 import statement. These should always be referenced by EImportIdentifier
// instead of an EIdentifier. When this is present, the expression should
// be printed as a property access off the namespace instead of as a bare
// identifier.
//
// For correctness, this must be stored on the symbol instead of indirectly
// associated with the Ref for the symbol somehow. In ES6 "flat bundling"
// mode, re-exported symbols are collapsed using MergeSymbols() and renamed
// symbols from other files that end up at this symbol must be able to tell
// if it has a namespace alias.
NamespaceAlias *NamespaceAlias
// This is the name that came from the parser. Printed names may be renamed
// during minification or to avoid name collisions. Do not use the original
// name during printing.
OriginalName string
// Used by the parser for single pass parsing. Symbols that have been merged
// form a linked-list where the last link is the symbol to use. This link is
// an invalid ref if it's the last link. If this isn't invalid, you need to
// FollowSymbols to get the real one.
Link Ref
// An estimate of the number of uses of this symbol. This is used to detect
// whether a symbol is used or not. For example, TypeScript imports that are
// unused must be removed because they are probably type-only imports. This
// is an estimate and may not be completely accurate due to oversights in the
// code. But it should always be non-zero when the symbol is used.
UseCountEstimate uint32
// This is for generating cross-chunk imports and exports for code splitting.
ChunkIndex Index32
// This is used for minification. Symbols that are declared in sibling scopes
// can share a name. A good heuristic (from Google Closure Compiler) is to
// assign names to symbols from sibling scopes in declaration order. That way
// local variable names are reused in each global function like this, which
// improves gzip compression:
//
// function x(a, b) { ... }
// function y(a, b, c) { ... }
//
// The parser fills this in for symbols inside nested scopes. There are three
// slot namespaces: regular symbols, label symbols, and private symbols.
NestedScopeSlot Index32
// Boolean values should all be flags instead to save space
Flags SymbolFlags
Kind SymbolKind
// We automatically generate import items for property accesses off of
// namespace imports. This lets us remove the expensive namespace imports
// while bundling in many cases, replacing them with a cheap import item
// instead:
//
// import * as ns from 'path'
// ns.foo()
//
// That can often be replaced by this, which avoids needing the namespace:
//
// import {foo} from 'path'
// foo()
//
// However, if the import is actually missing then we don't want to report a
// compile-time error like we do for real import items. This status lets us
// avoid this. We also need to be able to replace such import items with
// undefined, which this status is also used for.
ImportItemStatus ImportItemStatus
}
// You should call "MergeSymbols" instead of calling this directly
func (newSymbol *Symbol) MergeContentsWith(oldSymbol *Symbol) {
newSymbol.UseCountEstimate += oldSymbol.UseCountEstimate
if oldSymbol.Flags.Has(MustNotBeRenamed) && !newSymbol.Flags.Has(MustNotBeRenamed) {
newSymbol.OriginalName = oldSymbol.OriginalName
newSymbol.Flags |= MustNotBeRenamed
}
if oldSymbol.Flags.Has(MustStartWithCapitalLetterForJSX) {
newSymbol.Flags |= MustStartWithCapitalLetterForJSX
}
}
type SlotNamespace uint8
const (
SlotDefault SlotNamespace = iota
SlotLabel
SlotPrivateName
SlotMangledProp
SlotMustNotBeRenamed
)
func (s *Symbol) SlotNamespace() SlotNamespace {
if s.Kind == SymbolUnbound || s.Flags.Has(MustNotBeRenamed) {
return SlotMustNotBeRenamed
}
if s.Kind.IsPrivate() {
return SlotPrivateName
}
if s.Kind == SymbolLabel {
return SlotLabel
}
if s.Kind == SymbolMangledProp {
return SlotMangledProp
}
return SlotDefault
}
type SlotCounts [4]uint32
func (a *SlotCounts) UnionMax(b SlotCounts) {
for i := range *a {
ai := &(*a)[i]
bi := b[i]
if *ai < bi {
*ai = bi
}
}
}
type NamespaceAlias struct {
Alias string
NamespaceRef Ref
}
type SymbolMap struct {
// This could be represented as a "map[Ref]Symbol" but a two-level array was
// more efficient in profiles. This appears to be because it doesn't involve
// a hash. This representation also makes it trivial to quickly merge symbol
// maps from multiple files together. Each file only generates symbols in a
// single inner array, so you can join the maps together by just make a
// single outer array containing all of the inner arrays. See the comment on
// "Ref" for more detail.
SymbolsForSource [][]Symbol
}
func NewSymbolMap(sourceCount int) SymbolMap {
return SymbolMap{make([][]Symbol, sourceCount)}
}
func (sm SymbolMap) Get(ref Ref) *Symbol {
return &sm.SymbolsForSource[ref.SourceIndex][ref.InnerIndex]
}
// Returns the canonical ref that represents the ref for the provided symbol.
// This may not be the provided ref if the symbol has been merged with another
// symbol.
func FollowSymbols(symbols SymbolMap, ref Ref) Ref {
symbol := symbols.Get(ref)
if symbol.Link == InvalidRef {
return ref
}
link := FollowSymbols(symbols, symbol.Link)
// Only write if needed to avoid concurrent map update hazards
if symbol.Link != link {
symbol.Link = link
}
return link
}
// Use this before calling "FollowSymbols" from separate threads to avoid
// concurrent map update hazards. In Go, mutating a map is not threadsafe
// but reading from a map is. Calling "FollowAllSymbols" first ensures that
// all mutation is done up front.
func FollowAllSymbols(symbols SymbolMap) {
for sourceIndex, inner := range symbols.SymbolsForSource {
for symbolIndex := range inner {
FollowSymbols(symbols, Ref{uint32(sourceIndex), uint32(symbolIndex)})
}
}
}
// Makes "old" point to "new" by joining the linked lists for the two symbols
// together. That way "FollowSymbols" on both "old" and "new" will result in
// the same ref.
func MergeSymbols(symbols SymbolMap, old Ref, new Ref) Ref {
if old == new {
return new
}
oldSymbol := symbols.Get(old)
if oldSymbol.Link != InvalidRef {
oldSymbol.Link = MergeSymbols(symbols, oldSymbol.Link, new)
return oldSymbol.Link
}
newSymbol := symbols.Get(new)
if newSymbol.Link != InvalidRef {
newSymbol.Link = MergeSymbols(symbols, old, newSymbol.Link)
return newSymbol.Link
}
oldSymbol.Link = new
newSymbol.MergeContentsWith(oldSymbol)
return new
}
// This is a histogram of character frequencies for minification
type CharFreq [64]int32
func (freq *CharFreq) Scan(text string, delta int32) {
if delta == 0 {
return
}
// This matches the order in "DefaultNameMinifier"
for i, n := 0, len(text); i < n; i++ {
c := text[i]
switch {
case c >= 'a' && c <= 'z':
(*freq)[c-'a'] += delta
case c >= 'A' && c <= 'Z':
(*freq)[c-('A'-26)] += delta
case c >= '0' && c <= '9':
(*freq)[c+(52-'0')] += delta
case c == '_':
(*freq)[62] += delta
case c == '$':
(*freq)[63] += delta
}
}
}
func (freq *CharFreq) Include(other *CharFreq) {
for i := 0; i < 64; i++ {
(*freq)[i] += (*other)[i]
}
}
type NameMinifier struct {
head string
tail string
}
var DefaultNameMinifierJS = NameMinifier{
head: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$",
tail: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$",
}
var DefaultNameMinifierCSS = NameMinifier{
head: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_",
tail: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",
}
type charAndCount struct {
char string
count int32
index byte
}
// This type is just so we can use Go's native sort function
type charAndCountArray []charAndCount
func (a charAndCountArray) Len() int { return len(a) }
func (a charAndCountArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a charAndCountArray) Less(i int, j int) bool {
ai := a[i]
aj := a[j]
return ai.count > aj.count || (ai.count == aj.count && ai.index < aj.index)
}
func (source NameMinifier) ShuffleByCharFreq(freq CharFreq) NameMinifier {
// Sort the histogram in descending order by count
array := make(charAndCountArray, 64)
for i := 0; i < len(source.tail); i++ {
array[i] = charAndCount{
char: source.tail[i : i+1],
index: byte(i),
count: freq[i],
}
}
sort.Sort(array)
// Compute the identifier start and identifier continue sequences
minifier := NameMinifier{}
for _, item := range array {
if item.char < "0" || item.char > "9" {
minifier.head += item.char
}
minifier.tail += item.char
}
return minifier
}
func (minifier NameMinifier) NumberToMinifiedName(i int) string {
n_head := len(minifier.head)
n_tail := len(minifier.tail)
j := i % n_head
var name strings.Builder
name.WriteString(minifier.head[j : j+1])
i = i / n_head
for i > 0 {
i--
j := i % n_tail
name.WriteString(minifier.tail[j : j+1])
i = i / n_tail
}
return name.String()
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,291 @@
package bundler_tests
import (
"testing"
"github.com/evanw/esbuild/internal/config"
)
var glob_suite = suite{
name: "glob",
}
func TestGlobBasicNoBundle(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeConvertFormat,
OutputFormat: config.FormatCommonJS,
AbsOutputFile: "/out.js",
},
})
}
func TestGlobBasicNoSplitting(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
"/src/a.js": `module.exports = 'a'`,
"/src/b.js": `module.exports = 'b'`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSGlobBasicNoSplitting(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
"/src/a.ts": `module.exports = 'a'`,
"/src/b.ts": `module.exports = 'b'`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestGlobBasicSplitting(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
"/src/a.js": `module.exports = 'a'`,
"/src/b.js": `module.exports = 'b'`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
CodeSplitting: true,
},
})
}
func TestTSGlobBasicSplitting(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
"/src/a.ts": `module.exports = 'a'`,
"/src/b.ts": `module.exports = 'b'`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
CodeSplitting: true,
},
})
}
func TestGlobDirDoesNotExist(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab),
import: import('./src/' + ab),
},
template: {
require: require(` + "`./src/${ab}`" + `),
import: import(` + "`./src/${ab}`" + `),
},
})
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
CodeSplitting: true,
},
expectedScanLog: `entry.js: ERROR: Could not resolve require("./src/**/*")
entry.js: ERROR: Could not resolve import("./src/**/*")
`,
})
}
func TestGlobNoMatches(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab + '.json'),
import: import('./src/' + ab + '.json'),
},
template: {
require: require(` + "`./src/${ab}.json`" + `),
import: import(` + "`./src/${ab}.json`" + `),
},
})
`,
"/src/dummy.js": ``,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
CodeSplitting: true,
},
expectedScanLog: `entry.js: WARNING: The glob pattern require("./src/**/*.json") did not match any files
entry.js: WARNING: The glob pattern import("./src/**/*.json") did not match any files
`,
})
}
func TestGlobEntryPointAbsPath(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
works = true
`,
},
entryPaths: []string{"/Users/user/project/**/*.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
},
})
}
func TestGlobWildcardSlash(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/' + ab + '.js'),
import: import('./src/' + ab + '.js'),
},
template: {
require: require(` + "`./src/${ab}.js`" + `),
import: import(` + "`./src/${ab}.js`" + `),
},
})
`,
"/src/file-a.js": `module.exports = 'a'`,
"/src/file-b.js": `module.exports = 'b'`,
"/src/file-a.js.map": `DO NOT BUNDLE`,
"/src/file-b.js.map": `DO NOT BUNDLE`,
"/src/nested/dir/file-a.js": `module.exports = 'a'`,
"/src/nested/dir/file-b.js": `module.exports = 'b'`,
"/src/nested/dir/file-a.js.map": `DO NOT BUNDLE`,
"/src/nested/dir/file-b.js.map": `DO NOT BUNDLE`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestGlobWildcardNoSlash(t *testing.T) {
glob_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const ab = Math.random() < 0.5 ? 'a.js' : 'b.js'
console.log({
concat: {
require: require('./src/file-' + ab + '.js'),
import: import('./src/file-' + ab + '.js'),
},
template: {
require: require(` + "`./src/file-${ab}.js`" + `),
import: import(` + "`./src/file-${ab}.js`" + `),
},
})
`,
"/src/file-a.js": `module.exports = 'a'`,
"/src/file-b.js": `module.exports = 'b'`,
"/src/file-a.js.map": `DO NOT BUNDLE`,
"/src/file-b.js.map": `DO NOT BUNDLE`,
"/src/nested/dir/file-a.js": `DO NOT BUNDLE`,
"/src/nested/dir/file-b.js": `DO NOT BUNDLE`,
"/src/nested/dir/file-a.js.map": `DO NOT BUNDLE`,
"/src/nested/dir/file-b.js.map": `DO NOT BUNDLE`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
@@ -0,0 +1,435 @@
package bundler_tests
import (
"testing"
"github.com/evanw/esbuild/internal/config"
)
var importphase_suite = suite{
name: "importphase",
}
func TestImportDeferExternalESM(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
})
}
func TestImportDeferExternalCommonJS(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatCommonJS,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
expectedScanLog: `entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
`,
})
}
func TestImportDeferExternalIIFE(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatIIFE,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
expectedScanLog: `entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
`,
})
}
func TestImportDeferInternalESM(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
entry.js: ERROR: Bundling with deferred imports is not supported unless they are external
`,
})
}
func TestImportDeferInternalCommonJS(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatCommonJS,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "cjs" output format is not supported
`,
})
}
func TestImportDeferInternalIIFE(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import defer * as foo0 from './foo.json'
import defer * as foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.defer('./foo.json'),
import.defer('./foo.json', { with: { type: 'json' } }),
import.defer(` + "`./${foo}.json`" + `),
import.defer(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatIIFE,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
entry.js: ERROR: Bundling deferred imports with the "iife" output format is not supported
`,
})
}
func TestImportSourceExternalESM(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
})
}
func TestImportSourceExternalCommonJS(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatCommonJS,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
expectedScanLog: `entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
`,
})
}
func TestImportSourceExternalIIFE(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatIIFE,
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Patterns: []config.WildcardPattern{{Suffix: ".json"}},
},
},
},
expectedScanLog: `entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
`,
})
}
func TestImportSourceInternalESM(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
entry.js: ERROR: Bundling with source phase imports is not supported unless they are external
`,
})
}
func TestImportSourceInternalCommonJS(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatCommonJS,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "cjs" output format is not supported
`,
})
}
func TestImportSourceInternalIIFE(t *testing.T) {
importphase_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import source foo0 from './foo.json'
import source foo1 from './foo.json' with { type: 'json' }
console.log(
foo0,
foo1,
import.source('./foo.json'),
import.source('./foo.json', { with: { type: 'json' } }),
import.source(` + "`./${foo}.json`" + `),
import.source(` + "`./${foo}.json`" + `, { with: { type: 'json' } }),
)
`,
"/foo.json": `{}`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatIIFE,
AbsOutputFile: "/out.js",
},
expectedScanLog: `entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
entry.js: ERROR: Bundling source phase imports with the "iife" output format is not supported
`,
})
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,502 @@
package bundler_tests
import (
"testing"
"github.com/evanw/esbuild/internal/config"
)
var importstar_ts_suite = suite{
name: "importstar_ts",
}
func TestTSImportStarUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(foo)
`,
"/foo.ts": `
export const foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportImportStarUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
import * as ns from './foo'
export {ns}
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportImportStarNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
import * as ns from './foo'
export {ns}
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportImportStarCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(ns, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
import * as ns from './foo'
export {ns}
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarAsUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * as ns from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarAsNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * as ns from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarAsCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import {ns} from './bar'
let foo = 234
console.log(ns, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * as ns from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './bar'
let foo = 234
console.log(foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './bar'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarExportStarCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './bar'
let foo = 234
console.log(ns, ns.foo, foo)
`,
"/foo.ts": `
export const foo = 123
`,
"/bar.ts": `
export * from './foo'
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarCommonJSUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(foo)
`,
"/foo.ts": `
exports.foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarCommonJSCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns, ns.foo, foo)
`,
"/foo.ts": `
exports.foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarCommonJSNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
"/foo.ts": `
exports.foo = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarAndCommonJS(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import * as ns from './foo'
const ns2 = require('./foo')
console.log(ns.foo, ns2.foo)
`,
"/foo.ts": `
export const foo = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarNoBundleUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarNoBundleCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns, ns.foo, foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarNoBundleNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarMangleNoBundleUnused(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
MinifySyntax: true,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarMangleNoBundleCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns, ns.foo, foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
MinifySyntax: true,
AbsOutputFile: "/out.js",
},
})
}
func TestTSImportStarMangleNoBundleNoCapture(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './foo'
let foo = 234
console.log(ns.foo, ns.foo, foo)
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
MinifySyntax: true,
AbsOutputFile: "/out.js",
},
})
}
func TestTSReExportTypeOnlyFileES6(t *testing.T) {
importstar_ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import * as ns from './re-export'
console.log(ns.foo)
`,
"/re-export.ts": `
export * from './types1'
export * from './types2'
export * from './types3'
export * from './values'
`,
"/types1.ts": `
export interface Foo {}
export type Bar = number
console.log('some code')
`,
"/types2.ts": `
import {Foo} from "./type"
export {Foo}
console.log('some code')
`,
"/types3.ts": `
export {Foo} from "./type"
console.log('some code')
`,
"/values.ts": `
export let foo = 123
`,
"/type.ts": `
export type Foo = number
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,599 @@
package bundler_tests
import (
"testing"
"github.com/evanw/esbuild/internal/config"
)
var splitting_suite = suite{
name: "splitting",
}
func TestSplittingSharedES6IntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {foo} from "./shared.js"
console.log(foo)
`,
"/b.js": `
import {foo} from "./shared.js"
console.log(foo)
`,
"/shared.js": `export let foo = 123`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingSharedCommonJSIntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
const {foo} = require("./shared.js")
console.log(foo)
`,
"/b.js": `
const {foo} = require("./shared.js")
console.log(foo)
`,
"/shared.js": `exports.foo = 123`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicES6IntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import("./foo.js").then(({bar}) => console.log(bar))
`,
"/foo.js": `
export let bar = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicCommonJSIntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import("./foo.js").then(({default: {bar}}) => console.log(bar))
`,
"/foo.js": `
exports.bar = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicAndNotDynamicES6IntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import {bar as a} from "./foo.js"
import("./foo.js").then(({bar: b}) => console.log(a, b))
`,
"/foo.js": `
export let bar = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicAndNotDynamicCommonJSIntoES6(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import {bar as a} from "./foo.js"
import("./foo.js").then(({default: {bar: b}}) => console.log(a, b))
`,
"/foo.js": `
exports.bar = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingAssignToLocal(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {foo, setFoo} from "./shared.js"
setFoo(123)
console.log(foo)
`,
"/b.js": `
import {foo} from "./shared.js"
console.log(foo)
`,
"/shared.js": `
export let foo
export function setFoo(value) {
foo = value
}
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingSideEffectsWithoutDependencies(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {a} from "./shared.js"
console.log(a)
`,
"/b.js": `
import {b} from "./shared.js"
console.log(b)
`,
"/shared.js": `
export let a = 1
export let b = 2
console.log('side effect')
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingNestedDirectories(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/pages/pageA/page.js": `
import x from "../shared.js"
console.log(x)
`,
"/Users/user/project/src/pages/pageB/page.js": `
import x from "../shared.js"
console.log(-x)
`,
"/Users/user/project/src/pages/shared.js": `
export default 123
`,
},
entryPaths: []string{
"/Users/user/project/src/pages/pageA/page.js",
"/Users/user/project/src/pages/pageB/page.js",
},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/Users/user/project/out",
},
})
}
func TestSplittingCircularReferenceIssue251(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
export * from './b.js';
export var p = 5;
`,
"/b.js": `
export * from './a.js';
export var q = 6;
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingMissingLazyExport(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {foo} from './common.js'
console.log(foo())
`,
"/b.js": `
import {bar} from './common.js'
console.log(bar())
`,
"/common.js": `
import * as ns from './empty.js'
export function foo() { return [ns, ns.missing] }
export function bar() { return [ns.missing] }
`,
"/empty.js": `
// This forces the module into ES6 mode without importing or exporting anything
import.meta
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
expectedCompileLog: `common.js: WARNING: Import "missing" will always be undefined because the file "empty.js" has no exports
`,
})
}
func TestSplittingReExportIssue273(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
export const a = 1
`,
"/b.js": `
export { a } from './a'
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicImportIssue272(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import('./b')
`,
"/b.js": `
export default 1
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDynamicImportOutsideSourceTreeIssue264(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry1.js": `
import('package')
`,
"/Users/user/project/src/entry2.js": `
import('package')
`,
"/Users/user/project/node_modules/package/index.js": `
console.log('imported')
`,
},
entryPaths: []string{"/Users/user/project/src/entry1.js", "/Users/user/project/src/entry2.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingCrossChunkAssignmentDependencies(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {setValue} from './shared'
setValue(123)
`,
"/b.js": `
import './shared'
`,
"/shared.js": `
var observer;
var value;
export function setObserver(cb) {
observer = cb;
}
export function getValue() {
return value;
}
export function setValue(next) {
value = next;
if (observer) observer();
}
sideEffects(getValue);
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingCrossChunkAssignmentDependenciesRecursive(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import { setX } from './x'
setX()
`,
"/b.js": `
import { setZ } from './z'
setZ()
`,
"/c.js": `
import { setX2 } from './x'
import { setY2 } from './y'
import { setZ2 } from './z'
setX2();
setY2();
setZ2();
`,
"/x.js": `
let _x
export function setX(v) { _x = v }
export function setX2(v) { _x = v }
`,
"/y.js": `
import { setX } from './x'
let _y
export function setY(v) { _y = v }
export function setY2(v) { setX(v); _y = v }
`,
"/z.js": `
import { setY } from './y'
let _z
export function setZ(v) { _z = v }
export function setZ2(v) { setY(v); _z = v }
`,
},
entryPaths: []string{"/a.js", "/b.js", "/c.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingDuplicateChunkCollision(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import "./ab"
`,
"/b.js": `
import "./ab"
`,
"/c.js": `
import "./cd"
`,
"/d.js": `
import "./cd"
`,
"/ab.js": `
console.log(123)
`,
"/cd.js": `
console.log(123)
`,
},
entryPaths: []string{"/a.js", "/b.js", "/c.js", "/d.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
MinifyWhitespace: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingMinifyIdentifiersCrashIssue437(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import {foo} from "./shared"
console.log(foo)
`,
"/b.js": `
import {foo} from "./shared"
console.log(foo)
`,
"/c.js": `
import "./shared"
`,
"/shared.js": `
export function foo(bar) {}
`,
},
entryPaths: []string{"/a.js", "/b.js", "/c.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
MinifyIdentifiers: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingHybridESMAndCJSIssue617(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
export let foo
`,
"/b.js": `
export let bar = require('./a')
`,
},
entryPaths: []string{"/a.js", "/b.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
func TestSplittingPublicPathEntryName(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/a.js": `
import("./b")
`,
"/b.js": `
console.log('b')
`,
},
entryPaths: []string{"/a.js"},
options: config.Options{
Mode: config.ModeBundle,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
PublicPath: "/www/",
AbsOutputDir: "/out",
},
})
}
func TestSplittingChunkPathDirPlaceholderImplicitOutbase(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/project/entry.js": `
console.log(import('./output-path/should-contain/this-text/file'))
`,
"/project/output-path/should-contain/this-text/file.js": `
console.log('file.js')
`,
},
entryPaths: []string{"/project/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
CodeSplitting: true,
AbsOutputDir: "/out",
ChunkPathTemplate: []config.PathTemplate{
{Data: "./", Placeholder: config.DirPlaceholder},
{Data: "/", Placeholder: config.NamePlaceholder},
{Data: "-", Placeholder: config.HashPlaceholder},
},
},
})
}
func TestEdgeCaseIssue2793WithSplitting(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/src/a.js": `
export const A = 42;
`,
"/src/b.js": `
export const B = async () => (await import(".")).A
`,
"/src/index.js": `
export * from "./a"
export * from "./b"
`,
},
entryPaths: []string{"/src/index.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
CodeSplitting: true,
AbsOutputDir: "/out",
},
})
}
func TestEdgeCaseIssue2793WithoutSplitting(t *testing.T) {
splitting_suite.expectBundled(t, bundled{
files: map[string]string{
"/src/a.js": `
export const A = 42;
`,
"/src/b.js": `
export const B = async () => (await import(".")).A
`,
"/src/index.js": `
export * from "./a"
export * from "./b"
`,
},
entryPaths: []string{"/src/index.js"},
options: config.Options{
Mode: config.ModeBundle,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
})
}
@@ -0,0 +1,344 @@
package bundler_tests
// Bundling test results are stored in snapshot files, located in the
// "snapshots" directory. This allows test results to be updated easily without
// manually rewriting all of the expected values. To update the tests run
// "UPDATE_SNAPSHOTS=1 make test" and commit the updated values. Make sure to
// inspect the diff to ensure the expected values are valid.
import (
"fmt"
"io/ioutil"
"os"
"path"
"sort"
"strings"
"sync"
"testing"
"github.com/evanw/esbuild/internal/bundler"
"github.com/evanw/esbuild/internal/cache"
"github.com/evanw/esbuild/internal/compat"
"github.com/evanw/esbuild/internal/config"
"github.com/evanw/esbuild/internal/fs"
"github.com/evanw/esbuild/internal/linker"
"github.com/evanw/esbuild/internal/logger"
"github.com/evanw/esbuild/internal/test"
)
func es(version int) compat.JSFeature {
return compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{version}},
})
}
func assertLog(t *testing.T, msgs []logger.Msg, expected string) {
t.Helper()
var text strings.Builder
for _, msg := range msgs {
text.WriteString(msg.String(logger.OutputOptions{}, logger.TerminalInfo{}))
}
test.AssertEqualWithDiff(t, text.String(), expected)
}
func hasErrors(msgs []logger.Msg) bool {
for _, msg := range msgs {
if msg.Kind == logger.Error {
return true
}
}
return false
}
type bundled struct {
files map[string]string
entryPaths []string
entryPathsAdvanced []bundler.EntryPoint
expectedScanLog string
expectedCompileLog string
options config.Options
debugLogs bool
absWorkingDir string
}
type suite struct {
expectedSnapshots map[string]string
generatedSnapshots map[string]string
name string
path string
mutex sync.Mutex
}
func (s *suite) expectBundled(t *testing.T, args bundled) {
t.Helper()
s.__expectBundledImpl(t, args, fs.MockUnix)
// Handle conversion to Windows-style paths
{
files := make(map[string]string)
for k, v := range args.files {
files[unix2win(k)] = v
}
args.files = files
args.entryPaths = append([]string{}, args.entryPaths...)
for i, entry := range args.entryPaths {
args.entryPaths[i] = unix2win(entry)
}
args.absWorkingDir = unix2win(args.absWorkingDir)
args.options.InjectPaths = append([]string{}, args.options.InjectPaths...)
for i, absPath := range args.options.InjectPaths {
args.options.InjectPaths[i] = unix2win(absPath)
}
aliases := make(map[string]string)
for k, v := range args.options.PackageAliases {
if strings.HasPrefix(v, "/") {
v = unix2win(v)
}
aliases[k] = v
}
args.options.PackageAliases = aliases
replace := make(map[string]bool)
for k, v := range args.options.ExternalSettings.PostResolve.Exact {
replace[unix2win(k)] = v
}
args.options.ExternalSettings.PostResolve.Exact = replace
args.options.AbsOutputFile = unix2win(args.options.AbsOutputFile)
args.options.AbsOutputBase = unix2win(args.options.AbsOutputBase)
args.options.AbsOutputDir = unix2win(args.options.AbsOutputDir)
args.options.TSConfigPath = unix2win(args.options.TSConfigPath)
}
s.__expectBundledImpl(t, args, fs.MockWindows)
}
func (s *suite) expectBundledUnix(t *testing.T, args bundled) {
t.Helper()
s.__expectBundledImpl(t, args, fs.MockUnix)
}
func (s *suite) expectBundledWindows(t *testing.T, args bundled) {
t.Helper()
s.__expectBundledImpl(t, args, fs.MockWindows)
}
// Don't call this directly. Call the helpers above instead.
func (s *suite) __expectBundledImpl(t *testing.T, args bundled, fsKind fs.MockKind) {
t.Helper()
testName := t.Name()
subName := "Unix"
if fsKind == fs.MockWindows {
subName = "Windows"
}
t.Run(subName, func(t *testing.T) {
t.Helper()
// Prepare the options
if args.options.ExtensionOrder == nil {
args.options.ExtensionOrder = []string{".tsx", ".ts", ".jsx", ".js", ".css", ".json"}
}
if args.options.AbsOutputFile != "" {
if fsKind == fs.MockWindows {
args.options.AbsOutputDir = unix2win(path.Dir(win2unix(args.options.AbsOutputFile)))
} else {
args.options.AbsOutputDir = path.Dir(args.options.AbsOutputFile)
}
}
if args.options.Mode == config.ModeBundle || (args.options.Mode == config.ModeConvertFormat && args.options.OutputFormat == config.FormatIIFE) {
// Apply this default to all tests since it was not configurable when the tests were written
args.options.TreeShaking = true
}
if args.options.Mode == config.ModeBundle && args.options.OutputFormat == config.FormatPreserve {
// The format can't be "preserve" while bundling
args.options.OutputFormat = config.FormatESModule
}
logKind := logger.DeferLogNoVerboseOrDebug
if args.debugLogs {
logKind = logger.DeferLogAll
}
entryPoints := make([]bundler.EntryPoint, 0, len(args.entryPaths)+len(args.entryPathsAdvanced))
for _, path := range args.entryPaths {
entryPoints = append(entryPoints, bundler.EntryPoint{InputPath: path})
}
entryPoints = append(entryPoints, args.entryPathsAdvanced...)
if args.absWorkingDir == "" {
if fsKind == fs.MockWindows {
args.absWorkingDir = "C:\\"
} else {
args.absWorkingDir = "/"
}
}
if args.options.AbsOutputDir == "" {
args.options.AbsOutputDir = args.absWorkingDir // Match the behavior of the API in this case
}
// Run the bundler
log := logger.NewDeferLog(logKind, nil)
caches := cache.MakeCacheSet()
mockFS := fs.MockFS(args.files, fsKind, args.absWorkingDir)
args.options.OmitRuntimeForTests = true
bundle := bundler.ScanBundle(config.BuildCall, log, mockFS, caches, entryPoints, args.options, nil)
msgs := log.Done()
assertLog(t, msgs, args.expectedScanLog)
// Stop now if there were any errors during the scan
if hasErrors(msgs) {
return
}
log = logger.NewDeferLog(logKind, nil)
results, metafileJSON := bundle.Compile(log, nil, nil, linker.Link)
msgs = log.Done()
assertLog(t, msgs, args.expectedCompileLog)
// Stop now if there were any errors during the compile
if hasErrors(msgs) {
return
}
// Don't include source maps in results since they are just noise. Source
// map validity is tested separately in a test that uses Mozilla's source
// map parsing library.
generated := ""
for _, result := range results {
if generated != "" {
generated += "\n"
}
if fsKind == fs.MockWindows {
result.AbsPath = win2unix(result.AbsPath)
}
generated += fmt.Sprintf("---------- %s ----------\n%s", result.AbsPath, string(result.Contents))
}
if metafileJSON != "" {
generated += fmt.Sprintf("---------- metafile.json ----------\n%s", metafileJSON)
}
s.compareSnapshot(t, testName, generated)
})
}
const snapshotsDir = "snapshots"
const snapshotSplitter = "\n================================================================================\n"
var globalTestMutex sync.Mutex
var globalSuites map[*suite]bool
var globalUpdateSnapshots bool
func (s *suite) compareSnapshot(t *testing.T, testName string, generated string) {
t.Helper()
// Initialize the test suite during the first test
s.mutex.Lock()
defer s.mutex.Unlock()
if s.path == "" {
s.path = snapshotsDir + "/snapshots_" + s.name + ".txt"
s.generatedSnapshots = make(map[string]string)
s.expectedSnapshots = make(map[string]string)
if contents, err := ioutil.ReadFile(s.path); err == nil {
// Replacing CRLF with LF is necessary to fix tests in GitHub actions,
// which for some reason check out the source code in CLRF mode
for _, part := range strings.Split(strings.ReplaceAll(string(contents), "\r\n", "\n"), snapshotSplitter) {
if newline := strings.IndexByte(part, '\n'); newline != -1 {
key := part[:newline]
value := part[newline+1:]
s.expectedSnapshots[key] = value
} else {
s.expectedSnapshots[part] = ""
}
}
}
globalTestMutex.Lock()
defer globalTestMutex.Unlock()
if globalSuites == nil {
globalSuites = make(map[*suite]bool)
}
globalSuites[s] = true
_, globalUpdateSnapshots = os.LookupEnv("UPDATE_SNAPSHOTS")
}
// Check against the stored snapshot if present
s.generatedSnapshots[testName] = generated
if !globalUpdateSnapshots {
if expected, ok := s.expectedSnapshots[testName]; ok {
test.AssertEqualWithDiff(t, generated, expected)
} else {
t.Fatalf("No snapshot saved for %s\n%s%s%s",
testName,
logger.TerminalColors.Green,
generated,
logger.TerminalColors.Reset,
)
}
}
}
func (s *suite) updateSnapshots() {
os.Mkdir(snapshotsDir, 0755)
keys := make([]string, 0, len(s.generatedSnapshots))
for key := range s.generatedSnapshots {
keys = append(keys, key)
}
sort.Strings(keys)
var contents strings.Builder
for i, key := range keys {
if i > 0 {
contents.WriteString(snapshotSplitter)
}
contents.WriteString(fmt.Sprintf("%s\n%s", key, s.generatedSnapshots[key]))
}
if err := ioutil.WriteFile(s.path, []byte(contents.String()), 0644); err != nil {
panic(err)
}
}
func (s *suite) validateSnapshots() bool {
isValid := true
for key := range s.expectedSnapshots {
if _, ok := s.generatedSnapshots[key]; !ok {
if isValid {
fmt.Printf("%s\n", s.path)
}
fmt.Printf(" No test found for snapshot %s\n", key)
isValid = false
}
}
return isValid
}
func TestMain(m *testing.M) {
code := m.Run()
if globalSuites != nil {
if globalUpdateSnapshots {
for s := range globalSuites {
s.updateSnapshots()
}
} else {
for s := range globalSuites {
if !s.validateSnapshots() {
code = 1
}
}
}
}
os.Exit(code)
}
func win2unix(p string) string {
if strings.HasPrefix(p, "C:\\") {
p = p[2:]
}
p = strings.ReplaceAll(p, "\\", "/")
return p
}
func unix2win(p string) string {
p = strings.ReplaceAll(p, "/", "\\")
if strings.HasPrefix(p, "\\") {
p = "C:" + p
}
return p
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
package bundler_tests
import (
"testing"
"github.com/evanw/esbuild/internal/config"
)
var yarnpnp_suite = suite{
name: "yarnpnp",
}
// https://github.com/evanw/esbuild/issues/3698
func TestTsconfigPackageJsonExportsYarnPnP(t *testing.T) {
yarnpnp_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/packages/app/index.tsx": `
console.log(<div/>)
`,
"/Users/user/project/packages/app/tsconfig.json": `
{
"extends": "tsconfigs/config"
}
`,
"/Users/user/project/packages/tsconfigs/package.json": `
{
"exports": {
"./config": "./configs/tsconfig.json"
}
}
`,
"/Users/user/project/packages/tsconfigs/configs/tsconfig.json": `
{
"compilerOptions": {
"jsxFactory": "success"
}
}
`,
"/Users/user/project/.pnp.data.json": `
{
"packageRegistryData": [
[
"app",
[
[
"workspace:packages/app",
{
"packageLocation": "./packages/app/",
"packageDependencies": [
[
"tsconfigs",
"workspace:packages/tsconfigs"
]
],
"linkType": "SOFT"
}
]
]
],
[
"tsconfigs",
[
[
"workspace:packages/tsconfigs",
{
"packageLocation": "./packages/tsconfigs/",
"packageDependencies": [],
"linkType": "SOFT"
}
]
]
]
]
}
`,
},
entryPaths: []string{"/Users/user/project/packages/app/index.tsx"},
absWorkingDir: "/Users/user/project",
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}
// https://github.com/evanw/esbuild/issues/3915
func TestTsconfigStackOverflowYarnPnP(t *testing.T) {
yarnpnp_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/entry.jsx": `
console.log(<div />)
`,
"/Users/user/project/tsconfig.json": `
{
"extends": "tsconfigs/config"
}
`,
"/Users/user/project/packages/tsconfigs/package.json": `
{
"exports": {
"./config": "./configs/tsconfig.json"
}
}
`,
"/Users/user/project/packages/tsconfigs/configs/tsconfig.json": `
{
"compilerOptions": {
"jsxFactory": "success"
}
}
`,
"/Users/user/project/.pnp.data.json": `
{
"packageRegistryData": [
[null, [
[null, {
"packageLocation": "./",
"packageDependencies": [
["tsconfigs", "virtual:some-path"]
],
"linkType": "SOFT"
}]
]],
["tsconfigs", [
["virtual:some-path", {
"packageLocation": "./packages/tsconfigs/",
"packageDependencies": [
["tsconfigs", "virtual:some-path"]
],
"packagePeers": [],
"linkType": "SOFT"
}],
["workspace:packages/tsconfigs", {
"packageLocation": "./packages/tsconfigs/",
"packageDependencies": [
["tsconfigs", "workspace:packages/tsconfigs"]
],
"linkType": "SOFT"
}]
]]
]
}
`,
},
entryPaths: []string{"/Users/user/project/entry.jsx"},
absWorkingDir: "/Users/user/project",
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}
func TestWindowsCrossVolumeReferenceYarnPnP(t *testing.T) {
yarnpnp_suite.expectBundledWindows(t, bundled{
files: map[string]string{
"D:\\project\\entry.jsx": `
import * as React from 'react'
console.log(<div />)
`,
"C:\\Users\\user\\AppData\\Local\\Yarn\\Berry\\cache\\react.zip\\node_modules\\react\\index.js": `
export function createElement() {}
`,
"D:\\project\\.pnp.data.json": `
{
"packageRegistryData": [
[null, [
[null, {
"packageLocation": "./",
"packageDependencies": [
["react", "npm:19.1.1"],
["project", "workspace:."]
],
"linkType": "SOFT"
}]
]],
["react", [
["npm:19.1.1", {
"packageLocation": "../../C:/Users/user/AppData/Local/Yarn/Berry/cache/react.zip/node_modules/react/",
"packageDependencies": [
["react", "npm:19.1.1"]
],
"linkType": "HARD"
}]
]],
["project", [
["workspace:.", {
"packageLocation": "./",
"packageDependencies": [
["react", "npm:19.1.1"],
["project", "workspace:."]
],
"linkType": "SOFT"
}]
]]
]
}
`,
},
entryPaths: []string{"D:\\project\\entry.jsx"},
absWorkingDir: "D:\\project",
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "D:\\project\\out.js",
},
})
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,407 @@
TestGlobBasicNoBundle
---------- /out.js ----------
const ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: require("./src/" + ab),
import: import("./src/" + ab)
},
template: {
require: require(`./src/${ab}`),
import: import(`./src/${ab}`)
}
});
================================================================================
TestGlobBasicNoSplitting
---------- /out.js ----------
// src/a.js
var require_a = __commonJS({
"src/a.js"(exports, module) {
module.exports = "a";
}
});
// src/b.js
var require_b = __commonJS({
"src/b.js"(exports, module) {
module.exports = "b";
}
});
// require("./src/**/*") in entry.js
var globRequire_src = __glob({
"./src/a.js": () => require_a(),
"./src/b.js": () => require_b()
});
// import("./src/**/*") in entry.js
var globImport_src = __glob({
"./src/a.js": () => Promise.resolve().then(() => __toESM(require_a())),
"./src/b.js": () => Promise.resolve().then(() => __toESM(require_b()))
});
// entry.js
var ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: globRequire_src("./src/" + ab),
import: globImport_src("./src/" + ab)
},
template: {
require: globRequire_src(`./src/${ab}`),
import: globImport_src(`./src/${ab}`)
}
});
================================================================================
TestGlobBasicSplitting
---------- /out/entry.js ----------
import {
require_a
} from "./chunk-KO426RN2.js";
import {
require_b
} from "./chunk-SGVK3D4Q.js";
import {
__glob
} from "./chunk-WCFE7E2E.js";
// require("./src/**/*") in entry.js
var globRequire_src = __glob({
"./src/a.js": () => require_a(),
"./src/b.js": () => require_b()
});
// import("./src/**/*") in entry.js
var globImport_src = __glob({
"./src/a.js": () => import("./a-7QA47R6Z.js"),
"./src/b.js": () => import("./b-KY4MVCQS.js")
});
// entry.js
var ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: globRequire_src("./src/" + ab),
import: globImport_src("./src/" + ab)
},
template: {
require: globRequire_src(`./src/${ab}`),
import: globImport_src(`./src/${ab}`)
}
});
---------- /out/a-7QA47R6Z.js ----------
import {
require_a
} from "./chunk-KO426RN2.js";
import "./chunk-WCFE7E2E.js";
export default require_a();
---------- /out/chunk-KO426RN2.js ----------
import {
__commonJS
} from "./chunk-WCFE7E2E.js";
// src/a.js
var require_a = __commonJS({
"src/a.js"(exports, module) {
module.exports = "a";
}
});
export {
require_a
};
---------- /out/b-KY4MVCQS.js ----------
import {
require_b
} from "./chunk-SGVK3D4Q.js";
import "./chunk-WCFE7E2E.js";
export default require_b();
---------- /out/chunk-SGVK3D4Q.js ----------
import {
__commonJS
} from "./chunk-WCFE7E2E.js";
// src/b.js
var require_b = __commonJS({
"src/b.js"(exports, module) {
module.exports = "b";
}
});
export {
require_b
};
---------- /out/chunk-WCFE7E2E.js ----------
export {
__glob,
__commonJS
};
================================================================================
TestGlobEntryPointAbsPath
---------- /out/entry.js ----------
// Users/user/project/src/entry.js
works = true;
================================================================================
TestGlobNoMatches
---------- /out/entry.js ----------
// require("./src/**/*.json") in entry.js
var globRequire_src_json = __glob({});
// import("./src/**/*.json") in entry.js
var globImport_src_json = __glob({});
// entry.js
var ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: globRequire_src_json("./src/" + ab + ".json"),
import: globImport_src_json("./src/" + ab + ".json")
},
template: {
require: globRequire_src_json(`./src/${ab}.json`),
import: globImport_src_json(`./src/${ab}.json`)
}
});
================================================================================
TestGlobWildcardNoSlash
---------- /out.js ----------
// src/file-a.js
var require_file_a = __commonJS({
"src/file-a.js"(exports, module) {
module.exports = "a";
}
});
// src/file-b.js
var require_file_b = __commonJS({
"src/file-b.js"(exports, module) {
module.exports = "b";
}
});
// require("./src/file-*.js") in entry.js
var globRequire_src_file_js = __glob({
"./src/file-a.js": () => require_file_a(),
"./src/file-b.js": () => require_file_b()
});
// import("./src/file-*.js") in entry.js
var globImport_src_file_js = __glob({
"./src/file-a.js": () => Promise.resolve().then(() => __toESM(require_file_a())),
"./src/file-b.js": () => Promise.resolve().then(() => __toESM(require_file_b()))
});
// entry.js
var ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: globRequire_src_file_js("./src/file-" + ab + ".js"),
import: globImport_src_file_js("./src/file-" + ab + ".js")
},
template: {
require: globRequire_src_file_js(`./src/file-${ab}.js`),
import: globImport_src_file_js(`./src/file-${ab}.js`)
}
});
================================================================================
TestGlobWildcardSlash
---------- /out.js ----------
// src/file-a.js
var require_file_a = __commonJS({
"src/file-a.js"(exports, module) {
module.exports = "a";
}
});
// src/file-b.js
var require_file_b = __commonJS({
"src/file-b.js"(exports, module) {
module.exports = "b";
}
});
// src/nested/dir/file-a.js
var require_file_a2 = __commonJS({
"src/nested/dir/file-a.js"(exports, module) {
module.exports = "a";
}
});
// src/nested/dir/file-b.js
var require_file_b2 = __commonJS({
"src/nested/dir/file-b.js"(exports, module) {
module.exports = "b";
}
});
// require("./src/**/*.js") in entry.js
var globRequire_src_js = __glob({
"./src/file-a.js": () => require_file_a(),
"./src/file-b.js": () => require_file_b(),
"./src/nested/dir/file-a.js": () => require_file_a2(),
"./src/nested/dir/file-b.js": () => require_file_b2()
});
// import("./src/**/*.js") in entry.js
var globImport_src_js = __glob({
"./src/file-a.js": () => Promise.resolve().then(() => __toESM(require_file_a())),
"./src/file-b.js": () => Promise.resolve().then(() => __toESM(require_file_b())),
"./src/nested/dir/file-a.js": () => Promise.resolve().then(() => __toESM(require_file_a2())),
"./src/nested/dir/file-b.js": () => Promise.resolve().then(() => __toESM(require_file_b2()))
});
// entry.js
var ab = Math.random() < 0.5 ? "a.js" : "b.js";
console.log({
concat: {
require: globRequire_src_js("./src/" + ab + ".js"),
import: globImport_src_js("./src/" + ab + ".js")
},
template: {
require: globRequire_src_js(`./src/${ab}.js`),
import: globImport_src_js(`./src/${ab}.js`)
}
});
================================================================================
TestTSGlobBasicNoSplitting
---------- /out.js ----------
// src/a.ts
var require_a = __commonJS({
"src/a.ts"(exports, module) {
module.exports = "a";
}
});
// src/b.ts
var require_b = __commonJS({
"src/b.ts"(exports, module) {
module.exports = "b";
}
});
// require("./src/**/*") in entry.ts
var globRequire_src = __glob({
"./src/a.ts": () => require_a(),
"./src/b.ts": () => require_b()
});
// import("./src/**/*") in entry.ts
var globImport_src = __glob({
"./src/a.ts": () => Promise.resolve().then(() => __toESM(require_a())),
"./src/b.ts": () => Promise.resolve().then(() => __toESM(require_b()))
});
// entry.ts
var ab = Math.random() < 0.5 ? "a.ts" : "b.ts";
console.log({
concat: {
require: globRequire_src("./src/" + ab),
import: globImport_src("./src/" + ab)
},
template: {
require: globRequire_src(`./src/${ab}`),
import: globImport_src(`./src/${ab}`)
}
});
================================================================================
TestTSGlobBasicSplitting
---------- /out/entry.js ----------
import {
require_a
} from "./chunk-YMCIDKCT.js";
import {
require_b
} from "./chunk-2BST4PYI.js";
import {
__glob
} from "./chunk-WCFE7E2E.js";
// require("./src/**/*") in entry.ts
var globRequire_src = __glob({
"./src/a.ts": () => require_a(),
"./src/b.ts": () => require_b()
});
// import("./src/**/*") in entry.ts
var globImport_src = __glob({
"./src/a.ts": () => import("./a-YXM4MR7E.js"),
"./src/b.ts": () => import("./b-IPMBSSGN.js")
});
// entry.ts
var ab = Math.random() < 0.5 ? "a.ts" : "b.ts";
console.log({
concat: {
require: globRequire_src("./src/" + ab),
import: globImport_src("./src/" + ab)
},
template: {
require: globRequire_src(`./src/${ab}`),
import: globImport_src(`./src/${ab}`)
}
});
---------- /out/a-YXM4MR7E.js ----------
import {
require_a
} from "./chunk-YMCIDKCT.js";
import "./chunk-WCFE7E2E.js";
export default require_a();
---------- /out/chunk-YMCIDKCT.js ----------
import {
__commonJS
} from "./chunk-WCFE7E2E.js";
// src/a.ts
var require_a = __commonJS({
"src/a.ts"(exports, module) {
module.exports = "a";
}
});
export {
require_a
};
---------- /out/b-IPMBSSGN.js ----------
import {
require_b
} from "./chunk-2BST4PYI.js";
import "./chunk-WCFE7E2E.js";
export default require_b();
---------- /out/chunk-2BST4PYI.js ----------
import {
__commonJS
} from "./chunk-WCFE7E2E.js";
// src/b.ts
var require_b = __commonJS({
"src/b.ts"(exports, module) {
module.exports = "b";
}
});
export {
require_b
};
---------- /out/chunk-WCFE7E2E.js ----------
export {
__glob,
__commonJS
};
@@ -0,0 +1,52 @@
TestImportDeferExternalESM
---------- /out.js ----------
// entry.js
import defer * as foo0 from "./foo.json";
import defer * as foo1 from "./foo.json" with { type: "json" };
// import.defer("./**/*.json") in entry.js
var globImport_json = __glob({
"./foo.json": () => import.defer("./foo.json")
});
// import.defer("./**/*.json") in entry.js
var globImport_json2 = __glob({
"./foo.json": () => import.defer("./foo.json", { with: { type: "json" } })
});
// entry.js
console.log(
foo0,
foo1,
import.defer("./foo.json"),
import.defer("./foo.json", { with: { type: "json" } }),
globImport_json(`./${foo}.json`),
globImport_json2(`./${foo}.json`)
);
================================================================================
TestImportSourceExternalESM
---------- /out.js ----------
// entry.js
import source foo0 from "./foo.json";
import source foo1 from "./foo.json" with { type: "json" };
// import.source("./**/*.json") in entry.js
var globImport_json = __glob({
"./foo.json": () => import.source("./foo.json")
});
// import.source("./**/*.json") in entry.js
var globImport_json2 = __glob({
"./foo.json": () => import.source("./foo.json", { with: { type: "json" } })
});
// entry.js
console.log(
foo0,
foo1,
import.source("./foo.json"),
import.source("./foo.json", { with: { type: "json" } }),
globImport_json(`./${foo}.json`),
globImport_json2(`./${foo}.json`)
);
@@ -0,0 +1,247 @@
TestTSImportStarAndCommonJS
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo;
var init_foo = __esm({
"foo.ts"() {
foo = 123;
}
});
// entry.js
init_foo();
var ns2 = (init_foo(), __toCommonJS(foo_exports));
console.log(foo, ns2.foo);
================================================================================
TestTSImportStarCapture
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo_exports, foo, foo2);
================================================================================
TestTSImportStarCommonJSCapture
---------- /out.js ----------
// foo.ts
var require_foo = __commonJS({
"foo.ts"(exports) {
exports.foo = 123;
}
});
// entry.ts
var ns = __toESM(require_foo());
var foo2 = 234;
console.log(ns, ns.foo, foo2);
================================================================================
TestTSImportStarCommonJSNoCapture
---------- /out.js ----------
// foo.ts
var require_foo = __commonJS({
"foo.ts"(exports) {
exports.foo = 123;
}
});
// entry.ts
var ns = __toESM(require_foo());
var foo2 = 234;
console.log(ns.foo, ns.foo, foo2);
================================================================================
TestTSImportStarCommonJSUnused
---------- /out.js ----------
// entry.ts
var foo = 234;
console.log(foo);
================================================================================
TestTSImportStarExportImportStarCapture
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo_exports, foo_exports.foo, foo2);
================================================================================
TestTSImportStarExportImportStarNoCapture
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo_exports.foo, foo_exports.foo, foo2);
================================================================================
TestTSImportStarExportImportStarUnused
---------- /out.js ----------
// entry.ts
var foo = 234;
console.log(foo);
================================================================================
TestTSImportStarExportStarAsCapture
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo_exports, foo_exports.foo, foo2);
================================================================================
TestTSImportStarExportStarAsNoCapture
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__export(foo_exports, {
foo: () => foo
});
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo_exports.foo, foo_exports.foo, foo2);
================================================================================
TestTSImportStarExportStarAsUnused
---------- /out.js ----------
// entry.ts
var foo = 234;
console.log(foo);
================================================================================
TestTSImportStarExportStarCapture
---------- /out.js ----------
// bar.ts
var bar_exports = {};
__export(bar_exports, {
foo: () => foo
});
// foo.ts
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(bar_exports, foo, foo2);
================================================================================
TestTSImportStarExportStarNoCapture
---------- /out.js ----------
// foo.ts
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo, foo, foo2);
================================================================================
TestTSImportStarExportStarUnused
---------- /out.js ----------
// entry.ts
var foo = 234;
console.log(foo);
================================================================================
TestTSImportStarMangleNoBundleCapture
---------- /out.js ----------
import * as ns from "./foo";
let foo = 234;
console.log(ns, ns.foo, foo);
================================================================================
TestTSImportStarMangleNoBundleNoCapture
---------- /out.js ----------
import * as ns from "./foo";
let foo = 234;
console.log(ns.foo, ns.foo, foo);
================================================================================
TestTSImportStarMangleNoBundleUnused
---------- /out.js ----------
let foo = 234;
console.log(foo);
================================================================================
TestTSImportStarNoBundleCapture
---------- /out.js ----------
import * as ns from "./foo";
let foo = 234;
console.log(ns, ns.foo, foo);
================================================================================
TestTSImportStarNoBundleNoCapture
---------- /out.js ----------
import * as ns from "./foo";
let foo = 234;
console.log(ns.foo, ns.foo, foo);
================================================================================
TestTSImportStarNoBundleUnused
---------- /out.js ----------
let foo = 234;
console.log(foo);
================================================================================
TestTSImportStarNoCapture
---------- /out.js ----------
// foo.ts
var foo = 123;
// entry.ts
var foo2 = 234;
console.log(foo, foo, foo2);
================================================================================
TestTSImportStarUnused
---------- /out.js ----------
// entry.ts
var foo = 234;
console.log(foo);
================================================================================
TestTSReExportTypeOnlyFileES6
---------- /out.js ----------
// types1.ts
console.log("some code");
// types2.ts
console.log("some code");
// types3.ts
console.log("some code");
// values.ts
var foo = 123;
// entry.ts
console.log(foo);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,938 @@
TestCommonJSVariableInESMTypeModule
---------- /out.js ----------
// entry.js
module.exports = null;
================================================================================
TestConfusingNameCollisionsIssue4144
---------- /out.js ----------
// node_modules/mydependency/package/utils/utils.js
function it() {
return works;
}
// node_modules/mydependency/package/index.js
var works = true;
// entry.js
console.log(it());
================================================================================
TestPackageJsonBadExportsDefaultWarningIssue3867
---------- /out.js ----------
================================================================================
TestPackageJsonBadExportsImportAndRequireWarningIssue3867
---------- /out.js ----------
================================================================================
TestPackageJsonBadMain
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserIndexNoExt
---------- /Users/user/project/out.js ----------
// Users/user/project/src/demo-pkg/no-ext-browser/index.js
var browser = "browser";
// Users/user/project/src/demo-pkg/no-ext/index.js
var node = "node";
// Users/user/project/src/demo-pkg/ext-browser/index.js
var browser2 = "browser";
// Users/user/project/src/entry.js
console.log(browser);
console.log(node);
console.log(browser2);
console.log(browser2);
================================================================================
TestPackageJsonBrowserIssue2002A
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/sub/bar.js
var require_bar = __commonJS({
"Users/user/project/src/node_modules/sub/bar.js"() {
works();
}
});
// Users/user/project/src/node_modules/pkg/sub/foo.js
var require_foo = __commonJS({
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
require_bar();
}
});
// Users/user/project/src/entry.js
require_foo();
================================================================================
TestPackageJsonBrowserIssue2002B
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/pkg/sub/bar.js
var require_bar = __commonJS({
"Users/user/project/src/node_modules/pkg/sub/bar.js"() {
works();
}
});
// Users/user/project/src/node_modules/pkg/sub/foo.js
var require_foo = __commonJS({
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
require_bar();
}
});
// Users/user/project/src/entry.js
require_foo();
================================================================================
TestPackageJsonBrowserIssue2002C
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/sub/index.js
var require_sub = __commonJS({
"Users/user/project/src/node_modules/sub/index.js"() {
works();
}
});
// Users/user/project/src/node_modules/pkg/sub/foo.js
var require_foo = __commonJS({
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
require_sub();
}
});
// Users/user/project/src/entry.js
require_foo();
================================================================================
TestPackageJsonBrowserMapAvoidMissing
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/component-indexof/index.js
var require_component_indexof = __commonJS({
"Users/user/project/node_modules/component-indexof/index.js"(exports, module) {
module.exports = function() {
return 234;
};
}
});
// Users/user/project/node_modules/component-classes/index.js
try {
index = require_component_indexof();
} catch (err) {
index = require_component_indexof();
}
var index;
================================================================================
TestPackageJsonBrowserMapModuleDisabled
---------- /Users/user/project/out.js ----------
// (disabled):Users/user/project/node_modules/node-pkg/index.js
var require_node_pkg = __commonJS({
"(disabled):Users/user/project/node_modules/node-pkg/index.js"() {
}
});
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
var fn2 = require_node_pkg();
module.exports = function() {
return fn2();
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapModuleToModule
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/node-pkg-browser/index.js
var require_node_pkg_browser = __commonJS({
"Users/user/project/node_modules/node-pkg-browser/index.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
var fn2 = require_node_pkg_browser();
module.exports = function() {
return fn2();
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapModuleToRelative
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/node-pkg-browser.js
var require_node_pkg_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/node-pkg-browser.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
var fn2 = require_node_pkg_browser();
module.exports = function() {
return fn2();
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapNativeModuleDisabled
---------- /Users/user/project/out.js ----------
// (disabled):fs
var require_fs = __commonJS({
"(disabled):fs"() {
}
});
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
var fs = require_fs();
module.exports = function() {
return fs.readFile();
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapRelativeDisabled
---------- /Users/user/project/out.js ----------
// (disabled):Users/user/project/node_modules/demo-pkg/util-node
var require_util_node = __commonJS({
"(disabled):Users/user/project/node_modules/demo-pkg/util-node"() {
}
});
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
var util = require_util_node();
module.exports = function(obj) {
return util.inspect(obj);
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapRelativeToModule
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/util-browser/index.js
var require_util_browser = __commonJS({
"Users/user/project/node_modules/util-browser/index.js"(exports, module) {
module.exports = "util-browser";
}
});
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
var util = require_util_browser();
module.exports = function() {
return ["main", util];
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMapRelativeToRelative
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/lib/util-browser.js
var require_util_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/lib/util-browser.js"(exports, module) {
module.exports = "util-browser";
}
});
// Users/user/project/node_modules/demo-pkg/main-browser.js
var require_main_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/main-browser.js"(exports, module) {
var util = require_util_browser();
module.exports = function() {
return ["main-browser", util];
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main_browser());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserMatchingTrailingSlashIssue4187
---------- /out.js ----------
// node_modules/axios/browser/index.js
var require_browser = __commonJS({
"node_modules/axios/browser/index.js"(exports, module) {
module.exports = { get: () => new Promise("Browser") };
}
});
// node_modules/axios/index.js
var require_axios = __commonJS({
"node_modules/axios/index.js"(exports, module) {
module.exports = require_browser();
}
});
// entry.js
var import_axios = __toESM(require_axios());
================================================================================
TestPackageJsonBrowserNoExt
---------- /Users/user/project/out.js ----------
// Users/user/project/src/demo-pkg/no-ext-browser.js
var browser = "browser";
// Users/user/project/src/demo-pkg/no-ext.js
var node = "node";
// Users/user/project/src/demo-pkg/ext-browser.js
var browser2 = "browser";
// Users/user/project/src/entry.js
console.log(browser);
console.log(node);
console.log(browser2);
console.log(browser2);
================================================================================
TestPackageJsonBrowserNodeModulesIndexNoExt
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/no-ext-browser/index.js
var browser = "browser";
// Users/user/project/node_modules/demo-pkg/no-ext/index.js
var node = "node";
// Users/user/project/node_modules/demo-pkg/ext-browser/index.js
var browser2 = "browser";
// Users/user/project/src/entry.js
console.log(browser);
console.log(node);
console.log(browser2);
console.log(browser2);
================================================================================
TestPackageJsonBrowserNodeModulesNoExt
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/no-ext-browser.js
var browser = "browser";
// Users/user/project/node_modules/demo-pkg/no-ext.js
var node = "node";
// Users/user/project/node_modules/demo-pkg/ext-browser.js
var browser2 = "browser";
// Users/user/project/src/entry.js
console.log(browser);
console.log(node);
console.log(browser2);
console.log(browser2);
================================================================================
TestPackageJsonBrowserOverMainNode
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserOverModuleBrowser
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.browser.js
var require_main_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.browser.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main_browser());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserString
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/browser.js
var require_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/browser.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_browser());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserWithMainNode
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonBrowserWithModuleBrowser
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.browser.esm.js
function main_browser_esm_default() {
return 123;
}
// Users/user/project/src/entry.js
console.log(main_browser_esm_default());
================================================================================
TestPackageJsonDisabledTypeModuleIssue3367
---------- /out.js ----------
// (disabled):node_modules/foo/index.js
var require_foo = __commonJS({
"(disabled):node_modules/foo/index.js"() {
}
});
// entry.js
var import_foo = __toESM(require_foo());
(0, import_foo.default)();
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireBrowser
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.browser.js
var require_main_browser = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.browser.js"(exports, module) {
module.exports = "browser main";
}
});
// Users/user/project/src/test-main.js
console.log(require_main_browser());
// Users/user/project/src/test-module.js
var import_demo_pkg = __toESM(require_main_browser());
console.log(import_demo_pkg.default);
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireForceModuleBeforeMain
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/module.js
var module_exports = {};
__export(module_exports, {
default: () => module_default
});
var module_default;
var init_module = __esm({
"Users/user/project/node_modules/demo-pkg/module.js"() {
module_default = "module";
}
});
// Users/user/project/src/test-main.js
console.log((init_module(), __toCommonJS(module_exports)));
// Users/user/project/src/test-module.js
init_module();
console.log(module_default);
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireImplicitMain
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/index.js
var require_demo_pkg = __commonJS({
"Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
module.exports = "index";
}
});
// Users/user/project/src/test-index.js
console.log(require_demo_pkg());
// Users/user/project/src/test-module.js
var import_demo_pkg = __toESM(require_demo_pkg());
console.log(import_demo_pkg.default);
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireImplicitMainForceModuleBeforeMain
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/module.js
var module_exports = {};
__export(module_exports, {
default: () => module_default
});
var module_default;
var init_module = __esm({
"Users/user/project/node_modules/demo-pkg/module.js"() {
module_default = "module";
}
});
// Users/user/project/src/test-index.js
console.log((init_module(), __toCommonJS(module_exports)));
// Users/user/project/src/test-module.js
init_module();
console.log(module_default);
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireSameFile
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = "main";
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log(import_demo_pkg.default, require_main());
================================================================================
TestPackageJsonDualPackageHazardImportAndRequireSeparateFiles
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = "main";
}
});
// Users/user/project/src/test-main.js
console.log(require_main());
// Users/user/project/src/test-module.js
var import_demo_pkg = __toESM(require_main());
console.log(import_demo_pkg.default);
================================================================================
TestPackageJsonDualPackageHazardImportOnly
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/module.js
var module_default = "module";
// Users/user/project/src/entry.js
console.log(module_default);
================================================================================
TestPackageJsonDualPackageHazardRequireOnly
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = "main";
}
});
// Users/user/project/src/entry.js
console.log(require_main());
================================================================================
TestPackageJsonExportsBrowser
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/browser.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsCustomConditions
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg1/custom2.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsDefaultOverImportAndRequire
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/default.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsDefaultWarningIssue3887
---------- /out.js ----------
// node_modules/foo/dist/index.js
success();
================================================================================
TestPackageJsonExportsEntryPointImportOverRequire
---------- /out.js ----------
// node_modules/pkg/import.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsEntryPointMainOnly
---------- /out.js ----------
// node_modules/pkg/main.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsEntryPointModuleOverMain
---------- /out.js ----------
// node_modules/pkg/module.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsImportOverRequire
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/import.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsNeutral
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/default.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsNode
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/node.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsNotExactMissingExtension
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg1/dir/bar.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsOrderIndependent
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg1/2/bar.js
console.log("SUCCESS");
// Users/user/project/node_modules/pkg2/1/bar.js
console.log("SUCCESS");
================================================================================
TestPackageJsonExportsPatternTrailers
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/dir/baz-foo.js
console.log("works");
// Users/user/project/node_modules/pkg2/public/abc.js
console.log("abc");
// Users/user/project/node_modules/pkg2/public/xyz.js
console.log("xyz");
================================================================================
TestPackageJsonExportsRequireOverImport
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg/require.js
var require_require = __commonJS({
"Users/user/project/node_modules/pkg/require.js"() {
console.log("SUCCESS");
}
});
// Users/user/project/src/entry.js
require_require();
================================================================================
TestPackageJsonExportsWildcard
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/pkg1/file.js
console.log("SUCCESS");
// Users/user/project/node_modules/pkg1/file2.js
console.log("SUCCESS");
================================================================================
TestPackageJsonImportSelfUsingImport
---------- /Users/user/project/out.js ----------
// Users/user/project/src/foo-import.js
var foo_import_default = "foo";
// Users/user/project/src/index.js
var index_default = "index";
console.log(index_default, foo_import_default);
export {
index_default as default
};
================================================================================
TestPackageJsonImportSelfUsingImportScoped
---------- /Users/user/project/out.js ----------
// Users/user/project/src/foo-import.js
var foo_import_default = "foo";
// Users/user/project/src/index.js
var index_default = "index";
console.log(index_default, foo_import_default);
export {
index_default as default
};
================================================================================
TestPackageJsonImportSelfUsingRequire
---------- /Users/user/project/out.js ----------
// Users/user/project/src/foo-require.js
var require_foo_require = __commonJS({
"Users/user/project/src/foo-require.js"(exports, module) {
module.exports = "foo";
}
});
// Users/user/project/src/index.js
var require_index = __commonJS({
"Users/user/project/src/index.js"(exports, module) {
module.exports = "index";
console.log(
require_index(),
require_foo_require()
);
}
});
export default require_index();
================================================================================
TestPackageJsonImportSelfUsingRequireScoped
---------- /Users/user/project/out.js ----------
// Users/user/project/src/foo-require.js
var require_foo_require = __commonJS({
"Users/user/project/src/foo-require.js"(exports, module) {
module.exports = "foo";
}
});
// Users/user/project/src/index.js
var require_index = __commonJS({
"Users/user/project/src/index.js"(exports, module) {
module.exports = "index";
console.log(
require_index(),
require_foo_require()
);
}
});
export default require_index();
================================================================================
TestPackageJsonImports
---------- /Users/user/project/out.js ----------
// Users/user/project/src/a.js
console.log("a.js");
// Users/user/project/src/b.js
console.log("b.js");
// Users/user/project/src/some-star/c.js
console.log("c.js");
// Users/user/project/src/some-slash/d.js
console.log("d.js");
================================================================================
TestPackageJsonImportsRemapToOtherPackage
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/pkg/a.js
console.log("a.js");
// Users/user/project/src/node_modules/pkg/b.js
console.log("b.js");
// Users/user/project/src/node_modules/pkg/some-star/c.js
console.log("c.js");
// Users/user/project/src/node_modules/pkg/some-slash/d.js
console.log("d.js");
================================================================================
TestPackageJsonImportsHashSlashExactMatch
---------- /Users/user/project/out.js ----------
// Users/user/project/src/index.js
console.log("index.js");
// Users/user/project/src/utils.js
console.log("utils.js");
================================================================================
TestPackageJsonImportsHashSlashSymmetricWithExports
---------- /Users/user/project/out.js ----------
// Users/user/project/src/src/components/button.js
console.log("button.js");
// Users/user/project/src/src/utils/format.js
console.log("format.js");
================================================================================
TestPackageJsonImportsHashSlashWithConditions
---------- /Users/user/project/out.js ----------
// Users/user/project/src/esm/lib.js
console.log("esm/lib.js");
================================================================================
TestPackageJsonImportsHashSlashWithWildcard
---------- /Users/user/project/out.js ----------
// Users/user/project/src/src/foo.js
console.log("foo.js");
// Users/user/project/src/src/bar/baz.js
console.log("bar/baz.js");
================================================================================
TestPackageJsonMain
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/custom-main.js
var require_custom_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/custom-main.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_custom_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonMainFieldsA
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/a.js
var require_a = __commonJS({
"Users/user/project/node_modules/demo-pkg/a.js"(exports, module) {
module.exports = "a";
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_a());
console.log(import_demo_pkg.default);
================================================================================
TestPackageJsonMainFieldsB
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/b.js
var b_default = "b";
// Users/user/project/src/entry.js
console.log(b_default);
================================================================================
TestPackageJsonModule
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.esm.js
function main_esm_default() {
return 123;
}
// Users/user/project/src/entry.js
console.log(main_esm_default());
================================================================================
TestPackageJsonNeutralExplicitMainFields
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS({
"Users/user/project/node_modules/demo-pkg/main.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/entry.js
var import_demo_pkg = __toESM(require_main());
console.log((0, import_demo_pkg.default)());
================================================================================
TestPackageJsonNodePathsIssue2752
---------- /out.js ----------
// usr/lib/pkg/pkg1/foo.js
console.log("pkg1");
// lib/pkg/pkg2/bar.js
console.log("pkg2");
// var/lib/pkg/@scope/pkg3/baz-browser.js
console.log("pkg3");
// tmp/pkg/@scope/pkg4/bat.js
console.log("pkg4");
================================================================================
TestPackageJsonSubpathImportNodeBuiltinIssue3485
---------- /out.js ----------
// entry.js
import fs from "fs";
import http from "node:http";
fs.readFileSync();
http.createServer();
================================================================================
TestPackageJsonTypeShouldBeTypes
---------- /Users/user/project/out.js ----------
@@ -0,0 +1,623 @@
TestEdgeCaseIssue2793WithSplitting
---------- /out/index.js ----------
// src/a.js
var A = 42;
// src/b.js
var B = async () => (await import("./index.js")).A;
export {
A,
B
};
================================================================================
TestEdgeCaseIssue2793WithoutSplitting
---------- /out/index.js ----------
// src/a.js
var A;
var init_a = __esm({
"src/a.js"() {
A = 42;
}
});
// src/b.js
var B;
var init_b = __esm({
"src/b.js"() {
B = async () => (await Promise.resolve().then(() => (init_index(), index_exports))).A;
}
});
// src/index.js
var index_exports = {};
__export(index_exports, {
A: () => A,
B: () => B
});
var init_index = __esm({
"src/index.js"() {
init_a();
init_b();
}
});
init_index();
export {
A,
B
};
================================================================================
TestSplittingAssignToLocal
---------- /out/a.js ----------
import {
foo,
setFoo
} from "./chunk-GX7G2SBE.js";
// a.js
setFoo(123);
console.log(foo);
---------- /out/b.js ----------
import {
foo
} from "./chunk-GX7G2SBE.js";
// b.js
console.log(foo);
---------- /out/chunk-GX7G2SBE.js ----------
// shared.js
var foo;
function setFoo(value) {
foo = value;
}
export {
foo,
setFoo
};
================================================================================
TestSplittingChunkPathDirPlaceholderImplicitOutbase
---------- /out/entry.js ----------
// project/entry.js
console.log(import("./output-path/should-contain/this-text/file-G2XPANW2.js"));
---------- /out/output-path/should-contain/this-text/file-G2XPANW2.js ----------
// project/output-path/should-contain/this-text/file.js
console.log("file.js");
================================================================================
TestSplittingCircularReferenceIssue251
---------- /out/a.js ----------
import {
p,
q
} from "./chunk-HK23737J.js";
export {
p,
q
};
---------- /out/b.js ----------
import {
p,
q
} from "./chunk-HK23737J.js";
export {
p,
q
};
---------- /out/chunk-HK23737J.js ----------
// a.js
var p = 5;
// b.js
var q = 6;
export {
q,
p
};
================================================================================
TestSplittingCrossChunkAssignmentDependencies
---------- /out/a.js ----------
import {
setValue
} from "./chunk-3GNPIT25.js";
// a.js
setValue(123);
---------- /out/b.js ----------
import "./chunk-3GNPIT25.js";
---------- /out/chunk-3GNPIT25.js ----------
// shared.js
var observer;
var value;
function getValue() {
return value;
}
function setValue(next) {
value = next;
if (observer) observer();
}
sideEffects(getValue);
export {
setValue
};
================================================================================
TestSplittingCrossChunkAssignmentDependenciesRecursive
---------- /out/a.js ----------
import {
setX
} from "./chunk-NAKBUG5G.js";
// a.js
setX();
---------- /out/b.js ----------
import {
setZ
} from "./chunk-BSMDVSN6.js";
import "./chunk-NAKBUG5G.js";
// b.js
setZ();
---------- /out/c.js ----------
import {
setY2,
setZ2
} from "./chunk-BSMDVSN6.js";
import {
setX2
} from "./chunk-NAKBUG5G.js";
// c.js
setX2();
setY2();
setZ2();
---------- /out/chunk-BSMDVSN6.js ----------
import {
setX
} from "./chunk-NAKBUG5G.js";
// y.js
var _y;
function setY(v) {
_y = v;
}
function setY2(v) {
setX(v);
_y = v;
}
// z.js
var _z;
function setZ(v) {
_z = v;
}
function setZ2(v) {
setY(v);
_z = v;
}
export {
setY2,
setZ,
setZ2
};
---------- /out/chunk-NAKBUG5G.js ----------
// x.js
var _x;
function setX(v) {
_x = v;
}
function setX2(v) {
_x = v;
}
export {
setX,
setX2
};
================================================================================
TestSplittingDuplicateChunkCollision
---------- /out/a.js ----------
import"./chunk-QPOQRTMB.js";
---------- /out/b.js ----------
import"./chunk-QPOQRTMB.js";
---------- /out/chunk-QPOQRTMB.js ----------
console.log(123);
---------- /out/c.js ----------
import"./chunk-TOGNOMR3.js";
---------- /out/d.js ----------
import"./chunk-TOGNOMR3.js";
---------- /out/chunk-TOGNOMR3.js ----------
console.log(123);
================================================================================
TestSplittingDynamicAndNotDynamicCommonJSIntoES6
---------- /out/entry.js ----------
import {
__toESM,
require_foo
} from "./chunk-X3UWZZCR.js";
// entry.js
var import_foo = __toESM(require_foo());
import("./foo-BJYZ44Z3.js").then(({ default: { bar: b } }) => console.log(import_foo.bar, b));
---------- /out/foo-BJYZ44Z3.js ----------
import {
require_foo
} from "./chunk-X3UWZZCR.js";
export default require_foo();
---------- /out/chunk-X3UWZZCR.js ----------
// foo.js
var require_foo = __commonJS({
"foo.js"(exports) {
exports.bar = 123;
}
});
export {
__toESM,
require_foo
};
================================================================================
TestSplittingDynamicAndNotDynamicES6IntoES6
---------- /out/entry.js ----------
import {
bar
} from "./chunk-UDDKLWVZ.js";
// entry.js
import("./foo-BNHN4WV6.js").then(({ bar: b }) => console.log(bar, b));
---------- /out/foo-BNHN4WV6.js ----------
import {
bar
} from "./chunk-UDDKLWVZ.js";
export {
bar
};
---------- /out/chunk-UDDKLWVZ.js ----------
// foo.js
var bar = 123;
export {
bar
};
================================================================================
TestSplittingDynamicCommonJSIntoES6
---------- /out/entry.js ----------
// entry.js
import("./foo-X6C7FV5C.js").then(({ default: { bar } }) => console.log(bar));
---------- /out/foo-X6C7FV5C.js ----------
// foo.js
var require_foo = __commonJS({
"foo.js"(exports) {
exports.bar = 123;
}
});
export default require_foo();
================================================================================
TestSplittingDynamicES6IntoES6
---------- /out/entry.js ----------
// entry.js
import("./foo-R2VCCZUR.js").then(({ bar }) => console.log(bar));
---------- /out/foo-R2VCCZUR.js ----------
// foo.js
var bar = 123;
export {
bar
};
================================================================================
TestSplittingDynamicImportIssue272
---------- /out/a.js ----------
// a.js
import("./b.js");
---------- /out/b.js ----------
// b.js
var b_default = 1;
export {
b_default as default
};
================================================================================
TestSplittingDynamicImportOutsideSourceTreeIssue264
---------- /out/entry1.js ----------
// Users/user/project/src/entry1.js
import("./package-ZBNDRRRB.js");
---------- /out/entry2.js ----------
// Users/user/project/src/entry2.js
import("./package-ZBNDRRRB.js");
---------- /out/package-ZBNDRRRB.js ----------
// Users/user/project/node_modules/package/index.js
console.log("imported");
================================================================================
TestSplittingHybridESMAndCJSIssue617
---------- /out/a.js ----------
import {
foo,
init_a
} from "./chunk-PDZFCFBH.js";
init_a();
export {
foo
};
---------- /out/b.js ----------
import {
__toCommonJS,
a_exports,
init_a
} from "./chunk-PDZFCFBH.js";
// b.js
var bar = (init_a(), __toCommonJS(a_exports));
export {
bar
};
---------- /out/chunk-PDZFCFBH.js ----------
// a.js
var a_exports = {};
__export(a_exports, {
foo: () => foo
});
var foo;
var init_a = __esm({
"a.js"() {
}
});
export {
__toCommonJS,
foo,
a_exports,
init_a
};
================================================================================
TestSplittingMinifyIdentifiersCrashIssue437
---------- /out/a.js ----------
import {
a as o
} from "./chunk-7N7J6VKT.js";
// a.js
console.log(o);
---------- /out/b.js ----------
import {
a as o
} from "./chunk-7N7J6VKT.js";
// b.js
console.log(o);
---------- /out/c.js ----------
import "./chunk-7N7J6VKT.js";
---------- /out/chunk-7N7J6VKT.js ----------
// shared.js
function f(o) {
}
export {
f as a
};
================================================================================
TestSplittingMissingLazyExport
---------- /out/a.js ----------
import {
foo
} from "./chunk-QVTGQSXT.js";
// a.js
console.log(foo());
---------- /out/b.js ----------
import {
bar
} from "./chunk-QVTGQSXT.js";
// b.js
console.log(bar());
---------- /out/chunk-QVTGQSXT.js ----------
// empty.js
var empty_exports = {};
// common.js
function foo() {
return [empty_exports, void 0];
}
function bar() {
return [void 0];
}
export {
foo,
bar
};
================================================================================
TestSplittingNestedDirectories
---------- /Users/user/project/out/pageA/page.js ----------
import {
shared_default
} from "../chunk-GWC2ABNX.js";
// Users/user/project/src/pages/pageA/page.js
console.log(shared_default);
---------- /Users/user/project/out/pageB/page.js ----------
import {
shared_default
} from "../chunk-GWC2ABNX.js";
// Users/user/project/src/pages/pageB/page.js
console.log(-shared_default);
---------- /Users/user/project/out/chunk-GWC2ABNX.js ----------
// Users/user/project/src/pages/shared.js
var shared_default = 123;
export {
shared_default
};
================================================================================
TestSplittingPublicPathEntryName
---------- /out/a.js ----------
// a.js
import("/www/b-AQIID5BE.js");
---------- /out/b-AQIID5BE.js ----------
// b.js
console.log("b");
================================================================================
TestSplittingReExportIssue273
---------- /out/a.js ----------
import {
a
} from "./chunk-RLFZNZQZ.js";
export {
a
};
---------- /out/b.js ----------
import {
a
} from "./chunk-RLFZNZQZ.js";
export {
a
};
---------- /out/chunk-RLFZNZQZ.js ----------
// a.js
var a = 1;
export {
a
};
================================================================================
TestSplittingSharedCommonJSIntoES6
---------- /out/a.js ----------
import {
require_shared
} from "./chunk-JQJBVS2P.js";
// a.js
var { foo } = require_shared();
console.log(foo);
---------- /out/b.js ----------
import {
require_shared
} from "./chunk-JQJBVS2P.js";
// b.js
var { foo } = require_shared();
console.log(foo);
---------- /out/chunk-JQJBVS2P.js ----------
// shared.js
var require_shared = __commonJS({
"shared.js"(exports) {
exports.foo = 123;
}
});
export {
require_shared
};
================================================================================
TestSplittingSharedES6IntoES6
---------- /out/a.js ----------
import {
foo
} from "./chunk-25TWIR6T.js";
// a.js
console.log(foo);
---------- /out/b.js ----------
import {
foo
} from "./chunk-25TWIR6T.js";
// b.js
console.log(foo);
---------- /out/chunk-25TWIR6T.js ----------
// shared.js
var foo = 123;
export {
foo
};
================================================================================
TestSplittingSideEffectsWithoutDependencies
---------- /out/a.js ----------
import {
a
} from "./chunk-Y3CWGI3W.js";
// a.js
console.log(a);
---------- /out/b.js ----------
import {
b
} from "./chunk-Y3CWGI3W.js";
// b.js
console.log(b);
---------- /out/chunk-Y3CWGI3W.js ----------
// shared.js
var a = 1;
var b = 2;
console.log("side effect");
export {
a,
b
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,865 @@
TestJsconfigJsonBaseUrl
---------- /Users/user/project/out.js ----------
// Users/user/project/src/lib/util.js
var require_util = __commonJS({
"Users/user/project/src/lib/util.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/app/entry.js
var import_util = __toESM(require_util());
console.log((0, import_util.default)());
================================================================================
TestTsconfigAlwaysStrictTrueEmitDirectiveBundleCJS
---------- /Users/user/project/out/implicit.js ----------
"use strict";
// Users/user/project/src/implicit.ts
console.log('this file should start with "use strict"');
---------- /Users/user/project/out/explicit.js ----------
"use strict";
// Users/user/project/src/explicit.ts
console.log('this file should start with "use strict"');
================================================================================
TestTsconfigAlwaysStrictTrueEmitDirectiveBundleESM
---------- /Users/user/project/out/implicit.js ----------
// Users/user/project/src/implicit.ts
console.log('this file should not start with "use strict"');
---------- /Users/user/project/out/explicit.js ----------
// Users/user/project/src/explicit.ts
console.log('this file should not start with "use strict"');
================================================================================
TestTsconfigAlwaysStrictTrueEmitDirectiveBundleIIFE
---------- /Users/user/project/out/implicit.js ----------
"use strict";
(() => {
// Users/user/project/src/implicit.ts
console.log('this file should start with "use strict"');
})();
---------- /Users/user/project/out/explicit.js ----------
"use strict";
(() => {
// Users/user/project/src/explicit.ts
console.log('this file should start with "use strict"');
})();
================================================================================
TestTsconfigAlwaysStrictTrueEmitDirectiveFormat
---------- /Users/user/project/out/implicit.js ----------
"use strict";
console.log('this file should start with "use strict"');
---------- /Users/user/project/out/explicit.js ----------
"use strict";
console.log('this file should start with "use strict"');
================================================================================
TestTsconfigAlwaysStrictTrueEmitDirectivePassThrough
---------- /Users/user/project/out/implicit.js ----------
"use strict";
console.log('this file should start with "use strict"');
---------- /Users/user/project/out/explicit.js ----------
"use strict";
console.log('this file should start with "use strict"');
================================================================================
TestTsconfigBaseURLExtendsPaths
---------- /Users/user/project/out.js ----------
// Users/user/project/lib/foo.ts
var foo = 123;
// Users/user/project/src/entry.ts
console.log(foo);
================================================================================
TestTsconfigDecoratorsUseDefineForClassFieldsFalse
---------- /Users/user/project/out/entry.js ----------
// Users/user/project/src/entry.ts
var Class = class {
};
var ClassMethod = class {
foo() {
}
};
var ClassField = class {
constructor() {
this.foo = 123;
}
};
var ClassAccessor = class {
accessor foo = 123;
accessor bar;
};
new Class();
new ClassMethod();
new ClassField();
new ClassAccessor();
---------- /Users/user/project/out/entrywithdec.js ----------
// Users/user/project/src/entrywithdec.ts
var _Class_decorators, _init;
_Class_decorators = [dec];
var Class = class {
};
_init = __decoratorStart(null);
Class = __decorateElement(_init, 0, "Class", _Class_decorators, Class);
__runInitializers(_init, 1, Class);
var _foo_dec, _init2;
_foo_dec = [dec];
var ClassMethod = class {
constructor() {
__runInitializers(_init2, 5, this);
}
foo() {
}
};
_init2 = __decoratorStart(null);
__decorateElement(_init2, 1, "foo", _foo_dec, ClassMethod);
__decoratorMetadata(_init2, ClassMethod);
var _bar_dec, _foo_dec2, _init3;
_foo_dec2 = [dec], _bar_dec = [dec];
var ClassField = class {
constructor() {
this.foo = __runInitializers(_init3, 8, this, 123), __runInitializers(_init3, 11, this);
this.bar = __runInitializers(_init3, 12, this), __runInitializers(_init3, 15, this);
}
};
_init3 = __decoratorStart(null);
__decorateElement(_init3, 5, "foo", _foo_dec2, ClassField);
__decorateElement(_init3, 5, "bar", _bar_dec, ClassField);
__decoratorMetadata(_init3, ClassField);
var _bar_dec2, _foo_dec3, _init4, _foo, _bar;
_foo_dec3 = [dec], _bar_dec2 = [dec];
var ClassAccessor = class {
constructor() {
__privateAdd(this, _foo, __runInitializers(_init4, 8, this, 123)), __runInitializers(_init4, 11, this);
__privateAdd(this, _bar, __runInitializers(_init4, 12, this)), __runInitializers(_init4, 15, this);
}
};
_init4 = __decoratorStart(null);
_foo = new WeakMap();
_bar = new WeakMap();
__decorateElement(_init4, 4, "foo", _foo_dec3, ClassAccessor, _foo);
__decorateElement(_init4, 4, "bar", _bar_dec2, ClassAccessor, _bar);
__decoratorMetadata(_init4, ClassAccessor);
new Class();
new ClassMethod();
new ClassField();
new ClassAccessor();
================================================================================
TestTsconfigExtendsArray
---------- /Users/user/project/out/main.js ----------
console.log(/* @__PURE__ */ h(frag, null, /* @__PURE__ */ h("div", null)));
================================================================================
TestTsconfigExtendsArrayNested
---------- /Users/user/project/out/main.js ----------
import { foo } from "foo";
export class Foo {
constructor() {
this.render = () => /* @__PURE__ */ b(bBase, null, /* @__PURE__ */ b("div", null));
}
}
================================================================================
TestTsconfigExtendsDotDotWithSlash
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ React.createElement("div", null));
================================================================================
TestTsconfigExtendsDotDotWithoutSlash
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigExtendsDotWithSlash
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ React.createElement("div", null));
================================================================================
TestTsconfigExtendsDotWithoutSlash
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigExtendsWithExports
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigExtendsWithExportsRequire
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigExtendsWithExportsStar
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigExtendsWithExportsStarTrailing
---------- /Users/user/project/out/main.js ----------
// Users/user/project/src/main.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigIgnoreInsideNodeModules
---------- /Users/user/project/out/main.js ----------
// Users/user/project/node_modules/pkg/index.js
var foo = "foo";
var bar = "bar";
// Users/user/project/node_modules/js-pkg/index.js
var foo2 = foo;
// Users/user/project/node_modules/ts-pkg/index.ts
var bar2 = bar;
// Users/user/project/shim.ts
var foo3 = "shimFoo";
var bar3 = "shimBar";
// Users/user/project/src/main.ts
if (foo2 !== "foo") throw "fail: foo";
if (bar2 !== "bar") throw "fail: bar";
if (foo3 !== "shimFoo") throw "fail: shimFoo";
if (bar3 !== "shimBar") throw "fail: shimBar";
================================================================================
TestTsconfigIgnoredTargetSilent
---------- /Users/user/project/out.js ----------
================================================================================
TestTsconfigImportsNotUsedAsValuesPreserve
---------- /Users/user/project/out.js ----------
import "./foo";
import "./foo";
import "./foo";
console.log(1, 2, 3);
================================================================================
TestTsconfigJSX
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
console.log(/* @__PURE__ */ R.c(R.F, null, /* @__PURE__ */ R.c("div", null), /* @__PURE__ */ R.c("div", null)));
================================================================================
TestTsconfigJsonAbsoluteBaseUrl
---------- /Users/user/project/out.js ----------
// Users/user/project/src/lib/util.js
var require_util = __commonJS({
"Users/user/project/src/lib/util.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/app/entry.js
var import_util = __toESM(require_util());
console.log((0, import_util.default)());
================================================================================
TestTsconfigJsonAsteriskNameCollisionIssue3354
---------- /Users/user/project/out.js ----------
// web/bar/foo/foo.ts
function foo() {
console.log("bar/foo");
}
// web/foo.ts
function foo2() {
console.log("web/foo");
foo();
}
// entry.ts
foo2();
================================================================================
TestTsconfigJsonBaseUrl
---------- /Users/user/project/out.js ----------
// Users/user/project/src/lib/util.js
var require_util = __commonJS({
"Users/user/project/src/lib/util.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/app/entry.js
var import_util = __toESM(require_util());
console.log((0, import_util.default)());
================================================================================
TestTsconfigJsonBaseUrlIssue3307
---------- /Users/user/project/out.js ----------
// test.ts
var foo = "well, this is correct...";
export {
foo
};
================================================================================
TestTsconfigJsonCommentAllowed
---------- /Users/user/project/out.js ----------
// Users/user/project/src/lib/util.js
var require_util = __commonJS({
"Users/user/project/src/lib/util.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/app/entry.js
var import_util = __toESM(require_util());
console.log((0, import_util.default)());
================================================================================
TestTsconfigJsonConfigDirBaseURL
---------- /Users/user/project/out.js ----------
// Users/user/project/lib/foo/bar
console.log("works");
================================================================================
TestTsconfigJsonConfigDirBaseURLInheritedPaths
---------- /Users/user/project/out.js ----------
// Users/user/project/lib/foo/bar
console.log("works");
================================================================================
TestTsconfigJsonConfigDirPaths
---------- /Users/user/project/out.js ----------
// Users/user/project/lib/foo/bar
console.log("works");
================================================================================
TestTsconfigJsonExtends
---------- /out.js ----------
// entry.jsx
console.log(/* @__PURE__ */ baseFactory("div", null), /* @__PURE__ */ baseFactory(derivedFragment, null));
================================================================================
TestTsconfigJsonExtendsAbsolute
---------- /out.js ----------
// Users/user/project/entry.jsx
console.log(/* @__PURE__ */ baseFactory("div", null), /* @__PURE__ */ baseFactory(derivedFragment, null));
================================================================================
TestTsconfigJsonExtendsArrayIssue3898
---------- /Users/user/project/out.js ----------
import {} from "MUST_KEEP";
console.log(SUCCESS(WORKS, null, SUCCESS("div", null)));
================================================================================
TestTsconfigJsonExtendsLoop
---------- /out.js ----------
// entry.js
console.log(123);
================================================================================
TestTsconfigJsonExtendsPackage
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.jsx
console.log(/* @__PURE__ */ worked("div", null));
================================================================================
TestTsconfigJsonExtendsThreeLevels
---------- /out.js ----------
// Users/user/project/src/path2/works/import.js
console.log("works");
// Users/user/project/src/entry.jsx
console.log(/* @__PURE__ */ baseFactory("div", null), /* @__PURE__ */ baseFactory(derivedFragment, null));
================================================================================
TestTsconfigJsonInsideNodeModules
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/foo/index.tsx
console.log(/* @__PURE__ */ React.createElement("div", null));
================================================================================
TestTsconfigJsonNodeModulesImplicitFile
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.tsx
console.log(/* @__PURE__ */ worked("div", null));
================================================================================
TestTsconfigJsonNodeModulesTsconfigPathBad
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.tsx
console.log(/* @__PURE__ */ React.createElement("div", null));
================================================================================
TestTsconfigJsonNodeModulesTsconfigPathDirectory
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.tsx
console.log(/* @__PURE__ */ worked("div", null));
================================================================================
TestTsconfigJsonNodeModulesTsconfigPathExact
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.tsx
console.log(/* @__PURE__ */ worked("div", null));
================================================================================
TestTsconfigJsonNodeModulesTsconfigPathImplicitJson
---------- /Users/user/project/out.js ----------
// Users/user/project/src/app/entry.tsx
console.log(/* @__PURE__ */ worked("div", null));
================================================================================
TestTsconfigJsonOverrideMissing
---------- /Users/user/project/out.js ----------
// Users/user/project/other/foo-good.ts
console.log("good");
================================================================================
TestTsconfigJsonOverrideNodeModules
---------- /Users/user/project/out.js ----------
// Users/user/project/other/foo-good.ts
console.log("good");
================================================================================
TestTsconfigJsonPackagesExternal
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.js
import truePkg from "pkg1";
// Users/user/project/src/stuff/pkg2.js
var pkg2_default = success;
// Users/user/project/src/entry.js
truePkg();
pkg2_default();
================================================================================
TestTsconfigJsonTopLevelMistakeWarning
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.ts
var Foo = @foo class {
};
================================================================================
TestTsconfigJsonTrailingCommaAllowed
---------- /Users/user/project/out.js ----------
// Users/user/project/src/lib/util.js
var require_util = __commonJS({
"Users/user/project/src/lib/util.js"(exports, module) {
module.exports = function() {
return 123;
};
}
});
// Users/user/project/src/app/entry.js
var import_util = __toESM(require_util());
console.log((0, import_util.default)());
================================================================================
TestTsconfigNestedJSX
---------- /Users/user/project/out.js ----------
// Users/user/project/factory/index.tsx
var factory_default = /* @__PURE__ */ h(React.Fragment, null, /* @__PURE__ */ h("div", null), /* @__PURE__ */ h("div", null));
// Users/user/project/fragment/index.tsx
var fragment_default = /* @__PURE__ */ React.createElement(a.b, null, /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement("div", null));
// Users/user/project/both/index.tsx
var both_default = /* @__PURE__ */ R.c(R.F, null, /* @__PURE__ */ R.c("div", null), /* @__PURE__ */ R.c("div", null));
// Users/user/project/entry.ts
console.log(factory_default, fragment_default, both_default);
================================================================================
TestTsconfigPaths
---------- /Users/user/project/out.js ----------
// Users/user/project/baseurl_dot/test0-success.ts
var test0_success_default = "test0-success";
// Users/user/project/baseurl_dot/test1-success.ts
var test1_success_default = "test1-success";
// Users/user/project/baseurl_dot/test2-success/foo.ts
var foo_default = "test2-success";
// Users/user/project/baseurl_dot/test3-success.ts
var test3_success_default = "test3-success";
// Users/user/project/baseurl_dot/test4-first/foo.ts
var foo_default2 = "test4-success";
// Users/user/project/baseurl_dot/test5-second/foo.ts
var foo_default3 = "test5-success";
// Users/user/project/baseurl_dot/actual/test.ts
var test_default = "absolute-success";
// Users/user/project/baseurl_dot/index.ts
var baseurl_dot_default = {
test0: test0_success_default,
test1: test1_success_default,
test2: foo_default,
test3: test3_success_default,
test4: foo_default2,
test5: foo_default3,
absoluteIn: test_default,
absoluteInStar: test_default,
absoluteOut: test_default,
absoluteOutStar: test_default
};
// Users/user/project/baseurl_nested/nested/test0-success.ts
var test0_success_default2 = "test0-success";
// Users/user/project/baseurl_nested/nested/test1-success.ts
var test1_success_default2 = "test1-success";
// Users/user/project/baseurl_nested/nested/test2-success/foo.ts
var foo_default4 = "test2-success";
// Users/user/project/baseurl_nested/nested/test3-success.ts
var test3_success_default2 = "test3-success";
// Users/user/project/baseurl_nested/nested/test4-first/foo.ts
var foo_default5 = "test4-success";
// Users/user/project/baseurl_nested/nested/test5-second/foo.ts
var foo_default6 = "test5-success";
// Users/user/project/baseurl_nested/nested/actual/test.ts
var test_default2 = "absolute-success";
// Users/user/project/baseurl_nested/index.ts
var baseurl_nested_default = {
test0: test0_success_default2,
test1: test1_success_default2,
test2: foo_default4,
test3: test3_success_default2,
test4: foo_default5,
test5: foo_default6,
absoluteIn: test_default2,
absoluteInStar: test_default2,
absoluteOut: test_default2,
absoluteOutStar: test_default2
};
// Users/user/project/entry.ts
console.log(baseurl_dot_default, baseurl_nested_default);
================================================================================
TestTsconfigPathsExtendsBaseURL
---------- /Users/user/project/out.js ----------
// Users/user/project/base/test/lib/foo.ts
var foo = 123;
// Users/user/project/src/entry.ts
console.log(foo);
================================================================================
TestTsconfigPathsInNodeModulesIssue2386
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/wow/dist/first.js
var first_default = "dist";
// Users/user/project/node_modules/wow/dist/next.js
var next_default = first_default;
// Users/user/project/main.js
console.log(first_default, next_default);
================================================================================
TestTsconfigPathsNoBaseURL
---------- /Users/user/project/out.js ----------
// Users/user/project/simple/test0-success.ts
var test0_success_default = "test0-success";
// Users/user/project/simple/test1-success.ts
var test1_success_default = "test1-success";
// Users/user/project/simple/test2-success/foo.ts
var foo_default = "test2-success";
// Users/user/project/simple/test3-success.ts
var test3_success_default = "test3-success";
// Users/user/project/simple/test4-first/foo.ts
var foo_default2 = "test4-success";
// Users/user/project/simple/test5-second/foo.ts
var foo_default3 = "test5-success";
// Users/user/project/simple/actual/test.ts
var test_default = "absolute-success";
// Users/user/project/simple/index.ts
var simple_default = {
test0: test0_success_default,
test1: test1_success_default,
test2: foo_default,
test3: test3_success_default,
test4: foo_default2,
test5: foo_default3,
absolute: test_default
};
// Users/user/project/extended/nested/test0-success.ts
var test0_success_default2 = "test0-success";
// Users/user/project/extended/nested/test1-success.ts
var test1_success_default2 = "test1-success";
// Users/user/project/extended/nested/test2-success/foo.ts
var foo_default4 = "test2-success";
// Users/user/project/extended/nested/test3-success.ts
var test3_success_default2 = "test3-success";
// Users/user/project/extended/nested/test4-first/foo.ts
var foo_default5 = "test4-success";
// Users/user/project/extended/nested/test5-second/foo.ts
var foo_default6 = "test5-success";
// Users/user/project/extended/nested/actual/test.ts
var test_default2 = "absolute-success";
// Users/user/project/extended/index.ts
var extended_default = {
test0: test0_success_default2,
test1: test1_success_default2,
test2: foo_default4,
test3: test3_success_default2,
test4: foo_default5,
test5: foo_default6,
absolute: test_default2
};
// Users/user/project/entry.ts
console.log(simple_default, extended_default);
================================================================================
TestTsconfigPathsOverriddenBaseURL
---------- /Users/user/project/out.js ----------
// Users/user/project/src/test.ts
var test_default = 123;
// Users/user/project/src/entry.ts
console.log(test_default);
================================================================================
TestTsconfigPathsOverriddenBaseURLDifferentDir
---------- /Users/user/project/out.js ----------
// Users/user/project/src/test.ts
var test_default = 123;
// Users/user/project/src/entry.ts
console.log(test_default);
================================================================================
TestTsconfigPathsTypeOnly
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/fib/index.js
function fib(input) {
if (input < 2) {
return input;
}
return fib(input - 1) + fib(input - 2);
}
// Users/user/project/entry.ts
console.log(fib(10));
================================================================================
TestTsconfigPreserveJSX
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
console.log(/* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement("div", null)));
================================================================================
TestTsconfigPreserveJSXAutomatic
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
console.log(/* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", {}),
/* @__PURE__ */ jsx("div", {})
] }));
================================================================================
TestTsconfigPreserveUnusedImports
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.ts
import "./src/foo";
console.log(1);
================================================================================
TestTsconfigPreserveValueImports
---------- /Users/user/project/out.js ----------
import { b1 } from "b";
import { c1 } from "c";
import { d1, d2 } from "d";
import f1 from "f";
import g1, { g2 } from "g";
import h1 from "h";
import * as i1 from "i";
import "j";
================================================================================
TestTsconfigPreserveValueImportsAndImportsNotUsedAsValuesPreserve
---------- /Users/user/project/out.js ----------
import {} from "a";
import { b1 } from "b";
import { c1 } from "c";
import { d1, d2 } from "d";
import {} from "e";
import f1, {} from "f";
import g1, { g2 } from "g";
import h1, {} from "h";
import * as i1 from "i";
import "j";
================================================================================
TestTsconfigReactJSX
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
import { Fragment, jsx, jsxs } from "notreact/jsx-runtime";
console.log(/* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", {}),
/* @__PURE__ */ jsx("div", {})
] }));
================================================================================
TestTsconfigReactJSXDev
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
import { Fragment, jsxDEV } from "react/jsx-dev-runtime";
console.log(/* @__PURE__ */ jsxDEV(Fragment, { children: [
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 19
}),
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 25
})
] }, void 0, true, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 17
}));
================================================================================
TestTsconfigReactJSXWithDevInMainConfig
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
import { Fragment, jsxDEV } from "react/jsx-dev-runtime";
console.log(/* @__PURE__ */ jsxDEV(Fragment, { children: [
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 19
}),
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 25
})
] }, void 0, true, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 17
}));
================================================================================
TestTsconfigRemoveUnusedImports
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.ts
console.log(1);
================================================================================
TestTsconfigUnrecognizedTargetWarning
---------- /Users/user/project/out.js ----------
================================================================================
TestTsconfigUseDefineForClassFieldsES2020
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.ts
Foo = class {
constructor() {
this.useDefine = false;
}
};
================================================================================
TestTsconfigUseDefineForClassFieldsESNext
---------- /Users/user/project/out.js ----------
// Users/user/project/src/entry.ts
Foo = class {
useDefine = true;
};
================================================================================
TestTsconfigVerbatimModuleSyntaxFalse
---------- /Users/user/project/out/main.js ----------
export { Car } from "./car";
================================================================================
TestTsconfigVerbatimModuleSyntaxTrue
---------- /Users/user/project/out/main.js ----------
export { Car } from "./car";
import {} from "./car";
export {} from "./car";
import { b } from "bcd";
import {} from "xyz";
================================================================================
TestTsconfigWarningsInsideNodeModules
---------- /Users/user/project/out.js ----------
================================================================================
TestTsconfigWithStatementAlwaysStrictFalse
---------- /Users/user/project/out.js ----------
(() => {
// Users/user/project/src/entry.ts
with (x) y;
})();
================================================================================
TestTsconfigWithStatementStrictFalse
---------- /Users/user/project/out.js ----------
(() => {
// Users/user/project/src/entry.ts
with (x) y;
})();
================================================================================
TestTsconfigWithStatementStrictTrueAlwaysStrictFalse
---------- /Users/user/project/out.js ----------
(() => {
// Users/user/project/src/entry.ts
with (x) y;
})();
@@ -0,0 +1,20 @@
TestTsconfigPackageJsonExportsYarnPnP
---------- /Users/user/project/out.js ----------
// packages/app/index.tsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestTsconfigStackOverflowYarnPnP
---------- /Users/user/project/out.js ----------
// entry.jsx
console.log(/* @__PURE__ */ success("div", null));
================================================================================
TestWindowsCrossVolumeReferenceYarnPnP
---------- D:/project/out.js ----------
// C:/Users/user/AppData/Local/Yarn/Berry/cache/react.zip/node_modules/react/index.js
function createElement() {
}
// entry.jsx
console.log(/* @__PURE__ */ createElement("div", null));
+115
View File
@@ -0,0 +1,115 @@
package cache
import (
"sync"
"github.com/evanw/esbuild/internal/logger"
"github.com/evanw/esbuild/internal/runtime"
)
// This is a cache of the parsed contents of a set of files. The idea is to be
// able to reuse the results of parsing between builds and make subsequent
// builds faster by avoiding redundant parsing work. This only works if:
//
// - The AST information in the cache must be considered immutable. There is
// no way to enforce this in Go, but please be disciplined about this. The
// ASTs are shared in between builds. Any information that must be mutated
// in the AST during a build must be done on a shallow clone of the data if
// the mutation happens after parsing (i.e. a clone that clones everything
// that will be mutated and shares only the parts that won't be mutated).
//
// - The information in the cache must not depend at all on the contents of
// any file other than the file being cached. Invalidating an entry in the
// cache does not also invalidate any entries that depend on that file, so
// caching information that depends on other files can result in incorrect
// results due to reusing stale data. For example, do not "bake in" some
// value imported from another file.
//
// - Cached ASTs must only be reused if the parsing options are identical
// between builds. For example, it would be bad if the AST parser depended
// on options inherited from a nearby "package.json" file but those options
// were not part of the cache key. Then the cached AST could incorrectly be
// reused even if the contents of that "package.json" file have changed.
type CacheSet struct {
FSCache FSCache
CSSCache CSSCache
JSONCache JSONCache
JSCache JSCache
SourceIndexCache SourceIndexCache
}
func MakeCacheSet() *CacheSet {
return &CacheSet{
SourceIndexCache: SourceIndexCache{
globEntries: make(map[uint64]uint32),
entries: make(map[sourceIndexKey]uint32),
nextSourceIndex: runtime.SourceIndex + 1,
},
FSCache: FSCache{
entries: make(map[string]*fsEntry),
},
CSSCache: CSSCache{
entries: make(map[logger.Path]*cssCacheEntry),
},
JSONCache: JSONCache{
entries: make(map[logger.Path]*jsonCacheEntry),
},
JSCache: JSCache{
entries: make(map[logger.Path]*jsCacheEntry),
},
}
}
type SourceIndexCache struct {
globEntries map[uint64]uint32
entries map[sourceIndexKey]uint32
mutex sync.Mutex
nextSourceIndex uint32
}
type SourceIndexKind uint8
const (
SourceIndexNormal SourceIndexKind = iota
SourceIndexJSStubForCSS
)
type sourceIndexKey struct {
path logger.Path
kind SourceIndexKind
}
func (c *SourceIndexCache) LenHint() uint32 {
c.mutex.Lock()
defer c.mutex.Unlock()
// Add some extra room at the end for a new file or two without reallocating
const someExtraRoom = 16
return c.nextSourceIndex + someExtraRoom
}
func (c *SourceIndexCache) Get(path logger.Path, kind SourceIndexKind) uint32 {
key := sourceIndexKey{path: path, kind: kind}
c.mutex.Lock()
defer c.mutex.Unlock()
if sourceIndex, ok := c.entries[key]; ok {
return sourceIndex
}
sourceIndex := c.nextSourceIndex
c.nextSourceIndex++
c.entries[key] = sourceIndex
return sourceIndex
}
func (c *SourceIndexCache) GetGlob(parentSourceIndex uint32, globIndex uint32) uint32 {
key := (uint64(parentSourceIndex) << 32) | uint64(globIndex)
c.mutex.Lock()
defer c.mutex.Unlock()
if sourceIndex, ok := c.globEntries[key]; ok {
return sourceIndex
}
sourceIndex := c.nextSourceIndex
c.nextSourceIndex++
c.globEntries[key] = sourceIndex
return sourceIndex
}
+190
View File
@@ -0,0 +1,190 @@
package cache
import (
"sync"
"github.com/evanw/esbuild/internal/css_ast"
"github.com/evanw/esbuild/internal/css_parser"
"github.com/evanw/esbuild/internal/js_ast"
"github.com/evanw/esbuild/internal/js_parser"
"github.com/evanw/esbuild/internal/logger"
)
// This cache intends to avoid unnecessarily re-parsing files in subsequent
// builds. For a given path, parsing can be avoided if the contents of the file
// and the options for the parser are the same as last time. Even if the
// contents of the file are the same, the options for the parser may have
// changed if they depend on some other file ("package.json" for example).
//
// This cache checks if the file contents have changed even though we have
// the ability to detect if a file has changed on the file system by reading
// its metadata. First of all, if the file contents are cached then they should
// be the same pointer, which makes the comparison trivial. Also we want to
// cache the AST for plugins in the common case that the plugin output stays
// the same.
////////////////////////////////////////////////////////////////////////////////
// CSS
type CSSCache struct {
entries map[logger.Path]*cssCacheEntry
mutex sync.Mutex
}
type cssCacheEntry struct {
source logger.Source
msgs []logger.Msg
ast css_ast.AST
options css_parser.Options
}
func (c *CSSCache) Parse(log logger.Log, source logger.Source, options css_parser.Options) css_ast.AST {
// Check the cache
entry := func() *cssCacheEntry {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.entries[source.KeyPath]
}()
// Cache hit
if entry != nil && entry.source == source && entry.options.Equal(&options) {
for _, msg := range entry.msgs {
log.AddMsg(msg)
}
return entry.ast
}
// Cache miss
tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides)
ast := css_parser.Parse(tempLog, source, options)
msgs := tempLog.Done()
for _, msg := range msgs {
log.AddMsg(msg)
}
// Create the cache entry
entry = &cssCacheEntry{
source: source,
options: options,
ast: ast,
msgs: msgs,
}
// Save for next time
c.mutex.Lock()
defer c.mutex.Unlock()
c.entries[source.KeyPath] = entry
return ast
}
////////////////////////////////////////////////////////////////////////////////
// JSON
type JSONCache struct {
entries map[logger.Path]*jsonCacheEntry
mutex sync.Mutex
}
type jsonCacheEntry struct {
expr js_ast.Expr
msgs []logger.Msg
source logger.Source
options js_parser.JSONOptions
ok bool
}
func (c *JSONCache) Parse(log logger.Log, source logger.Source, options js_parser.JSONOptions) (js_ast.Expr, bool) {
// Check the cache
entry := func() *jsonCacheEntry {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.entries[source.KeyPath]
}()
// Cache hit
if entry != nil && entry.source == source && entry.options == options {
for _, msg := range entry.msgs {
log.AddMsg(msg)
}
return entry.expr, entry.ok
}
// Cache miss
tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides)
expr, ok := js_parser.ParseJSON(tempLog, source, options)
msgs := tempLog.Done()
for _, msg := range msgs {
log.AddMsg(msg)
}
// Create the cache entry
entry = &jsonCacheEntry{
source: source,
options: options,
expr: expr,
ok: ok,
msgs: msgs,
}
// Save for next time
c.mutex.Lock()
defer c.mutex.Unlock()
c.entries[source.KeyPath] = entry
return expr, ok
}
////////////////////////////////////////////////////////////////////////////////
// JS
type JSCache struct {
entries map[logger.Path]*jsCacheEntry
mutex sync.Mutex
}
type jsCacheEntry struct {
source logger.Source
msgs []logger.Msg
options js_parser.Options
ast js_ast.AST
ok bool
}
func (c *JSCache) Parse(log logger.Log, source logger.Source, options js_parser.Options) (js_ast.AST, bool) {
// Check the cache
entry := func() *jsCacheEntry {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.entries[source.KeyPath]
}()
// Cache hit
if entry != nil && entry.source == source && entry.options.Equal(&options) {
for _, msg := range entry.msgs {
log.AddMsg(msg)
}
return entry.ast, entry.ok
}
// Cache miss
tempLog := logger.NewDeferLog(logger.DeferLogAll, log.Overrides)
ast, ok := js_parser.Parse(tempLog, source, options)
msgs := tempLog.Done()
for _, msg := range msgs {
log.AddMsg(msg)
}
// Create the cache entry
entry = &jsCacheEntry{
source: source,
options: options,
ast: ast,
ok: ok,
msgs: msgs,
}
// Save for next time
c.mutex.Lock()
defer c.mutex.Unlock()
c.entries[source.KeyPath] = entry
return ast, ok
}
+52
View File
@@ -0,0 +1,52 @@
package cache
import (
"sync"
"github.com/evanw/esbuild/internal/fs"
)
// This cache uses information from the "stat" syscall to try to avoid re-
// reading files from the file system during subsequent builds if the file
// hasn't changed. The assumption is reading the file metadata is faster than
// reading the file contents.
type FSCache struct {
entries map[string]*fsEntry
mutex sync.Mutex
}
type fsEntry struct {
contents string
modKey fs.ModKey
isModKeyUsable bool
}
func (c *FSCache) ReadFile(fs fs.FS, path string) (contents string, canonicalError error, originalError error) {
entry := func() *fsEntry {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.entries[path]
}()
// If the file's modification key hasn't changed since it was cached, assume
// the contents of the file are also the same and skip reading the file.
modKey, modKeyErr := fs.ModKey(path)
if entry != nil && entry.isModKeyUsable && modKeyErr == nil && entry.modKey == modKey {
return entry.contents, nil, nil
}
contents, err, originalError := fs.ReadFile(path)
if err != nil {
return "", err, originalError
}
c.mutex.Lock()
defer c.mutex.Unlock()
c.entries[path] = &fsEntry{
contents: contents,
modKey: modKey,
isModKeyUsable: modKeyErr == nil,
}
return contents, nil, nil
}
@@ -0,0 +1,64 @@
// This package contains internal CLI-related code that must be shared with
// other internal code outside of the CLI package.
package cli_helpers
import (
"fmt"
"github.com/evanw/esbuild/pkg/api"
)
type ErrorWithNote struct {
Text string
Note string
}
func MakeErrorWithNote(text string, note string) *ErrorWithNote {
return &ErrorWithNote{
Text: text,
Note: note,
}
}
func ParseLoader(text string) (api.Loader, *ErrorWithNote) {
switch text {
case "base64":
return api.LoaderBase64, nil
case "binary":
return api.LoaderBinary, nil
case "copy":
return api.LoaderCopy, nil
case "css":
return api.LoaderCSS, nil
case "dataurl":
return api.LoaderDataURL, nil
case "default":
return api.LoaderDefault, nil
case "empty":
return api.LoaderEmpty, nil
case "file":
return api.LoaderFile, nil
case "global-css":
return api.LoaderGlobalCSS, nil
case "js":
return api.LoaderJS, nil
case "json":
return api.LoaderJSON, nil
case "jsx":
return api.LoaderJSX, nil
case "local-css":
return api.LoaderLocalCSS, nil
case "text":
return api.LoaderText, nil
case "ts":
return api.LoaderTS, nil
case "tsx":
return api.LoaderTSX, nil
default:
return api.LoaderNone, MakeErrorWithNote(
fmt.Sprintf("Invalid loader value: %q", text),
"Valid values are \"base64\", \"binary\", \"copy\", \"css\", \"dataurl\", \"empty\", \"file\", \"global-css\", \"js\", \"json\", \"jsx\", \"local-css\", \"text\", \"ts\", or \"tsx\".",
)
}
}
@@ -0,0 +1,92 @@
package compat
import (
"strconv"
"strings"
"github.com/evanw/esbuild/internal/ast"
)
type v struct {
major uint16
minor uint8
patch uint8
}
type Semver struct {
// "1.2.3-alpha" => { Parts: {1, 2, 3}, PreRelease: "-alpha" }
Parts []int
PreRelease string
}
func (v Semver) String() string {
b := strings.Builder{}
for _, part := range v.Parts {
if b.Len() > 0 {
b.WriteRune('.')
}
b.WriteString(strconv.Itoa(part))
}
b.WriteString(v.PreRelease)
return b.String()
}
// Returns <0 if "a < b"
// Returns 0 if "a == b"
// Returns >0 if "a > b"
func compareVersions(a v, b Semver) int {
diff := int(a.major)
if len(b.Parts) > 0 {
diff -= b.Parts[0]
}
if diff == 0 {
diff = int(a.minor)
if len(b.Parts) > 1 {
diff -= b.Parts[1]
}
}
if diff == 0 {
diff = int(a.patch)
if len(b.Parts) > 2 {
diff -= b.Parts[2]
}
}
if diff == 0 && len(b.PreRelease) != 0 {
return 1 // "1.0.0" > "1.0.0-alpha"
}
return diff
}
// The start is inclusive and the end is exclusive
type versionRange struct {
start v
end v // Use 0.0.0 for "no end"
}
func isVersionSupported(ranges []versionRange, version Semver) bool {
for _, r := range ranges {
if compareVersions(r.start, version) <= 0 && (r.end == (v{}) || compareVersions(r.end, version) > 0) {
return true
}
}
return false
}
func SymbolFeature(kind ast.SymbolKind) JSFeature {
switch kind {
case ast.SymbolPrivateField:
return ClassPrivateField
case ast.SymbolPrivateMethod:
return ClassPrivateMethod
case ast.SymbolPrivateGet, ast.SymbolPrivateSet, ast.SymbolPrivateGetSetPair:
return ClassPrivateAccessor
case ast.SymbolPrivateStaticField:
return ClassPrivateStaticField
case ast.SymbolPrivateStaticMethod:
return ClassPrivateStaticMethod
case ast.SymbolPrivateStaticGet, ast.SymbolPrivateStaticSet, ast.SymbolPrivateStaticGetSetPair:
return ClassPrivateStaticAccessor
default:
return 0
}
}
@@ -0,0 +1,63 @@
package compat
import (
"fmt"
"testing"
"github.com/evanw/esbuild/internal/test"
)
func TestCompareVersions(t *testing.T) {
t.Helper()
check := func(a v, b Semver, expected rune) {
t.Helper()
at := fmt.Sprintf("%d.%d.%d", a.major, a.minor, a.patch)
bt := b.String()
t.Run(fmt.Sprintf("%q ? %q", at, bt), func(t *testing.T) {
observed := '='
if result := compareVersions(a, b); result < 0 {
observed = '<'
} else if result > 0 {
observed = '>'
}
if observed != expected {
test.AssertEqual(t, fmt.Sprintf("%c", observed), fmt.Sprintf("%c", expected))
}
})
}
check(v{0, 0, 0}, Semver{}, '=')
check(v{1, 0, 0}, Semver{}, '>')
check(v{0, 1, 0}, Semver{}, '>')
check(v{0, 0, 1}, Semver{}, '>')
check(v{0, 0, 0}, Semver{Parts: []int{1}}, '<')
check(v{0, 0, 0}, Semver{Parts: []int{0, 1}}, '<')
check(v{0, 0, 0}, Semver{Parts: []int{0, 0, 1}}, '<')
check(v{0, 4, 0}, Semver{Parts: []int{0, 5, 0}}, '<')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=')
check(v{0, 6, 0}, Semver{Parts: []int{0, 5, 0}}, '>')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 1}}, '<')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=')
check(v{0, 5, 1}, Semver{Parts: []int{0, 5, 0}}, '>')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5}}, '=')
check(v{0, 5, 1}, Semver{Parts: []int{0, 5}}, '>')
check(v{1, 0, 0}, Semver{Parts: []int{1}}, '=')
check(v{1, 1, 0}, Semver{Parts: []int{1}}, '>')
check(v{1, 0, 1}, Semver{Parts: []int{1}}, '>')
check(v{1, 2, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>')
check(v{1, 2, 1}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>')
check(v{1, 1, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '<')
check(v{1, 2, 3}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '>')
check(v{1, 2, 2}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '<')
}
@@ -0,0 +1,422 @@
// This file was automatically generated by "css_table.ts"
package compat
import (
"github.com/evanw/esbuild/internal/css_ast"
)
type CSSFeature uint16
const (
ColorFunctions CSSFeature = 1 << iota
GradientDoublePosition
GradientInterpolation
GradientMidpoints
HWB
HexRGBA
InlineStyle
InsetProperty
IsPseudoClass
MediaRange
Modern_RGB_HSL
Nesting
RebeccaPurple
)
var StringToCSSFeature = map[string]CSSFeature{
"color-functions": ColorFunctions,
"gradient-double-position": GradientDoublePosition,
"gradient-interpolation": GradientInterpolation,
"gradient-midpoints": GradientMidpoints,
"hwb": HWB,
"hex-rgba": HexRGBA,
"inline-style": InlineStyle,
"inset-property": InsetProperty,
"is-pseudo-class": IsPseudoClass,
"media-range": MediaRange,
"modern-rgb-hsl": Modern_RGB_HSL,
"nesting": Nesting,
"rebecca-purple": RebeccaPurple,
}
func (features CSSFeature) Has(feature CSSFeature) bool {
return (features & feature) != 0
}
func (features CSSFeature) ApplyOverrides(overrides CSSFeature, mask CSSFeature) CSSFeature {
return (features & ^mask) | (overrides & mask)
}
var cssTable = map[CSSFeature]map[Engine][]versionRange{
ColorFunctions: {
Chrome: {{start: v{111, 0, 0}}},
Edge: {{start: v{111, 0, 0}}},
Firefox: {{start: v{113, 0, 0}}},
IOS: {{start: v{15, 4, 0}}},
Opera: {{start: v{97, 0, 0}}},
Safari: {{start: v{15, 4, 0}}},
},
GradientDoublePosition: {
Chrome: {{start: v{72, 0, 0}}},
Edge: {{start: v{79, 0, 0}}},
Firefox: {{start: v{83, 0, 0}}},
IOS: {{start: v{12, 2, 0}}},
Opera: {{start: v{60, 0, 0}}},
Safari: {{start: v{12, 1, 0}}},
},
GradientInterpolation: {
Chrome: {{start: v{111, 0, 0}}},
Edge: {{start: v{111, 0, 0}}},
Firefox: {{start: v{137, 0, 0}}},
IOS: {{start: v{16, 2, 0}}},
Opera: {{start: v{97, 0, 0}}},
Safari: {{start: v{16, 2, 0}}},
},
GradientMidpoints: {
Chrome: {{start: v{40, 0, 0}}},
Edge: {{start: v{79, 0, 0}}},
Firefox: {{start: v{36, 0, 0}}},
IOS: {{start: v{7, 0, 0}}},
Opera: {{start: v{27, 0, 0}}},
Safari: {{start: v{7, 0, 0}}},
},
HWB: {
Chrome: {{start: v{101, 0, 0}}},
Edge: {{start: v{101, 0, 0}}},
Firefox: {{start: v{96, 0, 0}}},
IOS: {{start: v{15, 0, 0}}},
Opera: {{start: v{87, 0, 0}}},
Safari: {{start: v{15, 0, 0}}},
},
HexRGBA: {
Chrome: {{start: v{62, 0, 0}}},
Edge: {{start: v{79, 0, 0}}},
Firefox: {{start: v{49, 0, 0}}},
IOS: {{start: v{9, 3, 0}}},
Opera: {{start: v{49, 0, 0}}},
Safari: {{start: v{10, 0, 0}}},
},
InlineStyle: {},
InsetProperty: {
Chrome: {{start: v{87, 0, 0}}},
Edge: {{start: v{87, 0, 0}}},
Firefox: {{start: v{66, 0, 0}}},
IOS: {{start: v{14, 5, 0}}},
Opera: {{start: v{73, 0, 0}}},
Safari: {{start: v{14, 1, 0}}},
},
IsPseudoClass: {
Chrome: {{start: v{88, 0, 0}}},
Edge: {{start: v{88, 0, 0}}},
Firefox: {{start: v{78, 0, 0}}},
IOS: {{start: v{14, 0, 0}}},
Opera: {{start: v{75, 0, 0}}},
Safari: {{start: v{14, 0, 0}}},
},
MediaRange: {
Chrome: {{start: v{104, 0, 0}}},
Edge: {{start: v{104, 0, 0}}},
Firefox: {{start: v{63, 0, 0}}},
IOS: {{start: v{16, 4, 0}}},
Opera: {{start: v{91, 0, 0}}},
Safari: {{start: v{16, 4, 0}}},
},
Modern_RGB_HSL: {
Chrome: {{start: v{66, 0, 0}}},
Edge: {{start: v{79, 0, 0}}},
Firefox: {{start: v{52, 0, 0}}},
IOS: {{start: v{12, 2, 0}}},
Opera: {{start: v{53, 0, 0}}},
Safari: {{start: v{12, 1, 0}}},
},
Nesting: {
Chrome: {{start: v{120, 0, 0}}},
Edge: {{start: v{120, 0, 0}}},
Firefox: {{start: v{117, 0, 0}}},
IOS: {{start: v{17, 2, 0}}},
Opera: {{start: v{106, 0, 0}}},
Safari: {{start: v{17, 2, 0}}},
},
RebeccaPurple: {
Chrome: {{start: v{38, 0, 0}}},
Edge: {{start: v{12, 0, 0}}},
Firefox: {{start: v{33, 0, 0}}},
IE: {{start: v{11, 0, 0}}},
IOS: {{start: v{8, 0, 0}}},
Opera: {{start: v{25, 0, 0}}},
Safari: {{start: v{9, 0, 0}}},
},
}
// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine]Semver) (unsupported CSSFeature) {
for feature, engines := range cssTable {
if feature == InlineStyle {
continue // This is purely user-specified
}
for engine, version := range constraints {
if !engine.IsBrowser() {
// Specifying "--target=es2020" shouldn't affect CSS
continue
}
if versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
unsupported |= feature
}
}
}
return
}
type CSSPrefix uint8
const (
KhtmlPrefix CSSPrefix = 1 << iota
MozPrefix
MsPrefix
OPrefix
WebkitPrefix
NoPrefix CSSPrefix = 0
)
type prefixData struct {
// Note: In some cases, earlier versions did not require a prefix but later
// ones do. This is the case for Microsoft Edge for example, which switched
// the underlying browser engine from a custom one to the one from Chrome.
// However, we assume that users specifying a browser version for CSS mean
// "works in this version or newer", so we still add a prefix when a target
// is an old Edge version.
engine Engine
withoutPrefix v
prefix CSSPrefix
}
var cssPrefixTable = map[css_ast.D][]prefixData{
css_ast.DAppearance: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{84, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{84, 0, 0}},
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{80, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{73, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DBackdropFilter: {
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{18, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{18, 0, 0}},
},
css_ast.DBackgroundClip: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: MsPrefix, withoutPrefix: v{15, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{5, 0, 0}},
},
css_ast.DBoxDecorationBreak: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{130, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{130, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{116, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DClipPath: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{55, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{42, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{13, 1, 0}},
},
css_ast.DFontKerning: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{33, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{12, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{20, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{9, 1, 0}},
},
css_ast.DHeight: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Firefox, prefix: WebkitPrefix},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DHyphens: {
{engine: Edge, prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{43, 0, 0}},
{engine: IE, prefix: MsPrefix},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{17, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{17, 0, 0}},
},
css_ast.DInitialLetter: {
{engine: IOS, prefix: WebkitPrefix},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DMask: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskComposite: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskImage: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskOrigin: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskPosition: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskRepeat: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaskSize: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{120, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DMaxHeight: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DMaxWidth: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DMinHeight: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DMinWidth: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DPosition: {
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
},
css_ast.DPrintColorAdjust: {
{engine: Chrome, prefix: WebkitPrefix},
{engine: Edge, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},
css_ast.DTabSize: {
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{91, 0, 0}},
{engine: Opera, prefix: OPrefix, withoutPrefix: v{15, 0, 0}},
},
css_ast.DTextDecorationColor: {
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{36, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{12, 2, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{12, 1, 0}},
},
css_ast.DTextDecorationLine: {
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{36, 0, 0}},
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{12, 2, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{12, 1, 0}},
},
css_ast.DTextDecorationSkip: {
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{12, 2, 0}},
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{12, 1, 0}},
},
css_ast.DTextEmphasisColor: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{85, 0, 0}},
},
css_ast.DTextEmphasisPosition: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{85, 0, 0}},
},
css_ast.DTextEmphasisStyle: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{99, 0, 0}},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{85, 0, 0}},
},
css_ast.DTextOrientation: {
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{14, 0, 0}},
},
css_ast.DTextSizeAdjust: {
{engine: Edge, prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
{engine: IOS, prefix: WebkitPrefix},
},
css_ast.DUserSelect: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{54, 0, 0}},
{engine: Edge, prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{69, 0, 0}},
{engine: IE, prefix: MsPrefix},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{41, 0, 0}},
{engine: Safari, prefix: KhtmlPrefix, withoutPrefix: v{3, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
css_ast.DWidth: {
{engine: Chrome, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Edge, prefix: WebkitPrefix, withoutPrefix: v{138, 0, 0}},
{engine: Firefox, prefix: WebkitPrefix},
{engine: IOS, prefix: WebkitPrefix},
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{122, 0, 0}},
{engine: Safari, prefix: WebkitPrefix},
},
}
func CSSPrefixData(constraints map[Engine]Semver) (entries map[css_ast.D]CSSPrefix) {
for property, items := range cssPrefixTable {
prefixes := NoPrefix
for engine, version := range constraints {
if !engine.IsBrowser() {
// Specifying "--target=es2020" shouldn't affect CSS
continue
}
for _, item := range items {
if item.engine == engine && (item.withoutPrefix == v{} || compareVersions(item.withoutPrefix, version) > 0) {
prefixes |= item.prefix
}
}
}
if prefixes != NoPrefix {
if entries == nil {
entries = make(map[css_ast.D]CSSPrefix)
}
entries[property] = prefixes
}
}
return
}

Some files were not shown because too many files have changed in this diff Show More