userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
@@ -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,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]
|
||||
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)'
|
||||
}
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
-->
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)**
|
||||
@@ -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
|
||||
}
|
||||
@@ -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`.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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 |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -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 |
|
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
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
`,
|
||||
})
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
};
|
||||
@@ -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));
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||