From 13dcd80fbf0ccc20d75cabe7977d9993b778e92c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 21 Jan 2025 17:14:45 -0600 Subject: [PATCH] Optimize wasm bindgen asset with manganis (#3531) --- .github/workflows/main.yml | 24 +- .github/workflows/publish.yml | 2 +- Cargo.lock | 1007 ++++------------- Cargo.toml | 1 + flake.lock | 32 +- packages/cli-opt/Cargo.toml | 81 +- packages/cli-opt/src/file.rs | 48 +- packages/cli-opt/src/folder.rs | 5 +- packages/cli-opt/src/js.rs | 277 ++++- packages/cli/Cargo.toml | 3 +- packages/cli/src/build/bundle.rs | 79 +- packages/cli/src/build/request.rs | 24 + packages/cli/src/build/web.rs | 107 +- packages/cli/src/cli/mod.rs | 2 +- packages/cli/src/serve/proxy.rs | 1 - packages/desktop/src/app.rs | 1 + packages/desktop/src/webview.rs | 1 + packages/liveview/src/history.rs | 4 +- packages/manganis/manganis-core/src/asset.rs | 15 + packages/manganis/manganis-core/src/hash.rs | 94 ++ packages/manganis/manganis-core/src/lib.rs | 2 + packages/manganis/manganis-macro/src/asset.rs | 117 +- .../manganis/manganis/src/macro_helpers.rs | 36 +- .../playwright-tests/cli-optimization.spec.js | 11 + .../cli-optimization/.gitignore | 3 + .../cli-optimization/Cargo.toml | 15 + .../cli-optimization/build.rs | 14 + .../cli-optimization/src/main.rs | 34 + .../playwright-tests/playwright.config.js | 10 + 29 files changed, 947 insertions(+), 1103 deletions(-) create mode 100644 packages/manganis/manganis-core/src/hash.rs create mode 100644 packages/playwright-tests/cli-optimization.spec.js create mode 100644 packages/playwright-tests/cli-optimization/.gitignore create mode 100644 packages/playwright-tests/cli-optimization/Cargo.toml create mode 100644 packages/playwright-tests/cli-optimization/build.rs create mode 100644 packages/playwright-tests/cli-optimization/src/main.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c5b9dedb0a..693bd5ce30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" @@ -77,7 +77,7 @@ jobs: swap-storage: false - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 @@ -100,7 +100,7 @@ jobs: swap-storage: false - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 @@ -115,7 +115,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 with: components: rustfmt - uses: Swatinem/rust-cache@v2 @@ -156,7 +156,7 @@ jobs: - uses: actions/checkout@v4 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" @@ -170,7 +170,7 @@ jobs: - uses: actions/checkout@v4 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 @@ -247,7 +247,7 @@ jobs: - { target: aarch64-apple-darwin, os: macos-latest, - toolchain: "1.79.0", + toolchain: "1.84.0", cross: false, command: "test", args: "--all --tests", @@ -255,7 +255,7 @@ jobs: - { target: x86_64-apple-darwin, os: macos-13, - toolchain: "1.79.0", + toolchain: "1.84.0", cross: false, command: "test", args: "--all --tests", @@ -263,7 +263,7 @@ jobs: - { target: aarch64-apple-ios, os: macos-latest, - toolchain: "1.79.0", + toolchain: "1.84.0", cross: false, command: "build", args: "--package dioxus-mobile", @@ -271,7 +271,7 @@ jobs: - { target: aarch64-linux-android, os: ubuntu-latest, - toolchain: "1.79.0", + toolchain: "1.84.0", cross: true, command: "build", args: "--package dioxus-mobile", @@ -327,7 +327,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: dtolnay/rust-toolchain@1.79.0 + - uses: dtolnay/rust-toolchain@1.84.0 with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 @@ -360,7 +360,7 @@ jobs: # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@v4 - # - uses: dtolnay/rust-toolchain@1.79.0 + # - uses: dtolnay/rust-toolchain@1.84.0 # - uses: Swatinem/rust-cache@v2 # with: # cache-all-crates: "true" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 11fbbe8b59..09d660686f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -76,7 +76,7 @@ jobs: - name: Install stable uses: dtolnay/rust-toolchain@master with: - toolchain: "1.79.0" + toolchain: "1.84.0" targets: ${{ matrix.platform.target }} - uses: Swatinem/rust-cache@v2 diff --git a/Cargo.lock b/Cargo.lock index a394333cde..152f619fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "addr2line" version = "0.24.2" @@ -539,9 +529,9 @@ dependencies = [ [[package]] name = "ast_node" -version = "0.9.9" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9184f2b369b3e8625712493c89b785881f27eedc6cde480a81883cef78868b2" +checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" dependencies = [ "proc-macro2", "quote", @@ -1575,9 +1565,9 @@ dependencies = [ [[package]] name = "better_scoped_tls" -version = "0.1.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297b153aa5e573b5863108a6ddc9d5c968bd0b20e75cc614ee9821d2f45679c7" +checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" dependencies = [ "scoped-tls", ] @@ -1881,7 +1871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", - "ptr_meta", + "ptr_meta 0.1.4", "simdutf8", ] @@ -2778,15 +2768,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog 1.1.1", +] + [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ - "crc-catalog", + "crc-catalog 2.4.0", ] +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc-catalog" version = "2.4.0" @@ -3511,6 +3516,7 @@ dependencies = [ "krates", "local-ip-address", "log", + "manganis", "manganis-core", "notify", "object 0.36.5", @@ -3577,60 +3583,45 @@ dependencies = [ "rayon", "serde", "serde_json", - "swc", "swc_allocator", "swc_atoms", + "swc_bundler", "swc_cached", "swc_common", - "swc_compiler_base", "swc_config", "swc_config_macro", "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_codegen_macros", - "swc_ecma_compat_bugfixes", - "swc_ecma_compat_common", - "swc_ecma_compat_es2015", - "swc_ecma_compat_es2016", - "swc_ecma_compat_es2017", - "swc_ecma_compat_es2018", - "swc_ecma_compat_es2019", - "swc_ecma_compat_es2020", - "swc_ecma_compat_es2021", - "swc_ecma_compat_es2022", - "swc_ecma_compat_es3", - "swc_ecma_ext_transforms", - "swc_ecma_lints", "swc_ecma_loader", "swc_ecma_minifier", "swc_ecma_parser", - "swc_ecma_preset_env", - "swc_ecma_transforms", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_classes 0.134.0", - "swc_ecma_transforms_compat", + "swc_ecma_transforms_base", "swc_ecma_transforms_macros", - "swc_ecma_transforms_module", "swc_ecma_transforms_optimization", - "swc_ecma_transforms_proposal 0.178.0", - "swc_ecma_transforms_react", - "swc_ecma_transforms_typescript", "swc_ecma_usage_analyzer", "swc_ecma_utils", "swc_ecma_visit", "swc_eq_ignore_macros", - "swc_error_reporters", "swc_fast_graph", + "swc_graph_analyzer", "swc_macros_common", - "swc_node_comments", + "swc_parallel", "swc_timer", - "swc_trace_macro", - "swc_transform_common", - "swc_typescript", "swc_visit", "tracing", ] +[[package]] +name = "dioxus-cli-optimization-test" +version = "0.0.1" +dependencies = [ + "dioxus", + "flate2", + "reqwest 0.12.9", + "tar", +] + [[package]] name = "dioxus-config-macro" version = "0.6.1" @@ -5009,9 +5000,9 @@ dependencies = [ [[package]] name = "from_variant" -version = "0.1.9" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" +checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" dependencies = [ "proc-macro2", "swc_macros_common", @@ -7205,15 +7196,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "jsonc-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b56a20e76235284255a09fcd1f45cf55d3c524ea657ebd3854735925c57743d" -dependencies = [ - "serde_json", -] - [[package]] name = "jsonptr" version = "0.4.7" @@ -7932,31 +7914,6 @@ dependencies = [ "paste", ] -[[package]] -name = "miette" -version = "7.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" -dependencies = [ - "cfg-if", - "miette-derive", - "owo-colors", - "textwrap", - "thiserror 1.0.69", - "unicode-width 0.1.14", -] - -[[package]] -name = "miette-derive" -version = "7.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "mime" version = "0.3.17" @@ -9079,12 +9036,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" -[[package]] -name = "path-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - [[package]] name = "path-dedot" version = "3.1.1" @@ -9611,24 +9562,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "preset_env_base" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b30eab18be480c194938e433e269d5298a279f6410f02fbc73f3576a325c110" -dependencies = [ - "ahash 0.8.11", - "anyhow", - "browserslist-rs", - "dashmap", - "from_variant", - "once_cell", - "semver 1.0.23", - "serde", - "st-map", - "tracing", -] - [[package]] name = "presser" version = "0.3.1" @@ -9826,7 +9759,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "ptr_meta_derive", + "ptr_meta_derive 0.1.4", +] + +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive 0.3.0", ] [[package]] @@ -9840,6 +9782,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "qoi" version = "0.4.1" @@ -10244,6 +10197,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "remove_dir_all" version = "0.8.4" @@ -10324,6 +10283,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.7", @@ -10481,7 +10441,7 @@ dependencies = [ "bytecheck", "bytes", "hashbrown 0.12.3", - "ptr_meta", + "ptr_meta 0.1.4", "rend", "rkyv_derive", "seahash", @@ -11639,7 +11599,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc", + "crc 3.2.1", "crossbeam-queue", "either", "event-listener 2.5.3", @@ -11729,7 +11689,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc", + "crc 3.2.1", "digest", "dotenvy", "either", @@ -11776,7 +11736,7 @@ dependencies = [ "bitflags 2.6.0", "byteorder", "chrono", - "crc", + "crc 3.2.1", "dotenvy", "etcetera", "futures-channel", @@ -11836,16 +11796,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "st-map" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8257dd592de7614be71a2342d36ba2d527ddad3f9a0c8d09d6ceed4c371531e4" -dependencies = [ - "arrayvec", - "static-map-macro", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -11865,17 +11815,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "static-map-macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e9696ef338691287aeb937ee6ffe60022f579d3c8d2fd9d58973a9a10a466" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "static-self" version = "0.1.2" @@ -11931,9 +11870,9 @@ dependencies = [ [[package]] name = "string_enum" -version = "0.4.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90" +checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" dependencies = [ "proc-macro2", "quote", @@ -12033,74 +11972,24 @@ dependencies = [ "serde", ] -[[package]] -name = "swc" -version = "0.285.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ccac8796c84e723cc9e949dfa7126e1f1b06b36127b1e21ec5f8fc21bbbbc1" -dependencies = [ - "anyhow", - "base64 0.21.7", - "dashmap", - "either", - "indexmap 2.7.0", - "jsonc-parser", - "lru 0.10.1", - "once_cell", - "parking_lot", - "pathdiff", - "regex", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "sourcemap", - "swc_atoms", - "swc_cached", - "swc_common", - "swc_compiler_base", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_ext_transforms", - "swc_ecma_lints", - "swc_ecma_loader", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_preset_env", - "swc_ecma_transforms", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_compat", - "swc_ecma_transforms_optimization", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_error_reporters", - "swc_node_comments", - "swc_timer", - "swc_transform_common", - "swc_typescript", - "swc_visit", - "tracing", - "url", -] - [[package]] name = "swc_allocator" -version = "0.1.8" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc8bd3075d1c6964010333fae9ddcd91ad422a4f8eb8b3206a9b2b6afb4209e" +checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" dependencies = [ "bumpalo", "hashbrown 0.14.5", - "ptr_meta", + "ptr_meta 0.3.0", "rustc-hash 1.1.0", "triomphe", ] [[package]] name = "swc_atoms" -version = "0.6.7" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6567e4e67485b3e7662b486f1565bdae54bd5b9d6b16b2ba1a9babb1e42125" +checksum = "a640bf2e4430a149c87b5eaf377477ce8615ca7cb808054dd20e79e42da5d6ba" dependencies = [ "hstr", "once_cell", @@ -12108,11 +11997,41 @@ dependencies = [ "serde", ] +[[package]] +name = "swc_bundler" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88a91910cd8430f88f8987019cf3a96d92a5d5dded3e0ba8203e0379e4a2f6f" +dependencies = [ + "anyhow", + "crc 2.1.0", + "indexmap 2.7.0", + "is-macro", + "once_cell", + "parking_lot", + "petgraph", + "radix_fmt", + "relative-path", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_loader", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_optimization", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_fast_graph", + "swc_graph_analyzer", + "tracing", +] + [[package]] name = "swc_cached" -version = "0.3.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83406221c501860fce9c27444f44125eafe9e598b8b81be7563d7036784cd05c" +checksum = "96b6a5ef4cfec51d3fa30b73600f206453a37fc30cf1141e4644a57b1ed88616" dependencies = [ "ahash 0.8.11", "anyhow", @@ -12124,11 +12043,10 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.37.5" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12d0a8eaaf1606c9207077d75828008cb2dfb51b095a766bd2b72ef893576e31" +checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" dependencies = [ - "ahash 0.8.11", "ast_node", "better_scoped_tls", "cfg-if", @@ -12137,51 +12055,24 @@ dependencies = [ "new_debug_unreachable", "num-bigint", "once_cell", - "parking_lot", "rustc-hash 1.1.0", "serde", "siphasher", - "sourcemap", "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", + "termcolor", "tracing", "unicode-width 0.1.14", "url", ] -[[package]] -name = "swc_compiler_base" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb87f8dc7be1a034d5c29bcc4be9d504ddfd2f8aa1a1338fc568e104e087d29" -dependencies = [ - "anyhow", - "base64 0.21.7", - "once_cell", - "pathdiff", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "sourcemap", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_visit", - "swc_timer", -] - [[package]] name = "swc_config" -version = "0.1.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4740e53eaf68b101203c1df0937d5161a29f3c13bceed0836ddfe245b72dd000" +checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" dependencies = [ "anyhow", "indexmap 2.7.0", @@ -12194,9 +12085,9 @@ dependencies = [ [[package]] name = "swc_config_macro" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f56139042c1a95b54f5ca48baa0e0172d369bcc9d3d473dad1de36bae8399" +checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" dependencies = [ "proc-macro2", "quote", @@ -12206,9 +12097,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.118.2" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f866d12e4d519052b92a0a86d1ac7ff17570da1272ca0c89b3d6f802cd79df" +checksum = "82f448db2d1c52ffd2bd3788d89cafd8b5a75b97f0dc8aae00874dda2647f6b6" dependencies = [ "bitflags 2.6.0", "is-macro", @@ -12219,18 +12110,20 @@ dependencies = [ "string_enum", "swc_atoms", "swc_common", + "swc_visit", "unicode-id-start", ] [[package]] name = "swc_ecma_codegen" -version = "0.155.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7641608ef117cfbef9581a99d02059b522fcca75e5244fa0cbbd8606689c6f" +checksum = "7f93692de35a77d920ce8d96a46217735e5f86bf42f76cc8f1a60628c347c4c8" dependencies = [ "memchr", "num-bigint", "once_cell", + "regex", "serde", "sourcemap", "swc_allocator", @@ -12243,9 +12136,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen_macros" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859fabde36db38634f3fad548dd5e3410c1aebba1b67a3c63e67018fa57a0bca" +checksum = "5f9a42f479a6475647e248fa9750982c87cd985e19d1016a1fc18a70682305d1" dependencies = [ "proc-macro2", "quote", @@ -12254,273 +12147,43 @@ dependencies = [ ] [[package]] -name = "swc_ecma_compat_bugfixes" -version = "0.12.0" +name = "swc_ecma_loader" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75429b44cc479cbe018d5994eddae5ac7ab887ebefeb3596720921bc4cdff551" +checksum = "1a19b132079bfcd19d6fdabce7e55ece93a30787f3b8684c8646ddaf2237812d" dependencies = [ + "anyhow", + "dashmap", + "lru 0.10.1", + "normpath 0.2.0", + "once_cell", + "parking_lot", + "path-clean", + "pathdiff", + "serde", + "serde_json", "swc_atoms", "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_es2015", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", "tracing", ] [[package]] -name = "swc_ecma_compat_common" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9acdf402b36f8e83084b10e119d7ba9d07e5229ef39e1343f147db816c7b73e" -dependencies = [ - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", -] - -[[package]] -name = "swc_ecma_compat_es2015" -version = "0.12.0" +name = "swc_ecma_minifier" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c988d9018d6abb22b0fcc2da6a624be2db7c56681b6180d1cb5faa2672fd8001" +checksum = "164291b068cca947462d87ede1baf276f69da137db1a0c66059a8aed81b785b2" dependencies = [ "arrayvec", "indexmap 2.7.0", - "is-macro", + "num-bigint", + "num_cpus", + "once_cell", + "parking_lot", + "phf 0.11.2", + "radix_fmt", + "regex", "rustc-hash 1.1.0", - "serde", - "serde_derive", - "smallvec", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_classes 0.134.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2016" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7a3e086151c70ff940531ddcd04c01351ae80aa4593fd2906255d18a836b4f" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2017" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b74c89c9bd4fa532fba3d1ec47b129ec450b4143d3914118cd61b0e44d4a4b" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2018" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40bf74a06c433eee502ea6347596d5766d77da8baf32653d14a6655df4e181a" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2019" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10afb20890ffda37eefdfe06c3bb0d12e5ec8698667cb9e3689b74066b398845" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2020" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0608c4814a362d5362bc536507d8c89b287521778e8b678fe4590bfa1843803a" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_es2022", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2021" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f12ffb0f4282f4b333efa98c9653d181d89e1b5339d4be0d789189a246ef34b" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2022" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc16be9dd64e1b32569375b0b73ecc7dc74f9d848e8caaf2007896e2cf8d68a7" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_classes 0.134.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es3" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e684ae87d26ad3012e588d0e268158cadee10ddc0cda261069f0f280a8b23ce7" -dependencies = [ - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_ext_transforms" -version = "0.120.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad03ee53c734eb74757d03c07ec71b1a982261830c9253ef3e2e4a089f9af25d" -dependencies = [ - "phf 0.11.2", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_lints" -version = "0.100.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89907376ce67b56d8fbf79cca830a12cb41f93dccc306008c07d8eba8f6d388e" -dependencies = [ - "auto_impl", - "dashmap", - "parking_lot", - "rayon", - "regex", - "serde", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_loader" -version = "0.49.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55fa3d55045b97894bfb04d38aff6d6302ac8a6a38e3bb3dfb0d20475c4974a9" -dependencies = [ - "anyhow", - "dashmap", - "lru 0.10.1", - "normpath 0.2.0", - "once_cell", - "parking_lot", - "path-clean 0.1.0", - "pathdiff", - "serde", - "serde_json", - "swc_atoms", - "swc_cached", - "swc_common", - "tracing", -] - -[[package]] -name = "swc_ecma_minifier" -version = "0.204.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d88917a66b8f457c5953d2ff2d7788259658c89578636b28e9ac6ae56bbfd9" -dependencies = [ - "arrayvec", - "indexmap 2.7.0", - "num-bigint", - "num_cpus", - "once_cell", - "parking_lot", - "phf 0.11.2", - "radix_fmt", - "regex", - "rustc-hash 1.1.0", - "ryu-js", + "ryu-js", "serde", "serde_json", "swc_allocator", @@ -12530,20 +12193,21 @@ dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_parser", - "swc_ecma_transforms_base 0.145.0", + "swc_ecma_transforms_base", "swc_ecma_transforms_optimization", "swc_ecma_usage_analyzer", "swc_ecma_utils", "swc_ecma_visit", + "swc_parallel", "swc_timer", "tracing", ] [[package]] name = "swc_ecma_parser" -version = "0.149.1" +version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683dada14722714588b56481399c699378b35b2ba4deb5c4db2fb627a97fb54b" +checksum = "b92d3a25349d7f612c38d940f09f9c19c7b7aa3bf4d22fbe31ea44fd5354de02" dependencies = [ "either", "new_debug_unreachable", @@ -12561,79 +12225,11 @@ dependencies = [ "typed-arena", ] -[[package]] -name = "swc_ecma_preset_env" -version = "0.217.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51992e6bb854ef2e6c7a1b9a14ed8d0e3c8f905d348f694759f9a97bfa6a425" -dependencies = [ - "anyhow", - "dashmap", - "indexmap 2.7.0", - "once_cell", - "preset_env_base", - "rustc-hash 1.1.0", - "semver 1.0.23", - "serde", - "serde_json", - "st-map", - "string_enum", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82df2dd8048fe23f1df72acd52bfebf846b3d5a76e048eee32acf9af9bee6a98" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_compat", - "swc_ecma_transforms_module", - "swc_ecma_transforms_optimization", - "swc_ecma_transforms_proposal 0.179.0", - "swc_ecma_transforms_react", - "swc_ecma_transforms_typescript", - "swc_ecma_utils", - "swc_ecma_visit", -] - [[package]] name = "swc_ecma_transforms_base" -version = "0.144.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0a71579d030e12fd3cfbfc8712c4ce21afc526f2a759903c77d8df61950f5e" -dependencies = [ - "better_scoped_tls", - "bitflags 2.6.0", - "indexmap 2.7.0", - "once_cell", - "phf 0.11.2", - "rustc-hash 1.1.0", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_base" -version = "0.145.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f21494e75d0bd8ef42010b47cabab9caaed8f2207570e809f6f4eb51a710d1" +checksum = "09fdc36d220bcd51f70b1d78bdd8c1e1a172b4e594c385bdd9614b84a7c0e112" dependencies = [ "better_scoped_tls", "bitflags 2.6.0", @@ -12649,78 +12245,15 @@ dependencies = [ "swc_ecma_parser", "swc_ecma_utils", "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_classes" -version = "0.133.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f37ec04525798a09ce02e52dc15433acee2d86664da0b8ede55bb5cefd95384" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.144.0", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_classes" -version = "0.134.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3d884594385bea9405a2e1721151470d9a14d3ceec5dd773c0ca6894791601" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_compat" -version = "0.171.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f23da29c1279b6e0c1ac0df9d0f7fd6c955a141a9770e5a0a2d54292509bcf6" -dependencies = [ - "arrayvec", - "indexmap 2.7.0", - "is-macro", - "num-bigint", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_compat_bugfixes", - "swc_ecma_compat_common", - "swc_ecma_compat_es2015", - "swc_ecma_compat_es2016", - "swc_ecma_compat_es2017", - "swc_ecma_compat_es2018", - "swc_ecma_compat_es2019", - "swc_ecma_compat_es2020", - "swc_ecma_compat_es2021", - "swc_ecma_compat_es2022", - "swc_ecma_compat_es3", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_classes 0.134.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", + "swc_parallel", "tracing", ] [[package]] name = "swc_ecma_transforms_macros" -version = "0.5.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500a1dadad1e0e41e417d633b3d6d5de677c9e0d3159b94ba3348436cdb15aab" +checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" dependencies = [ "proc-macro2", "quote", @@ -12728,38 +12261,11 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "swc_ecma_transforms_module" -version = "0.190.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4d0255362149854b923125e9910ce0a5405ce6d03fb325c5fdd8e9f13a0845" -dependencies = [ - "Inflector", - "anyhow", - "bitflags 2.6.0", - "indexmap 2.7.0", - "is-macro", - "path-clean 1.0.1", - "pathdiff", - "regex", - "serde", - "swc_atoms", - "swc_cached", - "swc_common", - "swc_ecma_ast", - "swc_ecma_loader", - "swc_ecma_parser", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - [[package]] name = "swc_ecma_transforms_optimization" -version = "0.208.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d8447ea20ef76958a8240feef95743702485a84331e6df5bdbe7e383c87838" +checksum = "5e4232534b28fc57b745e8c709723544e5548af29abaa62281eab427099f611d" dependencies = [ "dashmap", "indexmap 2.7.0", @@ -12771,7 +12277,7 @@ dependencies = [ "swc_common", "swc_ecma_ast", "swc_ecma_parser", - "swc_ecma_transforms_base 0.145.0", + "swc_ecma_transforms_base", "swc_ecma_transforms_macros", "swc_ecma_utils", "swc_ecma_visit", @@ -12779,93 +12285,11 @@ dependencies = [ "tracing", ] -[[package]] -name = "swc_ecma_transforms_proposal" -version = "0.178.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7ddb3aae86f19eb9e41b0c62509d8e400c1dc79c0889df98f6df1ab893f3f" -dependencies = [ - "either", - "rustc-hash 1.1.0", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.144.0", - "swc_ecma_transforms_classes 0.133.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_proposal" -version = "0.179.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79938ff510fc647febd8c6c3ef4143d099fdad87a223680e632623d056dae2dd" -dependencies = [ - "either", - "rustc-hash 1.1.0", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_classes 0.134.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_react" -version = "0.191.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c76d8b9792ce51401d38da0fa62158d61f6d80d16d68fe5b03ce4bf5fba383" -dependencies = [ - "base64 0.21.7", - "dashmap", - "indexmap 2.7.0", - "once_cell", - "serde", - "sha1", - "string_enum", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_typescript" -version = "0.198.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15455da4768f97186c40523e83600495210c11825d3a44db43383fd81eace88d" -dependencies = [ - "ryu-js", - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base 0.145.0", - "swc_ecma_transforms_react", - "swc_ecma_utils", - "swc_ecma_visit", -] - [[package]] name = "swc_ecma_usage_analyzer" -version = "0.30.3" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7689421c6a892642c5907fd608c56d982fdef0d6456f9dba3cc418c6ea7e07" +checksum = "15eb86aaa82d7ec4c1a6c3a8a824b1fdbbaace73c3ed81035a1fbbac49f8e0bd" dependencies = [ "indexmap 2.7.0", "rustc-hash 1.1.0", @@ -12880,9 +12304,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.134.2" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029eec7dd485923a75b5a45befd04510288870250270292fc2c1b3a9e7547408" +checksum = "1c9d22b4883dc6d6c21a8216bbf5aacedd7f104230b1557367ae126a2ec3a2b5" dependencies = [ "indexmap 2.7.0", "num_cpus", @@ -12893,15 +12317,16 @@ dependencies = [ "swc_common", "swc_ecma_ast", "swc_ecma_visit", + "swc_parallel", "tracing", "unicode-id", ] [[package]] name = "swc_ecma_visit" -version = "0.104.8" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1c6802e68e51f336e8bc9644e9ff9da75d7da9c1a6247d532f2e908aa33e81" +checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" dependencies = [ "new_debug_unreachable", "num-bigint", @@ -12914,33 +12339,20 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" +checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", ] -[[package]] -name = "swc_error_reporters" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d049e9256abf29d9fc66d3db3ea44b6815a64ad565ce31e117a74ee96478bb3" -dependencies = [ - "anyhow", - "miette", - "once_cell", - "parking_lot", - "swc_common", -] - [[package]] name = "swc_fast_graph" -version = "0.25.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357e2c97bb51431d65080f25b436bc4e2fc1a7f64a643bc21a8353e478dc799f" +checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" dependencies = [ "indexmap 2.7.0", "petgraph", @@ -12949,41 +12361,23 @@ dependencies = [ ] [[package]] -name = "swc_macros_common" -version = "0.3.13" +name = "swc_graph_analyzer" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" +checksum = "79b9841af596d2ddb37e56defca81387b60a14863e251cede839d1e349e6209d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "swc_node_comments" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d016ab18b432523b2a3c104ce3aaf7d869db46c0a41477dbfb6201ddc86c1eb0" -dependencies = [ - "dashmap", - "swc_atoms", + "auto_impl", + "petgraph", "swc_common", -] - -[[package]] -name = "swc_timer" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5fb6f8b8b85512aacbb3d7140a828666e0e0b1bcc69bf84000a0cd36306bab" -dependencies = [ + "swc_fast_graph", "tracing", ] [[package]] -name = "swc_trace_macro" -version = "0.1.3" +name = "swc_macros_common" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9719b6085dd2824fd61938a881937be14b08f95e2d27c64c825a9f65e052ba" +checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" dependencies = [ "proc-macro2", "quote", @@ -12991,35 +12385,28 @@ dependencies = [ ] [[package]] -name = "swc_transform_common" -version = "0.1.1" +name = "swc_parallel" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda3e80e1ad638d3575bc07745a914af13dcb02215098659f864731078271f2c" +checksum = "b7cde1a0f344924be62d01de0c8a98e840feae271b77dc8c1d9d2e340687225c" dependencies = [ - "better_scoped_tls", "once_cell", - "rustc-hash 1.1.0", - "serde", - "serde_json", ] [[package]] -name = "swc_typescript" -version = "0.5.0" +name = "swc_timer" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d043347b109a8aebfe01aaeada4af322304ea0f54ae8e5721df9afcb9305ca" +checksum = "4db06b46cc832f7cf83c2ce21905fc465d01443a2bdccf63644383e1f5847532" dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "thiserror 1.0.69", + "tracing", ] [[package]] name = "swc_visit" -version = "0.6.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceb044142ba2719ef9eb3b6b454fce61ab849eb696c34d190f04651955c613d" +checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" dependencies = [ "either", "new_debug_unreachable", @@ -13406,16 +12793,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" -dependencies = [ - "unicode-linebreak", - "unicode-width 0.1.14", -] - [[package]] name = "thin-slice" version = "0.1.1" @@ -14247,12 +13624,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - [[package]] name = "unicode-normalization" version = "0.1.24" diff --git a/Cargo.toml b/Cargo.toml index 980e9c5852..a9b8bbe3f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ members = [ "packages/playwright-tests/fullstack-mounted", "packages/playwright-tests/suspense-carousel", "packages/playwright-tests/nested-suspense", + "packages/playwright-tests/cli-optimization", ] [workspace.package] diff --git a/flake.lock b/flake.lock index 2b411256e5..cca696a85e 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1722555600, - "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", + "lastModified": 1736143030, + "narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", + "rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1724395761, - "narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=", + "lastModified": 1736241350, + "narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c", + "rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f", "type": "github" }, "original": { @@ -36,23 +36,23 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1722555339, - "narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=", + "lastModified": 1735774519, + "narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" + "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" }, "original": { "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" + "url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz" } }, "nixpkgs_2": { "locked": { - "lastModified": 1718428119, - "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "lastModified": 1728538411, + "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", "type": "github" }, "original": { @@ -75,11 +75,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1724638882, - "narHash": "sha256-ap2jIQi/FuUHR6HCht6ASWhoz8EiB99XmI8Esot38VE=", + "lastModified": 1736438724, + "narHash": "sha256-m0CFbWVKtXsAr5OBgWNwR8Uam/jkPIjWgJkdH9DY46M=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "19b70f147b9c67a759e35824b241f1ed92e46694", + "rev": "15897cf5835bb95aa002b42c22dc61079c28f7c8", "type": "github" }, "original": { diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index 6a88b83cb9..8b772a5bc0 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -39,57 +39,30 @@ 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 = "=0.285.0" -swc_allocator = { version = "=0.1.8", default-features = false } -swc_atoms = { version = "=0.6.7", default-features = false } -swc_cached = { version = "=0.3.20", default-features = false } -swc_common = { version = "=0.37.5", default-features = false } -swc_compiler_base = { version = "=0.19.0", default-features = false } -swc_config = { version = "=0.1.15", default-features = false } -swc_config_macro = { version = "=0.1.4", default-features = false } -swc_ecma_ast = { version = "=0.118.2", default-features = false } -swc_ecma_codegen = { version = "=0.155.1", default-features = false } -swc_ecma_codegen_macros = { version = "=0.7.7", default-features = false } -swc_ecma_compat_bugfixes = { version = "=0.12.0", default-features = false } -swc_ecma_compat_common = { version = "=0.11.0", default-features = false } -swc_ecma_compat_es2015 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2016 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2017 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2018 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2019 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2020 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2021 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es2022 = { version = "=0.12.0", default-features = false } -swc_ecma_compat_es3 = { version = "=0.12.0", default-features = false } -swc_ecma_ext_transforms = { version = "=0.120.0", default-features = false } -swc_ecma_lints = { version = "=0.100.0", default-features = false } -swc_ecma_loader = { version = "=0.49.1", default-features = false } -swc_ecma_minifier = { version = "=0.204.0", default-features = false } -swc_ecma_parser = { version = "=0.149.1", default-features = false } -swc_ecma_preset_env = { version = "=0.217.0", default-features = false, features = [ - "serde", -] } -swc_ecma_transforms = { version = "=0.239.0", default-features = false } -swc_ecma_transforms_base = { version = "=0.145.0", default-features = false } -swc_ecma_transforms_classes = { version = "=0.134.0", default-features = false } -swc_ecma_transforms_compat = { version = "=0.171.0", default-features = false } -swc_ecma_transforms_macros = { version = "=0.5.5", default-features = false } -swc_ecma_transforms_module = { version = "=0.190.0", default-features = false } -swc_ecma_transforms_optimization = { version = "=0.208.0", default-features = false } -swc_ecma_transforms_proposal = { version = "=0.178.0", default-features = false } -swc_ecma_transforms_react = { version = "=0.191.0", default-features = false } -swc_ecma_transforms_typescript = { version = "=0.198.1", default-features = false } -swc_ecma_usage_analyzer = { version = "=0.30.3", default-features = false } -swc_ecma_utils = { version = "=0.134.2", default-features = false } -swc_ecma_visit = { version = "=0.104.8", default-features = false } -swc_eq_ignore_macros = { version = "=0.1.4", default-features = false } -swc_error_reporters = { version = "=0.21.0", default-features = false } -swc_fast_graph = { version = "=0.25.0", default-features = false } -swc_macros_common = { version = "=0.3.13", default-features = false } -swc_node_comments = { version = "=0.24.0", default-features = false } -swc_timer = { version = "=0.25.0", default-features = false } -swc_trace_macro = { version = "=0.1.3", default-features = false } -swc_transform_common = { version = "=0.1.1", default-features = false } -swc_typescript = { version = "=0.5.0", default-features = false } -swc_visit = { version = "=0.6.2", default-features = false } -browserslist-rs = { version = "=0.16.0" } \ No newline at end of file +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_timer = { version = "=1.0.0", default-features = false } +swc_visit = { version = "=2.0.0", default-features = false } +browserslist-rs = { version = "=0.16.0" } diff --git a/packages/cli-opt/src/file.rs b/packages/cli-opt/src/file.rs index 8ef22f3c7d..254a822436 100644 --- a/packages/cli-opt/src/file.rs +++ b/packages/cli-opt/src/file.rs @@ -1,6 +1,6 @@ use anyhow::Context; use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions}; -use std::path::Path; +use std::{ffi::OsStr, path::Path}; use crate::css::process_scss; @@ -14,6 +14,16 @@ pub fn process_file_to( options: &AssetOptions, source: &Path, output_path: &Path, +) -> anyhow::Result<()> { + process_file_to_with_options(options, source, output_path, false) +} + +/// Process a specific file asset with additional options +pub(crate) fn process_file_to_with_options( + options: &AssetOptions, + source: &Path, + output_path: &Path, + in_folder: bool, ) -> anyhow::Result<()> { // If the file already exists, then we must have a file with the same hash // already. The hash has the file contents and options, so if we find a file @@ -26,56 +36,68 @@ pub fn process_file_to( std::fs::create_dir_all(parent)?; } } + + // Processing can be slow. Write to a temporary file first and then rename it to the final output path. If everything + // goes well. Without this, the user could quit in the middle of processing and the file will look complete to the + // caching system even though it is empty. + let mut partial_ext = output_path.extension().unwrap_or_default().to_os_string(); + partial_ext.push(OsStr::new(".partial")); + let temp_output_path = output_path.with_extension(&partial_ext); + let temp_output_path = &temp_output_path; + match options { AssetOptions::Unknown => match source.extension().map(|e| e.to_string_lossy()).as_deref() { Some("css") => { - process_css(&CssAssetOptions::new(), source, output_path)?; + process_css(&CssAssetOptions::new(), source, temp_output_path)?; } Some("scss" | "sass") => { - process_scss(&CssAssetOptions::new(), source, output_path)?; + process_scss(&CssAssetOptions::new(), source, temp_output_path)?; } Some("js") => { - process_js(&JsAssetOptions::new(), source, output_path)?; + process_js(&JsAssetOptions::new(), source, temp_output_path, !in_folder)?; } Some("json") => { - process_json(source, output_path)?; + process_json(source, temp_output_path)?; } Some("jpg" | "jpeg" | "png" | "webp" | "avif") => { - process_image(&ImageAssetOptions::new(), source, output_path)?; + process_image(&ImageAssetOptions::new(), source, temp_output_path)?; } Some(_) | None => { if source.is_dir() { - process_folder(source, output_path)?; + process_folder(source, temp_output_path)?; } else { let source_file = std::fs::File::open(source)?; let mut reader = std::io::BufReader::new(source_file); - let output_file = std::fs::File::create(output_path)?; + let output_file = std::fs::File::create(temp_output_path)?; let mut writer = std::io::BufWriter::new(output_file); std::io::copy(&mut reader, &mut writer).with_context(|| { format!( "Failed to write file to output location: {}", - output_path.display() + temp_output_path.display() ) })?; } } }, AssetOptions::Css(options) => { - process_css(options, source, output_path)?; + process_css(options, source, temp_output_path)?; } AssetOptions::Js(options) => { - process_js(options, source, output_path)?; + process_js(options, source, temp_output_path, !in_folder)?; } AssetOptions::Image(options) => { - process_image(options, source, output_path)?; + process_image(options, source, temp_output_path)?; } AssetOptions::Folder(_) => { - process_folder(source, output_path)?; + process_folder(source, temp_output_path)?; } _ => { tracing::warn!("Unknown asset options: {:?}", options); } } + // If everything was successful, rename the temp file to the final output path + std::fs::rename(temp_output_path, output_path)?; + Ok(()) } diff --git a/packages/cli-opt/src/folder.rs b/packages/cli-opt/src/folder.rs index 699f6d64d8..8873565260 100644 --- a/packages/cli-opt/src/folder.rs +++ b/packages/cli-opt/src/folder.rs @@ -2,7 +2,7 @@ use std::path::Path; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use super::file::process_file_to; +use crate::file::process_file_to_with_options; /// Process a folder, optimizing and copying all assets into the output folder pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> { @@ -32,10 +32,11 @@ pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> /// Optimize a file without changing any of its contents significantly (e.g. by changing the extension) fn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> { - process_file_to( + process_file_to_with_options( &manganis_core::AssetOptions::Unknown, input_path, output_path, + true, )?; Ok(()) } diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs index 74e71a603d..d928b84b82 100644 --- a/packages/cli-opt/src/js.rs +++ b/packages/cli-opt/src/js.rs @@ -1,51 +1,220 @@ -use std::io::Read; use std::path::Path; -use std::sync::Arc; +use std::path::PathBuf; use anyhow::Context; use manganis_core::JsAssetOptions; -use swc::{config::JsMinifyOptions, try_with_handler, BoolOrDataConfig, JsMinifyExtras}; -use swc_common::{sync::Lrc, FileName}; -use swc_common::{SourceMap, GLOBALS}; -use swc_ecma_minifier::option::MangleOptions; - -pub(crate) fn minify_js(source: &Path) -> anyhow::Result { - let mut source_file = std::fs::File::open(source)?; - let cm = Arc::new(SourceMap::default()); - - let mut js = String::new(); - source_file.read_to_string(&mut js)?; - let c = swc::Compiler::new(cm.clone()); - let output = GLOBALS - .set(&Default::default(), || { - try_with_handler(cm.clone(), Default::default(), |handler| { - let filename = Lrc::new(FileName::Real(source.to_path_buf())); - let fm = cm.new_source_file(filename, js.to_string()); - - c.minify( - fm, - handler, - &JsMinifyOptions { - compress: BoolOrDataConfig::from_bool(true), - mangle: BoolOrDataConfig::from_obj(MangleOptions { - keep_fn_names: true, - ..Default::default() - }), - ..Default::default() - }, - JsMinifyExtras::default(), - ) - .context("failed to minify javascript") +use swc_common::errors::Emitter; +use swc_common::errors::Handler; +use swc_common::input::SourceFileInput; +use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; +use swc_ecma_parser::lexer::Lexer; +use swc_ecma_parser::Parser; +use swc_ecma_transforms_base::fixer::fixer; +use swc_ecma_visit::VisitMutWith; + +use std::collections::HashMap; + +use anyhow::Error; +use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; +use swc_common::{ + errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, +}; +use swc_ecma_ast::*; +use swc_ecma_codegen::text_writer::JsWriter; +use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; +use swc_ecma_parser::{parse_file_as_module, Syntax}; + +struct TracingEmitter; + +impl Emitter for TracingEmitter { + fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { + match db.level { + swc_common::errors::Level::Bug + | swc_common::errors::Level::Fatal + | swc_common::errors::Level::PhaseFatal + | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), + swc_common::errors::Level::Warning + | swc_common::errors::Level::FailureNote + | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), + swc_common::errors::Level::Note | swc_common::errors::Level::Help => { + tracing::trace!("{}", db.message()) + } + } + } +} + +fn bundle_js_to_writer( + file: PathBuf, + bundle: bool, + minify: bool, + write_to: &mut impl std::io::Write, +) -> anyhow::Result<()> { + let globals = Globals::new(); + let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); + GLOBALS.set(&globals, || { + HANDLER.set(&handler, || { + bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to) + }) + }) +} + +fn bundle_js_to_writer_inside_handler( + globals: &Globals, + file: PathBuf, + bundle: bool, + minify: bool, + write_to: &mut impl std::io::Write, +) -> anyhow::Result<()> { + let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let mut module = if bundle { + let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); + let mut bundler = Bundler::new( + globals, + cm.clone(), + PathLoader { cm: cm.clone() }, + node_resolver, + Config { + require: true, + ..Default::default() + }, + Box::new(Hook), + ); + let mut entries = HashMap::default(); + entries.insert("main".to_string(), FileName::Real(file)); + + let mut bundles = bundler + .bundle(entries) + .context("failed to bundle javascript with swc")?; + // Since we only inserted one entry, there should only be one bundle in the output + let bundle = bundles + .pop() + .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; + bundle.module + } else { + let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); + + let lexer = Lexer::new( + Default::default(), + Default::default(), + SourceFileInput::from(&*fm), + None, + ); + let mut parser = Parser::new_from(lexer); + + parser.parse_module().map_err(|err| { + HANDLER.with(|handler| { + let message = err.into_diagnostic(handler).message(); + anyhow::anyhow!("{}", message) + }) + })? + }; + + if minify { + module = swc_ecma_minifier::optimize( + std::mem::take(&mut module).into(), + cm.clone(), + None, + None, + &MinifyOptions { + rename: true, + compress: None, + mangle: None, + ..Default::default() + }, + &ExtraOptions { + unresolved_mark: Mark::new(), + top_level_mark: Mark::new(), + mangle_name_cache: None, + }, + ) + .expect_module(); + module.visit_mut_with(&mut fixer(None)); + } + + let mut emitter = swc_ecma_codegen::Emitter { + cfg: swc_ecma_codegen::Config::default().with_minify(minify), + cm: cm.clone(), + comments: None, + wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), + }; + + emitter.emit_module(&module)?; + + Ok(()) +} + +struct PathLoader { + cm: Lrc, +} + +impl Load for PathLoader { + fn load(&self, file: &FileName) -> anyhow::Result { + let file = match file { + FileName::Real(v) => v, + _ => anyhow::bail!("Only real files are supported"), + }; + + let fm = self.cm.load_file(file)?; + + let module = HANDLER.with(|handler| { + parse_file_as_module( + &fm, + Syntax::Es(Default::default()), + Default::default(), + None, + &mut Vec::new(), + ) + .map_err(|err| { + let message = err.into_diagnostic(handler).message(); + anyhow::anyhow!("{}", message) }) + .context("Failed to parse javascript") + })?; + + Ok(ModuleData { + fm, + module, + helpers: Default::default(), }) - .map(|output| output.code); + } +} - match output { - Ok(output) => Ok(output), - Err(err) => { - tracing::error!("Failed to minify javascript: {}", err); - Ok(js) - } +// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 +struct Hook; + +impl swc_bundler::Hook for Hook { + fn get_import_meta_props( + &self, + span: Span, + module_record: &ModuleRecord, + ) -> Result, Error> { + let file_name = module_record.file_name.to_string(); + + Ok(vec![ + KeyValueProp { + key: PropName::Ident(IdentName::new("url".into(), span)), + value: Box::new(Expr::Lit(Lit::Str(Str { + span, + raw: None, + value: file_name.into(), + }))), + }, + KeyValueProp { + key: PropName::Ident(IdentName::new("main".into(), span)), + value: Box::new(if module_record.is_entry { + Expr::Member(MemberExpr { + span, + obj: Box::new(Expr::MetaProp(MetaPropExpr { + span, + kind: MetaPropKind::ImportMeta, + })), + prop: MemberProp::Ident(IdentName::new("main".into(), span)), + }) + } else { + Expr::Lit(Lit::Bool(Bool { span, value: false })) + }), + }, + ]) } } @@ -53,22 +222,22 @@ pub(crate) fn process_js( js_options: &JsAssetOptions, source: &Path, output_path: &Path, + bundle: bool, ) -> anyhow::Result<()> { - let js = if js_options.minified() { - minify_js(source)? + let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?); + if js_options.minified() { + if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { + tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); + } } else { let mut source_file = std::fs::File::open(source)?; - let mut source = String::new(); - source_file.read_to_string(&mut source)?; - source - }; - - std::fs::write(output_path, js).with_context(|| { - format!( - "Failed to write js to output location: {}", - output_path.display() - ) - })?; + std::io::copy(&mut source_file, &mut writer).with_context(|| { + format!( + "Failed to write js to output location: {}", + output_path.display() + ) + })?; + } Ok(()) } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 048bf689bf..621a4f6b03 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -7,7 +7,7 @@ description = "CLI for building fullstack web, desktop, and mobile apps with a s repository = "https://github.com/DioxusLabs/dioxus/" license = "MIT OR Apache-2.0" keywords = ["mobile", "gui", "cli", "dioxus", "wasm"] -rust-version = "1.79.0" +rust-version = "1.81.0" [dependencies] dioxus-autofmt = { workspace = true } @@ -106,6 +106,7 @@ log = { version = "0.4", features = ["max_level_off", "release_max_level_off"] } # link intercept tempfile = "3.3" +manganis = { workspace = true } manganis-core = { workspace = true } # Extracting data from an executable diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 4aea947577..7e413a453a 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -5,7 +5,7 @@ use crate::{BuildRequest, Platform}; use crate::{Result, TraceSrc}; use anyhow::Context; use dioxus_cli_opt::{process_file_to, AssetManifest}; -use manganis_core::AssetOptions; +use manganis::{AssetOptions, JsAssetOptions}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::collections::HashSet; use std::future::Future; @@ -265,7 +265,7 @@ impl AppBundle { app: BuildArtifacts, server: Option, ) -> Result { - let bundle = Self { app, server, build }; + let mut bundle = Self { app, server, build }; tracing::debug!("Assembling app bundle"); @@ -301,7 +301,7 @@ impl AppBundle { /// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations /// Other platforms we might do some stripping or other optimizations /// Move the executable to the workdir - async fn write_main_executable(&self) -> Result<()> { + async fn write_main_executable(&mut self) -> Result<()> { match self.build.build.platform() { // Run wasm-bindgen on the wasm binary and set its output to be in the bundle folder // Also run wasm-opt on the wasm binary, and sets the index.html since that's also the "executable". @@ -328,7 +328,7 @@ impl AppBundle { Platform::Web => { // Run wasm-bindgen and drop its output into the assets folder under "dioxus" self.build.status_wasm_bindgen_start(); - self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.build.exe_dir()) + self.run_wasm_bindgen(&self.app.exe.with_extension("wasm")) .await?; // Only run wasm-opt if the feature is enabled @@ -339,7 +339,7 @@ impl AppBundle { // Write the index.html file with the pre-configured contents we got from pre-rendering std::fs::write( self.build.root_dir().join("index.html"), - self.build.prepare_html()?, + self.prepare_html()?, )?; } @@ -383,29 +383,41 @@ impl AppBundle { // First, clear the asset dir of any files that don't exist in the new manifest _ = tokio::fs::create_dir_all(&asset_dir).await; // Create a set of all the paths that new files will be bundled to - let bundled_output_paths: HashSet<_> = self + let mut keep_bundled_output_paths: HashSet<_> = self .app .assets .assets .values() .map(|a| asset_dir.join(a.bundled_path())) .collect(); + // The CLI creates a .version file in the asset dir to keep track of what version of the optimizer + // the asset was processed. If that version doesn't match the CLI version, we need to re-optimize + // all assets. + let version_file = self.build.asset_optimizer_version_file(); + let clear_cache = std::fs::read_to_string(&version_file) + .ok() + .filter(|s| s == crate::VERSION.as_str()) + .is_none(); + if clear_cache { + keep_bundled_output_paths.clear(); + } + // one possible implementation of walking a directory only visiting files fn remove_old_assets<'a>( path: &'a Path, - bundled_output_paths: &'a HashSet, + keep_bundled_output_paths: &'a HashSet, ) -> Pin> + Send + 'a>> { Box::pin(async move { // If this asset is in the manifest, we don't need to remove it let canon_path = dunce::canonicalize(path)?; - if bundled_output_paths.contains(canon_path.as_path()) { + if keep_bundled_output_paths.contains(canon_path.as_path()) { return Ok(()); } // Otherwise, if it is a directory, we need to walk it and remove child files if path.is_dir() { for entry in std::fs::read_dir(path)?.flatten() { let path = entry.path(); - remove_old_assets(&path, bundled_output_paths).await?; + remove_old_assets(&path, keep_bundled_output_paths).await?; } if path.read_dir()?.next().is_none() { // If the directory is empty, remove it @@ -420,7 +432,7 @@ impl AppBundle { } tracing::debug!("Removing old assets"); - remove_old_assets(&asset_dir, &bundled_output_paths).await?; + remove_old_assets(&asset_dir, &keep_bundled_output_paths).await?; // todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't let mut assets_to_transfer = vec![]; @@ -474,6 +486,15 @@ impl AppBundle { .await .map_err(|e| anyhow::anyhow!("A task failed while trying to copy assets: {e}"))??; + // Remove the wasm bindgen output directory if it exists + _ = std::fs::remove_dir_all(self.build.wasm_bindgen_out_dir()); + + // Write the version file so we know what version of the optimizer we used + std::fs::write( + self.build.asset_optimizer_version_file(), + crate::VERSION.as_str(), + )?; + Ok(()) } @@ -554,9 +575,9 @@ impl AppBundle { .krate .should_pre_compress_web_assets(self.build.build.release); - let bindgen_dir = self.build.exe_dir(); + let asset_dir = self.build.asset_dir(); tokio::task::spawn_blocking(move || { - crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress) + crate::fastfs::pre_compress_folder(&asset_dir, pre_compress) }) .await .unwrap()?; @@ -592,15 +613,14 @@ impl AppBundle { None } - pub(crate) async fn run_wasm_bindgen( - &self, - input_path: &Path, - bindgen_outdir: &Path, - ) -> anyhow::Result<()> { + pub(crate) async fn run_wasm_bindgen(&mut self, input_path: &Path) -> anyhow::Result<()> { tracing::debug!(dx_src = ?TraceSrc::Bundle, "Running wasm-bindgen"); let input_path = input_path.to_path_buf(); - let bindgen_outdir = bindgen_outdir.to_path_buf(); + // Make sure the bindgen output directory exists + let bindgen_outdir = self.build.wasm_bindgen_out_dir(); + std::fs::create_dir_all(&bindgen_outdir)?; + let name = self.build.krate.executable_name().to_string(); let keep_debug = // if we're in debug mode, or we're generating debug symbols, keep debug info @@ -630,6 +650,29 @@ impl AppBundle { .await .context("Failed to generate wasm-bindgen bindings")?; + // After running wasm-bindgen, add the js and wasm asset to the manifest + let js_output_path = self.build.wasm_bindgen_js_output_file(); + let wasm_output_path = self.build.wasm_bindgen_wasm_output_file(); + let new_assets = [ + ( + js_output_path, + AssetOptions::Js(JsAssetOptions::new().with_minify(true).with_preload(true)), + ), + (wasm_output_path, AssetOptions::Unknown), + ]; + for (asset_path, options) in new_assets { + let hash = manganis_core::hash::AssetHash::hash_file_contents(&asset_path)?; + let output_path_str = asset_path.to_str().ok_or(anyhow::anyhow!( + "Failed to convert wasm bindgen output path to string" + ))?; + let bundled_asset = manganis::macro_helpers::create_bundled_asset( + output_path_str, + hash.bytes(), + options, + ); + self.app.assets.assets.insert(asset_path, bundled_asset); + } + tracing::debug!(dx_src = ?TraceSrc::Bundle, "wasm-bindgen complete in {:?}", start.elapsed()); Ok(()) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 7cd661e985..6183429d63 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -656,6 +656,25 @@ impl BuildRequest { } } + /// Get the path to the wasm bindgen temporary output folder + pub fn wasm_bindgen_out_dir(&self) -> PathBuf { + self.root_dir().join("wasm-bindgen") + } + + /// Get the path to the wasm bindgen javascript output file + pub fn wasm_bindgen_js_output_file(&self) -> PathBuf { + self.wasm_bindgen_out_dir() + .join(self.krate.executable_name()) + .with_extension("js") + } + + /// Get the path to the wasm bindgen wasm output file + pub fn wasm_bindgen_wasm_output_file(&self) -> PathBuf { + self.wasm_bindgen_out_dir() + .join(format!("{}_bg", self.krate.executable_name())) + .with_extension("wasm") + } + /// returns the path to root build folder. This will be our working directory for the build. /// /// we only add an extension to the folders where it sorta matters that it's named with the extension. @@ -718,6 +737,11 @@ impl BuildRequest { } } + /// Get the path to the asset optimizer version file + pub fn asset_optimizer_version_file(&self) -> PathBuf { + self.platform_dir().join(".cli-version") + } + pub fn platform_exe_name(&self) -> String { match self.build.platform() { Platform::MacOS => self.krate.executable_name().to_string(), diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index b113dbfb9f..a3fae3c889 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -1,17 +1,19 @@ use dioxus_cli_config::format_base_path_meta_element; +use manganis::AssetOptions; use crate::error::Result; -use crate::BuildRequest; use std::fmt::Write; use std::path::{Path, PathBuf}; +use super::AppBundle; + const DEFAULT_HTML: &str = include_str!("../../assets/web/index.html"); const TOAST_HTML: &str = include_str!("../../assets/web/toast.html"); -impl BuildRequest { +impl AppBundle { pub(crate) fn prepare_html(&self) -> Result { let mut html = { - let crate_root: &Path = &self.krate.crate_dir(); + let crate_root: &Path = &self.build.krate.crate_dir(); let custom_html_file = crate_root.join("index.html"); std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML)) }; @@ -25,7 +27,7 @@ impl BuildRequest { // Replace any special placeholders in the HTML with resolved values self.replace_template_placeholders(&mut html); - let title = self.krate.config.web.app.title.clone(); + let title = self.build.krate.config.web.app.title.clone(); replace_or_insert_before("{app_title}", " bool { - !self.build.release + !self.build.build.release } // Inject any resources from the config into the html fn inject_resources(&self, html: &mut String) -> Result<()> { // Collect all resources into a list of styles and scripts - let resources = &self.krate.config.web.resource; + let resources = &self.build.krate.config.web.resource; let mut style_list = resources.style.clone().unwrap_or_default(); let mut script_list = resources.script.clone().unwrap_or_default(); @@ -70,7 +72,7 @@ impl BuildRequest { // Add the base path to the head if this is a debug build if self.is_dev_build() { - if let Some(base_path) = &self.krate.config.web.app.base_path { + if let Some(base_path) = &self.build.krate.config.web.app.base_path { head_resources.push_str(&format_base_path_meta_element(base_path)); } } @@ -83,9 +85,45 @@ impl BuildRequest { } // Inject any resources from manganis into the head - // if let Some(assets) = assets { - // head_resources.push_str(&assets.head()); - // } + for asset in self.app.assets.assets.values() { + let asset_path = asset.bundled_path(); + match asset.options() { + AssetOptions::Css(css_options) => { + if css_options.preloaded() { + head_resources.push_str(&format!( + "" + )) + } + } + AssetOptions::Image(image_options) => { + if image_options.preloaded() { + head_resources.push_str(&format!( + "" + )) + } + } + AssetOptions::Js(js_options) => { + if js_options.preloaded() { + head_resources.push_str(&format!( + "" + )) + } + } + _ => {} + } + } + // Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system + let wasm_source_path = self.build.wasm_bindgen_wasm_output_file(); + let wasm_path = self + .app + .assets + .assets + .get(&wasm_source_path) + .expect("WASM asset should exist in web bundles") + .bundled_path(); + head_resources.push_str(&format!( + "" + )); replace_or_insert_before("{style_include}", " // We can't use a module script here because we need to start the script immediately when streaming - import("/{base_path}/wasm/{app_name}.js").then( + import("/{base_path}/{js_path}").then( ({ default: init }) => { - init("/{base_path}/wasm/{app_name}_bg.wasm").then((wasm) => { + init("/{base_path}/{wasm_path}").then((wasm) => { if (wasm.__wbindgen_start == undefined) { wasm.main(); } @@ -123,22 +161,41 @@ impl BuildRequest { true => html.replace("{DX_TOAST_UTILITIES}", TOAST_HTML), false => html.replace("{DX_TOAST_UTILITIES}", ""), }; - - // And try to insert preload links for the wasm and js files - *html = html.replace( - " - - = Lazy::new(|| { +pub(crate) static VERSION: Lazy = Lazy::new(|| { format!( "{} ({})", crate::dx_build_info::PKG_VERSION, diff --git a/packages/cli/src/serve/proxy.rs b/packages/cli/src/serve/proxy.rs index 7a7e2ae65c..3c29f2415d 100644 --- a/packages/cli/src/serve/proxy.rs +++ b/packages/cli/src/serve/proxy.rs @@ -153,7 +153,6 @@ pub(crate) fn proxy_to( if uri.path().starts_with("/assets") || uri.path().starts_with("/_dioxus") || uri.path().starts_with("/public") - || uri.path().starts_with("/wasm") { tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri); } else { diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 442199a355..48b1ac67da 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -598,6 +598,7 @@ fn hide_last_window(window: &Window) { // back to the app. `NSApplication::hide:` has the correct behaviour use objc::runtime::Object; use objc::{msg_send, sel, sel_impl}; + #[allow(unexpected_cfgs)] objc::rc::autoreleasepool(|| unsafe { let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication]; let nil = std::ptr::null_mut::(); diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index b1f8c72a8e..2c5ad4bd12 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -215,6 +215,7 @@ impl WebviewInstance { unsafe { let window: id = window.ns_window() as id; + #[allow(unexpected_cfgs)] let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged]; } } diff --git a/packages/liveview/src/history.rs b/packages/liveview/src/history.rs index aed8fcfc7f..20805e812f 100644 --- a/packages/liveview/src/history.rs +++ b/packages/liveview/src/history.rs @@ -320,7 +320,7 @@ impl History for LiveviewHistory { visited_indices .iter() .position(|&rhs| timeline.current_index == rhs) - .map_or(false, |index| { + .is_some_and(|index| { index > 0 && visited_indices[index - 1] == timeline.current_index - 1 }) } @@ -332,7 +332,7 @@ impl History for LiveviewHistory { visited_indices .iter() .rposition(|&rhs| timeline.current_index == rhs) - .map_or(false, |index| { + .is_some_and(|index| { index < visited_indices.len() - 1 && visited_indices[index + 1] == timeline.current_index + 1 }) diff --git a/packages/manganis/manganis-core/src/asset.rs b/packages/manganis/manganis-core/src/asset.rs index 97ceb6ef7e..28434b5382 100644 --- a/packages/manganis/manganis-core/src/asset.rs +++ b/packages/manganis/manganis-core/src/asset.rs @@ -59,6 +59,21 @@ impl BundledAsset { Self::new(absolute_source_path, bundled_path, options) } + #[doc(hidden)] + /// This should only be called from the macro + /// Create a new asset from const paths + pub const fn new_from_const( + absolute_source_path: ConstStr, + bundled_path: ConstStr, + options: AssetOptions, + ) -> Self { + Self { + absolute_source_path, + bundled_path, + options, + } + } + /// Get the bundled name of the asset. This identifier cannot be used to read the asset directly pub fn bundled_path(&self) -> &str { self.bundled_path.as_str() diff --git a/packages/manganis/manganis-core/src/hash.rs b/packages/manganis/manganis-core/src/hash.rs new file mode 100644 index 0000000000..3609d3d220 --- /dev/null +++ b/packages/manganis/manganis-core/src/hash.rs @@ -0,0 +1,94 @@ +//! Utilities for creating hashed paths to assets in Manganis. This module defines [`AssetHash`] which is used to create a hashed path to an asset in both the CLI and the macro. + +use std::{ + error::Error, + hash::{Hash, Hasher}, + io::Read, + path::{Path, PathBuf}, +}; + +/// An error that can occur when hashing an asset +#[derive(Debug)] +#[non_exhaustive] +pub enum AssetHashError { + /// An io error occurred + IoError { err: std::io::Error, path: PathBuf }, +} + +impl std::fmt::Display for AssetHashError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AssetHashError::IoError { path, err } => { + write!(f, "Failed to read file: {}; {}", path.display(), err) + } + } + } +} + +impl Error for AssetHashError {} + +/// The opaque hash type manganis uses to identify assets. Each time an asset or asset options change, this hash will +/// change. This hash is included in the URL of the bundled asset for cache busting. +pub struct AssetHash { + /// We use a wrapper type here to hide the exact size of the hash so we can switch to a sha hash in a minor version bump + hash: [u8; 8], +} + +impl AssetHash { + /// Create a new asset hash + const fn new(hash: u64) -> Self { + Self { + hash: hash.to_le_bytes(), + } + } + + /// Get the hash bytes + pub const fn bytes(&self) -> &[u8] { + &self.hash + } + + /// Create a new asset hash for a file. The input file to this function should be fully resolved + pub fn hash_file_contents(file_path: &Path) -> Result { + // Create a hasher + let mut hash = std::collections::hash_map::DefaultHasher::new(); + + // If this is a folder, hash the folder contents + if file_path.is_dir() { + let files = std::fs::read_dir(file_path).map_err(|err| AssetHashError::IoError { + err, + path: file_path.to_path_buf(), + })?; + for file in files.flatten() { + let path = file.path(); + Self::hash_file_contents(&path)?.bytes().hash(&mut hash); + } + let hash = hash.finish(); + return Ok(AssetHash::new(hash)); + } + + // Otherwise, open the file to get its contents + let mut file = std::fs::File::open(file_path).map_err(|err| AssetHashError::IoError { + err, + path: file_path.to_path_buf(), + })?; + + // We add a hash to the end of the file so it is invalidated when the bundled version of the file changes + // The hash includes the file contents, the options, and the version of manganis. From the macro, we just + // know the file contents, so we only include that hash + let mut buffer = [0; 8192]; + loop { + let read = file + .read(&mut buffer) + .map_err(|err| AssetHashError::IoError { + err, + path: file_path.to_path_buf(), + })?; + if read == 0 { + break; + } + hash.write(&buffer[..read]); + } + + Ok(AssetHash::new(hash.finish())) + } +} diff --git a/packages/manganis/manganis-core/src/lib.rs b/packages/manganis/manganis-core/src/lib.rs index b3dddf836b..52e6ac80b2 100644 --- a/packages/manganis/manganis-core/src/lib.rs +++ b/packages/manganis/manganis-core/src/lib.rs @@ -17,3 +17,5 @@ mod asset; pub use asset::*; pub mod linker; + +pub mod hash; diff --git a/packages/manganis/manganis-macro/src/asset.rs b/packages/manganis/manganis-macro/src/asset.rs index 315ab58cbf..9e274d52f1 100644 --- a/packages/manganis/manganis-macro/src/asset.rs +++ b/packages/manganis/manganis-macro/src/asset.rs @@ -1,15 +1,35 @@ +use manganis_core::hash::AssetHash; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens, TokenStreamExt}; -use std::{ - hash::Hasher, - io::Read, - path::{Path, PathBuf}, -}; +use std::path::PathBuf; use syn::{ parse::{Parse, ParseStream}, LitStr, Token, }; +#[derive(Debug)] +pub(crate) enum AssetParseError { + AssetDoesntExist { path: PathBuf }, + InvalidPath { path: PathBuf }, +} + +impl std::fmt::Display for AssetParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AssetParseError::AssetDoesntExist { path } => { + write!(f, "Asset at {} doesn't exist", path.display()) + } + AssetParseError::InvalidPath { path } => { + write!( + f, + "Asset path {} is invalid. Make sure the asset exists within this crate.", + path.display() + ) + } + } + } +} + fn resolve_path(raw: &str) -> Result { // Get the location of the root of the crate which is where all assets are relative to // @@ -53,75 +73,6 @@ fn resolve_path(raw: &str) -> Result { Ok(path) } -fn hash_file_contents(file_path: &Path) -> Result { - // Create a hasher - let mut hash = std::collections::hash_map::DefaultHasher::new(); - - // If this is a folder, hash the folder contents - if file_path.is_dir() { - let files = std::fs::read_dir(file_path).map_err(|err| AssetParseError::IoError { - err, - path: file_path.to_path_buf(), - })?; - for file in files.flatten() { - let path = file.path(); - hash_file_contents(&path)?; - } - return Ok(hash.finish()); - } - - // Otherwise, open the file to get its contents - let mut file = std::fs::File::open(file_path).map_err(|err| AssetParseError::IoError { - err, - path: file_path.to_path_buf(), - })?; - - // We add a hash to the end of the file so it is invalidated when the bundled version of the file changes - // The hash includes the file contents, the options, and the version of manganis. From the macro, we just - // know the file contents, so we only include that hash - let mut buffer = [0; 8192]; - loop { - let read = file - .read(&mut buffer) - .map_err(AssetParseError::FailedToReadAsset)?; - if read == 0 { - break; - } - hash.write(&buffer[..read]); - } - - Ok(hash.finish()) -} - -#[derive(Debug)] -pub(crate) enum AssetParseError { - AssetDoesntExist { path: PathBuf }, - IoError { err: std::io::Error, path: PathBuf }, - InvalidPath { path: PathBuf }, - FailedToReadAsset(std::io::Error), -} - -impl std::fmt::Display for AssetParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AssetParseError::AssetDoesntExist { path } => { - write!(f, "Asset at {} doesn't exist", path.display()) - } - AssetParseError::IoError { path, err } => { - write!(f, "Failed to read file: {}; {}", path.display(), err) - } - AssetParseError::InvalidPath { path } => { - write!( - f, - "Asset path {} is invalid. Make sure the asset exists within this crate.", - path.display() - ) - } - AssetParseError::FailedToReadAsset(err) => write!(f, "Failed to read asset: {}", err), - } - } -} - pub struct AssetParser { /// The span of the source string path_span: proc_macro2::Span, @@ -189,7 +140,7 @@ impl ToTokens for AssetParser { let mut asset_str = proc_macro2::Literal::string(&asset_str); asset_str.set_span(self.path_span); - let hash = match hash_file_contents(asset) { + let hash = match AssetHash::hash_file_contents(asset) { Ok(hash) => hash, Err(err) => { let err = err.to_string(); @@ -198,15 +149,17 @@ impl ToTokens for AssetParser { } }; + let hash = hash.bytes(); + // Generate the link section for the asset // The link section includes the source path and the output path of the asset let link_section = crate::generate_link_section(quote!(__ASSET)); // generate the asset::new method to deprecate the `./assets/blah.css` syntax let constructor = if asset.is_relative() { - quote::quote! { new_relative } + quote::quote! { create_bundled_asset_relative } } else { - quote::quote! { new } + quote::quote! { create_bundled_asset } }; let options = if self.options.is_empty() { @@ -218,22 +171,16 @@ impl ToTokens for AssetParser { tokens.extend(quote! { { // We keep a hash of the contents of the asset for cache busting - const __ASSET_HASH: u64 = #hash; + const __ASSET_HASH: &[u8] = &[#(#hash),*]; // The source is used by the CLI to copy the asset const __ASSET_SOURCE_PATH: &'static str = #asset_str; // The options give the CLI info about how to process the asset // Note: into_asset_options is not a trait, so we cannot accept the options directly // in the constructor. Stable rust doesn't have support for constant functions in traits const __ASSET_OPTIONS: manganis::AssetOptions = #options.into_asset_options(); - // We calculate the bundled path from the hash and any transformations done by the options - // This is the final path that the asset will be written to - const __ASSET_BUNDLED_PATH: manganis::macro_helpers::const_serialize::ConstStr = manganis::macro_helpers::generate_unique_path(__ASSET_SOURCE_PATH, __ASSET_HASH, &__ASSET_OPTIONS); - // Get the reference to the string that was generated. We cannot return &'static str from - // generate_unique_path because it would return a reference to data generated in the function - const __ASSET_BUNDLED_PATH_STR: &'static str = __ASSET_BUNDLED_PATH.as_str(); // Create the asset that the crate will use. This is used both in the return value and // added to the linker for the bundler to copy later - const __ASSET: manganis::BundledAsset = manganis::BundledAsset::#constructor(__ASSET_SOURCE_PATH, __ASSET_BUNDLED_PATH_STR, __ASSET_OPTIONS); + const __ASSET: manganis::BundledAsset = manganis::macro_helpers::#constructor(__ASSET_SOURCE_PATH, __ASSET_HASH, __ASSET_OPTIONS); #link_section diff --git a/packages/manganis/manganis/src/macro_helpers.rs b/packages/manganis/manganis/src/macro_helpers.rs index 26a27b2450..8da971fa5f 100644 --- a/packages/manganis/manganis/src/macro_helpers.rs +++ b/packages/manganis/manganis/src/macro_helpers.rs @@ -4,11 +4,45 @@ use manganis_core::{AssetOptions, BundledAsset}; use crate::hash::ConstHasher; +/// Create a bundled asset from the input path, the content hash, and the asset options +pub const fn create_bundled_asset( + input_path: &str, + content_hash: &[u8], + asset_config: AssetOptions, +) -> BundledAsset { + let hashed_path = generate_unique_path_with_byte_hash(input_path, content_hash, &asset_config); + BundledAsset::new_from_const(ConstStr::new(input_path), hashed_path, asset_config) +} + +/// Create a bundled asset from the input path, the content hash, and the asset options with a relative asset deprecation warning +/// +/// This method is deprecated and will be removed in a future release. +#[deprecated( + note = "Relative asset!() paths are not supported. Use a path like `/assets/myfile.png` instead of `./assets/myfile.png`" +)] +pub const fn create_bundled_asset_relative( + input_path: &str, + content_hash: &[u8], + asset_config: AssetOptions, +) -> BundledAsset { + create_bundled_asset(input_path, content_hash, asset_config) +} + /// Format the input path with a hash to create an unique output path for the macro in the form `{input_path}-{hash}.{extension}` pub const fn generate_unique_path( input_path: &str, content_hash: u64, asset_config: &AssetOptions, +) -> ConstStr { + let byte_hash = content_hash.to_le_bytes(); + generate_unique_path_with_byte_hash(input_path, &byte_hash, asset_config) +} + +/// Format the input path with a hash to create an unique output path for the macro in the form `{input_path}-{hash}.{extension}` +const fn generate_unique_path_with_byte_hash( + input_path: &str, + content_hash: &[u8], + asset_config: &AssetOptions, ) -> ConstStr { // Format the unique path with the format `{input_path}-{hash}.{extension}` // Start with the input path @@ -43,7 +77,7 @@ pub const fn generate_unique_path( // Hash the contents along with the asset config to create a unique hash for the asset // When this hash changes, the client needs to re-fetch the asset let mut hasher = ConstHasher::new(); - hasher = hasher.write(&content_hash.to_le_bytes()); + hasher = hasher.write(content_hash); hasher = hasher.hash_by_bytes(asset_config); let hash = hasher.finish(); diff --git a/packages/playwright-tests/cli-optimization.spec.js b/packages/playwright-tests/cli-optimization.spec.js new file mode 100644 index 0000000000..a4678bff93 --- /dev/null +++ b/packages/playwright-tests/cli-optimization.spec.js @@ -0,0 +1,11 @@ +// @ts-check +const { test, expect } = require("@playwright/test"); + +test("optimized scripts run", async ({ page }) => { + await page.goto("http://localhost:8989"); + + // Expect the page to load the script after optimizations have been applied. The script + // should add an editor to the page that shows a main function + const main = page.locator("#main"); + await expect(main).toContainText("hi"); +}); diff --git a/packages/playwright-tests/cli-optimization/.gitignore b/packages/playwright-tests/cli-optimization/.gitignore new file mode 100644 index 0000000000..613fd2f892 --- /dev/null +++ b/packages/playwright-tests/cli-optimization/.gitignore @@ -0,0 +1,3 @@ +dist +target +monaco-editor-0.52.2 \ No newline at end of file diff --git a/packages/playwright-tests/cli-optimization/Cargo.toml b/packages/playwright-tests/cli-optimization/Cargo.toml new file mode 100644 index 0000000000..b26db777ed --- /dev/null +++ b/packages/playwright-tests/cli-optimization/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "dioxus-cli-optimization-test" +version = "0.0.1" +edition = "2021" +description = "Playwright test for Dioxus CLI optimization" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +dioxus = { workspace = true, features = ["web"]} + +[build-dependencies] +reqwest = { workspace = true, features = ["blocking"] } +flate2 = "1.0.35" +tar = "0.4.43" diff --git a/packages/playwright-tests/cli-optimization/build.rs b/packages/playwright-tests/cli-optimization/build.rs new file mode 100644 index 0000000000..788ed1ed69 --- /dev/null +++ b/packages/playwright-tests/cli-optimization/build.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; + +fn main() { + // If the monaco editor folder doesn't exist, download it + let monaco_path = PathBuf::from("monaco-editor-0.52.2"); + if monaco_path.exists() { + return; + } + + let url = "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz"; + let bytes = reqwest::blocking::get(url).unwrap().bytes().unwrap(); + let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(bytes.as_ref())); + archive.unpack(&monaco_path).unwrap(); +} diff --git a/packages/playwright-tests/cli-optimization/src/main.rs b/packages/playwright-tests/cli-optimization/src/main.rs new file mode 100644 index 0000000000..32cecf8792 --- /dev/null +++ b/packages/playwright-tests/cli-optimization/src/main.rs @@ -0,0 +1,34 @@ +// This test checks the CLI optimizes assets correctly without breaking them + +use dioxus::prelude::*; + +const MONACO_FOLDER: Asset = asset!("/monaco-editor-0.52.2/package/min/vs"); + +fn main() { + dioxus::launch(App); +} + +#[component] +fn App() -> Element { + let script = format!("(() => {{ + require.config({{ paths: {{ vs: '{MONACO_FOLDER}' }} }}); + + require(['vs/editor/editor.main'], () => {{ + var model = monaco.editor.createModel('fn main() {{\\n\\tprintln!(\\\"hi\\\")\\n}}', 'rust'); + var editor = monaco.editor.create(document.getElementById('editor')); + editor.setModel(model); + }}) +}})()"); + rsx! { + div { + id: "editor", + width: "100vw", + height: "100vw", + } + // Monaco script + script { + src: "{MONACO_FOLDER}/loader.js", + "onload": script + } + } +} diff --git a/packages/playwright-tests/playwright.config.js b/packages/playwright-tests/playwright.config.js index 7eaa94619c..130e95fdfa 100644 --- a/packages/playwright-tests/playwright.config.js +++ b/packages/playwright-tests/playwright.config.js @@ -138,5 +138,15 @@ module.exports = defineConfig({ reuseExistingServer: !process.env.CI, stdout: "pipe", }, + { + cwd: path.join(process.cwd(), "cli-optimization"), + // Remove the cache folder for the cli-optimization build to force a full cache reset + command: + 'cargo run --package dioxus-cli --release -- serve --addr "127.0.0.1" --port 8989', + port: 8989, + timeout: 50 * 60 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, ], });