mirror of
https://github.com/denoland/deno.git
synced 2025-07-07 13:25:07 +00:00
feat(bundle, unstable): bundling backed by esbuild (#29470)
Some checks are pending
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / build wasm32 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
Some checks are pending
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / build wasm32 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
todo: - [ ] cleanup cli, decide what flags we want to commit to - [x] decide what to do about node addons - (you can mark them external via `--external`) - [x] move `esbuild_rs` to the `denoland` org - [x] figure out the dynamic require issue - [x] figure out how to test this - [x] clean up / revert all the random changes
This commit is contained in:
parent
1323aca15e
commit
7a837f9fdb
28 changed files with 1548 additions and 156 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -43,3 +43,5 @@ Untitled*.ipynb
|
|||
|
||||
# playwright browser binary cache
|
||||
/.ms-playwright
|
||||
|
||||
**/.claude/settings.local.json
|
||||
|
|
112
Cargo.lock
generated
112
Cargo.lock
generated
|
@ -193,9 +193,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
|
@ -1493,6 +1493,7 @@ dependencies = [
|
|||
"dprint-plugin-jupyter",
|
||||
"dprint-plugin-markdown",
|
||||
"dprint-plugin-typescript",
|
||||
"esbuild_client",
|
||||
"eszip",
|
||||
"fancy-regex",
|
||||
"faster-hex",
|
||||
|
@ -1503,7 +1504,7 @@ dependencies = [
|
|||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"jsonc-parser",
|
||||
"lazy-regex",
|
||||
"libc",
|
||||
|
@ -1692,7 +1693,7 @@ dependencies = [
|
|||
"deno_media_type",
|
||||
"deno_path_util",
|
||||
"http 1.1.0",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
|
@ -1732,7 +1733,7 @@ dependencies = [
|
|||
"glob",
|
||||
"ignore",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"jsonc-parser",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
|
@ -1771,7 +1772,7 @@ dependencies = [
|
|||
"deno_path_util",
|
||||
"deno_unsync",
|
||||
"futures",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
|
@ -1863,7 +1864,7 @@ dependencies = [
|
|||
"handlebars",
|
||||
"html-escape",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"itoa",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
|
@ -2015,7 +2016,7 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
"futures",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"monch",
|
||||
"once_cell",
|
||||
|
@ -2147,7 +2148,7 @@ dependencies = [
|
|||
"deno_terminal 0.2.2",
|
||||
"env_logger",
|
||||
"faster-hex",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"libsui",
|
||||
"log",
|
||||
"node_resolver",
|
||||
|
@ -2358,7 +2359,7 @@ dependencies = [
|
|||
"deno_lockfile",
|
||||
"deno_semver",
|
||||
"futures",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"monch",
|
||||
"serde",
|
||||
|
@ -2442,7 +2443,7 @@ version = "0.226.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28b12489187c71fa123731cc783d48beb17ae5df04da991909cc2ae5a3d0ef9"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"proc-macro-rules",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2484,7 +2485,7 @@ dependencies = [
|
|||
"deno_error",
|
||||
"deno_path_util",
|
||||
"deno_semver",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sys_traits",
|
||||
|
@ -2594,7 +2595,7 @@ dependencies = [
|
|||
"futures",
|
||||
"http 1.1.0",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"node_resolver",
|
||||
"once_cell",
|
||||
|
@ -2854,7 +2855,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"deno_error",
|
||||
"deno_unsync",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -3003,7 +3004,7 @@ dependencies = [
|
|||
"deno_snapshots",
|
||||
"deno_terminal 0.2.2",
|
||||
"import_map",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"libsui",
|
||||
"log",
|
||||
"memmap2",
|
||||
|
@ -3088,18 +3089,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.0"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.0"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
|
@ -3109,9 +3110,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.0"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.87",
|
||||
|
@ -3255,7 +3256,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"bumpalo",
|
||||
"hashbrown 0.15.2",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"unicode-width 0.2.0",
|
||||
|
@ -3451,7 +3452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "48cede2bb1b07dd598d269f973792c43e0cd92686d3b452bd6e01d7a8eb01211"
|
||||
dependencies = [
|
||||
"debug-ignore",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"zerocopy",
|
||||
|
@ -3571,6 +3572,23 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8"
|
||||
|
||||
[[package]]
|
||||
name = "esbuild_client"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "241d235e88635b3622ee664b3ed9bf70cb286b3067cff2ea2b9be11c7e2f8d19"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"deno_unsync",
|
||||
"derive_builder",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eszip"
|
||||
version = "0.92.0"
|
||||
|
@ -3587,7 +3605,7 @@ dependencies = [
|
|||
"deno_semver",
|
||||
"futures",
|
||||
"hashlink 0.8.4",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
@ -4042,7 +4060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
|
@ -4189,7 +4207,7 @@ dependencies = [
|
|||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -4208,7 +4226,7 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -4869,7 +4887,7 @@ checksum = "f315e535cb94a0e80704278d630990bb48834c8c8d976acf0a2f6bc8fede7c38"
|
|||
dependencies = [
|
||||
"boxed_error",
|
||||
"deno_error",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
|
@ -4890,9 +4908,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
|
@ -5583,7 +5601,7 @@ dependencies = [
|
|||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
"hexf-parse",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"rustc-hash 1.1.0",
|
||||
"serde",
|
||||
|
@ -6146,9 +6164,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-clean"
|
||||
|
@ -6248,7 +6266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset 0.4.2",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6258,7 +6276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||
dependencies = [
|
||||
"fixedbitset 0.5.7",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6631,7 +6649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d1a341ae463320e9f8f34adda49c8a85d81d4e8f34cce4397fb0350481552224"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror 1.0.69",
|
||||
|
@ -7446,7 +7464,7 @@ version = "1.0.140"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
|
@ -7906,7 +7924,7 @@ checksum = "ebb953a99152e2a62f6b84d6c144fc4adf9c406093b093046e90a681a76d93dd"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"crc",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"is-macro",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
|
@ -7963,7 +7981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a01bfcbbdea182bdda93713aeecd997749ae324686bf7944f54d128e56be4ea9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"swc_config_macro",
|
||||
|
@ -8111,7 +8129,7 @@ checksum = "6856da3da598f4da001b7e4ce225ee8970bc9d5cbaafcaf580190cf0a6031ec5"
|
|||
dependencies = [
|
||||
"better_scoped_tls",
|
||||
"bitflags 2.8.0",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"once_cell",
|
||||
"par-core",
|
||||
"phf",
|
||||
|
@ -8160,7 +8178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d2b14c8f6c1c82b7556d7534de44714401901fcb9b618f817172015565ce7d47"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"once_cell",
|
||||
"par-core",
|
||||
"petgraph 0.7.1",
|
||||
|
@ -8205,7 +8223,7 @@ checksum = "baae39c70229103a72090119887922fc5e32f934f5ca45c0423a5e65dac7e549"
|
|||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dashmap",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"once_cell",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
|
@ -8248,7 +8266,7 @@ version = "13.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ed837406d5dbbfbf5792b1dc90964245a0cf659753d4745fe177ffebe8598b9"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"par-core",
|
||||
|
@ -8848,7 +8866,7 @@ version = "0.22.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"toml_datetime",
|
||||
"winnow 0.7.1",
|
||||
]
|
||||
|
@ -9293,7 +9311,7 @@ checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6"
|
|||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"encoding_rs",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
|
@ -9568,7 +9586,7 @@ dependencies = [
|
|||
"bitflags 2.8.0",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"naga",
|
||||
"once_cell",
|
||||
|
@ -10231,7 +10249,7 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
|
|
@ -120,6 +120,7 @@ dprint-plugin-json.workspace = true
|
|||
dprint-plugin-jupyter.workspace = true
|
||||
dprint-plugin-markdown.workspace = true
|
||||
dprint-plugin-typescript.workspace = true
|
||||
esbuild_client = { version = "0.1.1" }
|
||||
fancy-regex.workspace = true
|
||||
faster-hex.workspace = true
|
||||
# If you disable the default __vendored_zlib_ng feature above, you _must_ be able to link against `-lz`.
|
||||
|
|
|
@ -464,12 +464,57 @@ pub struct CleanFlags {
|
|||
pub dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct BundleFlags {
|
||||
pub entrypoints: Vec<String>,
|
||||
pub output_path: Option<String>,
|
||||
pub output_dir: Option<String>,
|
||||
pub external: Vec<String>,
|
||||
pub format: BundleFormat,
|
||||
pub minify: bool,
|
||||
pub code_splitting: bool,
|
||||
pub one_file: bool,
|
||||
pub packages: PackageHandling,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
|
||||
pub enum BundleFormat {
|
||||
Esm,
|
||||
Cjs,
|
||||
Iife,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BundleFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BundleFormat::Esm => write!(f, "esm"),
|
||||
BundleFormat::Cjs => write!(f, "cjs"),
|
||||
BundleFormat::Iife => write!(f, "iife"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
|
||||
pub enum PackageHandling {
|
||||
Bundle,
|
||||
External,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PackageHandling {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PackageHandling::Bundle => write!(f, "bundle"),
|
||||
PackageHandling::External => write!(f, "external"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DenoSubcommand {
|
||||
Add(AddFlags),
|
||||
Remove(RemoveFlags),
|
||||
Bench(BenchFlags),
|
||||
Bundle,
|
||||
Bundle(BundleFlags),
|
||||
Cache(CacheFlags),
|
||||
Check(CheckFlags),
|
||||
Clean(CleanFlags),
|
||||
|
@ -1404,7 +1449,7 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
|
|||
"add" => add_parse(&mut flags, &mut m)?,
|
||||
"remove" => remove_parse(&mut flags, &mut m),
|
||||
"bench" => bench_parse(&mut flags, &mut m)?,
|
||||
"bundle" => bundle_parse(&mut flags, &mut m),
|
||||
"bundle" => bundle_parse(&mut flags, &mut m)?,
|
||||
"cache" => cache_parse(&mut flags, &mut m)?,
|
||||
"check" => check_parse(&mut flags, &mut m)?,
|
||||
"clean" => clean_parse(&mut flags, &mut m),
|
||||
|
@ -1866,10 +1911,105 @@ If you specify a directory instead of a file, the path is expanded to all contai
|
|||
}
|
||||
|
||||
fn bundle_subcommand() -> Command {
|
||||
command("bundle", "`deno bundle` was removed in Deno 2.
|
||||
fn format_parser(s: &str) -> Result<BundleFormat, clap::Error> {
|
||||
match s {
|
||||
"esm" => Ok(BundleFormat::Esm),
|
||||
"cjs" => Ok(BundleFormat::Cjs),
|
||||
"iife" => Ok(BundleFormat::Iife),
|
||||
_ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
|
||||
}
|
||||
}
|
||||
fn packages_parser(s: &str) -> Result<PackageHandling, clap::Error> {
|
||||
match s {
|
||||
"bundle" => Ok(PackageHandling::Bundle),
|
||||
"external" => Ok(PackageHandling::External),
|
||||
_ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
|
||||
}
|
||||
}
|
||||
command(
|
||||
"bundle",
|
||||
"Output a single JavaScript file with all dependencies.
|
||||
|
||||
See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", UnstableArgsConfig::ResolutionOnly)
|
||||
.hide(true)
|
||||
deno bundle https://deno.land/std/examples/colors.ts colors.bundle.js
|
||||
|
||||
If no output file is given, the output is written to standard output:
|
||||
|
||||
deno bundle https://deno.land/std/examples/colors.ts
|
||||
",
|
||||
UnstableArgsConfig::ResolutionOnly,
|
||||
)
|
||||
.defer(|cmd| {
|
||||
compile_args(cmd)
|
||||
.arg(check_arg(false))
|
||||
.arg(
|
||||
Arg::new("file")
|
||||
.num_args(1..)
|
||||
.required_unless_present("help")
|
||||
.value_hint(ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output")
|
||||
.long("output")
|
||||
.short('o')
|
||||
.help("Output path`")
|
||||
.num_args(1)
|
||||
.value_parser(value_parser!(String))
|
||||
.value_hint(ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("outdir")
|
||||
.long("outdir")
|
||||
.help("Output directory for bundled files")
|
||||
.num_args(1)
|
||||
.value_parser(value_parser!(String))
|
||||
.value_hint(ValueHint::DirPath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("external")
|
||||
.long("external")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(1)
|
||||
.value_parser(value_parser!(String)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("format")
|
||||
.long("format")
|
||||
.value_parser(clap::builder::ValueParser::new(format_parser))
|
||||
.default_value("esm"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("packages")
|
||||
.long("packages")
|
||||
.help("How to handle packages. Accepted values are 'bundle' or 'external'")
|
||||
.value_parser(clap::builder::ValueParser::new(packages_parser))
|
||||
.default_value("bundle"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("minify")
|
||||
.long("minify")
|
||||
.help("Minify the output")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("code-splitting")
|
||||
.long("code-splitting")
|
||||
.help("Enable code splitting")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("one-file")
|
||||
.long("one-file")
|
||||
.help("Bundle into one file")
|
||||
.require_equals(true)
|
||||
.default_value("true")
|
||||
.default_missing_value("true")
|
||||
.value_parser(value_parser!(bool))
|
||||
.num_args(0..=1)
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.arg(allow_scripts_arg())
|
||||
.arg(allow_import_arg())
|
||||
})
|
||||
}
|
||||
|
||||
fn cache_subcommand() -> Command {
|
||||
|
@ -4677,8 +4817,30 @@ fn bench_parse(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn bundle_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
|
||||
flags.subcommand = DenoSubcommand::Bundle;
|
||||
fn bundle_parse(
|
||||
flags: &mut Flags,
|
||||
matches: &mut ArgMatches,
|
||||
) -> clap::error::Result<()> {
|
||||
let file = matches.remove_many::<String>("file").unwrap();
|
||||
let output = matches.remove_one::<String>("output");
|
||||
let outdir = matches.remove_one::<String>("outdir");
|
||||
compile_args_without_check_parse(flags, matches)?;
|
||||
unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
|
||||
flags.subcommand = DenoSubcommand::Bundle(BundleFlags {
|
||||
entrypoints: file.collect(),
|
||||
output_path: output,
|
||||
output_dir: outdir,
|
||||
external: matches
|
||||
.remove_many::<String>("external")
|
||||
.map(|f| f.collect::<Vec<_>>())
|
||||
.unwrap_or_default(),
|
||||
format: matches.remove_one::<BundleFormat>("format").unwrap(),
|
||||
packages: matches.remove_one::<PackageHandling>("packages").unwrap(),
|
||||
minify: matches.get_flag("minify"),
|
||||
code_splitting: matches.get_flag("code-splitting"),
|
||||
one_file: matches.get_flag("one-file"),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cache_parse(
|
||||
|
|
|
@ -755,6 +755,12 @@ impl CliFactory {
|
|||
self.cjs_module_export_analyzer().await?;
|
||||
Ok(Arc::new(NodeCodeTranslator::new(
|
||||
module_export_analyzer.clone(),
|
||||
match self.cli_options()?.sub_command() {
|
||||
DenoSubcommand::Bundle(_) => {
|
||||
node_resolver::analyze::NodeCodeTranslatorMode::Bundling
|
||||
}
|
||||
_ => node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader,
|
||||
},
|
||||
)))
|
||||
}
|
||||
.boxed_local(),
|
||||
|
@ -1014,24 +1020,14 @@ impl CliFactory {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn create_cli_main_worker_factory_with_roots(
|
||||
pub async fn create_module_loader_factory(
|
||||
&self,
|
||||
roots: LibWorkerFactoryRoots,
|
||||
) -> Result<CliMainWorkerFactory, AnyError> {
|
||||
) -> Result<CliModuleLoaderFactory, AnyError> {
|
||||
let cli_options = self.cli_options()?;
|
||||
let fs = self.fs();
|
||||
let node_resolver = self.node_resolver().await?;
|
||||
let npm_resolver = self.npm_resolver().await?;
|
||||
let cli_npm_resolver = self.npm_resolver().await?.clone();
|
||||
let in_npm_pkg_checker = self.in_npm_pkg_checker()?;
|
||||
let maybe_file_watcher_communicator = if cli_options.has_hmr() {
|
||||
Some(self.watcher_communicator.clone().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let node_code_translator = self.node_code_translator().await?;
|
||||
let cjs_tracker = self.cjs_tracker()?.clone();
|
||||
let pkg_json_resolver = self.pkg_json_resolver()?;
|
||||
let npm_req_resolver = self.npm_req_resolver()?;
|
||||
let workspace_factory = self.workspace_factory()?;
|
||||
let npm_registry_permission_checker = {
|
||||
|
@ -1080,6 +1076,25 @@ impl CliFactory {
|
|||
maybe_eszip_loader,
|
||||
);
|
||||
|
||||
Ok(module_loader_factory)
|
||||
}
|
||||
|
||||
pub async fn create_cli_main_worker_factory_with_roots(
|
||||
&self,
|
||||
roots: LibWorkerFactoryRoots,
|
||||
) -> Result<CliMainWorkerFactory, AnyError> {
|
||||
let cli_options = self.cli_options()?;
|
||||
let fs = self.fs();
|
||||
let node_resolver = self.node_resolver().await?;
|
||||
let npm_resolver = self.npm_resolver().await?;
|
||||
let maybe_file_watcher_communicator = if cli_options.has_hmr() {
|
||||
Some(self.watcher_communicator.clone().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pkg_json_resolver = self.pkg_json_resolver()?;
|
||||
let module_loader_factory = self.create_module_loader_factory().await?;
|
||||
|
||||
let lib_main_worker_factory = LibMainWorkerFactory::new(
|
||||
self.blob_store().clone(),
|
||||
if cli_options.code_cache_enabled() {
|
||||
|
|
10
cli/main.rs
10
cli/main.rs
|
@ -127,7 +127,15 @@ async fn run_subcommand(
|
|||
tools::bench::run_benchmarks(flags, bench_flags).await
|
||||
}
|
||||
}),
|
||||
DenoSubcommand::Bundle => exit_with_message("⚠️ `deno bundle` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1),
|
||||
DenoSubcommand::Bundle(bundle_flags) => {
|
||||
spawn_subcommand(async {
|
||||
log::warn!(
|
||||
"⚠️ {} is experimental and subject to changes",
|
||||
colors::cyan("deno bundle")
|
||||
);
|
||||
tools::bundle::bundle(flags, bundle_flags).await
|
||||
})
|
||||
},
|
||||
DenoSubcommand::Deploy => {
|
||||
spawn_subcommand(async { tools::deploy::deploy(flags, roots).await })
|
||||
}
|
||||
|
|
|
@ -817,8 +817,10 @@ pub async fn run(
|
|||
pkg_json_resolver.clone(),
|
||||
sys.clone(),
|
||||
));
|
||||
let node_code_translator =
|
||||
Arc::new(NodeCodeTranslator::new(cjs_module_export_analyzer));
|
||||
let node_code_translator = Arc::new(NodeCodeTranslator::new(
|
||||
cjs_module_export_analyzer,
|
||||
node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader,
|
||||
));
|
||||
let workspace_resolver = {
|
||||
let import_map = match metadata.workspace_resolver.import_map {
|
||||
Some(import_map) => Some(
|
||||
|
|
118
cli/tools/bundle/esbuild.rs
Normal file
118
cli/tools/bundle/esbuild.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_core::anyhow;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_npm::npm_rc::ResolvedNpmRc;
|
||||
use deno_npm::registry::NpmRegistryApi;
|
||||
use deno_npm_cache::TarballCache;
|
||||
use deno_resolver::workspace::WorkspaceNpmPatchPackages;
|
||||
use deno_semver::package::PackageNv;
|
||||
|
||||
use crate::cache::DenoDir;
|
||||
use crate::npm::CliNpmCache;
|
||||
use crate::npm::CliNpmCacheHttpClient;
|
||||
use crate::npm::CliNpmRegistryInfoProvider;
|
||||
use crate::sys::CliSys;
|
||||
|
||||
pub const ESBUILD_VERSION: &str = "0.25.5";
|
||||
|
||||
fn esbuild_platform() -> &'static str {
|
||||
match (std::env::consts::ARCH, std::env::consts::OS) {
|
||||
("x86_64", "linux") => "linux-x64",
|
||||
("aarch64", "linux") => "linux-arm64",
|
||||
("x86_64", "macos" | "apple") => "darwin-x64",
|
||||
("aarch64", "macos" | "apple") => "darwin-arm64",
|
||||
("x86_64", "windows") => "win32-x64",
|
||||
("aarch64", "windows") => "win32-arm64",
|
||||
_ => panic!(
|
||||
"Unsupported platform: {} {}",
|
||||
std::env::consts::ARCH,
|
||||
std::env::consts::OS
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ensure_esbuild(
|
||||
deno_dir: &DenoDir,
|
||||
npmrc: &ResolvedNpmRc,
|
||||
npm_registry_info: &Arc<CliNpmRegistryInfoProvider>,
|
||||
workspace_patch_packages: &Arc<WorkspaceNpmPatchPackages>,
|
||||
tarball_cache: &Arc<TarballCache<CliNpmCacheHttpClient, CliSys>>,
|
||||
npm_cache: &CliNpmCache,
|
||||
) -> Result<PathBuf, AnyError> {
|
||||
let target = esbuild_platform();
|
||||
let mut esbuild_path = deno_dir
|
||||
.dl_folder_path()
|
||||
.join(format!("esbuild-{}", ESBUILD_VERSION))
|
||||
.join(format!("esbuild-{}", target));
|
||||
if cfg!(windows) {
|
||||
esbuild_path.set_extension("exe");
|
||||
}
|
||||
|
||||
if esbuild_path.exists() {
|
||||
return Ok(esbuild_path);
|
||||
}
|
||||
|
||||
let pkg_name = format!("@esbuild/{}", target);
|
||||
let nv =
|
||||
PackageNv::from_str(&format!("{}@{}", pkg_name, ESBUILD_VERSION)).unwrap();
|
||||
let api = npm_registry_info.as_npm_registry_api();
|
||||
let info = api.package_info(&pkg_name).await?;
|
||||
let version_info = info.version_info(&nv, &workspace_patch_packages.0)?;
|
||||
if let Some(dist) = &version_info.dist {
|
||||
let registry_url = npmrc.get_registry_url(&nv.name);
|
||||
let package_folder =
|
||||
npm_cache.package_folder_for_nv_and_url(&nv, registry_url);
|
||||
let existed = package_folder.exists();
|
||||
|
||||
if !existed {
|
||||
tarball_cache
|
||||
.ensure_package(&nv, dist)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to download esbuild package tarball {} from {}",
|
||||
nv, dist.tarball
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let path = if cfg!(windows) {
|
||||
package_folder.join("esbuild.exe")
|
||||
} else {
|
||||
package_folder.join("bin").join("esbuild")
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(esbuild_path.parent().unwrap()).with_context(
|
||||
|| {
|
||||
format!(
|
||||
"failed to create directory {}",
|
||||
esbuild_path.parent().unwrap().display()
|
||||
)
|
||||
},
|
||||
)?;
|
||||
std::fs::copy(&path, &esbuild_path).with_context(|| {
|
||||
format!(
|
||||
"failed to copy esbuild binary from {} to {}",
|
||||
path.display(),
|
||||
esbuild_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !existed {
|
||||
std::fs::remove_dir_all(&package_folder).with_context(|| {
|
||||
format!("failed to remove directory {}", package_folder.display())
|
||||
})?;
|
||||
}
|
||||
Ok(esbuild_path)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"could not get fetch esbuild binary; download it manually and copy it to {}",
|
||||
esbuild_path.display()
|
||||
);
|
||||
}
|
||||
}
|
703
cli/tools/bundle/mod.rs
Normal file
703
cli/tools/bundle/mod.rs
Normal file
|
@ -0,0 +1,703 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
mod esbuild;
|
||||
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_ast::ModuleSpecifier;
|
||||
use deno_config::deno_json::TsTypeLib;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_url_or_path;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::ModuleLoader;
|
||||
use deno_error::JsError;
|
||||
use deno_graph::Position;
|
||||
use deno_lib::worker::ModuleLoaderFactory;
|
||||
use deno_resolver::npm::managed::ResolvePkgFolderFromDenoModuleError;
|
||||
use deno_runtime::deno_permissions::PermissionsContainer;
|
||||
use deno_semver::npm::NpmPackageReqReference;
|
||||
use esbuild_client::protocol;
|
||||
use esbuild_client::EsbuildFlagsBuilder;
|
||||
use esbuild_client::EsbuildService;
|
||||
use indexmap::IndexMap;
|
||||
use node_resolver::errors::PackageSubpathResolveError;
|
||||
use node_resolver::NodeResolutionKind;
|
||||
use node_resolver::ResolutionMode;
|
||||
use regex::Regex;
|
||||
use sys_traits::EnvCurrentDir;
|
||||
|
||||
use crate::args::BundleFlags;
|
||||
use crate::args::BundleFormat;
|
||||
use crate::args::Flags;
|
||||
use crate::args::PackageHandling;
|
||||
use crate::factory::CliFactory;
|
||||
use crate::graph_container::MainModuleGraphContainer;
|
||||
use crate::graph_container::ModuleGraphContainer;
|
||||
use crate::graph_container::ModuleGraphUpdatePermit;
|
||||
use crate::module_loader::ModuleLoadPreparer;
|
||||
use crate::module_loader::PrepareModuleLoadOptions;
|
||||
use crate::node::CliNodeResolver;
|
||||
use crate::npm::CliNpmResolver;
|
||||
use crate::resolver::CliResolver;
|
||||
|
||||
/// Given a set of pattern indicating files to mark as external,
|
||||
/// return a regex that matches any of those patterns.
|
||||
///
|
||||
/// For instance given, `--external="*.node" --external="*.wasm"`, the regex will match
|
||||
/// any path that ends with `.node` or `.wasm`.
|
||||
pub fn externals_regex(external: &[String]) -> Regex {
|
||||
let mut regex_str = String::new();
|
||||
for (i, e) in external.iter().enumerate() {
|
||||
if i > 0 {
|
||||
regex_str.push('|');
|
||||
}
|
||||
regex_str.push_str("(^");
|
||||
if e.starts_with("/") {
|
||||
regex_str.push_str(".*");
|
||||
}
|
||||
regex_str.push_str(®ex::escape(e).replace("\\*", ".*"));
|
||||
regex_str.push(')');
|
||||
}
|
||||
regex::Regex::new(®ex_str).unwrap()
|
||||
}
|
||||
|
||||
pub async fn bundle(
|
||||
flags: Arc<Flags>,
|
||||
bundle_flags: BundleFlags,
|
||||
) -> Result<(), AnyError> {
|
||||
let factory = CliFactory::from_flags(flags);
|
||||
|
||||
let installer_factory = factory.npm_installer_factory()?;
|
||||
let npmrc = factory.npmrc()?;
|
||||
let deno_dir = factory.deno_dir()?;
|
||||
let resolver_factory = factory.resolver_factory()?;
|
||||
let workspace_factory = resolver_factory.workspace_factory();
|
||||
let npm_registry_info = installer_factory.registry_info_provider()?;
|
||||
let esbuild_path = esbuild::ensure_esbuild(
|
||||
deno_dir,
|
||||
npmrc,
|
||||
npm_registry_info,
|
||||
workspace_factory.workspace_npm_patch_packages()?,
|
||||
installer_factory.tarball_cache()?,
|
||||
factory.npm_cache()?,
|
||||
)
|
||||
.await?;
|
||||
let resolver = factory.resolver().await?.clone();
|
||||
let module_load_preparer = factory.module_load_preparer().await?.clone();
|
||||
let root_permissions = factory.root_permissions_container()?;
|
||||
let npm_resolver = factory.npm_resolver().await?.clone();
|
||||
let node_resolver = factory.node_resolver().await?.clone();
|
||||
let cli_options = factory.cli_options()?;
|
||||
let module_loader = factory
|
||||
.create_module_loader_factory()
|
||||
.await?
|
||||
.create_for_main(root_permissions.clone())
|
||||
.module_loader;
|
||||
let sys = factory.sys();
|
||||
let init_cwd = cli_options.initial_cwd().canonicalize()?;
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
let plugin_handler = Arc::new(DenoPluginHandler {
|
||||
resolver: resolver.clone(),
|
||||
module_load_preparer,
|
||||
module_graph_container: factory
|
||||
.main_module_graph_container()
|
||||
.await?
|
||||
.clone(),
|
||||
permissions: root_permissions.clone(),
|
||||
npm_resolver: npm_resolver.clone(),
|
||||
node_resolver: node_resolver.clone(),
|
||||
module_loader: module_loader.clone(),
|
||||
// TODO(nathanwhit): look at the external patterns to give diagnostics for probably incorrect patterns
|
||||
externals_regex: if bundle_flags.external.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(externals_regex(&bundle_flags.external))
|
||||
},
|
||||
});
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let entrypoint = bundle_flags
|
||||
.entrypoints
|
||||
.first()
|
||||
.iter()
|
||||
.map(|e| resolve_url_or_path(e, &init_cwd).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let resolved = {
|
||||
let mut resolved = vec![];
|
||||
let init_cwd_url = Url::from_directory_path(&init_cwd).unwrap();
|
||||
for e in &entrypoint {
|
||||
let r = resolver
|
||||
.resolve(
|
||||
e.as_str(),
|
||||
&init_cwd_url,
|
||||
Position::new(0, 0),
|
||||
ResolutionMode::Import,
|
||||
NodeResolutionKind::Execution,
|
||||
)
|
||||
.unwrap();
|
||||
resolved.push(r);
|
||||
}
|
||||
resolved
|
||||
};
|
||||
let _ = plugin_handler.prepare_module_load(&resolved).await;
|
||||
|
||||
let roots = resolved
|
||||
.into_iter()
|
||||
.map(|url| {
|
||||
if let Ok(v) = NpmPackageReqReference::from_specifier(&url) {
|
||||
let referrer =
|
||||
ModuleSpecifier::from_directory_path(sys.env_current_dir().unwrap())
|
||||
.unwrap();
|
||||
let package_folder = npm_resolver
|
||||
.resolve_pkg_folder_from_deno_module_req(v.req(), &referrer)
|
||||
.unwrap();
|
||||
let main_module = node_resolver
|
||||
.resolve_binary_export(&package_folder, v.sub_path())
|
||||
.unwrap();
|
||||
Url::from_file_path(&main_module).unwrap()
|
||||
} else {
|
||||
url
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let _ = plugin_handler.prepare_module_load(&roots).await;
|
||||
let esbuild = EsbuildService::new(
|
||||
esbuild_path,
|
||||
esbuild::ESBUILD_VERSION,
|
||||
plugin_handler.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let client = esbuild.client().clone();
|
||||
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
let res = esbuild.wait_for_exit().await;
|
||||
log::warn!("esbuild exited: {:?}", res);
|
||||
});
|
||||
}
|
||||
|
||||
let mut builder = EsbuildFlagsBuilder::default();
|
||||
builder
|
||||
.bundle(bundle_flags.one_file)
|
||||
.minify(bundle_flags.minify)
|
||||
.splitting(bundle_flags.code_splitting)
|
||||
.external(bundle_flags.external.clone())
|
||||
.tree_shaking(true)
|
||||
.format(match bundle_flags.format {
|
||||
BundleFormat::Esm => esbuild_client::Format::Esm,
|
||||
BundleFormat::Cjs => esbuild_client::Format::Cjs,
|
||||
BundleFormat::Iife => esbuild_client::Format::Iife,
|
||||
})
|
||||
.packages(match bundle_flags.packages {
|
||||
PackageHandling::External => esbuild_client::PackagesHandling::External,
|
||||
PackageHandling::Bundle => esbuild_client::PackagesHandling::Bundle,
|
||||
});
|
||||
if let Some(outdir) = bundle_flags.output_dir.clone() {
|
||||
builder.outdir(outdir);
|
||||
} else if let Some(output_path) = bundle_flags.output_path.clone() {
|
||||
builder.outfile(output_path);
|
||||
}
|
||||
let flags = builder.build().unwrap();
|
||||
|
||||
let entries = roots.into_iter().map(|e| ("".into(), e.into())).collect();
|
||||
|
||||
let response = client
|
||||
.send_build_request(protocol::BuildRequest {
|
||||
entries,
|
||||
key: 0,
|
||||
flags: flags.to_flags(),
|
||||
write: true,
|
||||
stdin_contents: None.into(),
|
||||
stdin_resolve_dir: None.into(),
|
||||
abs_working_dir: init_cwd.to_string_lossy().to_string(),
|
||||
context: false,
|
||||
mangle_cache: None,
|
||||
node_paths: vec![],
|
||||
plugins: Some(vec![protocol::BuildPlugin {
|
||||
name: "deno".into(),
|
||||
on_start: false,
|
||||
on_end: false,
|
||||
on_resolve: (vec![protocol::OnResolveSetupOptions {
|
||||
id: 0,
|
||||
filter: ".*".into(),
|
||||
namespace: "".into(),
|
||||
}]),
|
||||
on_load: vec![protocol::OnLoadSetupOptions {
|
||||
id: 0,
|
||||
filter: ".*".into(),
|
||||
namespace: "".into(),
|
||||
}],
|
||||
}]),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for error in &response.errors {
|
||||
log::error!(
|
||||
"{}: {}",
|
||||
deno_terminal::colors::red("bundler error"),
|
||||
format_message(error)
|
||||
);
|
||||
}
|
||||
|
||||
for warning in &response.warnings {
|
||||
log::warn!(
|
||||
"{}: {}",
|
||||
deno_terminal::colors::yellow("bundler warning"),
|
||||
format_message(warning)
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(stdout) = response.write_to_stdout {
|
||||
let stdout = replace_require_shim(&String::from_utf8_lossy(&stdout));
|
||||
#[allow(clippy::print_stdout)]
|
||||
{
|
||||
println!("{}", stdout);
|
||||
}
|
||||
} else if response.errors.is_empty() {
|
||||
if bundle_flags.output_dir.is_none()
|
||||
&& std::env::var("NO_DENO_BUNDLE_HACK").is_err()
|
||||
&& bundle_flags.output_path.is_some()
|
||||
{
|
||||
let out = bundle_flags.output_path.as_ref().unwrap();
|
||||
let contents = std::fs::read_to_string(out).unwrap();
|
||||
let contents = replace_require_shim(&contents);
|
||||
std::fs::write(out, contents).unwrap();
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"{}",
|
||||
deno_terminal::colors::green(format!(
|
||||
"bundled in {}",
|
||||
crate::display::human_elapsed(start.elapsed().as_millis()),
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if !response.errors.is_empty() {
|
||||
deno_core::anyhow::bail!("bundling failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO(nathanwhit): MASSIVE HACK
|
||||
// See tests::specs::bundle::requires_node_builtin for why this is needed.
|
||||
// Without this hack, that test would fail with "Dynamic require of "util" is not supported"
|
||||
fn replace_require_shim(contents: &str) -> String {
|
||||
contents.replace(
|
||||
r#"var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
||||
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
||||
}) : x)(function(x) {
|
||||
if (typeof require !== "undefined") return require.apply(this, arguments);
|
||||
throw Error('Dynamic require of "' + x + '" is not supported');
|
||||
});"#,
|
||||
r#"import { createRequire } from "node:module";
|
||||
var __require = createRequire(import.meta.url);
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn format_message(message: &esbuild_client::protocol::Message) -> String {
|
||||
format!(
|
||||
"{}{}",
|
||||
message.text,
|
||||
if let Some(location) = &message.location {
|
||||
format!(
|
||||
"\n at {} {}:{}",
|
||||
location.file, location.line, location.column
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)
|
||||
}
|
||||
#[derive(Debug, thiserror::Error, JsError)]
|
||||
#[class(generic)]
|
||||
enum BundleError {
|
||||
#[error(transparent)]
|
||||
Resolver(#[from] deno_resolver::graph::ResolveWithGraphError),
|
||||
#[error(transparent)]
|
||||
Url(#[from] deno_core::url::ParseError),
|
||||
#[error(transparent)]
|
||||
ResolveNpmPkg(#[from] ResolvePkgFolderFromDenoModuleError),
|
||||
#[error(transparent)]
|
||||
SubpathResolve(#[from] PackageSubpathResolveError),
|
||||
#[error(transparent)]
|
||||
PathToUrlError(#[from] deno_path_util::PathToUrlError),
|
||||
#[error(transparent)]
|
||||
UrlToPathError(#[from] deno_path_util::UrlToFilePathError),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
ResolveUrlOrPathError(#[from] deno_path_util::ResolveUrlOrPathError),
|
||||
#[error(transparent)]
|
||||
PrepareModuleLoad(#[from] crate::module_loader::PrepareModuleLoadError),
|
||||
#[error(transparent)]
|
||||
ResolveReqWithSubPath(#[from] deno_resolver::npm::ResolveReqWithSubPathError),
|
||||
#[error(transparent)]
|
||||
PackageReqReferenceParse(
|
||||
#[from] deno_semver::package::PackageReqReferenceParseError,
|
||||
),
|
||||
#[allow(dead_code)]
|
||||
#[error("Http cache error")]
|
||||
HttpCache,
|
||||
}
|
||||
|
||||
struct DenoPluginHandler {
|
||||
resolver: Arc<CliResolver>,
|
||||
module_load_preparer: Arc<ModuleLoadPreparer>,
|
||||
module_graph_container: Arc<MainModuleGraphContainer>,
|
||||
permissions: PermissionsContainer,
|
||||
npm_resolver: CliNpmResolver,
|
||||
node_resolver: Arc<CliNodeResolver>,
|
||||
module_loader: Rc<dyn ModuleLoader>,
|
||||
externals_regex: Option<Regex>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl esbuild_client::PluginHandler for DenoPluginHandler {
|
||||
async fn on_resolve(
|
||||
&self,
|
||||
args: esbuild_client::OnResolveArgs,
|
||||
) -> Result<Option<esbuild_client::OnResolveResult>, AnyError> {
|
||||
log::debug!("{}: {args:?}", deno_terminal::colors::cyan("on_resolve"));
|
||||
if let Some(reg) = &self.externals_regex {
|
||||
if reg.is_match(&args.path) {
|
||||
return Ok(Some(esbuild_client::OnResolveResult {
|
||||
external: Some(true),
|
||||
path: Some(args.path),
|
||||
plugin_name: Some("deno".to_string()),
|
||||
plugin_data: None,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
}
|
||||
let result = self.bundle_resolve(
|
||||
&args.path,
|
||||
args.importer.as_deref(),
|
||||
args.resolve_dir.as_deref(),
|
||||
args.kind,
|
||||
args.with,
|
||||
)?;
|
||||
|
||||
Ok(result.map(|r| {
|
||||
esbuild_client::OnResolveResult {
|
||||
namespace: if r.starts_with("jsr:")
|
||||
|| r.starts_with("https:")
|
||||
|| r.starts_with("http:")
|
||||
|| r.starts_with("data:")
|
||||
{
|
||||
Some("deno".into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
external: Some(
|
||||
r.starts_with("node:")
|
||||
|| self
|
||||
.externals_regex
|
||||
.as_ref()
|
||||
.map(|reg| reg.is_match(&r))
|
||||
.unwrap_or(false),
|
||||
),
|
||||
path: Some(r),
|
||||
plugin_name: Some("deno".to_string()),
|
||||
plugin_data: None,
|
||||
..Default::default()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn on_load(
|
||||
&self,
|
||||
args: esbuild_client::OnLoadArgs,
|
||||
) -> Result<Option<esbuild_client::OnLoadResult>, AnyError> {
|
||||
let result = self.bundle_load(&args.path, "").await?;
|
||||
log::trace!(
|
||||
"{}: {:?}",
|
||||
deno_terminal::colors::magenta("on_load"),
|
||||
result.as_ref().map(|(code, loader)| format!(
|
||||
"{}: {:?}",
|
||||
String::from_utf8_lossy(code),
|
||||
loader
|
||||
))
|
||||
);
|
||||
if let Some((code, loader)) = result {
|
||||
Ok(Some(esbuild_client::OnLoadResult {
|
||||
contents: Some(code),
|
||||
loader: Some(loader),
|
||||
..Default::default()
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_start(
|
||||
&self,
|
||||
_args: esbuild_client::OnStartArgs,
|
||||
) -> Result<Option<esbuild_client::OnStartResult>, AnyError> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn import_kind_to_resolution_mode(
|
||||
kind: esbuild_client::protocol::ImportKind,
|
||||
) -> ResolutionMode {
|
||||
match kind {
|
||||
protocol::ImportKind::EntryPoint
|
||||
| protocol::ImportKind::ImportStatement
|
||||
| protocol::ImportKind::ComposesFrom
|
||||
| protocol::ImportKind::DynamicImport
|
||||
| protocol::ImportKind::ImportRule
|
||||
| protocol::ImportKind::UrlToken => ResolutionMode::Import,
|
||||
protocol::ImportKind::RequireCall
|
||||
| protocol::ImportKind::RequireResolve => ResolutionMode::Require,
|
||||
}
|
||||
}
|
||||
|
||||
impl DenoPluginHandler {
|
||||
fn bundle_resolve(
|
||||
&self,
|
||||
path: &str,
|
||||
importer: Option<&str>,
|
||||
resolve_dir: Option<&str>,
|
||||
kind: esbuild_client::protocol::ImportKind,
|
||||
// TODO: use this / store it for later usage when loading
|
||||
with: IndexMap<String, String>,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
log::debug!(
|
||||
"bundle_resolve: {:?} {:?} {:?} {:?} {:?}",
|
||||
path,
|
||||
importer,
|
||||
resolve_dir,
|
||||
kind,
|
||||
with
|
||||
);
|
||||
let mut resolve_dir = resolve_dir.unwrap_or("").to_string();
|
||||
let resolver = self.resolver.clone();
|
||||
if !resolve_dir.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
resolve_dir.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
let resolve_dir_path = Path::new(&resolve_dir);
|
||||
let mut referrer =
|
||||
resolve_url_or_path(importer.unwrap_or(""), resolve_dir_path)
|
||||
.unwrap_or_else(|_| {
|
||||
Url::from_directory_path(std::env::current_dir().unwrap()).unwrap()
|
||||
});
|
||||
if referrer.scheme() == "file" {
|
||||
let pth = referrer.to_file_path().unwrap();
|
||||
if (pth.is_dir()) && !pth.ends_with(std::path::MAIN_SEPARATOR_STR) {
|
||||
referrer.set_path(&format!(
|
||||
"{}{}",
|
||||
referrer.path(),
|
||||
std::path::MAIN_SEPARATOR
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"{}: {} {} {} {:?}",
|
||||
deno_terminal::colors::magenta("op_bundle_resolve"),
|
||||
path,
|
||||
resolve_dir,
|
||||
referrer,
|
||||
import_kind_to_resolution_mode(kind)
|
||||
);
|
||||
|
||||
let graph = self.module_graph_container.graph();
|
||||
let result = resolver.resolve_with_graph(
|
||||
&graph,
|
||||
path,
|
||||
&referrer,
|
||||
Position::new(0, 0),
|
||||
import_kind_to_resolution_mode(kind),
|
||||
NodeResolutionKind::Execution,
|
||||
);
|
||||
|
||||
log::debug!(
|
||||
"{}: {:?}",
|
||||
deno_terminal::colors::cyan("op_bundle_resolve result"),
|
||||
result
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(specifier) => Ok(Some(file_path_or_url(&specifier)?)),
|
||||
Err(e) => {
|
||||
log::debug!("{}: {:?}", deno_terminal::colors::red("error"), e);
|
||||
Err(BundleError::Resolver(e).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare_module_load(
|
||||
&self,
|
||||
specifiers: &[ModuleSpecifier],
|
||||
) -> Result<(), AnyError> {
|
||||
let mut graph_permit =
|
||||
self.module_graph_container.acquire_update_permit().await;
|
||||
let graph: &mut deno_graph::ModuleGraph = graph_permit.graph_mut();
|
||||
self
|
||||
.module_load_preparer
|
||||
.prepare_module_load(
|
||||
graph,
|
||||
specifiers,
|
||||
PrepareModuleLoadOptions {
|
||||
is_dynamic: false,
|
||||
lib: TsTypeLib::default(),
|
||||
permissions: self.permissions.clone(),
|
||||
ext_overwrite: None,
|
||||
allow_unknown_media_types: true,
|
||||
skip_graph_roots_validation: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
graph_permit.commit();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn bundle_load(
|
||||
&self,
|
||||
specifier: &str,
|
||||
resolve_dir: &str,
|
||||
) -> Result<Option<(Vec<u8>, esbuild_client::BuiltinLoader)>, AnyError> {
|
||||
log::debug!(
|
||||
"{}: {:?} {:?}",
|
||||
deno_terminal::colors::magenta("bundle_load"),
|
||||
specifier,
|
||||
resolve_dir
|
||||
);
|
||||
|
||||
let resolve_dir = Path::new(&resolve_dir);
|
||||
let specifier = deno_core::resolve_url_or_path(specifier, resolve_dir)?;
|
||||
|
||||
let (specifier, loader) = if let Some((specifier, loader)) =
|
||||
self.specifier_and_type_from_graph(&specifier)?
|
||||
{
|
||||
(specifier, loader)
|
||||
} else {
|
||||
log::debug!(
|
||||
"{}: no specifier and type from graph for {}",
|
||||
deno_terminal::colors::yellow("warn"),
|
||||
specifier
|
||||
);
|
||||
|
||||
if specifier.scheme() == "data" {
|
||||
return Ok(Some((
|
||||
specifier.to_string().as_bytes().to_vec(),
|
||||
esbuild_client::BuiltinLoader::DataUrl,
|
||||
)));
|
||||
}
|
||||
|
||||
let (media_type, _) =
|
||||
deno_media_type::resolve_media_type_and_charset_from_content_type(
|
||||
&specifier, None,
|
||||
);
|
||||
if media_type == deno_media_type::MediaType::Unknown {
|
||||
return Ok(None);
|
||||
}
|
||||
(specifier, media_type_to_loader(media_type))
|
||||
};
|
||||
let loaded = self.module_loader.load(
|
||||
&specifier,
|
||||
None,
|
||||
false,
|
||||
deno_core::RequestedModuleType::None,
|
||||
);
|
||||
|
||||
match loaded {
|
||||
deno_core::ModuleLoadResponse::Sync(module_source) => {
|
||||
Ok(Some((module_source?.code.as_bytes().to_vec(), loader)))
|
||||
}
|
||||
deno_core::ModuleLoadResponse::Async(pin) => {
|
||||
let pin = pin.await?;
|
||||
Ok(Some((pin.code.as_bytes().to_vec(), loader)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn specifier_and_type_from_graph(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Result<Option<(ModuleSpecifier, esbuild_client::BuiltinLoader)>, AnyError>
|
||||
{
|
||||
let graph = self.module_graph_container.graph();
|
||||
let Some(module) = graph.get(specifier) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (specifier, loader) = match module {
|
||||
deno_graph::Module::Js(js_module) => (
|
||||
js_module.specifier.clone(),
|
||||
media_type_to_loader(js_module.media_type),
|
||||
),
|
||||
deno_graph::Module::Json(json_module) => (
|
||||
json_module.specifier.clone(),
|
||||
esbuild_client::BuiltinLoader::Json,
|
||||
),
|
||||
deno_graph::Module::Wasm(_) => todo!(),
|
||||
deno_graph::Module::Npm(module) => {
|
||||
let package_folder = self
|
||||
.npm_resolver
|
||||
.as_managed()
|
||||
.unwrap() // byonm won't create a Module::Npm
|
||||
.resolve_pkg_folder_from_deno_module(module.nv_reference.nv())?;
|
||||
let path = self
|
||||
.node_resolver
|
||||
.resolve_package_subpath_from_deno_module(
|
||||
&package_folder,
|
||||
module.nv_reference.sub_path(),
|
||||
None,
|
||||
ResolutionMode::Import,
|
||||
NodeResolutionKind::Execution,
|
||||
)?;
|
||||
let url = path.clone().into_url()?;
|
||||
let (media_type, _charset) =
|
||||
deno_media_type::resolve_media_type_and_charset_from_content_type(
|
||||
&url, None,
|
||||
);
|
||||
(url, media_type_to_loader(media_type))
|
||||
}
|
||||
deno_graph::Module::Node(_) => {
|
||||
return Ok(None);
|
||||
}
|
||||
deno_graph::Module::External(_) => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Ok(Some((specifier, loader)))
|
||||
}
|
||||
}
|
||||
|
||||
fn file_path_or_url(url: &Url) -> Result<String, AnyError> {
|
||||
if url.scheme() == "file" {
|
||||
Ok(
|
||||
deno_path_util::url_to_file_path(url)?
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Ok(url.to_string())
|
||||
}
|
||||
}
|
||||
fn media_type_to_loader(
|
||||
media_type: deno_media_type::MediaType,
|
||||
) -> esbuild_client::BuiltinLoader {
|
||||
use deno_ast::MediaType::*;
|
||||
match media_type {
|
||||
JavaScript | Cjs | Mjs | Mts => esbuild_client::BuiltinLoader::Js,
|
||||
TypeScript | Cts | Dts | Dmts | Dcts => esbuild_client::BuiltinLoader::Ts,
|
||||
Jsx | Tsx => esbuild_client::BuiltinLoader::Jsx,
|
||||
Css => esbuild_client::BuiltinLoader::Css,
|
||||
Json => esbuild_client::BuiltinLoader::Json,
|
||||
SourceMap => esbuild_client::BuiltinLoader::Text,
|
||||
Html => esbuild_client::BuiltinLoader::Text,
|
||||
Sql => esbuild_client::BuiltinLoader::Text,
|
||||
Wasm => esbuild_client::BuiltinLoader::Binary,
|
||||
Unknown => esbuild_client::BuiltinLoader::Binary,
|
||||
// _ => esbuild_client::BuiltinLoader::External,
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
pub mod bench;
|
||||
pub mod bundle;
|
||||
pub mod check;
|
||||
pub mod clean;
|
||||
pub mod compile;
|
||||
|
|
|
@ -261,6 +261,14 @@ impl<
|
|||
if referrer.scheme() == "file"
|
||||
&& self.in_npm_pkg_checker.in_npm_package(referrer)
|
||||
{
|
||||
log::debug!(
|
||||
"{}: specifier={} referrer={} mode={:?} kind={:?}",
|
||||
deno_terminal::colors::magenta("resolving in npm package"),
|
||||
raw_specifier,
|
||||
referrer,
|
||||
resolution_mode,
|
||||
resolution_kind
|
||||
);
|
||||
return node_resolver
|
||||
.resolve(raw_specifier, referrer, resolution_mode, resolution_kind)
|
||||
.and_then(|res| {
|
||||
|
|
|
@ -528,6 +528,13 @@ pub struct NodeCodeTranslator<
|
|||
TNpmPackageFolderResolver,
|
||||
TSys,
|
||||
>,
|
||||
mode: NodeCodeTranslatorMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum NodeCodeTranslatorMode {
|
||||
Bundling,
|
||||
ModuleLoader,
|
||||
}
|
||||
|
||||
impl<
|
||||
|
@ -553,9 +560,11 @@ impl<
|
|||
TNpmPackageFolderResolver,
|
||||
TSys,
|
||||
>,
|
||||
mode: NodeCodeTranslatorMode,
|
||||
) -> Self {
|
||||
Self {
|
||||
module_export_analyzer,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,32 +579,37 @@ impl<
|
|||
entry_specifier: &Url,
|
||||
source: Option<Cow<'a, str>>,
|
||||
) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
|
||||
let analysis = self
|
||||
.module_export_analyzer
|
||||
.analyze_all_exports(entry_specifier, source)
|
||||
.await?;
|
||||
let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Bundling) {
|
||||
// let the bundler handle it instead of the module loader
|
||||
return Ok(source.unwrap());
|
||||
} else {
|
||||
let analysis = self
|
||||
.module_export_analyzer
|
||||
.analyze_all_exports(entry_specifier, source)
|
||||
.await?;
|
||||
|
||||
let all_exports = match analysis {
|
||||
ResolvedCjsAnalysis::Esm(source) => return Ok(source),
|
||||
ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
|
||||
match analysis {
|
||||
ResolvedCjsAnalysis::Esm(source) => return Ok(source),
|
||||
ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
|
||||
}
|
||||
};
|
||||
|
||||
// todo(dsherret): use capacity_builder here to remove all these heap
|
||||
// allocations and make the string writing faster
|
||||
let mut temp_var_count = 0;
|
||||
let mut source = vec![
|
||||
r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
||||
const require = __internalCreateRequire(import.meta.url);"#
|
||||
.to_string(),
|
||||
];
|
||||
r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
||||
const require = __internalCreateRequire(import.meta.url);"#
|
||||
.to_string(),
|
||||
];
|
||||
|
||||
source.push(format!(
|
||||
r#"let mod;
|
||||
if (import.meta.main) {{
|
||||
mod = __internalModule._load("{0}", null, true)
|
||||
}} else {{
|
||||
mod = require("{0}");
|
||||
}}"#,
|
||||
if (import.meta.main) {{
|
||||
mod = __internalModule._load("{0}", null, true)
|
||||
}} else {{
|
||||
mod = require("{0}");
|
||||
}}"#,
|
||||
url_to_file_path(entry_specifier)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
|
|
|
@ -215,5 +215,5 @@ pub static FEATURE_DESCRIPTIONS: &[UnstableFeatureDescription] = &[
|
|||
kind: UnstableFeatureKind::Runtime,
|
||||
config_option: ConfigFileOption::SameAsFlagName,
|
||||
env_var: None,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
|
113
tests/specs/bundle/main/__test__.jsonc
Normal file
113
tests/specs/bundle/main/__test__.jsonc
Normal file
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"tests": {
|
||||
"npm_specifier": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "i -e main.ts",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "run -A main.ts",
|
||||
"output": "Hello, world!\n"
|
||||
},
|
||||
{
|
||||
"args": "bundle --output=out.js main.ts",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": "clean",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "Hello, world!\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"npm_specifier_with_import_map": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "i npm:chalk",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "run -A main2.ts",
|
||||
"output": "Hello, world!\n"
|
||||
},
|
||||
{
|
||||
"args": "bundle --output=out.js main2.ts",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": "clean",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "Hello, world!\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jsr_specifier": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "i -e main_jsr.ts",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "bundle --output=out.js main_jsr.ts",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": "clean",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "2\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"requires_node_builtin": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "bundle --output=out.js uses_node_builtin.cjs",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "{ a: 1, b: 'hello' }\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"json_import": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "bundle --output=out.js imports_json.ts",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": ["eval", "console.log(Deno.readTextFileSync('./out.js'))"],
|
||||
"output": "imports_json.out"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "{ hi: \"bye\", thing: { other: \"thing\" } }\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sloppy_imports": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "bundle --unstable-sloppy-imports --output=out.js sloppy.ts",
|
||||
"output": "[WILDCARD]\nbundled in [WILDCARD]s\n"
|
||||
},
|
||||
{
|
||||
"args": "run --no-lock --cached-only --no-config -A out.js",
|
||||
"output": "{ hi: \"bye\", thing: { other: \"thing\" } }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
2
tests/specs/bundle/main/deno.jsonc
Normal file
2
tests/specs/bundle/main/deno.jsonc
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
6
tests/specs/bundle/main/foo.json
Normal file
6
tests/specs/bundle/main/foo.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"hi": "bye",
|
||||
"thing": {
|
||||
"other": "thing"
|
||||
}
|
||||
}
|
11
tests/specs/bundle/main/imports_json.out
Normal file
11
tests/specs/bundle/main/imports_json.out
Normal file
|
@ -0,0 +1,11 @@
|
|||
// [WILDCARD]foo.json
|
||||
var foo_default = {
|
||||
hi: "bye",
|
||||
thing: {
|
||||
other: "thing"
|
||||
}
|
||||
};
|
||||
|
||||
// [WILDCARD]imports_json.ts
|
||||
console.log(foo_default);
|
||||
|
3
tests/specs/bundle/main/imports_json.ts
Normal file
3
tests/specs/bundle/main/imports_json.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import foo from "./foo.json" with { type: "json" };
|
||||
|
||||
console.log(foo);
|
2
tests/specs/bundle/main/main.ts
Normal file
2
tests/specs/bundle/main/main.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
import chalk from "npm:chalk";
|
||||
console.log(chalk.green("Hello, world!"));
|
3
tests/specs/bundle/main/main2.ts
Normal file
3
tests/specs/bundle/main/main2.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import chalk from "chalk";
|
||||
|
||||
console.log(chalk.green("Hello, world!"));
|
3
tests/specs/bundle/main/main_jsr.ts
Normal file
3
tests/specs/bundle/main/main_jsr.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { subtract } from "jsr:@denotest/subtract";
|
||||
|
||||
console.log(subtract(3, 1));
|
1
tests/specs/bundle/main/sloppy.ts
Normal file
1
tests/specs/bundle/main/sloppy.ts
Normal file
|
@ -0,0 +1 @@
|
|||
import "./imports_json.js";
|
6
tests/specs/bundle/main/uses_node_builtin.cjs
Normal file
6
tests/specs/bundle/main/uses_node_builtin.cjs
Normal file
|
@ -0,0 +1,6 @@
|
|||
const { inspect } = require("util");
|
||||
|
||||
console.log(inspect({
|
||||
a: 1,
|
||||
b: "hello",
|
||||
}));
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"steps": [
|
||||
{
|
||||
"args": "bundle",
|
||||
"output": "bundle.out",
|
||||
"exitCode": 1
|
||||
},
|
||||
{
|
||||
"args": "bundle --help",
|
||||
"output": "bundle_help.out"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
error: ⚠️ `deno bundle` was removed in Deno 2.
|
||||
|
||||
See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations
|
|
@ -1,11 +0,0 @@
|
|||
`deno bundle` was removed in Deno 2.
|
||||
|
||||
See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations
|
||||
|
||||
Usage: deno bundle [OPTIONS]
|
||||
|
||||
Options:
|
||||
-h, --help[=<CONTEXT>] [possible values: unstable, full]
|
||||
-q, --quiet Suppress diagnostic output
|
||||
--unstable The `--unstable` flag has been deprecated. Use granular `--unstable-*` flags instead
|
||||
To view the list of individual unstable feature flags, run this command again with --help=unstable
|
|
@ -14,12 +14,14 @@ use once_cell::sync::Lazy;
|
|||
use parking_lot::Mutex;
|
||||
use tar::Builder;
|
||||
|
||||
use crate::root_path;
|
||||
use crate::tests_path;
|
||||
use crate::PathRef;
|
||||
|
||||
pub const DENOTEST_SCOPE_NAME: &str = "@denotest";
|
||||
pub const DENOTEST2_SCOPE_NAME: &str = "@denotest2";
|
||||
pub const DENOTEST3_SCOPE_NAME: &str = "@denotest3";
|
||||
pub const ESBUILD_VERSION: &str = "0.25.5";
|
||||
|
||||
pub static PUBLIC_TEST_NPM_REGISTRY: Lazy<TestNpmRegistry> = Lazy::new(|| {
|
||||
TestNpmRegistry::new(
|
||||
|
@ -197,6 +199,15 @@ impl TestNpmRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
let prefix1 = format!("/{}/", "@esbuild");
|
||||
let prefix2 = format!("/{}%2f", "@esbuild");
|
||||
let maybe_package_name_with_path = uri_path
|
||||
.strip_prefix(&prefix1)
|
||||
.or_else(|| uri_path.strip_prefix(&prefix2));
|
||||
if let Some(package_name_with_path) = maybe_package_name_with_path {
|
||||
return Some(("@esbuild", package_name_with_path));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -255,11 +266,210 @@ fn append_dir_all<W: std::io::Write>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn create_package_version_info(
|
||||
version_folder: &PathRef,
|
||||
version: &str,
|
||||
package_name: &str,
|
||||
registry_hostname: &str,
|
||||
) -> Result<(Vec<u8>, serde_json::Map<String, serde_json::Value>)> {
|
||||
let tarball_bytes = create_tarball_from_dir(version_folder.as_path())?;
|
||||
|
||||
let mut dist = serde_json::Map::new();
|
||||
if package_name != "@denotest/no-shasums" {
|
||||
let tarball_checksum = get_tarball_checksum(&tarball_bytes);
|
||||
dist.insert(
|
||||
"integrity".to_string(),
|
||||
format!("sha512-{tarball_checksum}").into(),
|
||||
);
|
||||
dist.insert("shasum".to_string(), "dummy-value".into());
|
||||
}
|
||||
dist.insert(
|
||||
"tarball".to_string(),
|
||||
format!("{registry_hostname}/{package_name}/{version}.tgz").into(),
|
||||
);
|
||||
|
||||
let package_json_path = version_folder.join("package.json");
|
||||
let package_json_bytes = fs::read(&package_json_path).with_context(|| {
|
||||
format!("Error reading package.json at {}", package_json_path)
|
||||
})?;
|
||||
let package_json_text = String::from_utf8_lossy(&package_json_bytes);
|
||||
let mut version_info: serde_json::Map<String, serde_json::Value> =
|
||||
serde_json::from_str(&package_json_text)?;
|
||||
version_info.insert("dist".to_string(), dist.into());
|
||||
|
||||
Ok((tarball_bytes, version_info))
|
||||
}
|
||||
|
||||
fn get_esbuild_platform_info(
|
||||
platform_name: &str,
|
||||
) -> Option<(&'static str, &'static str, bool)> {
|
||||
match platform_name {
|
||||
"linux-x64" => Some(("esbuild-x64", "linux64", false)),
|
||||
"linux-arm64" => Some(("esbuild-aarch64", "linux64", false)),
|
||||
"darwin-x64" => Some(("esbuild-x64", "mac", false)),
|
||||
"darwin-arm64" => Some(("esbuild-aarch64", "mac", false)),
|
||||
"win32-x64" => Some(("esbuild-x64.exe", "win", true)),
|
||||
"win32-arm64" => Some(("esbuild-arm64.exe", "win", true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_esbuild_binary(
|
||||
package_dir: &Path,
|
||||
esbuild_prebuilt: &Path,
|
||||
is_windows: bool,
|
||||
) -> Result<&'static str> {
|
||||
let binary_name = if is_windows { "esbuild.exe" } else { "esbuild" };
|
||||
|
||||
if is_windows {
|
||||
std::fs::copy(esbuild_prebuilt, package_dir.join(binary_name))?;
|
||||
Ok(binary_name)
|
||||
} else {
|
||||
let bin_dir = package_dir.join("bin");
|
||||
std::fs::create_dir_all(&bin_dir)?;
|
||||
let binary_path = bin_dir.join(binary_name);
|
||||
std::fs::copy(esbuild_prebuilt, &binary_path)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = std::fs::metadata(&binary_path)?.permissions();
|
||||
perms.set_mode(0o755); // rwxr-xr-x
|
||||
std::fs::set_permissions(&binary_path, perms)?;
|
||||
}
|
||||
|
||||
Ok("bin/esbuild")
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tarball_from_dir(package_dir: &Path) -> Result<Vec<u8>> {
|
||||
let mut tarball_bytes = Vec::new();
|
||||
{
|
||||
let mut encoder =
|
||||
GzEncoder::new(&mut tarball_bytes, Compression::default());
|
||||
{
|
||||
let mut builder = Builder::new(&mut encoder);
|
||||
append_dir_all(&mut builder, Path::new("package"), package_dir)?;
|
||||
builder.finish()?;
|
||||
}
|
||||
encoder.finish()?;
|
||||
}
|
||||
Ok(tarball_bytes)
|
||||
}
|
||||
|
||||
fn create_npm_registry_response(
|
||||
package_name: &str,
|
||||
version: &str,
|
||||
description: &str,
|
||||
bin_path: &str,
|
||||
tarball_bytes: Vec<u8>,
|
||||
registry_hostname: &str,
|
||||
) -> Result<CustomNpmPackage> {
|
||||
let tarball_checksum = get_tarball_checksum(&tarball_bytes);
|
||||
let mut dist = serde_json::Map::new();
|
||||
dist.insert(
|
||||
"integrity".to_string(),
|
||||
format!("sha512-{tarball_checksum}").into(),
|
||||
);
|
||||
dist.insert("shasum".to_string(), "dummy-value".into());
|
||||
dist.insert(
|
||||
"tarball".to_string(),
|
||||
format!("{registry_hostname}/{package_name}/{version}.tgz").into(),
|
||||
);
|
||||
|
||||
let mut version_info = serde_json::Map::new();
|
||||
version_info.insert("name".to_string(), package_name.into());
|
||||
version_info.insert("version".to_string(), version.into());
|
||||
version_info.insert("description".to_string(), description.into());
|
||||
version_info.insert("bin".to_string(), bin_path.into());
|
||||
version_info.insert("dist".to_string(), dist.into());
|
||||
|
||||
let mut versions = serde_json::Map::new();
|
||||
versions.insert(version.to_string(), version_info.into());
|
||||
|
||||
let mut dist_tags = serde_json::Map::new();
|
||||
dist_tags.insert("latest".to_string(), version.into());
|
||||
|
||||
let mut registry_file = serde_json::Map::new();
|
||||
registry_file.insert("name".to_string(), package_name.into());
|
||||
registry_file.insert("versions".to_string(), versions.into());
|
||||
registry_file.insert("dist-tags".to_string(), dist_tags.into());
|
||||
|
||||
let mut tarballs = HashMap::new();
|
||||
tarballs.insert(version.to_string(), tarball_bytes);
|
||||
|
||||
Ok(CustomNpmPackage {
|
||||
registry_file: serde_json::to_string(®istry_file)?,
|
||||
tarballs,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_esbuild_package(
|
||||
registry_hostname: &str,
|
||||
package_name: &str,
|
||||
) -> Result<Option<CustomNpmPackage>> {
|
||||
let platform_name = package_name.strip_prefix("@esbuild/").unwrap();
|
||||
|
||||
let (bin_name, folder, is_windows) =
|
||||
match get_esbuild_platform_info(platform_name) {
|
||||
Some(info) => info,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let esbuild_prebuilt = root_path()
|
||||
.join("third_party/prebuilt")
|
||||
.join(folder)
|
||||
.join(bin_name);
|
||||
|
||||
if !esbuild_prebuilt.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let package_dir = temp_dir.path().join("package");
|
||||
std::fs::create_dir_all(&package_dir)?;
|
||||
|
||||
let bin_path =
|
||||
setup_esbuild_binary(&package_dir, esbuild_prebuilt.as_path(), is_windows)?;
|
||||
|
||||
let package_json = serde_json::json!({
|
||||
"name": package_name,
|
||||
"version": ESBUILD_VERSION,
|
||||
"description": format!("The {} binary for esbuild", platform_name),
|
||||
"bin": bin_path
|
||||
});
|
||||
|
||||
std::fs::write(
|
||||
package_dir.join("package.json"),
|
||||
serde_json::to_string_pretty(&package_json)?,
|
||||
)?;
|
||||
|
||||
let tarball_bytes = create_tarball_from_dir(&package_dir)?;
|
||||
let package = create_npm_registry_response(
|
||||
package_name,
|
||||
ESBUILD_VERSION,
|
||||
&format!("The {} binary for esbuild", platform_name),
|
||||
bin_path,
|
||||
tarball_bytes,
|
||||
registry_hostname,
|
||||
)?;
|
||||
|
||||
Ok(Some(package))
|
||||
}
|
||||
|
||||
fn get_npm_package(
|
||||
registry_hostname: &str,
|
||||
local_path: &str,
|
||||
package_name: &str,
|
||||
) -> Result<Option<CustomNpmPackage>> {
|
||||
if package_name.starts_with("@esbuild/") {
|
||||
if let Some(esbuild_package) =
|
||||
create_esbuild_package(registry_hostname, package_name)?
|
||||
{
|
||||
return Ok(Some(esbuild_package));
|
||||
}
|
||||
}
|
||||
|
||||
let registry_hostname = if package_name == "@denotest/tarballs-privateserver2"
|
||||
{
|
||||
"http://localhost:4262"
|
||||
|
@ -288,51 +498,14 @@ fn get_npm_package(
|
|||
let version = entry.file_name().to_string_lossy().to_string();
|
||||
let version_folder = package_folder.join(&version);
|
||||
|
||||
// create the tarball
|
||||
let mut tarball_bytes = Vec::new();
|
||||
{
|
||||
let mut encoder =
|
||||
GzEncoder::new(&mut tarball_bytes, Compression::default());
|
||||
{
|
||||
let mut builder = Builder::new(&mut encoder);
|
||||
append_dir_all(
|
||||
&mut builder,
|
||||
Path::new("package"),
|
||||
version_folder.as_path(),
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("Error adding tarball for directory {}", version_folder,)
|
||||
})?;
|
||||
builder.finish()?;
|
||||
}
|
||||
encoder.finish()?;
|
||||
}
|
||||
|
||||
// create the registry file JSON for this version
|
||||
let mut dist = serde_json::Map::new();
|
||||
if package_name != "@denotest/no-shasums" {
|
||||
let tarball_checksum = get_tarball_checksum(&tarball_bytes);
|
||||
dist.insert(
|
||||
"integrity".to_string(),
|
||||
format!("sha512-{tarball_checksum}").into(),
|
||||
);
|
||||
dist.insert("shasum".to_string(), "dummy-value".into());
|
||||
}
|
||||
dist.insert(
|
||||
"tarball".to_string(),
|
||||
format!("{registry_hostname}/{package_name}/{version}.tgz").into(),
|
||||
);
|
||||
let (tarball_bytes, mut version_info) = create_package_version_info(
|
||||
&version_folder,
|
||||
&version,
|
||||
package_name,
|
||||
registry_hostname,
|
||||
)?;
|
||||
|
||||
tarballs.insert(version.clone(), tarball_bytes);
|
||||
let package_json_path = version_folder.join("package.json");
|
||||
let package_json_bytes =
|
||||
fs::read(&package_json_path).with_context(|| {
|
||||
format!("Error reading package.json at {}", package_json_path)
|
||||
})?;
|
||||
let package_json_text = String::from_utf8_lossy(&package_json_bytes);
|
||||
let mut version_info: serde_json::Map<String, serde_json::Value> =
|
||||
serde_json::from_str(&package_json_text)?;
|
||||
version_info.insert("dist".to_string(), dist.into());
|
||||
|
||||
if let Some(maybe_optional_deps) = version_info.get("optionalDependencies")
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ use super::string_body;
|
|||
use super::ServerKind;
|
||||
use super::ServerOptions;
|
||||
use crate::npm;
|
||||
use crate::root_path;
|
||||
|
||||
pub fn public_npm_registry(port: u16) -> Vec<LocalBoxFuture<'static, ()>> {
|
||||
run_npm_server(port, "npm registry server error", {
|
||||
|
@ -100,6 +101,7 @@ async fn run_npm_server_for_addr<F, S>(
|
|||
F: Fn(Request<hyper::body::Incoming>) -> S + Copy + 'static,
|
||||
S: Future<Output = HandlerOutput> + 'static,
|
||||
{
|
||||
ensure_esbuild_prebuilt().await.unwrap();
|
||||
run_server(
|
||||
ServerOptions {
|
||||
addr,
|
||||
|
@ -378,3 +380,45 @@ async fn download_npm_registry_file(
|
|||
std::fs::write(testdata_file_path, bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PREBUILT_URL: &str = "https://raw.githubusercontent.com/denoland/deno_third_party/de0d517e6f703fb4735b7aa5806f69fbdbb1d907/prebuilt/";
|
||||
|
||||
async fn ensure_esbuild_prebuilt() -> Result<(), anyhow::Error> {
|
||||
let bin_name = match (std::env::consts::ARCH, std::env::consts::OS) {
|
||||
("x86_64", "linux" | "macos" | "apple") => "esbuild-x64",
|
||||
("aarch64", "linux" | "macos" | "apple") => "esbuild-aarch64",
|
||||
("x86_64", "windows") => "esbuild-x64.exe",
|
||||
("aarch64", "windows") => "esbuild-arm64.exe",
|
||||
_ => return Err(anyhow::anyhow!("unsupported platform")),
|
||||
};
|
||||
|
||||
let folder = match std::env::consts::OS {
|
||||
"linux" => "linux64",
|
||||
"windows" => "win",
|
||||
"macos" | "apple" => "mac",
|
||||
_ => return Err(anyhow::anyhow!("unsupported platform")),
|
||||
};
|
||||
let esbuild_prebuilt = root_path()
|
||||
.join("third_party/prebuilt")
|
||||
.join(folder)
|
||||
.join(bin_name);
|
||||
if esbuild_prebuilt.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
let url = format!("{PREBUILT_URL}{folder}/{bin_name}");
|
||||
let response = reqwest::get(url).await?;
|
||||
let bytes = response.bytes().await?;
|
||||
|
||||
tokio::fs::create_dir_all(esbuild_prebuilt.parent()).await?;
|
||||
tokio::fs::write(&esbuild_prebuilt, bytes).await?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = tokio::fs::metadata(&esbuild_prebuilt).await?.permissions();
|
||||
perms.set_mode(0o755); // rwxr-xr-x
|
||||
tokio::fs::set_permissions(&esbuild_prebuilt, perms).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue