feat(bundle): runtime API for deno bundle (#29949)
Some checks are pending
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 libs (push) Blocked by required conditions
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 / publish canary (push) Blocked by required conditions

This commit is contained in:
Nathan Whitaker 2025-09-04 09:47:27 -07:00 committed by GitHub
parent 8205e12825
commit c76c3f7c13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1484 additions and 212 deletions

119
Cargo.lock generated
View file

@ -546,7 +546,7 @@ version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.12.1",
@ -569,7 +569,7 @@ version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.13.0", "itertools 0.13.0",
@ -589,7 +589,7 @@ version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.13.0", "itertools 0.13.0",
@ -626,9 +626,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1347,7 +1347,7 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"crossterm_winapi", "crossterm_winapi",
"mio 1.0.3", "mio 1.0.3",
"parking_lot", "parking_lot",
@ -1554,6 +1554,7 @@ dependencies = [
"dashmap", "dashmap",
"deno_ast", "deno_ast",
"deno_bench_util", "deno_bench_util",
"deno_bundle_runtime",
"deno_cache_dir", "deno_cache_dir",
"deno_config", "deno_config",
"deno_core", "deno_core",
@ -1745,6 +1746,18 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "deno_bundle_runtime"
version = "0.1.0"
dependencies = [
"async-trait",
"deno_core",
"deno_error",
"deno_web",
"regex",
"serde",
]
[[package]] [[package]]
name = "deno_cache" name = "deno_cache"
version = "0.146.0" version = "0.146.0"
@ -2247,6 +2260,7 @@ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"base64 0.22.1", "base64 0.22.1",
"capacity_builder", "capacity_builder",
"deno_bundle_runtime",
"deno_error", "deno_error",
"deno_fs", "deno_fs",
"deno_media_type", "deno_media_type",
@ -2746,9 +2760,11 @@ dependencies = [
name = "deno_runtime" name = "deno_runtime"
version = "0.222.0" version = "0.222.0"
dependencies = [ dependencies = [
"async-trait",
"color-print", "color-print",
"deno_ast", "deno_ast",
"deno_broadcast_channel", "deno_broadcast_channel",
"deno_bundle_runtime",
"deno_cache", "deno_cache",
"deno_canvas", "deno_canvas",
"deno_console", "deno_console",
@ -2787,6 +2803,7 @@ dependencies = [
"http-body-util", "http-body-util",
"hyper 1.6.0", "hyper 1.6.0",
"hyper-util", "hyper-util",
"indexmap 2.9.0",
"libc", "libc",
"log", "log",
"nix 0.27.1", "nix 0.27.1",
@ -2794,6 +2811,7 @@ dependencies = [
"notify", "notify",
"ntapi", "ntapi",
"once_cell", "once_cell",
"regex",
"rustyline", "rustyline",
"same-file", "same-file",
"serde", "serde",
@ -3454,7 +3472,7 @@ checksum = "2c1d827947704a9495f705d6aeed270fa21a67f825f22902c28f38dc3af7a9ae"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bumpalo", "bumpalo",
"hashbrown 0.15.2", "hashbrown 0.15.5",
"indexmap 2.9.0", "indexmap 2.9.0",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"serde", "serde",
@ -4368,7 +4386,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"gpu-alloc-types", "gpu-alloc-types",
] ]
@ -4378,7 +4396,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
] ]
[[package]] [[package]]
@ -4399,7 +4417,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"gpu-descriptor-types", "gpu-descriptor-types",
"hashbrown 0.14.5", "hashbrown 0.14.5",
] ]
@ -4410,7 +4428,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
] ]
[[package]] [[package]]
@ -4523,9 +4541,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@ -4547,7 +4565,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [ dependencies = [
"hashbrown 0.15.2", "hashbrown 0.15.5",
] ]
[[package]] [[package]]
@ -5131,7 +5149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown 0.15.5",
"serde", "serde",
] ]
@ -5171,7 +5189,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@ -5272,9 +5290,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
@ -5540,7 +5558,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"libc", "libc",
] ]
@ -5739,9 +5757,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
@ -5767,7 +5785,7 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"block", "block",
"core-graphics-types", "core-graphics-types",
"foreign-types 0.5.0", "foreign-types 0.5.0",
@ -5876,7 +5894,7 @@ checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-set", "bit-set",
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg_aliases", "cfg_aliases",
"codespan-reporting", "codespan-reporting",
"hexf-parse", "hexf-parse",
@ -5956,7 +5974,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"libc", "libc",
"memoffset", "memoffset",
@ -5968,7 +5986,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@ -6030,7 +6048,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"crossbeam-channel", "crossbeam-channel",
"filetime", "filetime",
"fsevent-sys", "fsevent-sys",
@ -6089,11 +6107,10 @@ dependencies = [
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.4" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [ dependencies = [
"autocfg",
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand 0.8.5", "rand 0.8.5",
@ -6240,7 +6257,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"foreign-types 0.3.2", "foreign-types 0.3.2",
"libc", "libc",
@ -6877,9 +6894,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -6958,7 +6975,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4e75767fbc9d92b90e4d0c011f61358cde9513b31ef07ea3631b15ffc3b4fd" checksum = "cb4e75767fbc9d92b90e4d0c011f61358cde9513b31ef07ea3631b15ffc3b4fd"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"memchr", "memchr",
"unicase", "unicase",
] ]
@ -7213,7 +7230,7 @@ version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
] ]
[[package]] [[package]]
@ -7255,7 +7272,7 @@ checksum = "145c1c267e14f20fb0f88aa76a1c5ffec42d592c1d28b3cd9148ae35916158d3"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"bumpalo", "bumpalo",
"hashbrown 0.15.2", "hashbrown 0.15.5",
"log", "log",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"smallvec", "smallvec",
@ -7393,7 +7410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bitflags 2.8.0", "bitflags 2.9.3",
"serde", "serde",
"serde_derive", "serde_derive",
] ]
@ -7461,7 +7478,7 @@ version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"fallible-iterator", "fallible-iterator",
"fallible-streaming-iterator", "fallible-streaming-iterator",
"hashlink 0.10.0", "hashlink 0.10.0",
@ -7520,7 +7537,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -7624,7 +7641,7 @@ version = "13.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg-if", "cfg-if",
"clipboard-win", "clipboard-win",
"fd-lock", "fd-lock",
@ -7767,7 +7784,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -8162,7 +8179,7 @@ version = "0.3.0+sdk-1.3.268.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
] ]
[[package]] [[package]]
@ -8409,7 +8426,7 @@ version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65c25af97d53cf8aab66a6c68f3418663313fc969ad267fc2a4d19402c329be1" checksum = "65c25af97d53cf8aab66a6c68f3418663313fc969ad267fc2a4d19402c329be1"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"is-macro", "is-macro",
"num-bigint", "num-bigint",
"once_cell", "once_cell",
@ -8465,7 +8482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c3bd958a5a67e2cc3f74abdd41fda688e54e7a25b866569260ef7018b67972" checksum = "67c3bd958a5a67e2cc3f74abdd41fda688e54e7a25b866569260ef7018b67972"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bitflags 2.8.0", "bitflags 2.9.3",
"either", "either",
"num-bigint", "num-bigint",
"phf", "phf",
@ -9194,7 +9211,7 @@ dependencies = [
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"hashbrown 0.15.2", "hashbrown 0.15.5",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
"tokio", "tokio",
@ -9311,7 +9328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags 2.8.0", "bitflags 2.9.3",
"bytes", "bytes",
"futures-core", "futures-core",
"http 1.1.0", "http 1.1.0",
@ -9662,7 +9679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33995a1fee055ff743281cde33a41f0d618ee0bdbe8bdf6859e11864499c2595" checksum = "33995a1fee055ff743281cde33a41f0d618ee0bdbe8bdf6859e11864499c2595"
dependencies = [ dependencies = [
"bindgen 0.71.1", "bindgen 0.71.1",
"bitflags 2.8.0", "bitflags 2.9.3",
"fslock", "fslock",
"gzip-header", "gzip-header",
"home", "home",
@ -9677,7 +9694,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6" checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"encoding_rs", "encoding_rs",
"indexmap 2.9.0", "indexmap 2.9.0",
"num-bigint", "num-bigint",
@ -9960,7 +9977,7 @@ checksum = "82a39b8842dc9ffcbe34346e3ab6d496b32a47f6497e119d762c97fcaae3cb37"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-vec", "bit-vec",
"bitflags 2.8.0", "bitflags 2.9.3",
"cfg_aliases", "cfg_aliases",
"document-features", "document-features",
"indexmap 2.9.0", "indexmap 2.9.0",
@ -9989,7 +10006,7 @@ dependencies = [
"arrayvec", "arrayvec",
"ash", "ash",
"bit-set", "bit-set",
"bitflags 2.8.0", "bitflags 2.9.3",
"block", "block",
"bytemuck", "bytemuck",
"cfg_aliases", "cfg_aliases",
@ -10030,7 +10047,7 @@ version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
"js-sys", "js-sys",
"log", "log",
"serde", "serde",
@ -10505,7 +10522,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.3",
] ]
[[package]] [[package]]

View file

@ -9,6 +9,7 @@ members = [
"cli/rt", "cli/rt",
"cli/snapshot", "cli/snapshot",
"ext/broadcast_channel", "ext/broadcast_channel",
"ext/bundle",
"ext/cache", "ext/cache",
"ext/canvas", "ext/canvas",
"ext/console", "ext/console",
@ -87,6 +88,7 @@ denokv_sqlite = { default-features = false, version = "0.12.0" }
# exts # exts
deno_broadcast_channel = { version = "0.208.0", path = "./ext/broadcast_channel" } deno_broadcast_channel = { version = "0.208.0", path = "./ext/broadcast_channel" }
deno_bundle_runtime = { version = "0.1.0", path = "./ext/bundle" }
deno_cache = { version = "0.146.0", path = "./ext/cache" } deno_cache = { version = "0.146.0", path = "./ext/cache" }
deno_canvas = { version = "0.83.0", path = "./ext/canvas" } deno_canvas = { version = "0.83.0", path = "./ext/canvas" }
deno_console = { version = "0.214.0", path = "./ext/console" } deno_console = { version = "0.214.0", path = "./ext/console" }

View file

@ -67,6 +67,7 @@ winres.workspace = true
[dependencies] [dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit", "utils"] } deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit", "utils"] }
deno_bundle_runtime.workspace = true
deno_cache_dir = { workspace = true, features = ["sync"] } deno_cache_dir = { workspace = true, features = ["sync"] }
deno_config = { workspace = true, features = ["sync", "workspace"] } deno_config = { workspace = true, features = ["sync", "workspace"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }

View file

@ -23,6 +23,10 @@ use clap::builder::styling::AnsiColor;
use clap::error::ErrorKind; use clap::error::ErrorKind;
use clap::value_parser; use clap::value_parser;
use color_print::cstr; use color_print::cstr;
use deno_bundle_runtime::BundleFormat;
use deno_bundle_runtime::BundlePlatform;
use deno_bundle_runtime::PackageHandling;
use deno_bundle_runtime::SourceMapType;
use deno_config::deno_json::NodeModulesDirMode; use deno_config::deno_json::NodeModulesDirMode;
use deno_config::glob::FilePatterns; use deno_config::glob::FilePatterns;
use deno_config::glob::PathOrPatternSet; use deno_config::glob::PathOrPatternSet;
@ -490,61 +494,6 @@ pub struct BundleFlags {
pub watch: bool, pub watch: bool,
} }
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
pub enum BundlePlatform {
Browser,
Deno,
}
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
pub enum BundleFormat {
Esm,
Cjs,
Iife,
}
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
pub enum SourceMapType {
Linked,
Inline,
External,
}
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"),
}
}
}
impl std::fmt::Display for SourceMapType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceMapType::Linked => write!(f, "linked"),
SourceMapType::Inline => write!(f, "inline"),
SourceMapType::External => write!(f, "external"),
}
}
}
#[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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum DenoSubcommand { pub enum DenoSubcommand {
Add(AddFlags), Add(AddFlags),

View file

@ -6,6 +6,7 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use deno_bundle_runtime::BundlePlatform;
use deno_cache_dir::GlobalOrLocalHttpCache; use deno_cache_dir::GlobalOrLocalHttpCache;
use deno_cache_dir::npm::NpmCacheDir; use deno_cache_dir::npm::NpmCacheDir;
use deno_config::workspace::WorkspaceDirectory; use deno_config::workspace::WorkspaceDirectory;
@ -59,7 +60,6 @@ use once_cell::sync::OnceCell;
use sys_traits::EnvCurrentDir; use sys_traits::EnvCurrentDir;
use crate::args::BundleFlags; use crate::args::BundleFlags;
use crate::args::BundlePlatform;
use crate::args::CliLockfile; use crate::args::CliLockfile;
use crate::args::CliOptions; use crate::args::CliOptions;
use crate::args::ConfigFlag; use crate::args::ConfigFlag;
@ -1036,6 +1036,9 @@ impl CliFactory {
self.sys(), self.sys(),
self.create_lib_main_worker_options()?, self.create_lib_main_worker_options()?,
roots, roots,
Some(Arc::new(crate::tools::bundle::CliBundleProvider::new(
self.flags.clone(),
))),
); );
Ok(CliMainWorkerFactory::new( Ok(CliMainWorkerFactory::new(

View file

@ -18,6 +18,7 @@ arc-swap.workspace = true
aws-lc-rs.workspace = true aws-lc-rs.workspace = true
base64.workspace = true base64.workspace = true
capacity_builder.workspace = true capacity_builder.workspace = true
deno_bundle_runtime.workspace = true
deno_error.workspace = true deno_error.workspace = true
deno_fs = { workspace = true, features = ["sync_fs"] } deno_fs = { workspace = true, features = ["sync_fs"] }
deno_media_type.workspace = true deno_media_type.workspace = true

View file

@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use deno_bundle_runtime::BundleProvider;
use deno_core::error::JsError; use deno_core::error::JsError;
use deno_node::NodeRequireLoaderRc; use deno_node::NodeRequireLoaderRc;
use deno_path_util::url_from_file_path; use deno_path_util::url_from_file_path;
@ -376,6 +377,7 @@ struct LibWorkerFactorySharedState<TSys: DenoLibSys> {
storage_key_resolver: StorageKeyResolver, storage_key_resolver: StorageKeyResolver,
sys: TSys, sys: TSys,
options: LibMainWorkerOptions, options: LibMainWorkerOptions,
bundle_provider: Option<Arc<dyn BundleProvider>>,
} }
impl<TSys: DenoLibSys> LibWorkerFactorySharedState<TSys> { impl<TSys: DenoLibSys> LibWorkerFactorySharedState<TSys> {
@ -540,6 +542,7 @@ impl<TSys: DenoLibSys> LibMainWorkerFactory<TSys> {
sys: TSys, sys: TSys,
options: LibMainWorkerOptions, options: LibMainWorkerOptions,
roots: LibWorkerFactoryRoots, roots: LibWorkerFactoryRoots,
bundle_provider: Option<Arc<dyn BundleProvider>>,
) -> Self { ) -> Self {
Self { Self {
shared: Arc::new(LibWorkerFactorySharedState { shared: Arc::new(LibWorkerFactorySharedState {
@ -560,6 +563,7 @@ impl<TSys: DenoLibSys> LibMainWorkerFactory<TSys> {
storage_key_resolver, storage_key_resolver,
sys, sys,
options, options,
bundle_provider,
}), }),
} }
} }
@ -646,6 +650,7 @@ impl<TSys: DenoLibSys> LibMainWorkerFactory<TSys> {
feature_checker, feature_checker,
permissions, permissions,
v8_code_cache: shared.code_cache.clone(), v8_code_cache: shared.code_cache.clone(),
bundle_provider: shared.bundle_provider.clone(),
}; };
let options = WorkerOptions { let options = WorkerOptions {

View file

@ -1044,6 +1044,7 @@ pub async fn run(
sys.clone(), sys.clone(),
lib_main_worker_options, lib_main_worker_options,
Default::default(), Default::default(),
None,
); );
// Initialize v8 once from the main thread. // Initialize v8 once from the main thread.

View file

@ -2,6 +2,7 @@
mod esbuild; mod esbuild;
mod externals; mod externals;
mod provider;
mod transform; mod transform;
use std::borrow::Cow; use std::borrow::Cow;
@ -16,7 +17,12 @@ use std::time::Duration;
use deno_ast::EmitOptions; use deno_ast::EmitOptions;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_ast::ModuleKind;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_bundle_runtime::BundleFormat;
use deno_bundle_runtime::BundlePlatform;
use deno_bundle_runtime::PackageHandling;
use deno_bundle_runtime::SourceMapType;
use deno_config::workspace::TsTypeLib; use deno_config::workspace::TsTypeLib;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::FutureExt as _; use deno_core::futures::FutureExt as _;
@ -27,6 +33,7 @@ use deno_error::JsError;
use deno_graph::ModuleErrorKind; use deno_graph::ModuleErrorKind;
use deno_graph::Position; use deno_graph::Position;
use deno_path_util::resolve_url_or_path; use deno_path_util::resolve_url_or_path;
use deno_resolver::cache::ParsedSourceCache;
use deno_resolver::graph::ResolveWithGraphError; use deno_resolver::graph::ResolveWithGraphError;
use deno_resolver::graph::ResolveWithGraphOptions; use deno_resolver::graph::ResolveWithGraphOptions;
use deno_resolver::loader::LoadCodeSourceError; use deno_resolver::loader::LoadCodeSourceError;
@ -48,24 +55,23 @@ use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode; use node_resolver::ResolutionMode;
use node_resolver::errors::PackageNotFoundError; use node_resolver::errors::PackageNotFoundError;
use node_resolver::errors::PackageSubpathResolveError; use node_resolver::errors::PackageSubpathResolveError;
pub use provider::CliBundleProvider;
use sys_traits::EnvCurrentDir; use sys_traits::EnvCurrentDir;
use crate::args::BundleFlags; use crate::args::BundleFlags;
use crate::args::BundleFormat;
use crate::args::BundlePlatform;
use crate::args::Flags; use crate::args::Flags;
use crate::args::PackageHandling;
use crate::args::SourceMapType;
use crate::factory::CliFactory; use crate::factory::CliFactory;
use crate::file_fetcher::CliFileFetcher; use crate::file_fetcher::CliFileFetcher;
use crate::graph_container::MainModuleGraphContainer; use crate::graph_container::MainModuleGraphContainer;
use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphContainer;
use crate::graph_container::ModuleGraphUpdatePermit; use crate::graph_container::ModuleGraphUpdatePermit;
use crate::module_loader::CliDenoResolverModuleLoader; use crate::module_loader::CliDenoResolverModuleLoader;
use crate::module_loader::CliEmitter;
use crate::module_loader::ModuleLoadPreparer; use crate::module_loader::ModuleLoadPreparer;
use crate::module_loader::PrepareModuleLoadOptions; use crate::module_loader::PrepareModuleLoadOptions;
use crate::node::CliNodeResolver; use crate::node::CliNodeResolver;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::resolver::CliCjsTracker;
use crate::resolver::CliResolver; use crate::resolver::CliResolver;
use crate::sys::CliSys; use crate::sys::CliSys;
use crate::tools::bundle::externals::ExternalsMatcher; use crate::tools::bundle::externals::ExternalsMatcher;
@ -74,10 +80,10 @@ use crate::util::file_watcher::WatcherRestartMode;
static DISABLE_HACK: LazyLock<bool> = static DISABLE_HACK: LazyLock<bool> =
LazyLock::new(|| std::env::var("NO_DENO_BUNDLE_HACK").is_err()); LazyLock::new(|| std::env::var("NO_DENO_BUNDLE_HACK").is_err());
pub async fn bundle( pub async fn bundle_init(
mut flags: Arc<Flags>, mut flags: Arc<Flags>,
bundle_flags: BundleFlags, bundle_flags: &BundleFlags,
) -> Result<(), AnyError> { ) -> Result<EsbuildBundler, AnyError> {
{ {
let flags_mut = Arc::make_mut(&mut flags); let flags_mut = Arc::make_mut(&mut flags);
flags_mut.unstable_config.sloppy_imports = true; flags_mut.unstable_config.sloppy_imports = true;
@ -113,9 +119,11 @@ pub async fn bundle(
Some(ExternalsMatcher::new(&bundle_flags.external, &init_cwd)) Some(ExternalsMatcher::new(&bundle_flags.external, &init_cwd))
}, },
on_end_tx, on_end_tx,
deferred_resolve_errors: Arc::new(Mutex::new(vec![])), parsed_source_cache: factory.parsed_source_cache()?.clone(),
cjs_tracker: factory.cjs_tracker()?.clone(),
emitter: factory.emitter()?.clone(),
deferred_resolve_errors: Default::default(),
}); });
let start = std::time::Instant::now();
let resolved_entrypoints = let resolved_entrypoints =
resolve_entrypoints(&resolver, &init_cwd, &bundle_flags.entrypoints)?; resolve_entrypoints(&resolver, &init_cwd, &bundle_flags.entrypoints)?;
@ -142,7 +150,7 @@ pub async fn bundle(
log::warn!("esbuild exited: {:?}", res); log::warn!("esbuild exited: {:?}", res);
}); });
let esbuild_flags = configure_esbuild_flags(&bundle_flags); let esbuild_flags = configure_esbuild_flags(bundle_flags);
let entries = roots.into_iter().map(|e| ("".into(), e.into())).collect(); let entries = roots.into_iter().map(|e| ("".into(), e.into())).collect();
let bundler = EsbuildBundler::new( let bundler = EsbuildBundler::new(
client, client,
@ -156,7 +164,24 @@ pub async fn bundle(
esbuild_flags, esbuild_flags,
entries, entries,
); );
Ok(bundler)
}
pub async fn bundle(
mut flags: Arc<Flags>,
bundle_flags: BundleFlags,
) -> Result<(), AnyError> {
{
let flags_mut = Arc::make_mut(&mut flags);
flags_mut.unstable_config.sloppy_imports = true;
}
let bundler = bundle_init(flags.clone(), &bundle_flags).await?;
let init_cwd = bundler.cwd.clone();
let start = std::time::Instant::now();
let response = bundler.build().await?; let response = bundler.build().await?;
let end = std::time::Instant::now();
let duration = end.duration_since(start);
if bundle_flags.watch { if bundle_flags.watch {
return bundle_watch( return bundle_watch(
@ -171,7 +196,7 @@ pub async fn bundle(
handle_esbuild_errors_and_warnings( handle_esbuild_errors_and_warnings(
&response, &response,
&init_cwd, &init_cwd,
&plugin_handler.take_deferred_resolve_errors(), &bundler.plugin_handler.take_deferred_resolve_errors(),
); );
if response.errors.is_empty() { if response.errors.is_empty() {
@ -179,12 +204,12 @@ pub async fn bundle(
let output_infos = process_result( let output_infos = process_result(
&response, &response,
&init_cwd, &init_cwd,
*DISABLE_HACK && matches!(bundle_flags.platform, BundlePlatform::Deno), should_replace_require_shim(bundle_flags.platform),
bundle_flags.minify, bundle_flags.minify,
)?; )?;
if bundle_flags.output_dir.is_some() || bundle_flags.output_path.is_some() { if bundle_flags.output_dir.is_some() || bundle_flags.output_path.is_some() {
print_finished_message(&metafile, &output_infos, start.elapsed())?; print_finished_message(&metafile, &output_infos, duration)?;
} }
} }
@ -254,7 +279,7 @@ async fn bundle_watch(
let output_infos = process_result( let output_infos = process_result(
&response, &response,
&bundler.cwd, &bundler.cwd,
*DISABLE_HACK && matches!(platform, BundlePlatform::Deno), should_replace_require_shim(platform),
minified, minified,
)?; )?;
print_finished_message(&metafile, &output_infos, start.elapsed())?; print_finished_message(&metafile, &output_infos, start.elapsed())?;
@ -277,6 +302,10 @@ async fn bundle_watch(
Ok(()) Ok(())
} }
pub fn should_replace_require_shim(platform: BundlePlatform) -> bool {
*DISABLE_HACK && matches!(platform, BundlePlatform::Deno)
}
fn get_input_paths_for_watch(response: &BuildResponse) -> Vec<PathBuf> { fn get_input_paths_for_watch(response: &BuildResponse) -> Vec<PathBuf> {
let metafile = serde_json::from_str::<esbuild_client::Metafile>( let metafile = serde_json::from_str::<esbuild_client::Metafile>(
response response
@ -294,7 +323,7 @@ fn get_input_paths_for_watch(response: &BuildResponse) -> Vec<PathBuf> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum BundlingMode { pub enum BundlingMode {
OneShot, OneShot,
Watch, Watch,
@ -372,6 +401,7 @@ impl EsbuildBundler {
.send_build_request(self.make_build_request()) .send_build_request(self.make_build_request())
.await .await
.unwrap(); .unwrap();
Ok(response) Ok(response)
} }
@ -550,6 +580,10 @@ pub struct DenoPluginHandler {
externals_matcher: Option<ExternalsMatcher>, externals_matcher: Option<ExternalsMatcher>,
on_end_tx: tokio::sync::mpsc::Sender<esbuild_client::OnEndArgs>, on_end_tx: tokio::sync::mpsc::Sender<esbuild_client::OnEndArgs>,
deferred_resolve_errors: Arc<Mutex<Vec<DeferredResolveError>>>, deferred_resolve_errors: Arc<Mutex<Vec<DeferredResolveError>>>,
parsed_source_cache: Arc<ParsedSourceCache>,
cjs_tracker: Arc<CliCjsTracker>,
emitter: Arc<CliEmitter>,
} }
impl DenoPluginHandler { impl DenoPluginHandler {
@ -558,6 +592,56 @@ impl DenoPluginHandler {
} }
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
enum PluginImportKind {
EntryPoint,
ImportStatement,
RequireCall,
DynamicImport,
RequireResolve,
ImportRule,
ComposesFrom,
UrlToken,
}
impl From<protocol::ImportKind> for PluginImportKind {
fn from(kind: protocol::ImportKind) -> Self {
match kind {
protocol::ImportKind::EntryPoint => PluginImportKind::EntryPoint,
protocol::ImportKind::ImportStatement => {
PluginImportKind::ImportStatement
}
protocol::ImportKind::RequireCall => PluginImportKind::RequireCall,
protocol::ImportKind::DynamicImport => PluginImportKind::DynamicImport,
protocol::ImportKind::RequireResolve => PluginImportKind::RequireResolve,
protocol::ImportKind::ImportRule => PluginImportKind::ImportRule,
protocol::ImportKind::ComposesFrom => PluginImportKind::ComposesFrom,
protocol::ImportKind::UrlToken => PluginImportKind::UrlToken,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct PluginOnResolveArgs {
path: String,
importer: Option<String>,
kind: PluginImportKind,
namespace: Option<String>,
resolve_dir: Option<String>,
with: IndexMap<String, String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct PluginOnLoadArgs {
path: String,
namespace: String,
suffix: String,
with: IndexMap<String, String>,
}
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl esbuild_client::PluginHandler for DenoPluginHandler { impl esbuild_client::PluginHandler for DenoPluginHandler {
async fn on_resolve( async fn on_resolve(
@ -576,6 +660,7 @@ impl esbuild_client::PluginHandler for DenoPluginHandler {
..Default::default() ..Default::default()
})); }));
} }
let result = self.bundle_resolve( let result = self.bundle_resolve(
&args.path, &args.path,
args.importer.as_deref(), args.importer.as_deref(),
@ -589,7 +674,7 @@ impl esbuild_client::PluginHandler for DenoPluginHandler {
Err(e) => { Err(e) => {
return Ok(Some(esbuild_client::OnResolveResult { return Ok(Some(esbuild_client::OnResolveResult {
errors: Some(vec![esbuild_client::protocol::PartialMessage { errors: Some(vec![esbuild_client::protocol::PartialMessage {
id: "myerror".into(), id: "deno_error".into(),
plugin_name: "deno".into(), plugin_name: "deno".into(),
text: e.to_string(), text: e.to_string(),
..Default::default() ..Default::default()
@ -633,6 +718,7 @@ impl esbuild_client::PluginHandler for DenoPluginHandler {
&self, &self,
args: esbuild_client::OnLoadArgs, args: esbuild_client::OnLoadArgs,
) -> Result<Option<esbuild_client::OnLoadResult>, AnyError> { ) -> Result<Option<esbuild_client::OnLoadResult>, AnyError> {
log::debug!("{}: {args:?}", deno_terminal::colors::cyan("on_load"));
let result = self let result = self
.bundle_load(&args.path, &requested_type_from_map(&args.with)) .bundle_load(&args.path, &requested_type_from_map(&args.with))
.await; .await;
@ -684,7 +770,6 @@ impl esbuild_client::PluginHandler for DenoPluginHandler {
&self, &self,
_args: esbuild_client::OnEndArgs, _args: esbuild_client::OnEndArgs,
) -> Result<Option<esbuild_client::OnEndResult>, AnyError> { ) -> Result<Option<esbuild_client::OnEndResult>, AnyError> {
log::debug!("{}: {_args:?}", deno_terminal::colors::magenta("on_end"));
self.on_end_tx.send(_args).await?; self.on_end_tx.send(_args).await?;
Ok(None) Ok(None)
} }
@ -726,11 +811,27 @@ pub enum BundleLoadError {
#[error("UTF-8 conversion error")] #[error("UTF-8 conversion error")]
Utf8(#[from] std::str::Utf8Error), Utf8(#[from] std::str::Utf8Error),
#[class(generic)] #[class(generic)]
#[error("UTF-8 conversion error")]
StringUtf8(#[from] std::string::FromUtf8Error),
#[class(generic)]
#[error("Parse error")] #[error("Parse error")]
Parse(#[from] deno_ast::ParseDiagnostic), Parse(#[from] deno_ast::ParseDiagnostic),
#[class(generic)] #[class(generic)]
#[error("Emit error")] #[error("Emit error")]
Emit(#[from] deno_ast::EmitError), Emit(#[from] deno_ast::EmitError),
#[class(generic)]
#[error("Prepare module load error")]
PrepareModuleLoad(#[from] crate::module_loader::PrepareModuleLoadError),
#[class(generic)]
#[error("Package.json load error")]
PackageJsonLoadError(#[from] node_resolver::errors::PackageJsonLoadError),
#[class(generic)]
#[error("Emit parsed source helper error")]
EmitParsedSourceHelperError(
#[from] deno_resolver::emit::EmitParsedSourceHelperError,
),
} }
impl BundleLoadError { impl BundleLoadError {
@ -901,7 +1002,7 @@ impl DenoPluginHandler {
async fn prepare_module_load( async fn prepare_module_load(
&self, &self,
specifiers: &[ModuleSpecifier], specifiers: &[ModuleSpecifier],
) -> Result<(), AnyError> { ) -> Result<(), BundleLoadError> {
let mut graph_permit = let mut graph_permit =
self.module_graph_container.acquire_update_permit().await; self.module_graph_container.acquire_update_permit().await;
let graph: &mut deno_graph::ModuleGraph = graph_permit.graph_mut(); let graph: &mut deno_graph::ModuleGraph = graph_permit.graph_mut();
@ -941,23 +1042,15 @@ impl DenoPluginHandler {
specifier, specifier,
Path::new(""), // should be absolute already, feels kind of hacky though Path::new(""), // should be absolute already, feels kind of hacky though
)?; )?;
let (specifier, media_type, loader) = let (specifier, media_type) =
if let RequestedModuleType::Bytes = requested_type { if let RequestedModuleType::Bytes = requested_type {
( (specifier, MediaType::Unknown)
specifier,
MediaType::Unknown,
esbuild_client::BuiltinLoader::Binary,
)
} else if let RequestedModuleType::Text = requested_type { } else if let RequestedModuleType::Text = requested_type {
( (specifier, MediaType::Unknown)
specifier, } else if let Some((specifier, media_type, _)) =
MediaType::Unknown,
esbuild_client::BuiltinLoader::Text,
)
} else if let Some((specifier, media_type, loader)) =
self.specifier_and_type_from_graph(&specifier)? self.specifier_and_type_from_graph(&specifier)?
{ {
(specifier, media_type, loader) (specifier, media_type)
} else { } else {
log::debug!( log::debug!(
"{}: no specifier and type from graph for {}", "{}: no specifier and type from graph for {}",
@ -979,13 +1072,76 @@ impl DenoPluginHandler {
if media_type == deno_media_type::MediaType::Unknown { if media_type == deno_media_type::MediaType::Unknown {
return Ok(None); return Ok(None);
} }
(specifier, media_type, media_type_to_loader(media_type)) (specifier, media_type)
}; };
let graph = self.module_graph_container.graph(); let graph = self.module_graph_container.graph();
let module_or_asset = self let module_or_asset = self
.module_loader .module_loader
.load(&graph, &specifier, None, requested_type) .load(&graph, &specifier, None, requested_type)
.await;
let module_or_asset = match module_or_asset {
Ok(module_or_asset) => module_or_asset,
Err(e) => match e.as_kind() {
LoadCodeSourceErrorKind::LoadUnpreparedModule(_) => {
let file = self
.file_fetcher
.fetch(&specifier, &self.permissions)
.await?; .await?;
let media_type = MediaType::from_specifier_and_headers(
&specifier,
file.maybe_headers.as_ref(),
);
match requested_type {
RequestedModuleType::Text | RequestedModuleType::Bytes => {
return self
.create_module_response(
&graph,
&specifier,
media_type,
&file.source,
Some(requested_type),
)
.await
.map(Some);
}
RequestedModuleType::None
| RequestedModuleType::Json
| RequestedModuleType::Other(_) => {
if media_type.is_emittable() {
let str = String::from_utf8_lossy(&file.source);
let value = str.into();
let source = self
.maybe_transpile(&file.url, media_type, &value, None)
.await?;
return self
.create_module_response(
&graph,
&file.url,
media_type,
source.as_bytes(),
Some(requested_type),
)
.await
.map(Some);
} else {
return self
.create_module_response(
&graph,
&file.url,
media_type,
&file.source,
Some(requested_type),
)
.await
.map(Some);
}
}
}
}
_ => return Err(e.into()),
},
};
let loaded_code = match module_or_asset { let loaded_code = match module_or_asset {
LoadedModuleOrAsset::Module(loaded_module) => loaded_module.source, LoadedModuleOrAsset::Module(loaded_module) => loaded_module.source,
LoadedModuleOrAsset::ExternalAsset { LoadedModuleOrAsset::ExternalAsset {
@ -1000,6 +1156,40 @@ impl DenoPluginHandler {
), ),
}; };
Ok(Some(
self
.create_module_response(
&graph,
&specifier,
media_type,
loaded_code.as_bytes(),
Some(requested_type),
)
.await?,
))
}
async fn create_module_response(
&self,
graph: &deno_graph::ModuleGraph,
specifier: &Url,
media_type: MediaType,
source: &[u8],
requested_type: Option<&RequestedModuleType<'_>>,
) -> Result<(Vec<u8>, esbuild_client::BuiltinLoader), BundleLoadError> {
match requested_type {
Some(RequestedModuleType::Text) => {
return Ok((source.to_vec(), esbuild_client::BuiltinLoader::Text));
}
Some(RequestedModuleType::Bytes) => {
return Ok((source.to_vec(), esbuild_client::BuiltinLoader::Binary));
}
Some(RequestedModuleType::Json) => {
return Ok((source.to_vec(), esbuild_client::BuiltinLoader::Json));
}
Some(RequestedModuleType::Other(_) | RequestedModuleType::None)
| None => {}
}
if matches!( if matches!(
media_type, media_type,
MediaType::JavaScript MediaType::JavaScript
@ -1010,32 +1200,66 @@ impl DenoPluginHandler {
| MediaType::Cts | MediaType::Cts
| MediaType::Jsx | MediaType::Jsx
| MediaType::Tsx | MediaType::Tsx
) && !graph.roots.contains(&specifier) ) && !graph.roots.contains(specifier)
{ {
let code = self.apply_transform( let module_graph_container = self.module_graph_container.clone();
let specifier = specifier.clone();
let code = source.to_vec();
let code = tokio::task::spawn_blocking(move || {
Self::apply_transform(
&module_graph_container,
&specifier, &specifier,
media_type, media_type,
std::str::from_utf8(loaded_code.as_bytes())?, &String::from_utf8(code)?,
)?; )
Ok(Some((code.into_bytes(), loader))) })
.await
.unwrap()?;
Ok((code.into_bytes(), media_type_to_loader(media_type)))
} else { } else {
Ok(Some((loaded_code.as_bytes().to_vec(), loader))) Ok((source.to_vec(), media_type_to_loader(media_type)))
} }
} }
async fn maybe_transpile(
&self,
specifier: &Url,
media_type: MediaType,
source: &Arc<str>,
is_known_script: Option<bool>,
) -> Result<Arc<str>, BundleLoadError> {
let parsed_source = self.parsed_source_cache.get_matching_parsed_source(
specifier,
media_type,
source.clone(),
)?;
let is_cjs = if let Some(is_known_script) = is_known_script {
self.cjs_tracker.is_cjs_with_known_is_script(
specifier,
media_type,
is_known_script,
)?
} else {
self.cjs_tracker.is_maybe_cjs(specifier, media_type)?
&& parsed_source.compute_is_script()
};
let module_kind = ModuleKind::from_is_cjs(is_cjs);
let source = self
.emitter
.maybe_emit_parsed_source(parsed_source, module_kind)
.await?;
Ok(source)
}
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
fn apply_transform( fn apply_transform(
&self, module_graph_container: &MainModuleGraphContainer,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
media_type: deno_ast::MediaType, media_type: deno_ast::MediaType,
code: &str, code: &str,
) -> Result<String, BundleLoadError> { ) -> Result<String, BundleLoadError> {
let mut transform = transform::BundleImportMetaMainTransform::new( let mut transform = transform::BundleImportMetaMainTransform::new(
self module_graph_container.graph().roots.contains(specifier),
.module_graph_container
.graph()
.roots
.contains(specifier),
); );
let parsed_source = deno_ast::parse_program_with_post_process( let parsed_source = deno_ast::parse_program_with_post_process(
deno_ast::ParseParams { deno_ast::ParseParams {
@ -1284,10 +1508,10 @@ fn configure_esbuild_flags(bundle_flags: &BundleFlags) -> EsbuildFlags {
builder.metafile(true); builder.metafile(true);
match bundle_flags.platform { match bundle_flags.platform {
crate::args::BundlePlatform::Browser => { deno_bundle_runtime::BundlePlatform::Browser => {
builder.platform(esbuild_client::Platform::Browser); builder.platform(esbuild_client::Platform::Browser);
} }
crate::args::BundlePlatform::Deno => {} deno_bundle_runtime::BundlePlatform::Deno => {}
} }
builder.build().unwrap() builder.build().unwrap()
@ -1351,12 +1575,43 @@ fn is_js(path: &Path) -> bool {
} }
} }
struct OutputFileInfo { pub struct OutputFileInfo {
relative_path: PathBuf, relative_path: PathBuf,
size: usize, size: usize,
is_js: bool, is_js: bool,
} }
fn process_result(
pub struct ProcessedContents {
contents: Option<Vec<u8>>,
is_js: bool,
}
pub fn maybe_process_contents(
file: &esbuild_client::protocol::BuildOutputFile,
should_replace_require_shim: bool,
minified: bool,
) -> Result<ProcessedContents, AnyError> {
let path = Path::new(&file.path);
let is_js = is_js(path) || file.path.ends_with("<stdout>");
if is_js {
let string = String::from_utf8(file.contents.clone())?;
let string = if should_replace_require_shim {
replace_require_shim(&string, minified)
} else {
string
};
Ok(ProcessedContents {
contents: Some(string.into_bytes()),
is_js,
})
} else {
Ok(ProcessedContents {
contents: None,
is_js,
})
}
}
pub fn process_result(
response: &BuildResponse, response: &BuildResponse,
cwd: &Path, cwd: &Path,
should_replace_require_shim: bool, should_replace_require_shim: bool,
@ -1370,21 +1625,16 @@ fn process_result(
.unwrap_or_default(); .unwrap_or_default();
let mut output_infos = Vec::new(); let mut output_infos = Vec::new();
for file in output_files.iter() { for file in output_files.iter() {
let processed_contents =
maybe_process_contents(file, should_replace_require_shim, minified)?;
let path = Path::new(&file.path); let path = Path::new(&file.path);
let relative_path = let relative_path =
pathdiff::diff_paths(path, cwd).unwrap_or_else(|| path.to_path_buf()); pathdiff::diff_paths(path, cwd).unwrap_or_else(|| path.to_path_buf());
let is_js = is_js(path); let is_js = processed_contents.is_js;
let bytes = if is_js || file.path.ends_with("<stdout>") { let bytes = processed_contents
let string = String::from_utf8(file.contents.clone())?; .contents
let string = if should_replace_require_shim { .map(Cow::Owned)
replace_require_shim(&string, minified) .unwrap_or_else(|| Cow::Borrowed(&file.contents));
} else {
string
};
Cow::Owned(string.into_bytes())
} else {
Cow::Borrowed(&file.contents)
};
if file.path.ends_with("<stdout>") { if file.path.ends_with("<stdout>") {
crate::display::write_to_stdout_ignore_sigpipe(bytes.as_slice())?; crate::display::write_to_stdout_ignore_sigpipe(bytes.as_slice())?;

View file

@ -0,0 +1,171 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::sync::Arc;
use deno_bundle_runtime as rt_bundle;
use deno_bundle_runtime::BundleOptions as RtBundleOptions;
use deno_bundle_runtime::BundleProvider;
use deno_core::error::AnyError;
use crate::args::DenoSubcommand;
use crate::args::Flags;
pub struct CliBundleProvider {
flags: Arc<Flags>,
}
impl CliBundleProvider {
pub fn new(flags: Arc<Flags>) -> Self {
Self { flags }
}
}
impl From<RtBundleOptions> for crate::args::BundleFlags {
fn from(value: RtBundleOptions) -> Self {
Self {
entrypoints: value.entrypoints,
output_path: value.output_path,
output_dir: value.output_dir,
external: value.external,
format: value.format,
minify: value.minify,
code_splitting: value.code_splitting,
platform: value.platform,
watch: false,
sourcemap: value.sourcemap,
inline_imports: value.inline_imports,
packages: value.packages,
}
}
}
fn convert_note(note: esbuild_client::protocol::Note) -> rt_bundle::Note {
rt_bundle::Note {
text: note.text,
location: note.location.map(convert_location),
}
}
fn convert_location(
location: esbuild_client::protocol::Location,
) -> rt_bundle::Location {
rt_bundle::Location {
file: location.file,
namespace: Some(location.namespace),
line: location.line,
column: location.column,
length: Some(location.length),
suggestion: Some(location.suggestion),
}
}
fn convert_message(
message: esbuild_client::protocol::Message,
) -> rt_bundle::Message {
rt_bundle::Message {
text: message.text,
location: message.location.map(convert_location),
notes: message.notes.into_iter().map(convert_note).collect(),
}
}
fn convert_build_output_file(
file: esbuild_client::protocol::BuildOutputFile,
) -> rt_bundle::BuildOutputFile {
rt_bundle::BuildOutputFile {
path: file.path,
contents: Some(file.contents),
hash: file.hash,
}
}
pub fn convert_build_response(
response: esbuild_client::protocol::BuildResponse,
) -> rt_bundle::BuildResponse {
rt_bundle::BuildResponse {
errors: response.errors.into_iter().map(convert_message).collect(),
warnings: response.warnings.into_iter().map(convert_message).collect(),
output_files: response
.output_files
.map(|files| files.into_iter().map(convert_build_output_file).collect()),
}
}
fn process_output_files(
bundle_flags: &crate::args::BundleFlags,
response: &mut esbuild_client::protocol::BuildResponse,
) -> Result<(), AnyError> {
if let Some(files) = &mut response.output_files {
for file in files {
let processed_contents = crate::tools::bundle::maybe_process_contents(
file,
crate::tools::bundle::should_replace_require_shim(
bundle_flags.platform,
),
bundle_flags.minify,
)?;
if let Some(contents) = processed_contents.contents {
file.contents = contents;
}
}
}
Ok(())
}
#[async_trait::async_trait]
impl BundleProvider for CliBundleProvider {
async fn bundle(
&self,
options: RtBundleOptions,
) -> Result<rt_bundle::BuildResponse, AnyError> {
let mut flags_clone = (*self.flags).clone();
flags_clone.type_check_mode = crate::args::TypeCheckMode::None;
let write_output = options.write
&& (options.output_dir.is_some() || options.output_path.is_some());
let bundle_flags: crate::args::BundleFlags = options.into();
flags_clone.subcommand = DenoSubcommand::Bundle(bundle_flags.clone());
let (tx, rx) = tokio::sync::oneshot::channel();
std::thread::spawn(move || {
deno_runtime::tokio_util::create_and_run_current_thread(async move {
let flags = Arc::new(flags_clone);
let bundler = match super::bundle_init(flags, &bundle_flags).await {
Ok(bundler) => bundler,
Err(e) => {
log::trace!("bundle_init error: {e:?}");
let _ = tx.send(Err(e));
return Ok(());
}
};
log::trace!("bundler.build");
let mut result = match bundler.build().await {
Ok(result) => result,
Err(e) => {
log::trace!("bundler.build error: {e:?}");
let _ = tx.send(Err(e));
return Ok(());
}
};
log::trace!("process_result");
if write_output {
super::process_result(
&result,
&bundler.cwd,
true,
bundle_flags.minify,
)?;
result.output_files = None;
} else {
process_output_files(&bundle_flags, &mut result)?;
}
log::trace!("convert_build_response");
let result = convert_build_response(result);
log::trace!("send result");
let _ = tx.send(Ok(result));
Ok::<_, AnyError>(())
})
});
log::trace!("rx.await");
let response = rx.await??;
log::trace!("response: {:?}", response);
Ok(response)
}
}

View file

@ -9,6 +9,171 @@
declare namespace Deno { declare namespace Deno {
export {}; // stop default export type behavior export {}; // stop default export type behavior
/**
* @category Bundler
* @experimental
*/
export namespace bundle {
/**
* The target platform of the bundle.
* @category Bundler
* @experimental
*/
export type Platform = "browser" | "deno";
/**
* The output format of the bundle.
* @category Bundler
* @experimental
*/
export type Format = "esm" | "cjs" | "iife";
/**
* The source map type of the bundle.
* @category Bundler
* @experimental
*/
export type SourceMapType = "linked" | "inline" | "external";
/**
* How to handle packages.
*
* - `bundle`: packages are inlined into the bundle.
* - `external`: packages are excluded from the bundle, and treated as external dependencies.
* @category Bundler
* @experimental
*/
export type PackageHandling = "bundle" | "external";
/**
* Options for the bundle.
* @category Bundler
* @experimental
*/
export interface Options {
/**
* The entrypoints of the bundle.
*/
entrypoints: string[];
/**
* Output file path.
*/
outputPath?: string;
/**
* Output directory path.
*/
outputDir?: string;
/**
* External modules to exclude from bundling.
*/
external?: string[];
/**
* Bundle format.
*/
format?: Format;
/**
* Whether to minify the output.
*/
minify?: boolean;
/**
* Whether to enable code splitting.
*/
codeSplitting?: boolean;
/**
* Whether to inline imports.
*/
inlineImports?: boolean;
/**
* How to handle packages.
*/
packages?: PackageHandling;
/**
* Source map configuration.
*/
sourcemap?: SourceMapType;
/**
* Target platform.
*/
platform?: Platform;
/**
* Whether to write the output to the filesystem.
*
* @default true if outputDir or outputPath is set, false otherwise
*/
write?: boolean;
}
/**
* The location of a message.
* @category Bundler
* @experimental
*/
export interface MessageLocation {
file: string;
namespace?: string;
line: number;
column: number;
length: number;
suggestion?: string;
}
/**
* A note about a message.
* @category Bundler
* @experimental
*/
export interface MessageNote {
text: string;
location?: MessageLocation;
}
/**
* A message emitted from the bundler.
* @category Bundler
* @experimental
*/
export interface Message {
text: string;
location?: MessageLocation;
notes?: MessageNote[];
}
/**
* An output file in the bundle.
* @category Bundler
* @experimental
*/
export interface OutputFile {
path: string;
contents?: Uint8Array;
hash: string;
text(): string;
}
/**
* The result of bundling.
* @category Bundler
* @experimental
*/
export interface Result {
errors: Message[];
warnings: Message[];
success: boolean;
outputFiles?: OutputFile[];
}
}
/** **UNSTABLE**: New API, yet to be vetted.
*
* Bundle Typescript/Javascript code
* @category Bundle
* @experimental
*/
export function bundle(
options: Deno.bundle.Options,
): Promise<Deno.bundle.Result>;
/** **UNSTABLE**: New API, yet to be vetted. /** **UNSTABLE**: New API, yet to be vetted.
* *
* Creates a presentable WebGPU surface from given window and * Creates a presentable WebGPU surface from given window and

View file

@ -547,6 +547,7 @@ mod tests {
compiled_wasm_module_store: Default::default(), compiled_wasm_module_store: Default::default(),
v8_code_cache: Default::default(), v8_code_cache: Default::default(),
fs, fs,
bundle_provider: None,
}, },
options, options,
) )

17
ext/bundle/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
# Copyright 2018-2025 the Deno authors. MIT license.
[package]
name = "deno_bundle_runtime"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
async-trait.workspace = true
deno_core.workspace = true
deno_error.workspace = true
deno_web.workspace = true
regex.workspace = true
serde.workspace = true

45
ext/bundle/bundle.ts Normal file
View file

@ -0,0 +1,45 @@
// Copyright 2018-2025 the Deno authors. MIT license.
/// <reference path="../../cli/tsc/dts/lib.deno.unstable.d.ts" />
import { op_bundle } from "ext:core/ops";
import { primordials } from "ext:core/mod.js";
import { TextDecoder } from "ext:deno_web/08_text_encoding.js";
const { SafeArrayIterator, Uint8Array, ObjectPrototypeIsPrototypeOf } =
primordials;
const decoder = new TextDecoder();
export async function bundle(
options: Deno.bundle.Options,
): Promise<Deno.bundle.Result> {
const result = {
success: false,
...await op_bundle(
options,
),
};
result.success = result.errors.length === 0;
for (
const f of new SafeArrayIterator(
// deno-lint-ignore no-explicit-any
result.outputFiles as any ?? [],
)
) {
// deno-lint-ignore no-explicit-any
const file = f as any;
if (file.contents?.length === 0) {
delete file.contents;
file.text = () => "";
} else {
if (!ObjectPrototypeIsPrototypeOf(Uint8Array, file.contents)) {
file.contents = new Uint8Array(file.contents);
}
file.text = () => decoder.decode(file.contents ?? "");
}
}
if (result.outputFiles?.length === 0) {
delete result.outputFiles;
}
return result;
}

241
ext/bundle/src/lib.rs Normal file
View file

@ -0,0 +1,241 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use async_trait::async_trait;
use deno_core::OpState;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_error::JsErrorBox;
deno_core::extension!(
deno_bundle_runtime,
deps = [
deno_web
],
ops = [
op_bundle,
],
esm = [
"bundle.ts"
],
options = {
bundle_provider: Option<Arc<dyn BundleProvider>>,
},
state = |state, options| {
if let Some(bundle_provider) = options.bundle_provider {
state.put(bundle_provider);
} else {
state.put::<Arc<dyn BundleProvider>>(Arc::new(()));
}
},
);
#[async_trait]
impl BundleProvider for () {
async fn bundle(
&self,
_options: BundleOptions,
) -> Result<BuildResponse, AnyError> {
Err(deno_core::anyhow::anyhow!(
"default BundleProvider does not do anything"
))
}
}
#[async_trait]
pub trait BundleProvider: Send + Sync {
async fn bundle(
&self,
options: BundleOptions,
) -> Result<BuildResponse, AnyError>;
}
#[derive(Clone, Debug, Eq, PartialEq, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleOptions {
pub entrypoints: Vec<String>,
#[serde(default)]
pub output_path: Option<String>,
#[serde(default)]
pub output_dir: Option<String>,
#[serde(default)]
pub external: Vec<String>,
#[serde(default)]
pub format: BundleFormat,
#[serde(default)]
pub minify: bool,
#[serde(default)]
pub code_splitting: bool,
#[serde(default = "tru")]
pub inline_imports: bool,
#[serde(default)]
pub packages: PackageHandling,
#[serde(default)]
pub sourcemap: Option<SourceMapType>,
#[serde(default)]
pub platform: BundlePlatform,
#[serde(default = "tru")]
pub write: bool,
}
fn tru() -> bool {
true
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BundlePlatform {
Browser,
#[default]
Deno,
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BundleFormat {
#[default]
Esm,
Cjs,
Iife,
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SourceMapType {
#[default]
Linked,
Inline,
External,
}
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"),
}
}
}
impl std::fmt::Display for SourceMapType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceMapType::Linked => write!(f, "linked"),
SourceMapType::Inline => write!(f, "inline"),
SourceMapType::External => write!(f, "external"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PackageHandling {
#[default]
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(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Message {
pub text: String,
pub location: Option<Location>,
pub notes: Vec<Note>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PartialMessage {
pub id: Option<String>,
pub plugin_name: Option<String>,
pub text: Option<String>,
pub location: Option<Location>,
pub notes: Option<Vec<Note>>,
pub detail: Option<u32>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildOutputFile {
pub path: String,
pub contents: Option<Vec<u8>>,
pub hash: String,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildResponse {
pub errors: Vec<Message>,
pub warnings: Vec<Message>,
pub output_files: Option<Vec<BuildOutputFile>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Note {
pub text: String,
pub location: Option<Location>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Location {
pub file: String,
pub namespace: Option<String>,
pub line: u32,
pub column: u32,
pub length: Option<u32>,
pub suggestion: Option<String>,
}
fn deserialize_regex<'de, D>(deserializer: D) -> Result<regex::Regex, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let s = String::deserialize(deserializer)?;
regex::Regex::new(&s).map_err(serde::de::Error::custom)
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnResolveOptions {
#[serde(deserialize_with = "deserialize_regex")]
pub filter: regex::Regex,
pub namespace: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnLoadOptions {
#[serde(deserialize_with = "deserialize_regex")]
pub filter: regex::Regex,
pub namespace: Option<String>,
}
#[op2(async)]
#[serde]
pub async fn op_bundle(
state: Rc<RefCell<OpState>>,
#[serde] options: BundleOptions,
) -> Result<BuildResponse, JsErrorBox> {
// eprintln!("op_bundle: {:?}", options);
let provider = {
let state = state.borrow();
state.borrow::<Arc<dyn BundleProvider>>().clone()
};
provider
.bundle(options)
.await
.map_err(|e| JsErrorBox::generic(e.to_string()))
}

View file

@ -528,7 +528,7 @@ fn transpile(
let transpiled_source = match transpile_result { let transpiled_source = match transpile_result {
TranspileResult::Owned(source) => source, TranspileResult::Owned(source) => source,
TranspileResult::Cloned(source) => { TranspileResult::Cloned(source) => {
debug_assert!(false, "Transpile owned failed."); // debug_assert!(false, "Transpile owned failed.");
source source
} }
}; };

View file

@ -40,6 +40,7 @@ path = "lib.rs"
[dependencies] [dependencies]
deno_ast = { workspace = true, optional = true } deno_ast = { workspace = true, optional = true }
deno_broadcast_channel.workspace = true deno_broadcast_channel.workspace = true
deno_bundle_runtime.workspace = true
deno_cache.workspace = true deno_cache.workspace = true
deno_canvas.workspace = true deno_canvas.workspace = true
deno_console.workspace = true deno_console.workspace = true
@ -74,6 +75,7 @@ deno_websocket.workspace = true
deno_webstorage.workspace = true deno_webstorage.workspace = true
node_resolver = { workspace = true, features = ["sync"] } node_resolver = { workspace = true, features = ["sync"] }
async-trait.workspace = true
color-print.workspace = true color-print.workspace = true
encoding_rs.workspace = true encoding_rs.workspace = true
fastwebsockets.workspace = true fastwebsockets.workspace = true
@ -81,10 +83,12 @@ http.workspace = true
http-body-util.workspace = true http-body-util.workspace = true
hyper.workspace = true hyper.workspace = true
hyper-util.workspace = true hyper-util.workspace = true
indexmap.workspace = true
libc.workspace = true libc.workspace = true
log.workspace = true log.workspace = true
notify.workspace = true notify.workspace = true
once_cell.workspace = true once_cell.workspace = true
regex.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] } rustyline = { workspace = true, features = ["custom-bindings"] }
same-file.workspace = true same-file.workspace = true
serde.workspace = true serde.workspace = true

View file

@ -224,4 +224,12 @@ pub static FEATURE_DESCRIPTIONS: &[UnstableFeatureDescription] = &[
config_option: ConfigFileOption::SameAsFlagName, config_option: ConfigFileOption::SameAsFlagName,
env_var: None, env_var: None,
}, },
UnstableFeatureDescription {
name: "bundle",
help_text: "Enable unstable bundle runtime API",
show_in_help: true,
kind: UnstableFeatureKind::Runtime,
config_option: ConfigFileOption::SameAsFlagName,
env_var: None,
},
]; ];

View file

@ -8,20 +8,21 @@
export const unstableIds = { export const unstableIds = {
broadcastChannel: 1, broadcastChannel: 1,
cron: 3, bundle: 2,
ffi: 5, cron: 4,
fs: 6, ffi: 6,
http: 7, fs: 7,
kv: 8, http: 8,
net: 11, kv: 9,
noLegacyAbort: 12, net: 12,
nodeGlobals: 13, noLegacyAbort: 13,
otel: 15, nodeGlobals: 14,
process: 16, otel: 16,
rawImports: 17, process: 17,
temporal: 20, rawImports: 18,
unsafeProto: 21, temporal: 21,
vsock: 22, unsafeProto: 22,
webgpu: 23, vsock: 23,
workerOptions: 24, webgpu: 24,
workerOptions: 25,
}; };

View file

@ -25,12 +25,21 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "broadcast-channel", config_file_option: "broadcast-channel",
}, },
UnstableFeatureDefinition {
name: "bundle",
flag_name: "unstable-bundle",
help_text: "Enable unstable bundle runtime API",
show_in_help: true,
id: 2,
kind: UnstableFeatureKind::Runtime,
config_file_option: "bundle",
},
UnstableFeatureDefinition { UnstableFeatureDefinition {
name: "byonm", name: "byonm",
flag_name: "unstable-byonm", flag_name: "unstable-byonm",
help_text: "", help_text: "",
show_in_help: false, show_in_help: false,
id: 2, id: 3,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "byonm", config_file_option: "byonm",
}, },
@ -39,7 +48,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-cron", flag_name: "unstable-cron",
help_text: "Enable unstable `Deno.cron` API", help_text: "Enable unstable `Deno.cron` API",
show_in_help: true, show_in_help: true,
id: 3, id: 4,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "cron", config_file_option: "cron",
}, },
@ -48,7 +57,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-detect-cjs", flag_name: "unstable-detect-cjs",
help_text: "Treats ambiguous .js, .jsx, .ts, .tsx files as CommonJS modules in more cases", help_text: "Treats ambiguous .js, .jsx, .ts, .tsx files as CommonJS modules in more cases",
show_in_help: true, show_in_help: true,
id: 4, id: 5,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "detect-cjs", config_file_option: "detect-cjs",
}, },
@ -57,7 +66,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-ffi", flag_name: "unstable-ffi",
help_text: "Enable unstable FFI APIs", help_text: "Enable unstable FFI APIs",
show_in_help: false, show_in_help: false,
id: 5, id: 6,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "ffi", config_file_option: "ffi",
}, },
@ -66,7 +75,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-fs", flag_name: "unstable-fs",
help_text: "Enable unstable file system APIs", help_text: "Enable unstable file system APIs",
show_in_help: false, show_in_help: false,
id: 6, id: 7,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "fs", config_file_option: "fs",
}, },
@ -75,7 +84,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-http", flag_name: "unstable-http",
help_text: "Enable unstable HTTP APIs", help_text: "Enable unstable HTTP APIs",
show_in_help: false, show_in_help: false,
id: 7, id: 8,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "http", config_file_option: "http",
}, },
@ -84,7 +93,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-kv", flag_name: "unstable-kv",
help_text: "Enable unstable KV APIs", help_text: "Enable unstable KV APIs",
show_in_help: true, show_in_help: true,
id: 8, id: 9,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "kv", config_file_option: "kv",
}, },
@ -93,7 +102,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-lazy-dynamic-imports", flag_name: "unstable-lazy-dynamic-imports",
help_text: "Lazily loads statically analyzable dynamic imports when not running with type checking. Warning: This may change the order of semver specifier resolution.", help_text: "Lazily loads statically analyzable dynamic imports when not running with type checking. Warning: This may change the order of semver specifier resolution.",
show_in_help: true, show_in_help: true,
id: 9, id: 10,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "lazy-dynamic-imports", config_file_option: "lazy-dynamic-imports",
}, },
@ -102,7 +111,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-lockfile-v5", flag_name: "unstable-lockfile-v5",
help_text: "Enable unstable lockfile v5", help_text: "Enable unstable lockfile v5",
show_in_help: true, show_in_help: true,
id: 10, id: 11,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "lockfile-v5", config_file_option: "lockfile-v5",
}, },
@ -111,7 +120,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-net", flag_name: "unstable-net",
help_text: "enable unstable net APIs", help_text: "enable unstable net APIs",
show_in_help: true, show_in_help: true,
id: 11, id: 12,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "net", config_file_option: "net",
}, },
@ -120,7 +129,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-no-legacy-abort", flag_name: "unstable-no-legacy-abort",
help_text: "Enable abort signal in Deno.serve without legacy behavior. This will not abort the server when the request is handled successfully.", help_text: "Enable abort signal in Deno.serve without legacy behavior. This will not abort the server when the request is handled successfully.",
show_in_help: true, show_in_help: true,
id: 12, id: 13,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "no-legacy-abort", config_file_option: "no-legacy-abort",
}, },
@ -129,7 +138,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-node-globals", flag_name: "unstable-node-globals",
help_text: "Expose Node globals everywhere", help_text: "Expose Node globals everywhere",
show_in_help: false, show_in_help: false,
id: 13, id: 14,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "node-globals", config_file_option: "node-globals",
}, },
@ -138,7 +147,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-npm-lazy-caching", flag_name: "unstable-npm-lazy-caching",
help_text: "Enable unstable lazy caching of npm dependencies, downloading them only as needed (disabled: all npm packages in package.json are installed on startup; enabled: only npm packages that are actually referenced in an import are installed", help_text: "Enable unstable lazy caching of npm dependencies, downloading them only as needed (disabled: all npm packages in package.json are installed on startup; enabled: only npm packages that are actually referenced in an import are installed",
show_in_help: true, show_in_help: true,
id: 14, id: 15,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "npm-lazy-caching", config_file_option: "npm-lazy-caching",
}, },
@ -147,7 +156,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-otel", flag_name: "unstable-otel",
help_text: "Enable unstable OpenTelemetry features", help_text: "Enable unstable OpenTelemetry features",
show_in_help: false, show_in_help: false,
id: 15, id: 16,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "otel", config_file_option: "otel",
}, },
@ -156,7 +165,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-process", flag_name: "unstable-process",
help_text: "Enable unstable process APIs", help_text: "Enable unstable process APIs",
show_in_help: false, show_in_help: false,
id: 16, id: 17,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "process", config_file_option: "process",
}, },
@ -165,7 +174,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-raw-imports", flag_name: "unstable-raw-imports",
help_text: "Enable unstable 'bytes' and 'text' imports.", help_text: "Enable unstable 'bytes' and 'text' imports.",
show_in_help: true, show_in_help: true,
id: 17, id: 18,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "raw-imports", config_file_option: "raw-imports",
}, },
@ -174,7 +183,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-sloppy-imports", flag_name: "unstable-sloppy-imports",
help_text: "Enable unstable resolving of specifiers by extension probing, .js to .ts, and directory probing", help_text: "Enable unstable resolving of specifiers by extension probing, .js to .ts, and directory probing",
show_in_help: true, show_in_help: true,
id: 18, id: 19,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "sloppy-imports", config_file_option: "sloppy-imports",
}, },
@ -183,7 +192,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-subdomain-wildcards", flag_name: "unstable-subdomain-wildcards",
help_text: "Enable subdomain wildcards support for the `--allow-net` flag", help_text: "Enable subdomain wildcards support for the `--allow-net` flag",
show_in_help: false, show_in_help: false,
id: 19, id: 20,
kind: UnstableFeatureKind::Cli, kind: UnstableFeatureKind::Cli,
config_file_option: "subdomain-wildcards", config_file_option: "subdomain-wildcards",
}, },
@ -192,7 +201,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-temporal", flag_name: "unstable-temporal",
help_text: "Enable unstable Temporal API", help_text: "Enable unstable Temporal API",
show_in_help: true, show_in_help: true,
id: 20, id: 21,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "temporal", config_file_option: "temporal",
}, },
@ -201,7 +210,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-unsafe-proto", flag_name: "unstable-unsafe-proto",
help_text: "Enable unsafe __proto__ support. This is a security risk.", help_text: "Enable unsafe __proto__ support. This is a security risk.",
show_in_help: true, show_in_help: true,
id: 21, id: 22,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "unsafe-proto", config_file_option: "unsafe-proto",
}, },
@ -210,7 +219,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-vsock", flag_name: "unstable-vsock",
help_text: "Enable unstable VSOCK APIs", help_text: "Enable unstable VSOCK APIs",
show_in_help: false, show_in_help: false,
id: 22, id: 23,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "vsock", config_file_option: "vsock",
}, },
@ -219,7 +228,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-webgpu", flag_name: "unstable-webgpu",
help_text: "Enable unstable WebGPU APIs", help_text: "Enable unstable WebGPU APIs",
show_in_help: true, show_in_help: true,
id: 23, id: 24,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "webgpu", config_file_option: "webgpu",
}, },
@ -228,7 +237,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[
flag_name: "unstable-worker-options", flag_name: "unstable-worker-options",
help_text: "Enable unstable Web Worker APIs", help_text: "Enable unstable Web Worker APIs",
show_in_help: true, show_in_help: true,
id: 24, id: 25,
kind: UnstableFeatureKind::Runtime, kind: UnstableFeatureKind::Runtime,
config_file_option: "worker-options", config_file_option: "worker-options",
}, },

View file

@ -33,6 +33,7 @@ import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
import * as telemetry from "ext:deno_telemetry/telemetry.ts"; import * as telemetry from "ext:deno_telemetry/telemetry.ts";
import { unstableIds } from "ext:deno_features/flags.js"; import { unstableIds } from "ext:deno_features/flags.js";
import { loadWebGPU } from "ext:deno_webgpu/00_init.js"; import { loadWebGPU } from "ext:deno_webgpu/00_init.js";
import { bundle } from "ext:deno_bundle_runtime/bundle.ts";
const { ObjectDefineProperties, Float64Array } = primordials; const { ObjectDefineProperties, Float64Array } = primordials;
@ -171,6 +172,10 @@ const denoNs = {
const denoNsUnstableById = { __proto__: null }; const denoNsUnstableById = { __proto__: null };
denoNsUnstableById[unstableIds.bundle] = {
bundle,
};
// denoNsUnstableById[unstableIds.broadcastChannel] = { __proto__: null } // denoNsUnstableById[unstableIds.broadcastChannel] = { __proto__: null }
denoNsUnstableById[unstableIds.cron] = { denoNsUnstableById[unstableIds.cron] = {

View file

@ -23,7 +23,8 @@ extension!(runtime,
deno_napi, deno_napi,
deno_http, deno_http,
deno_io, deno_io,
deno_fs deno_fs,
deno_bundle_runtime
], ],
esm_entry_point = "ext:runtime/90_deno_ns.js", esm_entry_point = "ext:runtime/90_deno_ns.js",
esm = [ esm = [

View file

@ -64,6 +64,7 @@ pub fn create_runtime_snapshot(
ops::permissions::deno_permissions::lazy_init(), ops::permissions::deno_permissions::lazy_init(),
ops::tty::deno_tty::lazy_init(), ops::tty::deno_tty::lazy_init(),
ops::http::deno_http_runtime::lazy_init(), ops::http::deno_http_runtime::lazy_init(),
deno_bundle_runtime::deno_bundle_runtime::lazy_init(),
ops::bootstrap::deno_bootstrap::init(Some(snapshot_options), false), ops::bootstrap::deno_bootstrap::init(Some(snapshot_options), false),
runtime::lazy_init(), runtime::lazy_init(),
ops::web_worker::deno_web_worker::lazy_init(), ops::web_worker::deno_web_worker::lazy_init(),

View file

@ -257,6 +257,7 @@ pub fn get_extensions_in_snapshot() -> Vec<Extension> {
ops::permissions::deno_permissions::init(), ops::permissions::deno_permissions::init(),
ops::tty::deno_tty::init(), ops::tty::deno_tty::init(),
ops::http::deno_http_runtime::init(), ops::http::deno_http_runtime::init(),
deno_bundle_runtime::deno_bundle_runtime::init(None),
ops::bootstrap::deno_bootstrap::init(None, false), ops::bootstrap::deno_bootstrap::init(None, false),
runtime::init(), runtime::init(),
ops::web_worker::deno_web_worker::init(), ops::web_worker::deno_web_worker::init(),

View file

@ -600,6 +600,7 @@ impl WebWorker {
ops::permissions::deno_permissions::init(), ops::permissions::deno_permissions::init(),
ops::tty::deno_tty::init(), ops::tty::deno_tty::init(),
ops::http::deno_http_runtime::init(), ops::http::deno_http_runtime::init(),
deno_bundle_runtime::deno_bundle_runtime::init(None),
ops::bootstrap::deno_bootstrap::init( ops::bootstrap::deno_bootstrap::init(
options.startup_snapshot.and_then(|_| Default::default()), options.startup_snapshot.and_then(|_| Default::default()),
false, false,

View file

@ -214,6 +214,8 @@ pub struct WorkerServiceOptions<
/// V8 code cache for module and script source code. /// V8 code cache for module and script source code.
pub v8_code_cache: Option<Arc<dyn CodeCache>>, pub v8_code_cache: Option<Arc<dyn CodeCache>>,
pub bundle_provider: Option<Arc<dyn deno_bundle_runtime::BundleProvider>>,
} }
pub struct WorkerOptions { pub struct WorkerOptions {
@ -602,6 +604,9 @@ impl MainWorker {
options.create_web_worker_cb.clone(), options.create_web_worker_cb.clone(),
options.format_js_error_fn.clone(), options.format_js_error_fn.clone(),
), ),
deno_bundle_runtime::deno_bundle_runtime::args(
services.bundle_provider.clone(),
),
]) ])
.unwrap(); .unwrap();
@ -1080,6 +1085,7 @@ fn common_extensions<
ops::permissions::deno_permissions::init(), ops::permissions::deno_permissions::init(),
ops::tty::deno_tty::init(), ops::tty::deno_tty::init(),
ops::http::deno_http_runtime::init(), ops::http::deno_http_runtime::init(),
deno_bundle_runtime::deno_bundle_runtime::lazy_init(),
ops::bootstrap::deno_bootstrap::init( ops::bootstrap::deno_bootstrap::init(
has_snapshot.then(Default::default), has_snapshot.then(Default::default),
unconfigured_runtime, unconfigured_runtime,

View file

@ -6,6 +6,7 @@ use std::time::Duration;
use std::time::Instant; use std::time::Instant;
use test_util as util; use test_util as util;
use test_util::TestContextBuilder;
util::unit_test_factory!( util::unit_test_factory!(
js_unit_test, js_unit_test,
@ -17,6 +18,7 @@ util::unit_test_factory!(
body_test, body_test,
broadcast_channel_test, broadcast_channel_test,
build_test, build_test,
bundle_test,
cache_api_test, cache_api_test,
chmod_test, chmod_test,
chown_test, chown_test,
@ -117,8 +119,18 @@ util::unit_test_factory!(
fn js_unit_test(test: String) { fn js_unit_test(test: String) {
let _g = util::http_server(); let _g = util::http_server();
let context = TestContextBuilder::new()
.add_npm_env_vars()
.use_http_server()
.build();
let mut deno = util::deno_cmd() let mut deno = if test == "bundle_test" {
context.new_command()
} else {
util::deno_cmd()
};
deno = deno
.current_dir(util::root_path()) .current_dir(util::root_path())
.arg("test") .arg("test")
.arg("--config") .arg("--config")
@ -132,6 +144,10 @@ fn js_unit_test(test: String) {
.arg("--location=http://127.0.0.1:4545/") .arg("--location=http://127.0.0.1:4545/")
.arg("--no-prompt"); .arg("--no-prompt");
if test == "bundle_test" {
deno = deno.arg("--unstable-bundle");
}
if test == "broadcast_channel_test" { if test == "broadcast_channel_test" {
deno = deno.arg("--unstable-broadcast-channel"); deno = deno.arg("--unstable-broadcast-channel");
} }

315
tests/unit/bundle_test.ts Normal file
View file

@ -0,0 +1,315 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import {
assert,
assertEquals,
assertFalse,
assertStringIncludes,
unindent,
} from "./test_util.ts";
import { join, toFileUrl } from "@std/path";
class TempDir implements AsyncDisposable, Disposable {
private path: string;
constructor(options?: Deno.MakeTempOptions) {
this.path = Deno.makeTempDirSync(options);
}
async [Symbol.asyncDispose]() {
await Deno.remove(this.path, { recursive: true });
}
[Symbol.dispose]() {
Deno.removeSync(this.path, { recursive: true });
}
join(path: string) {
return join(this.path, path);
}
}
class TempFile implements AsyncDisposable, Disposable {
#path: string;
constructor(options?: Deno.MakeTempOptions) {
this.#path = Deno.makeTempFileSync(options);
}
async [Symbol.asyncDispose]() {
await Deno.remove(this.#path);
}
[Symbol.dispose]() {
Deno.removeSync(this.#path);
}
get path() {
return this.#path;
}
}
Deno.test("bundle: basic in-memory bundle succeeds and returns content", async () => {
using dir = new TempDir();
const entry = dir.join("index.ts");
const dep = dir.join("mod.ts");
await Deno.writeTextFile(
dep,
[
"export function greet(name: string) {",
" return `hello ${name}`;",
"}",
].join("\n"),
);
await Deno.writeTextFile(
entry,
[
"import { greet } from './mod.ts';",
"console.log(greet('world'));",
].join("\n"),
);
const result = await Deno.bundle({
entrypoints: [entry],
// keep readable to assert on content
minify: false,
write: false,
});
assertEquals(result.success, true);
assertEquals(result.errors.length, 0);
assert(Array.isArray(result.warnings));
assert(result.outputFiles !== undefined);
const withContent = result.outputFiles!.filter((f) => !!f.contents);
assert(withContent.length >= 1);
const content = withContent[0].text();
// should contain the string literal from the source
assertStringIncludes(content, "hello ");
// stripped of TS types
assertFalse(content.includes(": string"));
});
Deno.test("bundle: write to outputDir omits outputFiles and writes files", async () => {
using dir = new TempDir();
const srcDir = dir.join("src");
const outDir = dir.join("dist");
await Deno.mkdir(srcDir, { recursive: true });
const entry = join(srcDir, "main.ts");
const dep = join(srcDir, "util.ts");
await Deno.writeTextFile(
dep,
unindent`
export const msg: string = 'Hello bundle write';
export function upper(s: string) { return s.toUpperCase(); }
`,
);
await Deno.writeTextFile(
entry,
unindent`
import { msg, upper } from './util.ts';
console.log(upper(msg));
`,
);
const result = await Deno.bundle({
entrypoints: [entry],
outputDir: outDir,
// default write is true when outputDir/outputPath is set
// but be explicit here
write: true,
minify: false,
});
assertEquals(result.success, true);
assertEquals(result.errors.length, 0);
// when writing, the provider returns `null` for outputFiles currently
assert(result.outputFiles == null);
// verify a JS file was written to the output directory
const files = [] as string[];
for await (const e of Deno.readDir(outDir)) {
if (e.isFile && e.name.endsWith(".js")) files.push(e.name);
}
assert(files.length >= 1);
// read first file and check expected content present
const outJsPath = join(outDir, files[0]);
const outContent = await Deno.readTextFile(outJsPath);
assertStringIncludes(outContent, "Hello bundle write");
});
Deno.test("bundle: minify produces smaller output", async () => {
using dir = new TempDir();
const entry = dir.join("index.ts");
const dep = dir.join("calc.ts");
await Deno.writeTextFile(
dep,
unindent`
export function add(a: number, b: number) {
/* lots of spacing and comments to be minified */
const sum = a + b; // trailing comment
return sum;
}
`,
);
await Deno.writeTextFile(
entry,
unindent`
import { add } from './calc.ts';
console.log(add( 1, 2));
`,
);
const normal = await Deno.bundle({
entrypoints: [entry],
minify: false,
write: false,
});
assertEquals(normal.success, true);
const normalJs = normal.outputFiles!.find((f) => !!f.contents)!;
const minified = await Deno.bundle({
entrypoints: [entry],
minify: true,
write: false,
});
assertEquals(minified.success, true);
const minJs = minified.outputFiles!.find((f) => !!f.contents)!;
assert(minJs.text().length < normalJs.text().length);
});
Deno.test("bundle: code splitting with multiple entrypoints", async () => {
using dir = new TempDir();
const shared = dir.join("shared.ts");
const a = dir.join("a.ts");
const b = dir.join("b.ts");
await Deno.writeTextFile(
shared,
unindent`
export const shared = 'shared chunk';
`,
);
await Deno.writeTextFile(
a,
unindent`
import { shared } from './shared.ts';
console.log('a', shared);
`,
);
await Deno.writeTextFile(
b,
unindent`
import { shared } from './shared.ts';
console.log('b', shared);
`,
);
const outDir = dir.join("dist");
const result = await Deno.bundle({
entrypoints: [a, b],
codeSplitting: true,
// esbuild requires an output directory when splitting
outputDir: outDir,
write: false,
minify: false,
});
assertEquals(result.success, true);
assert(result.outputFiles !== undefined);
const jsFiles = result.outputFiles!.filter((f) => !!f.contents);
// 2 entries + at least 1 shared chunk
assert(jsFiles.length >= 3);
});
Deno.test("bundle: inline sourcemap is present", async () => {
using dir = new TempDir();
const entry = dir.join("index.ts");
await Deno.writeTextFile(entry, "export const x = 1;\n");
const result = await Deno.bundle({
entrypoints: [entry],
sourcemap: "inline",
write: false,
minify: false,
});
assertEquals(result.success, true);
const js = result.outputFiles!.find((f) => !!f.contents)!;
assertStringIncludes(
js.text(),
"sourceMappingURL=data:application/json;base64",
);
});
Deno.test("bundle: missing entrypoint rejects", async () => {
using dir = new TempDir();
const missing = dir.join("does_not_exist.ts");
let threw = false;
try {
await Deno.bundle({
entrypoints: [missing],
write: false,
});
} catch (_e) {
threw = true;
}
assert(threw);
});
Deno.test("bundle: returns errors for unresolved import", async () => {
using dir = new TempDir();
const entry = dir.join("main.ts");
// entry exists, but imports a non-existent module
await Deno.writeTextFile(
entry,
unindent`
import './missing.ts';
export const value = 42;
`,
);
const result = await Deno.bundle({
entrypoints: [entry],
write: false,
minify: false,
});
assertEquals(result.success, false);
assert(result.errors.length > 0);
// should reference the missing import path in one of the error messages
const texts = result.errors.map((e) => e.text).join("\n");
assertStringIncludes(texts, "missing.ts");
});
// deno-lint-ignore no-explicit-any
async function evalEsmString(code: string): Promise<any> {
await using file = new TempFile({ suffix: ".js" });
Deno.writeTextFileSync(file.path, code);
return await import(toFileUrl(file.path).toString());
}
Deno.test("bundle: replaces require shim when platform is deno", async () => {
using dir = new TempDir();
const entry = dir.join("index.cjs");
const input = unindent`
const sep = require("node:path").sep;
module.exports = ["good", sep.length];
`;
await Deno.writeTextFile(entry, input);
const result = await Deno.bundle({
entrypoints: [entry],
platform: "deno",
write: false,
});
assertEquals(result.success, true);
const js = result.outputFiles!.find((f) => !!f.contents)!;
const output = await evalEsmString(js.text());
assertEquals(output.default, ["good", 1]);
});

View file

@ -1349,3 +1349,38 @@ export class BufWriterSync extends AbstractBufBase implements WriterSync {
return totalBytesWritten; return totalBytesWritten;
} }
} }
/**
* Trims the minimum indent from each line of a multiline string,
* removing leading and trailing blank lines.
* @param text
* @returns the trimmed string
*/
export function trimIndent(text: string): string {
if (text.startsWith("\n")) {
text = text.slice(1);
}
const lines = text.split("\n");
const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
const indent = nonEmptyLines.length > 0
? Math.min(
...nonEmptyLines.map((line) => line.length - line.trimStart().length),
)
: 0;
return lines
.map((line) => {
if (line.length <= indent) {
return line.replace(/^ +/, "");
} else {
return line.slice(indent);
}
})
.join("\n");
}
export function unindent(strings: TemplateStringsArray, ...values: unknown[]) {
// normal template substitution
const raw = String.raw({ raw: strings }, ...values);
return trimIndent(raw);
}