diff --git a/Cargo.lock b/Cargo.lock index d0a3863d2a..dccd8003eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -721,9 +727,9 @@ dependencies = [ [[package]] name = "ast_node" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" +checksum = "c6ea666cbca3830383d6ce836593e88ade6f61b12c6066c09dc1257c3079a5b6" dependencies = [ "proc-macro2", "quote", @@ -1451,9 +1457,9 @@ dependencies = [ [[package]] name = "better_scoped_tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" dependencies = [ "scoped-tls", ] @@ -1834,27 +1840,9 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" -dependencies = [ - "ahash 0.8.12", - "chrono", - "either", - "indexmap 2.9.0", - "itertools 0.13.0", - "nom", - "once_cell", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "browserslist-rs" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95aff901882c66e4b642f3f788ceee152ef44f8a5ef12cb1ddee5479c483be" +checksum = "abf24e007a83ff1f58d2441b459fa26124aa1a7367da88948e9940f14e723d06" dependencies = [ "ahash 0.8.12", "chrono", @@ -1965,6 +1953,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytes-str" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] + [[package]] name = "bytesize" version = "1.3.3" @@ -2525,6 +2523,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa 1.0.15", + "ryu", + "static_assertions", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -3615,6 +3626,7 @@ dependencies = [ "dioxus-server", "dioxus-signals", "dioxus-ssr", + "dioxus-use-js-macro", "dioxus-web", "dioxus_server_macro", "env_logger 0.11.8", @@ -3796,7 +3808,7 @@ name = "dioxus-cli-opt" version = "0.7.0-alpha.1" dependencies = [ "anyhow", - "browserslist-rs 0.16.0", + "browserslist-rs", "built", "codemap", "const-serialize", @@ -3813,10 +3825,10 @@ dependencies = [ "serde", "serde_json", "swc_allocator", - "swc_atoms", + "swc_atoms 6.0.0", "swc_bundler", "swc_cached", - "swc_common", + "swc_common 13.0.1", "swc_config", "swc_config_macro", "swc_ecma_ast", @@ -4567,6 +4579,19 @@ dependencies = [ "manganis", ] +[[package]] +name = "dioxus-use-js-macro" +version = "0.7.0-alpha.1" +dependencies = [ + "proc-macro2", + "quote", + "swc_common 13.0.1", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_visit", + "syn 2.0.101", +] + [[package]] name = "dioxus-web" version = "0.7.0-alpha.1" @@ -5478,9 +5503,9 @@ dependencies = [ [[package]] name = "from_variant" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" +checksum = "accfe8b52dc15c1bace718020831f72ce91a4c096709a4d733868f4f4034e22a" dependencies = [ "proc-macro2", "swc_macros_common", @@ -6956,15 +6981,15 @@ dependencies = [ [[package]] name = "hstr" -version = "0.2.17" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" +checksum = "2d1638d2018a21b9ff65d7fc28c2271c76a5af6ff4f621b204d032bc649763a4" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", "once_cell", "phf 0.11.3", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "triomphe", ] @@ -8353,7 +8378,7 @@ checksum = "9a73ffa17de66534e4b527232f44aa0a89fad22c4f4e0735f9be35494f058e54" dependencies = [ "ahash 0.8.12", "bitflags 2.9.1", - "browserslist-rs 0.18.1", + "browserslist-rs", "const-str 0.3.2", "cssparser 0.33.0", "cssparser-color", @@ -10049,6 +10074,25 @@ dependencies = [ "system-deps", ] +[[package]] +name = "par-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" +dependencies = [ + "once_cell", +] + +[[package]] +name = "par-iter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eae0176a010bb94b9a67f0eb9da0fd31410817d58850649c54f485124c9a71a" +dependencies = [ + "either", + "par-core", +] + [[package]] name = "parcel_selectors" version = "0.28.2" @@ -11251,7 +11295,7 @@ checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ "bitflags 2.9.1", "cassowary", - "compact_str", + "compact_str 0.8.1", "crossterm 0.28.1", "indoc", "instability", @@ -11449,6 +11493,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regress" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" +dependencies = [ + "hashbrown 0.15.3", + "memchr", +] + [[package]] name = "relative-path" version = "1.9.3" @@ -12282,6 +12336,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.219" @@ -12897,24 +12957,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "sourcemap" -version = "9.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdee719193ae5c919a3ee43f64c2c0dd87f9b9a451d67918a2a5ec2e3c70561c" -dependencies = [ - "base64-simd 0.8.0", - "bitvec", - "data-encoding", - "debugid", - "if_chain", - "rustc-hash 2.1.1", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - [[package]] name = "spake2" version = "0.4.0" @@ -13257,9 +13299,9 @@ dependencies = [ [[package]] name = "string_enum" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" +checksum = "24b0e5369ebc6ec5fadbc400599467eb6ba5a614c03de094fcb233dddac2f5f4" dependencies = [ "proc-macro2", "quote", @@ -13597,34 +13639,47 @@ dependencies = [ [[package]] name = "swc_allocator" -version = "2.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" +checksum = "cc6b926f0d94bbb34031fe5449428cfa1268cdc0b31158d6ad9c97e0fc1e79dd" dependencies = [ + "allocator-api2", "bumpalo", "hashbrown 0.14.5", "ptr_meta 0.3.0", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "triomphe", ] [[package]] name = "swc_atoms" -version = "3.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a640bf2e4430a149c87b5eaf377477ce8615ca7cb808054dd20e79e42da5d6ba" +checksum = "9d7077ba879f95406459bc0c81f3141c529b34580bc64d7ab7bd15e7118a0391" dependencies = [ "hstr", "once_cell", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", + "serde", +] + +[[package]] +name = "swc_atoms" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf4c40238f7224596754940676547dab6bbf8f33d9f4560b966fc66f2fe00db" +dependencies = [ + "hstr", + "once_cell", + "rustc-hash 2.1.1", "serde", ] [[package]] name = "swc_bundler" -version = "7.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88a91910cd8430f88f8987019cf3a96d92a5d5dded3e0ba8203e0379e4a2f6f" +checksum = "179ea4f155224df226993151f012b0fabed186e10e42daf26de3b73a99de79a4" dependencies = [ "anyhow", "crc 2.1.0", @@ -13632,11 +13687,12 @@ dependencies = [ "is-macro", "once_cell", "parking_lot", - "petgraph 0.6.5", + "petgraph 0.7.1", "radix_fmt", "relative-path", - "swc_atoms", - "swc_common", + "rustc-hash 2.1.1", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_loader", @@ -13645,31 +13701,31 @@ dependencies = [ "swc_ecma_transforms_optimization", "swc_ecma_utils", "swc_ecma_visit", - "swc_fast_graph", "swc_graph_analyzer", "tracing", ] [[package]] name = "swc_cached" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6a5ef4cfec51d3fa30b73600f206453a37fc30cf1141e4644a57b1ed88616" +checksum = "d7133338c3bef796430deced151b0eaa5430710a90e38da19e8e3045e8e36eeb" dependencies = [ - "ahash 0.8.12", "anyhow", "dashmap 5.5.3", "once_cell", "regex", + "rustc-hash 2.1.1", "serde", ] [[package]] name = "swc_common" -version = "5.0.0" +version = "8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" +checksum = "4f8c8e4348383e4154f8d384cdad7e48f5d6d3daef78af376ac4e5ddbbf60c88" dependencies = [ + "anyhow", "ast_node", "better_scoped_tls", "cfg-if", @@ -13678,11 +13734,39 @@ dependencies = [ "new_debug_unreachable", "num-bigint", "once_cell", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", + "serde", + "siphasher 0.3.11", + "swc_allocator", + "swc_atoms 5.0.0", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width 0.1.14", + "url", +] + +[[package]] +name = "swc_common" +version = "13.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865f71f363e63306cedec3f3cf1cb9e80acaa9229261ba2569467a19060c7c8" +dependencies = [ + "anyhow", + "ast_node", + "better_scoped_tls", + "bytes-str", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash 2.1.1", "serde", "siphasher 0.3.11", "swc_allocator", - "swc_atoms", + "swc_atoms 6.0.0", "swc_eq_ignore_macros", "swc_visit", "termcolor", @@ -13693,24 +13777,30 @@ dependencies = [ [[package]] name = "swc_config" -version = "1.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" +checksum = "d94f41e0f3c4c119a06af5e164674b63ae7eb6d7c1c60e46036c4a548f9fbe44" dependencies = [ "anyhow", + "bytes-str", + "dashmap 5.5.3", + "globset", "indexmap 2.9.0", + "once_cell", + "regex", + "regress", + "rustc-hash 2.1.1", "serde", "serde_json", - "sourcemap", - "swc_cached", "swc_config_macro", + "swc_sourcemap", ] [[package]] name = "swc_config_macro" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" +checksum = "7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3" dependencies = [ "proc-macro2", "quote", @@ -13720,48 +13810,54 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "5.0.1" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f448db2d1c52ffd2bd3788d89cafd8b5a75b97f0dc8aae00874dda2647f6b6" +checksum = "f1ddc264ed13ae03aa30e1c89798502f9ddbe765a4ad695054add1074ffbc5cb" dependencies = [ "bitflags 2.9.1", "is-macro", "num-bigint", + "once_cell", "phf 0.11.3", + "rustc-hash 2.1.1", "scoped-tls", "serde", "string_enum", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_visit", "unicode-id-start", ] [[package]] name = "swc_ecma_codegen" -version = "5.0.1" +version = "15.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f93692de35a77d920ce8d96a46217735e5f86bf42f76cc8f1a60628c347c4c8" +checksum = "1719b3bb5bff1c99cfb6fbd2129e7a7a363d3ddf50e22b95143c1877559d872a" dependencies = [ + "ascii", + "compact_str 0.7.1", "memchr", "num-bigint", "once_cell", "regex", + "rustc-hash 2.1.1", + "ryu-js", "serde", - "sourcemap", "swc_allocator", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_codegen_macros", + "swc_sourcemap", "tracing", ] [[package]] name = "swc_ecma_codegen_macros" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9a42f479a6475647e248fa9750982c87cd985e19d1016a1fc18a70682305d1" +checksum = "845c8312c82545780f837992bb15fff1dc3464f644465d5ed0abd1196cd090d3" dependencies = [ "proc-macro2", "quote", @@ -13769,11 +13865,37 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "swc_ecma_lexer" +version = "17.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc377700d6e293f2c357660769625b97f9318b057d90ebc11c5f1e6d3b22729" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "either", + "new_debug_unreachable", + "num-bigint", + "num-traits", + "phf 0.11.3", + "rustc-hash 2.1.1", + "seq-macro", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms 6.0.0", + "swc_common 13.0.1", + "swc_ecma_ast", + "tracing", + "typed-arena", +] + [[package]] name = "swc_ecma_loader" -version = "5.0.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a19b132079bfcd19d6fdabce7e55ece93a30787f3b8684c8646ddaf2237812d" +checksum = "9b08fa5f55ac0188a35a75f27574329e129b06cbb0d517ab7b2093eff45b2745" dependencies = [ "anyhow", "dashmap 5.5.3", @@ -13783,35 +13905,39 @@ dependencies = [ "parking_lot", "path-clean", "pathdiff", + "rustc-hash 2.1.1", "serde", "serde_json", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "tracing", ] [[package]] name = "swc_ecma_minifier" -version = "7.0.1" +version = "23.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164291b068cca947462d87ede1baf276f69da137db1a0c66059a8aed81b785b2" +checksum = "50b6d32c649a7c78973768b629c8d60f06774be2a648b03c9c342200d4fd9f8b" dependencies = [ "arrayvec", + "bitflags 2.9.1", "indexmap 2.9.0", "num-bigint", "num_cpus", "once_cell", + "par-core", + "par-iter", "parking_lot", "phf 0.11.3", "radix_fmt", "regex", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "ryu-js", "serde", "serde_json", "swc_allocator", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_config", "swc_ecma_ast", "swc_ecma_codegen", @@ -13821,62 +13947,65 @@ dependencies = [ "swc_ecma_usage_analyzer", "swc_ecma_utils", "swc_ecma_visit", - "swc_parallel", "swc_timer", "tracing", ] [[package]] name = "swc_ecma_parser" -version = "6.0.2" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92d3a25349d7f612c38d940f09f9c19c7b7aa3bf4d22fbe31ea44fd5354de02" +checksum = "3ff5b5fbe4316b7d1272ac6f204a871f0d5785755d6a14ddf38e7fa8af73be30" dependencies = [ + "arrayvec", + "bitflags 2.9.1", "either", "new_debug_unreachable", "num-bigint", "num-traits", "phf 0.11.3", + "rustc-hash 2.1.1", "serde", "smallvec", "smartstring", "stacker", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", + "swc_ecma_lexer", "tracing", "typed-arena", ] [[package]] name = "swc_ecma_transforms_base" -version = "7.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fdc36d220bcd51f70b1d78bdd8c1e1a172b4e594c385bdd9614b84a7c0e112" +checksum = "a6a9971c1f27f6b3ebcad7424e81861c35bfd009be81786da71a6e3a7002d808" dependencies = [ "better_scoped_tls", "bitflags 2.9.1", "indexmap 2.9.0", "once_cell", + "par-core", "phf 0.11.3", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "serde", "smallvec", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_parser", "swc_ecma_utils", "swc_ecma_visit", - "swc_parallel", "tracing", ] [[package]] name = "swc_ecma_transforms_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" +checksum = "bc777288799bf6786e5200325a56e4fbabba590264a4a48a0c70b16ad0cf5cd8" dependencies = [ "proc-macro2", "quote", @@ -13886,38 +14015,40 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "7.0.1" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4232534b28fc57b745e8c709723544e5548af29abaa62281eab427099f611d" +checksum = "148b59208253c618c0e1c363f6f5bdd6ff309fb09743dfa4c0fa0b1bd220e454" dependencies = [ + "bytes-str", "dashmap 5.5.3", "indexmap 2.9.0", "once_cell", - "petgraph 0.6.5", - "rustc-hash 1.1.0", + "par-core", + "petgraph 0.7.1", + "rustc-hash 2.1.1", "serde_json", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_parser", "swc_ecma_transforms_base", "swc_ecma_transforms_macros", "swc_ecma_utils", "swc_ecma_visit", - "swc_fast_graph", "tracing", ] [[package]] name = "swc_ecma_usage_analyzer" -version = "7.0.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15eb86aaa82d7ec4c1a6c3a8a824b1fdbbaace73c3ed81035a1fbbac49f8e0bd" +checksum = "c357c7ff7ae2bf50adbe89c04ed94c9b99d34563df69386aba15a05e4560fc9a" dependencies = [ + "bitflags 2.9.1", "indexmap 2.9.0", - "rustc-hash 1.1.0", - "swc_atoms", - "swc_common", + "rustc-hash 2.1.1", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_utils", "swc_ecma_visit", @@ -13927,34 +14058,35 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "7.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9d22b4883dc6d6c21a8216bbf5aacedd7f104230b1557367ae126a2ec3a2b5" +checksum = "4e6aa9ebca452a7594290928de2a90464b92ae1634d1c22e2593a77b32953016" dependencies = [ "indexmap 2.9.0", "num_cpus", "once_cell", - "rustc-hash 1.1.0", + "par-core", + "par-iter", + "rustc-hash 2.1.1", "ryu-js", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_visit", - "swc_parallel", "tracing", "unicode-id", ] [[package]] name = "swc_ecma_visit" -version = "5.0.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" +checksum = "7ad28e3449b376bfe1f2bde28bfcf305961ba23c1e205bedb03a7c108a1d1ff6" dependencies = [ "new_debug_unreachable", "num-bigint", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_visit", "tracing", @@ -13962,9 +14094,9 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" dependencies = [ "proc-macro2", "quote", @@ -13973,34 +14105,34 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "6.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" +checksum = "bd24b9798b0538803d0a69cffa5f5e051087fa2bd0d23e5a2f05d32edf9ab671" dependencies = [ "indexmap 2.9.0", "petgraph 0.6.5", - "rustc-hash 1.1.0", - "swc_common", + "rustc-hash 2.1.1", + "swc_common 8.1.1", ] [[package]] name = "swc_graph_analyzer" -version = "5.0.0" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b9841af596d2ddb37e56defca81387b60a14863e251cede839d1e349e6209d" +checksum = "eb4b7c00f9a8b2038ae9b68b9762f8d3e7b8b48323d3985a55bae9e478531b87" dependencies = [ "auto_impl", - "petgraph 0.6.5", - "swc_common", - "swc_fast_graph", + "petgraph 0.7.1", + "rustc-hash 2.1.1", + "swc_common 13.0.1", "tracing", ] [[package]] name = "swc_macros_common" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" dependencies = [ "proc-macro2", "quote", @@ -14009,13 +14141,32 @@ dependencies = [ [[package]] name = "swc_parallel" -version = "1.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cde1a0f344924be62d01de0c8a98e840feae271b77dc8c1d9d2e340687225c" +checksum = "8f16052d5123ec45c1c49100781363f3f4e4a6be2da6d82f473b79db1e3abeb8" dependencies = [ "once_cell", ] +[[package]] +name = "swc_sourcemap" +version = "9.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9755c673c6a83c461e98fa018f681adb8394a3f44f89a06f27e80fd4fe4fa1e4" +dependencies = [ + "base64-simd 0.8.0", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + [[package]] name = "swc_timer" version = "1.0.0" @@ -14027,9 +14178,9 @@ dependencies = [ [[package]] name = "swc_visit" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" dependencies = [ "either", "new_debug_unreachable", diff --git a/Cargo.toml b/Cargo.toml index a3a04b7fc6..69272e5320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ members = [ "packages/asset-resolver", "packages/depinfo", "packages/server", + "packages/use-js-macro", # Playwright tests "packages/playwright-tests/liveview", @@ -175,6 +176,7 @@ dioxus-logger = { path = "packages/logger", version = "0.7.0-alpha.1" } dioxus-native = { path = "packages/native", version = "0.7.0-alpha.1" } dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.0-alpha.1" } dioxus-config-macros = { path = "packages/config-macros", version = "0.7.0-alpha.1" } +dioxus-use-js-macro = { path = "packages/use-js-macro", version = "0.7.0-alpha.1" } const-serialize = { path = "packages/const-serialize", version = "0.7.0-alpha.1" } const-serialize-macro = { path = "packages/const-serialize-macro", version = "0.7.0-alpha.1" } generational-box = { path = "packages/generational-box", version = "0.7.0-alpha.1" } diff --git a/examples/assets/example.js b/examples/assets/example.js new file mode 100644 index 0000000000..e212ac61de --- /dev/null +++ b/examples/assets/example.js @@ -0,0 +1,16 @@ +/** + * This is a doc comment + * second line +*/ +export function greeting(from, to) { + return `Hello ${to}, this is ${from} speaking from JavaScript!`; +} + +/// This is another doc comment +export function add(a, b) { + return a + b; +} + +export function processData(data) { + return data.map(item => item.toUpperCase()); +} \ No newline at end of file diff --git a/examples/use_js_macro.rs b/examples/use_js_macro.rs new file mode 100644 index 0000000000..aee8c26024 --- /dev/null +++ b/examples/use_js_macro.rs @@ -0,0 +1,49 @@ +use dioxus::prelude::*; + +// Generate the greeting function at compile time +use_js!("examples/assets/example.js"::greeting); + +// Or generate multiple functions: +// use_js!("examples/assets/example.js"::{greeting, add}); + +// Or generate all exported functions: +// use_js!("examples/assets/example.js"::*); + +fn main() { + launch(App); +} + +#[component] +fn App() -> Element { + let future = use_resource(|| async move { + let from = "dave"; + let to = "john"; + + // Now we can call the generated function directly! + let greeting_result = greeting(from, to) + .await + .map_err(Box::::from)?; + let greeting: String = + serde_json::from_value(greeting_result).map_err(Box::::from)?; + Ok::>(greeting) + }); + + rsx!( + div { + h1 { "Dioxus `use_js!` macro example!" } + { + match &*future.read() { + Some(Ok(greeting)) => rsx! { + p { "Greeting from JavaScript: {greeting}" } + }, + Some(Err(e)) => rsx! { + p { "Error: {e}" } + }, + None => rsx! { + p { "Running js..." } + }, + } + } + } + ) +} diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index dea1f9c871..0f73cbf8ab 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -40,33 +40,33 @@ grass = "0.13.4" codemap = "0.1.3" # Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates -swc_allocator = { version = "=2.0.0", default-features = false } -swc_atoms = { version = "=3.0.2", default-features = false } -swc_bundler = { version = "=7.0.0", default-features = false } -swc_cached = { version = "=1.0.0", default-features = false } -swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } -swc_config = { version = "=1.0.0", default-features = false } -swc_config_macro = { version = "=1.0.0", default-features = false } -swc_ecma_ast = { version = "=5.0.1", default-features = false } -swc_ecma_codegen = { version = "=5.0.1", default-features = false } -swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } -swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } -swc_ecma_minifier = { version = "=7.0.1", default-features = false } -swc_ecma_parser = { version = "=6.0.2", default-features = false } -swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } -swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } -swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } -swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } -swc_ecma_utils = { version = "=7.0.0", default-features = false } -swc_ecma_visit = { version = "=5.0.0", default-features = false } -swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } -swc_fast_graph = { version = "=6.0.0", default-features = false } -swc_graph_analyzer = { version = "=5.0.0", default-features = false } -swc_macros_common = { version = "=1.0.0", default-features = false } -swc_parallel = { version = "=1.0.1", default-features = false } +swc_allocator = { version = "=4.0.0", default-features = false } +swc_atoms = { version = "=6.0.0", default-features = false } +swc_bundler = { version = "=22.0.0", default-features = false } +swc_cached = { version = "=2.0.0", default-features = false } +swc_common = { version = "=13.0.1", features = ["tty-emitter"], default-features = false } +swc_config = { version = "=3.1.1", default-features = false } +swc_config_macro = { version = "=1.0.1", default-features = false } +swc_ecma_ast = { version = "=13.0.0", default-features = false } +swc_ecma_codegen = { version = "=15.0.1", default-features = false } +swc_ecma_codegen_macros = { version = "=2.0.1", default-features = false } +swc_ecma_loader = { version = "=13.0.0", features = ["cache", "node"], default-features = false } +swc_ecma_minifier = { version = "=23.0.2", default-features = false } +swc_ecma_parser = { version = "=17.0.1", default-features = false } +swc_ecma_transforms_base = { version = "=18.0.0", default-features = false } +swc_ecma_transforms_macros = { version = "=1.0.1", default-features = false } +swc_ecma_transforms_optimization = { version = "=19.0.0", default-features = false } +swc_ecma_usage_analyzer = { version = "=19.0.0", default-features = false } +swc_ecma_utils = { version = "=18.0.0", default-features = false } +swc_ecma_visit = { version = "=13.0.0", default-features = false } +swc_eq_ignore_macros = { version = "=1.0.1", default-features = false } +swc_fast_graph = { version = "=9.0.0", default-features = false } +swc_graph_analyzer = { version = "=14.0.0", default-features = false } +swc_macros_common = { version = "=1.0.1", default-features = false } +swc_parallel = { version = "=1.3.0", default-features = false } swc_timer = { version = "=1.0.0", default-features = false } -swc_visit = { version = "=2.0.0", default-features = false } -browserslist-rs = { version = "=0.16.0" } +swc_visit = { version = "=2.0.1", default-features = false } +browserslist-rs = { version = "=0.18.2" } [build-dependencies] built = { version = "0.7.5", features = ["git2"] } diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs index 2aacfb5268..52ab2b0c86 100644 --- a/packages/cli-opt/src/js.rs +++ b/packages/cli-opt/src/js.rs @@ -30,7 +30,7 @@ use crate::hash::hash_file_contents; struct TracingEmitter; impl Emitter for TracingEmitter { - fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { + fn emit(&mut self, db: &mut swc_common::errors::DiagnosticBuilder<'_>) { match db.level { swc_common::errors::Level::Bug | swc_common::errors::Level::Fatal diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index cf1992a5f9..a2eeff9e9d 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -30,6 +30,7 @@ dioxus-server = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } dioxus-native = { workspace = true, optional = true } dioxus_server_macro = { workspace = true, optional = true } +dioxus-use-js-macro = { workspace = true, optional = true } manganis = { workspace = true, features = ["dioxus"], optional = true } dioxus-logger = { workspace = true, optional = true } warnings = { workspace = true, optional = true } @@ -58,7 +59,7 @@ default = [ ] minimal = ["macro", "html", "signals", "hooks", "launch"] signals = ["dep:dioxus-signals"] -macro = ["dep:dioxus-core-macro"] +macro = ["dep:dioxus-core-macro", "dep:dioxus-use-js-macro"] html = ["dep:dioxus-html"] hooks = ["dep:dioxus-hooks"] devtools = ["dep:dioxus-devtools", "dioxus-web?/devtools", "dioxus-fullstack?/devtools"] diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index f14b72b811..098ec921ea 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -150,6 +150,10 @@ pub mod prelude { #[allow(deprecated)] pub use dioxus_core_macro::{component, rsx, Props}; + #[cfg(feature = "macro")] + #[cfg_attr(docsrs, doc(cfg(feature = "macro")))] + pub use dioxus_use_js_macro::use_js; + #[cfg(feature = "launch")] #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] pub use dioxus_config_macro::*; diff --git a/packages/use-js-macro/Cargo.toml b/packages/use-js-macro/Cargo.toml new file mode 100644 index 0000000000..43e2df0ff3 --- /dev/null +++ b/packages/use-js-macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dioxus-use-js-macro" +version = { workspace = true } +authors = ["Dillon Henry McMahon"] +edition = "2021" +description = "javascript use bridge macro for Dioxus" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["web", "desktop", "mobile", "gui", "wasm"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } +swc_ecma_parser = { version = "=17.0.1", default-features = false } +swc_ecma_ast = { version = "=13.0.0", default-features = false } +swc_ecma_visit = { version = "=13.0.0", default-features = false } +swc_common = { version = "=13.0.1", default-features = false } + diff --git a/packages/use-js-macro/README.md b/packages/use-js-macro/README.md new file mode 100644 index 0000000000..37e297b20c --- /dev/null +++ b/packages/use-js-macro/README.md @@ -0,0 +1,58 @@ +# Dioxus use-js-macro + +A to create rust binding to javascript functions + +## Usage +Example: +```rust +use dioxus::prelude::*; + +// Generate the greeting function at compile time +use_js!("assets/example.js"::greeting); + +// Or generate multiple functions: +// use_js!("assets/example.js"::{greeting, add}); + +// Or generate all exported functions: +// use_js!("assets/example.js"::*); + +fn main() { + launch(App); +} + +#[component] +fn App() -> Element { + let future = use_resource(|| async move { + let from = "dave"; + let to = "john"; + + // Now we can call the generated function directly! + let greeting_result = greeting(from, to) + .await + .map_err(Box::::from)?; + let greeting: String = + serde_json::from_value(greeting_result).map_err(Box::::from)?; + Ok::>(greeting) + }); + + rsx!( + div { + h1 { "Dioxus `use_js!` macro example!" } + { + match &*future.read() { + Some(Ok(greeting)) => rsx! { + p { "Greeting from JavaScript: {greeting}" } + }, + Some(Err(e)) => rsx! { + p { "Error: {e}" } + }, + None => rsx! { + p { "Running js..." } + }, + } + } + } + ) +} + +``` \ No newline at end of file diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs new file mode 100644 index 0000000000..353da8f301 --- /dev/null +++ b/packages/use-js-macro/src/lib.rs @@ -0,0 +1,481 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use std::{fs, path::Path}; +use swc_common::comments::{CommentKind, Comments}; +use swc_common::Spanned; +use swc_common::{comments::SingleThreadedComments, SourceMap, Span}; +use swc_ecma_ast::{ + Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, Param, Pat, + VarDeclarator, +}; +use swc_ecma_parser::EsSyntax; +use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; +use swc_ecma_visit::{Visit, VisitWith}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, Ident, LitStr, Result, Token, +}; + +#[derive(Debug, Clone)] +enum ImportSpec { + /// * + All, + /// {greeting, other_func} + Named(Vec), + /// greeting + Single(Ident), +} + +struct UseJsInput { + asset_path: LitStr, + import_spec: ImportSpec, +} + +impl Parse for UseJsInput { + fn parse(input: ParseStream) -> Result { + let asset_path: LitStr = input.parse()?; + input.parse::()?; + + let import_spec = if input.peek(Token![*]) { + input.parse::()?; + ImportSpec::All + } else if input.peek(syn::token::Brace) { + let content; + syn::braced!(content in input); + let mut functions = Vec::new(); + + loop { + let ident: Ident = content.parse()?; + functions.push(ident); + + if content.peek(Token![,]) { + content.parse::()?; + if content.is_empty() { + break; + } + } else { + break; + } + } + + ImportSpec::Named(functions) + } else { + let ident: Ident = input.parse()?; + ImportSpec::Single(ident) + }; + + Ok(UseJsInput { + asset_path, + import_spec, + }) + } +} + +#[derive(Debug, Clone)] +struct FunctionInfo { + name: String, + /// If specified in the use declaration + name_ident: Option, + params: Vec, + is_exported: bool, + /// The stripped lines + doc_comment: Vec, +} + +struct FunctionVisitor { + functions: Vec, + comments: SingleThreadedComments, +} + +impl FunctionVisitor { + fn new(comments: SingleThreadedComments) -> Self { + Self { + functions: Vec::new(), + comments, + } + } + + fn extract_doc_comment(&self, span: Span) -> Vec { + // Get leading comments for the span + let leading_comment = self.comments.get_leading(span.lo()); + + if let Some(comments) = leading_comment { + let mut doc_lines = Vec::new(); + + for comment in comments.iter() { + let comment_text = &comment.text; + match comment.kind { + // Handle `///`. `//` is already stripped + CommentKind::Line => { + if let Some(content) = comment_text.strip_prefix("/") { + let cleaned = content.trim_start(); + doc_lines.push(cleaned.to_string()); + } + } + // Handle `/*` `*/`. `/*` `*/` is already stripped + CommentKind::Block => { + for line in comment_text.lines() { + if let Some(cleaned) = line.trim_start().strip_prefix("*") { + doc_lines.push(cleaned.to_string()); + } + } + } + }; + } + + doc_lines + } else { + Vec::new() + } + } +} + +fn function_params_to_names(params: &[Param]) -> Vec { + params + .iter() + .enumerate() + .map(|(i, param)| { + if let Some(ident) = param.pat.as_ident() { + ident.id.sym.to_string() + } else { + format!("arg{}", i) + } + }) + .collect() +} + +fn function_pat_to_names(pats: &[Pat]) -> Vec { + pats.iter() + .enumerate() + .map(|(i, pat)| { + if let Some(ident) = pat.as_ident() { + ident.id.sym.to_string() + } else { + format!("arg{}", i) + } + }) + .collect() +} + +impl Visit for FunctionVisitor { + /// Visit function declarations: function foo() {} + fn visit_fn_decl(&mut self, node: &FnDecl) { + let doc_comment = self.extract_doc_comment(node.span()); + + self.functions.push(FunctionInfo { + name: node.ident.sym.to_string(), + name_ident: None, + params: function_params_to_names(&node.function.params), + is_exported: false, + doc_comment, + }); + node.visit_children_with(self); + } + + /// Visit function expressions: const foo = function() {} + fn visit_var_declarator(&mut self, node: &VarDeclarator) { + if let swc_ecma_ast::Pat::Ident(ident) = &node.name { + if let Some(init) = &node.init { + let doc_comment = self.extract_doc_comment(node.span()); + + match &**init { + swc_ecma_ast::Expr::Fn(fn_expr) => { + self.functions.push(FunctionInfo { + name: ident.id.sym.to_string(), + name_ident: None, + params: function_params_to_names(&fn_expr.function.params), + is_exported: false, + doc_comment, + }); + } + swc_ecma_ast::Expr::Arrow(arrow_fn) => { + self.functions.push(FunctionInfo { + name: ident.id.sym.to_string(), + name_ident: None, + params: function_pat_to_names(&arrow_fn.params), + is_exported: false, + doc_comment, + }); + } + _ => {} + } + } + } + node.visit_children_with(self); + } + + /// Visit export declarations: export function foo() {} + fn visit_export_decl(&mut self, node: &ExportDecl) { + if let Decl::Fn(fn_decl) = &node.decl { + let doc_comment = self.extract_doc_comment(node.span()); + + self.functions.push(FunctionInfo { + name: fn_decl.ident.sym.to_string(), + name_ident: None, + params: function_params_to_names(&fn_decl.function.params), + is_exported: true, + doc_comment, + }); + } + node.visit_children_with(self); + } + + /// Visit named exports: export { foo } + fn visit_named_export(&mut self, node: &NamedExport) { + for spec in &node.specifiers { + if let ExportSpecifier::Named(named) = spec { + let name = match &named.orig { + ModuleExportName::Ident(ident) => ident.sym.to_string(), + ModuleExportName::Str(str_lit) => str_lit.value.to_string(), + }; + + if let Some(func) = self.functions.iter_mut().find(|f| f.name == name) { + func.is_exported = true; + } + } + } + node.visit_children_with(self); + } +} + +fn parse_js_file(file_path: &Path) -> Result> { + let js_content = fs::read_to_string(file_path).map_err(|e| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Could not read JavaScript file '{}': {}", + file_path.display(), + e + ), + ) + })?; + + let cm = SourceMap::default(); + let fm = cm.new_source_file( + swc_common::FileName::Custom(file_path.display().to_string()).into(), + js_content.clone(), + ); + let comments = SingleThreadedComments::default(); + let lexer = Lexer::new( + Syntax::Es(EsSyntax::default()), + Default::default(), + StringInput::from(&*fm), + Some(&comments), + ); + + let mut parser = Parser::new_from(lexer); + + let module = parser.parse_module().map_err(|e| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Failed to parse JavaScript file '{}': {:?}", + file_path.display(), + e + ), + ) + })?; + + let mut visitor = FunctionVisitor::new(comments); + module.visit_with(&mut visitor); + + // Functions are added twice for some reason + visitor + .functions + .dedup_by(|e1, e2| e1.name.as_str() == e2.name.as_str()); + Ok(visitor.functions) +} + +fn remove_function_info(name: &str, functions: &mut Vec) -> Result { + if let Some(pos) = functions.iter().position(|f| f.name == name) { + Ok(functions.remove(pos)) + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!("Function '{}' not found in JavaScript file", name), + )) + } +} + +fn get_functions_to_generate( + mut functions: Vec, + import_spec: ImportSpec, +) -> Result> { + match import_spec { + ImportSpec::All => Ok(functions), + ImportSpec::Single(name) => { + let mut func = remove_function_info(name.to_string().as_str(), &mut functions)?; + func.name_ident.replace(name); + Ok(vec![func]) + } + ImportSpec::Named(names) => { + let mut result = Vec::new(); + for name in names { + let mut func = remove_function_info(name.to_string().as_str(), &mut functions)?; + func.name_ident.replace(name); + result.push(func); + } + Ok(result) + } + } +} + +fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenStream2 { + let send_calls: Vec = func + .params + .iter() + .map(|param| { + let param = format_ident!("{}", param); + quote! { + eval.send(#param)?; + } + }) + .collect(); + + let js_func_name = &func.name; + let mut js_format = format!(r#"const {{{{ {js_func_name} }}}} = await import("{{}}");"#); + for param in func.params.iter() { + js_format.push_str(&format!("\nlet {} = await dioxus.recv();", param)); + } + js_format.push_str(&format!("\nreturn {}(", js_func_name)); + for (i, param) in func.params.iter().enumerate() { + if i > 0 { + js_format.push_str(", "); + } + js_format.push_str(param.as_str()); + } + js_format.push_str(");"); + + let param_types: Vec<_> = func + .params + .iter() + .map(|param| { + let param = format_ident!("{}", param); + quote! { #param: impl serde::Serialize } + }) + .collect(); + + // Generate documentation comment if available - preserve original JSDoc format + let doc_comment = if func.doc_comment.is_empty() { + quote! {} + } else { + let doc_lines: Vec<_> = func + .doc_comment + .iter() + .map(|line| quote! { #[doc = #line] }) + .collect(); + quote! { #(#doc_lines)* } + }; + + let func_name = func + .name_ident + .clone() + // Can not exist if `::*` + .unwrap_or_else(|| Ident::new(func.name.as_str(), proc_macro2::Span::call_site())); + quote! { + #doc_comment + pub async fn #func_name(#(#param_types),*) -> Result { + const MODULE: Asset = asset!(#asset_path); + let js = format!(#js_format, MODULE); + let eval = document::eval(js.as_str()); + #(#send_calls)* + eval.await + } + } +} + +/// A macro to create rust binding to javascript functions. +///```rust,ignore +/// use dioxus::prelude::*; +/// +/// // Generate the greeting function at compile time +/// use_js!("examples/assets/example.js"::greeting); +/// +/// // Or generate multiple functions: +/// // use_js!("examples/assets/example.js"::{greeting, add}); +/// +/// // Or generate all exported functions: +/// // use_js!("examples/assets/example.js"::*); +/// +/// fn main() { +/// launch(App); +/// } +/// +/// #[component] +/// fn App() -> Element { +/// let future = use_resource(|| async move { +/// let from = "dave"; +/// let to = "john"; +/// +/// // Now we can call the generated function directly! +/// let greeting_result = greeting(from, to) +/// .await +/// .map_err(Box::::from)?; +/// let greeting: String = +/// serde_json::from_value(greeting_result).map_err(Box::::from)?; +/// Ok::>(greeting) +/// }); +/// +/// rsx!( +/// div { +/// h1 { "Dioxus `use_js!` macro example!" } +/// { +/// match &*future.read() { +/// Some(Ok(greeting)) => rsx! { +/// p { "Greeting from JavaScript: {greeting}" } +/// }, +/// Some(Err(e)) => rsx! { +/// p { "Error: {e}" } +/// }, +/// None => rsx! { +/// p { "Running js..." } +/// }, +/// } +/// } +/// } +/// ) +/// } +/// ``` +#[proc_macro] +pub fn use_js(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as UseJsInput); + + let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") { + Ok(dir) => dir, + Err(_) => { + return TokenStream::from( + syn::Error::new( + proc_macro2::Span::call_site(), + "CARGO_MANIFEST_DIR environment variable not found", + ) + .to_compile_error(), + ); + } + }; + + let asset_path = &input.asset_path; + let js_file_path = std::path::Path::new(&manifest_dir).join(asset_path.value()); + + let all_functions = match parse_js_file(&js_file_path) { + Ok(funcs) => funcs, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + + let import_spec = input.import_spec; + let functions_to_generate = match get_functions_to_generate(all_functions, import_spec) { + Ok(funcs) => funcs, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + + let function_wrappers: Vec = functions_to_generate + .iter() + .map(|func| generate_function_wrapper(func, asset_path)) + .collect(); + + let expanded = quote! { + #(#function_wrappers)* + }; + + TokenStream::from(expanded) +}