diff --git a/Cargo.lock b/Cargo.lock index 2461ee5c3..7e4ca771e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -35,13 +59,22 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64-simd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] + [[package]] name = "base64-simd" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "outref", + "outref 0.5.2", "vsimd", ] @@ -54,6 +87,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bstr" version = "1.12.1" @@ -64,6 +109,40 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "castaway" version = "0.2.4" @@ -125,6 +204,35 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21077772762a1002bb421c3af42ac1725fa56066bfc53d9a55bb79905df2aaf3" +dependencies = [ + "const-str-proc-macro", +] + +[[package]] +name = "const-str-proc-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1e0fdd2e5d3041e530e1b21158aeeef8b5d0e306bc5c1e3d6cf0930d10e25a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.11.0" @@ -140,6 +248,63 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cssparser" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser-color" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f" +dependencies = [ + "cssparser", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "ctor" version = "0.6.3" @@ -156,6 +321,34 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -164,7 +357,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -173,6 +366,21 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "dtor" version = "0.1.1" @@ -267,6 +475,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.32" @@ -323,7 +537,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -355,6 +569,29 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -363,7 +600,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -390,6 +627,21 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -550,6 +802,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -565,6 +826,16 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "json-escape-simd" version = "3.0.1" @@ -580,6 +851,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -613,6 +890,46 @@ dependencies = [ "libc", ] +[[package]] +name = "lightningcss" +version = "1.0.0-alpha.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6314c2f0590ac93c86099b98bb7ba8abcf759bfd89604ffca906472bb54937" +dependencies = [ + "ahash 0.8.12", + "bitflags", + "const-str", + "cssparser", + "cssparser-color", + "dashmap", + "data-encoding", + "getrandom 0.3.4", + "indexmap", + "itertools 0.10.5", + "lazy_static", + "lightningcss-derive", + "parcel_selectors", + "parcel_sourcemap", + "pastey", + "pathdiff", + "rayon", + "serde", + "serde-content", + "smallvec", +] + +[[package]] +name = "lightningcss-derive" +version = "1.0.0-alpha.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -625,12 +942,27 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "memchr" version = "2.8.0" @@ -674,12 +1006,12 @@ version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d" dependencies = [ - "convert_case", + "convert_case 0.11.0", "ctor", "napi-derive-backend", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -688,11 +1020,11 @@ version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" dependencies = [ - "convert_case", + "convert_case 0.11.0", "proc-macro2", "quote", "semver", - "syn", + "syn 2.0.117", ] [[package]] @@ -756,6 +1088,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "outref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" + [[package]] name = "outref" version = "0.5.2" @@ -791,7 +1129,7 @@ checksum = "d4faecb54d0971f948fbc1918df69b26007e6f279a204793669542e1e8b75eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -814,6 +1152,7 @@ version = "0.1.0" dependencies = [ "indexmap", "insta", + "lightningcss", "oxc-miette", "oxc_allocator", "oxc_ast", @@ -885,10 +1224,10 @@ version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d3d3eb4a7b45c9f407f96ecafddad0a88cbf085dca7d74f3e4191ba03d2d5f3" dependencies = [ - "phf", + "phf 0.13.1", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1007,7 +1346,7 @@ dependencies = [ "oxc_ast_macros", "oxc_diagnostics", "oxc_span", - "phf", + "phf 0.13.1", "rustc-hash", "unicode-id-start", ] @@ -1045,7 +1384,7 @@ version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ab63f46cfbf4ef58b5a78439202593fe4a8511281ba1de32303e2c28b0e5e8" dependencies = [ - "itertools", + "itertools 0.14.0", "memchr", "oxc_allocator", "oxc_ast", @@ -1067,7 +1406,7 @@ version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f89482522f3cd820817d48ee4ade5b10822060d6e5e4d419f05f6d8bd29d70" dependencies = [ - "base64-simd", + "base64-simd 0.8.0", "json-escape-simd", "rustc-hash", "serde", @@ -1117,7 +1456,7 @@ dependencies = [ "oxc_estree", "oxc_index", "oxc_span", - "phf", + "phf 0.13.1", "serde", "unicode-id-start", ] @@ -1132,6 +1471,55 @@ dependencies = [ "seize", ] +[[package]] +name = "parcel_selectors" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" +dependencies = [ + "bitflags", + "cssparser", + "log", + "phf 0.11.3", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "parcel_sourcemap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" +dependencies = [ + "base64-simd 0.7.0", + "data-url", + "rkyv", + "serde", + "serde_json", + "vlq", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1144,17 +1532,47 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.13.1", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -1162,7 +1580,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -1171,11 +1602,20 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", ] [[package]] @@ -1208,6 +1648,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "prettyplease" version = "0.2.37" @@ -1215,7 +1661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -1233,6 +1679,26 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.45" @@ -1242,12 +1708,68 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -1265,7 +1787,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1285,6 +1807,44 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -1325,6 +1885,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "seize" version = "0.5.1" @@ -1363,6 +1935,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1380,7 +1961,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1403,6 +1984,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref 0.1.0", +] + [[package]] name = "simd-json" version = "0.17.0" @@ -1466,6 +2056,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1485,9 +2086,15 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.26.0" @@ -1495,7 +2102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1529,7 +2136,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1542,6 +2149,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.50.0" @@ -1570,7 +2192,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1636,6 +2258,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "value-trait" version = "0.12.1" @@ -1648,6 +2280,18 @@ dependencies = [ "ryu", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + [[package]] name = "vsimd" version = "0.8.0" @@ -1664,6 +2308,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1682,6 +2332,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -1778,7 +2473,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1789,7 +2484,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1947,7 +2642,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1963,7 +2658,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2011,6 +2706,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yoke" version = "0.8.1" @@ -2030,10 +2734,30 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -2051,7 +2775,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2085,7 +2809,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/crates/oxc_angular_compiler/Cargo.toml b/crates/oxc_angular_compiler/Cargo.toml index fbecb13a2..0712ece2b 100644 --- a/crates/oxc_angular_compiler/Cargo.toml +++ b/crates/oxc_angular_compiler/Cargo.toml @@ -26,6 +26,7 @@ oxc_sourcemap = { workspace = true } miette = { workspace = true } rustc-hash = { workspace = true } indexmap = { workspace = true } +lightningcss = "1.0.0-alpha.71" oxc_resolver = { version = "11", optional = true } pathdiff = { version = "0.2", optional = true } semver = "1.0.27" diff --git a/crates/oxc_angular_compiler/src/component/definition.rs b/crates/oxc_angular_compiler/src/component/definition.rs index b84db57da..c0157adf4 100644 --- a/crates/oxc_angular_compiler/src/component/definition.rs +++ b/crates/oxc_angular_compiler/src/component/definition.rs @@ -23,6 +23,7 @@ use super::metadata::{ ViewEncapsulation, }; use super::namespace_registry::NamespaceRegistry; +use super::transform::TransformOptions; use crate::directive::{ create_host_directive_mappings_array, create_inputs_literal, create_outputs_literal, }; @@ -62,6 +63,7 @@ pub struct ComponentDefinitions<'a> { pub fn generate_component_definitions<'a>( allocator: &'a Allocator, metadata: &ComponentMetadata<'a>, + options: &TransformOptions, job: &mut ComponentCompilationJob<'a>, template_fn: FunctionExpr<'a>, host_binding_result: Option>, @@ -79,6 +81,7 @@ pub fn generate_component_definitions<'a>( let cmp_definition = generate_cmp_definition( allocator, metadata, + options, job, template_fn, host_binding_result, @@ -109,6 +112,7 @@ pub fn generate_component_definitions<'a>( fn generate_cmp_definition<'a>( allocator: &'a Allocator, metadata: &ComponentMetadata<'a>, + options: &TransformOptions, job: &mut ComponentCompilationJob<'a>, template_fn: FunctionExpr<'a>, host_binding_result: Option>, @@ -435,23 +439,17 @@ fn generate_cmp_definition<'a>( if !metadata.styles.is_empty() { let mut style_entries: OxcVec<'a, OutputExpression<'a>> = OxcVec::new_in(allocator); for style in &metadata.styles { - // Apply CSS scoping for Emulated encapsulation - let style_value = if metadata.encapsulation == ViewEncapsulation::Emulated { - // Use shim_css_text with %COMP% placeholder - // Angular's runtime will replace %COMP% with the actual component ID - let scoped = crate::styles::shim_css_text(style.as_str(), content_attr, host_attr); - // Skip empty styles - if scoped.trim().is_empty() { - continue; - } - Atom::from_in(scoped.as_str(), allocator) - } else { - // For None/ShadowDom, use styles as-is - if style.trim().is_empty() { - continue; - } - style.clone() - }; + let style = crate::styles::finalize_component_style( + style.as_str(), + metadata.encapsulation == ViewEncapsulation::Emulated, + content_attr, + host_attr, + options.minify_component_styles, + ); + if style.trim().is_empty() { + continue; + } + let style_value = Atom::from_in(style.as_str(), allocator); style_entries.push(OutputExpression::Literal(Box::new_in( LiteralExpr { value: LiteralValue::String(style_value), source_span: None }, diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs index e497c8bdd..636906434 100644 --- a/crates/oxc_angular_compiler/src/component/transform.rs +++ b/crates/oxc_angular_compiler/src/component/transform.rs @@ -174,6 +174,12 @@ pub struct TransformOptions { /// /// Default: false (metadata is dev-only and usually stripped in production) pub emit_class_metadata: bool, + + /// Minify final component styles before emitting them into `styles: [...]`. + /// + /// This runs after Angular style encapsulation, so it applies to the same + /// final CSS strings that are embedded in component definitions. + pub minify_component_styles: bool, } /// Input for host metadata when passed via TransformOptions. @@ -223,6 +229,7 @@ impl Default for TransformOptions { resolved_imports: None, // Class metadata for TestBed support (disabled by default) emit_class_metadata: false, + minify_component_styles: false, } } } @@ -2453,6 +2460,7 @@ fn compile_component_full<'a>( let definitions = generate_component_definitions( allocator, metadata, + options, &mut job, compiled.template_fn, host_binding_result, diff --git a/crates/oxc_angular_compiler/src/styles/minify.rs b/crates/oxc_angular_compiler/src/styles/minify.rs new file mode 100644 index 000000000..89f75a907 --- /dev/null +++ b/crates/oxc_angular_compiler/src/styles/minify.rs @@ -0,0 +1,68 @@ +use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, StyleSheet}; + +const COMPONENT_PLACEHOLDER: &str = "%COMP%"; +const MINIFY_PLACEHOLDER: &str = "OXCANGULARCOMPONENT"; + +/// Apply Angular style encapsulation and optionally minify the final CSS. +pub fn finalize_component_style( + style: &str, + encapsulate: bool, + content_attr: &str, + host_attr: &str, + minify: bool, +) -> String { + let style = if encapsulate { + super::shim_css_text(style, content_attr, host_attr) + } else { + style.to_string() + }; + + if !minify || style.trim().is_empty() { + return style; + } + + minify_component_style(&style).unwrap_or(style) +} + +/// Minify a final component CSS string while preserving Angular's `%COMP%` placeholder. +pub fn minify_component_style(style: &str) -> Option { + let css = style.replace(COMPONENT_PLACEHOLDER, MINIFY_PLACEHOLDER); + let mut stylesheet = StyleSheet::parse(&css, ParserOptions::default()).ok()?; + stylesheet.minify(MinifyOptions::default()).ok()?; + + let code = + stylesheet.to_css(PrinterOptions { minify: true, ..PrinterOptions::default() }).ok()?.code; + + Some(code.replace(MINIFY_PLACEHOLDER, COMPONENT_PLACEHOLDER)) +} + +#[cfg(test)] +mod tests { + use super::{finalize_component_style, minify_component_style}; + + #[test] + fn minifies_css_with_component_placeholders() { + let minified = minify_component_style( + "[_ngcontent-%COMP%] {\n color: red;\n background: transparent;\n}\n", + ) + .expect("style should minify"); + + assert_eq!(minified, "[_ngcontent-%COMP%]{color:red;background:0 0}"); + } + + #[test] + fn finalizes_emulated_styles_before_minifying() { + let finalized = finalize_component_style( + ":host {\n display: block;\n}\n.button {\n color: red;\n}\n", + true, + "_ngcontent-%COMP%", + "_nghost-%COMP%", + true, + ); + + assert_eq!( + finalized, + "[_nghost-%COMP%]{display:block}.button[_ngcontent-%COMP%]{color:red}" + ); + } +} diff --git a/crates/oxc_angular_compiler/src/styles/mod.rs b/crates/oxc_angular_compiler/src/styles/mod.rs index d11c1cea4..7d71b123b 100644 --- a/crates/oxc_angular_compiler/src/styles/mod.rs +++ b/crates/oxc_angular_compiler/src/styles/mod.rs @@ -5,5 +5,7 @@ //! - CSS transformation for component-scoped styles mod encapsulation; +mod minify; pub use encapsulation::{encapsulate_style, shim_css_text}; +pub use minify::{finalize_component_style, minify_component_style}; diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 9fcaebe71..e2cd13117 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -1524,6 +1524,34 @@ export class MultiStyledComponent {} insta::assert_snapshot!("component_with_multiple_styles", result.code); } +#[test] +fn test_component_with_minified_styles() { + let allocator = Allocator::default(); + let source = r#" +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-styled', + template: '
Hello
', + styles: ['.container { color: red; background: transparent; }'] +}) +export class StyledComponent {} +"#; + + let mut options = ComponentTransformOptions::default(); + options.minify_component_styles = true; + + let result = transform_angular_file(&allocator, "styled.component.ts", source, &options, None); + + assert_eq!(result.component_count, 1); + assert!(!result.has_errors(), "Should not have errors: {:?}", result.diagnostics); + assert!( + result.code.contains(".container[_ngcontent-%COMP%]{color:red;background:0 0}"), + "Generated code should contain minified component styles: {}", + result.code + ); +} + #[test] fn test_component_without_styles_downgrades_encapsulation() { let allocator = Allocator::default(); diff --git a/napi/angular-compiler/README.md b/napi/angular-compiler/README.md index 912344709..f63c921a3 100644 --- a/napi/angular-compiler/README.md +++ b/napi/angular-compiler/README.md @@ -139,6 +139,9 @@ interface TransformOptions { // i18n i18nUseExternalIds?: boolean + // Final component style output + minifyComponentStyles?: boolean + // Component metadata selector?: string standalone?: boolean @@ -176,6 +179,7 @@ interface AngularPluginOptions { // Style processing inlineStylesExtension?: string + minifyComponentStyles?: boolean | 'auto' // File replacements fileReplacements?: Array<{ @@ -185,6 +189,14 @@ interface AngularPluginOptions { } ``` +`minifyComponentStyles` resolves like this: + +- `true`: always minify component styles +- `false`: never minify component styles +- `"auto"` or `undefined`: follow the resolved Vite minification settings + +For `"auto"`, the plugin uses `build.cssMinify` when it is set, otherwise it falls back to `build.minify`. In dev, `"auto"` defaults to `false`. + ## Vite Plugin Architecture The Vite plugin consists of three sub-plugins: diff --git a/napi/angular-compiler/e2e/tests/build-minify-component-styles.spec.ts b/napi/angular-compiler/e2e/tests/build-minify-component-styles.spec.ts new file mode 100644 index 000000000..7e077b65e --- /dev/null +++ b/napi/angular-compiler/e2e/tests/build-minify-component-styles.spec.ts @@ -0,0 +1,97 @@ +import { execSync } from 'node:child_process' +import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { test, expect } from '@playwright/test' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const APP_DIR = join(__dirname, '../app') +const BUILD_OUT_DIR = join(APP_DIR, 'dist-minify') +const TEMP_CONFIG = join(APP_DIR, 'vite.config.minify.ts') + +function writeBuildConfig(minify: boolean): void { + writeFileSync( + TEMP_CONFIG, + ` +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { angular } from '@oxc-angular/vite'; +import { defineConfig } from 'vite'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const tsconfig = path.resolve(__dirname, './tsconfig.json'); + +export default defineConfig({ + plugins: [ + angular({ + tsconfig, + liveReload: false, + minifyComponentStyles: 'auto', + }), + ], + build: { + minify: ${minify}, + outDir: 'dist-minify', + rollupOptions: { + external: [/^@angular\\/.+$/, /^rxjs(?:\\/.+)?$/, /^tslib$/], + }, + }, +}); +`.trim(), + 'utf-8', + ) +} + +function cleanup(): void { + rmSync(TEMP_CONFIG, { force: true }) + rmSync(BUILD_OUT_DIR, { recursive: true, force: true }) +} + +function readBuiltJs(): string { + const assetDir = join(BUILD_OUT_DIR, 'assets') + const files = existsSync(assetDir) ? readdirSync(assetDir) : [] + const jsFiles = files.filter((file) => file.endsWith('.js')) + + expect(jsFiles.length).toBeGreaterThan(0) + + return jsFiles.map((file) => readFileSync(join(assetDir, file), 'utf-8')).join('\n') +} + +test.describe('build auto minify component styles', () => { + test.afterEach(() => { + cleanup() + }) + + test('minifies embedded component styles when build.minify is true', () => { + writeBuildConfig(true) + + execSync('npx vite build --config vite.config.minify.ts', { + cwd: APP_DIR, + stdio: 'pipe', + timeout: 60000, + }) + + const output = readBuiltJs() + + expect(output).toContain('.card-title[_ngcontent-%COMP%]{color:green;margin:0}') + }) + + test('keeps embedded component styles unminified when build.minify is false', () => { + writeBuildConfig(false) + + execSync('npx vite build --config vite.config.minify.ts', { + cwd: APP_DIR, + stdio: 'pipe', + timeout: 60000, + }) + + const output = readBuiltJs() + + expect(output).toContain( + '.card-title[_ngcontent-%COMP%] {\\n color: green;\\n margin: 0;\\n}', + ) + }) +}) diff --git a/napi/angular-compiler/index.d.ts b/napi/angular-compiler/index.d.ts index 0d1d2a6cb..e1740cbc5 100644 --- a/napi/angular-compiler/index.d.ts +++ b/napi/angular-compiler/index.d.ts @@ -826,6 +826,13 @@ export interface TransformOptions { * Default: false (metadata is dev-only and usually stripped in production) */ emitClassMetadata?: boolean + /** + * Minify final component styles before emitting them into `styles: [...]`. + * + * This runs after Angular style encapsulation, so it applies to the same + * final CSS strings that are embedded in generated component definitions. + */ + minifyComponentStyles?: boolean /** * Resolved import paths for host directives and other imports. * diff --git a/napi/angular-compiler/src/lib.rs b/napi/angular-compiler/src/lib.rs index 818984c9c..ba3133fe1 100644 --- a/napi/angular-compiler/src/lib.rs +++ b/napi/angular-compiler/src/lib.rs @@ -27,7 +27,7 @@ use oxc_angular_compiler::{ build_prop_decorators_metadata as core_build_prop_decorators_metadata, compile_template_for_hmr, compile_template_to_js_with_options, encapsulate_style as rust_encapsulate_style, generate_hmr_update_module_from_js, - generate_style_update_module, shim_css_text, + generate_style_update_module, }; use oxc_napi::OxcError; @@ -190,6 +190,12 @@ pub struct TransformOptions { /// Default: false (metadata is dev-only and usually stripped in production) pub emit_class_metadata: Option, + /// Minify final component styles before emitting them into `styles: [...]`. + /// + /// This runs after Angular style encapsulation, so it applies to the same + /// final CSS strings that are embedded in generated component definitions. + pub minify_component_styles: Option, + /// Resolved import paths for host directives and other imports. /// /// Maps local identifier name (e.g., "AriaDisableDirective") to the resolved @@ -231,6 +237,7 @@ impl From for RustTransformOptions { resolved_imports: options.resolved_imports, // Class metadata for TestBed support emit_class_metadata: options.emit_class_metadata.unwrap_or(false), + minify_component_styles: options.minify_component_styles.unwrap_or(false), } } } @@ -512,12 +519,21 @@ pub fn compile_for_hmr_sync( let encapsulated_styles: Option> = if all_styles.is_empty() { None } else { - Some( - all_styles - .iter() - .map(|style| shim_css_text(style, "_ngcontent-%COMP%", "_nghost-%COMP%")) - .collect(), - ) + let styles: Vec = all_styles + .iter() + .map(|style| { + oxc_angular_compiler::styles::finalize_component_style( + style, + true, + "_ngcontent-%COMP%", + "_nghost-%COMP%", + opts.minify_component_styles, + ) + }) + .filter(|style| !style.trim().is_empty()) + .collect(); + + if styles.is_empty() { None } else { Some(styles) } }; // Generate HMR module with declarations, encapsulated styles, and consts diff --git a/napi/angular-compiler/test/plugin.test.ts b/napi/angular-compiler/test/plugin.test.ts new file mode 100644 index 000000000..9f85de9e8 --- /dev/null +++ b/napi/angular-compiler/test/plugin.test.ts @@ -0,0 +1,200 @@ +import type { Plugin } from 'vite' +import { describe, expect, it } from 'vitest' + +import { angular } from '../vite-plugin/index.js' + +const COMPONENT_SOURCE = ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '
Hello
', + styles: ['.container { color: red; background: transparent; }'], + }) + export class AppComponent {} +` + +function getAngularPlugin() { + const plugin = angular().find((candidate) => candidate.name === '@oxc-angular/vite') + + if (!plugin) { + throw new Error('Failed to find @oxc-angular/vite plugin') + } + + return plugin +} + +async function callPluginHook( + hook: + | { + handler: (...args: TArgs) => TResult + } + | ((...args: TArgs) => TResult) + | undefined, + ...args: TArgs +): Promise { + if (!hook) { + return undefined + } + + if (typeof hook === 'function') { + return hook(...args) + } + + return hook.handler(...args) +} + +async function transformWithAutoMinify( + build: { cssMinify?: boolean | string; minify?: boolean | string }, + resolvedBuild: { cssMinify?: boolean | string; minify?: boolean | string }, +): Promise { + const plugin = getAngularPlugin() + + await callPluginHook( + plugin.config as Plugin['config'], + { build } as any, + { command: 'build', mode: 'production' } as any, + ) + await callPluginHook( + plugin.configResolved as Plugin['configResolved'], + { + build: resolvedBuild, + isProduction: true, + } as any, + ) + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + + const result = await plugin.transform.handler.call( + { + error(message: string) { + throw new Error(message) + }, + warn() {}, + } as any, + COMPONENT_SOURCE, + 'app.component.ts', + ) + + if (!result || typeof result !== 'object' || !('code' in result)) { + throw new Error('Expected transform result with code') + } + + return result.code as string +} + +async function transformWithAutoMinifyFromTransformContext(resolvedBuild: { + cssMinify?: boolean | string + minify?: boolean | string +}): Promise { + const plugin = getAngularPlugin() + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + + const result = await plugin.transform.handler.call( + { + environment: { + config: { + build: resolvedBuild, + }, + }, + error(message: string) { + throw new Error(message) + }, + warn() {}, + } as any, + COMPONENT_SOURCE, + 'app.component.ts', + ) + + if (!result || typeof result !== 'object' || !('code' in result)) { + throw new Error('Expected transform result with code') + } + + return result.code as string +} + +async function transformWithAutoMinifyFromOutputOptions( + outputMinify: boolean | string, + resolvedBuild: { cssMinify?: boolean | string; minify?: boolean | string }, +): Promise { + const plugin = getAngularPlugin() + + await callPluginHook( + plugin.outputOptions as Plugin['outputOptions'], + { + minify: outputMinify, + } as any, + ) + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + + const result = await plugin.transform.handler.call( + { + environment: { + config: { + build: resolvedBuild, + }, + }, + error(message: string) { + throw new Error(message) + }, + warn() {}, + } as any, + COMPONENT_SOURCE, + 'app.component.ts', + ) + + if (!result || typeof result !== 'object' || !('code' in result)) { + throw new Error('Expected transform result with code') + } + + return result.code as string +} + +describe('@oxc-angular/vite auto component style minification', () => { + it('should prefer inline build.minify when auto is used', async () => { + const code = await transformWithAutoMinify({ minify: false }, { cssMinify: true, minify: true }) + + expect(code).toContain('.container[_ngcontent-%COMP%] { color: red; background: transparent; }') + }) + + it('should prefer inline build.cssMinify when auto is used', async () => { + const code = await transformWithAutoMinify( + { cssMinify: false, minify: true }, + { cssMinify: true, minify: true }, + ) + + expect(code).toContain('.container[_ngcontent-%COMP%] { color: red; background: transparent; }') + }) + + it('should fall back to resolved config when inline build minify is not set', async () => { + const code = await transformWithAutoMinify({}, { cssMinify: true, minify: true }) + + expect(code).toContain('.container[_ngcontent-%COMP%]{color:red;background:0 0}') + }) + + it('should fall back to transform context build config when config hooks are skipped', async () => { + const code = await transformWithAutoMinifyFromTransformContext({ + cssMinify: true, + minify: true, + }) + + expect(code).toContain('.container[_ngcontent-%COMP%]{color:red;background:0 0}') + }) + + it('should prefer output minify when config hooks are skipped', async () => { + const code = await transformWithAutoMinifyFromOutputOptions(false, { + cssMinify: true, + minify: true, + }) + + expect(code).toContain('.container[_ngcontent-%COMP%] { color: red; background: transparent; }') + }) +}) diff --git a/napi/angular-compiler/test/ssr-hmr.test.ts b/napi/angular-compiler/test/ssr-hmr.test.ts index 4e8e4b7a6..ec9644e92 100644 --- a/napi/angular-compiler/test/ssr-hmr.test.ts +++ b/napi/angular-compiler/test/ssr-hmr.test.ts @@ -13,7 +13,7 @@ */ import { describe, it, expect } from 'vitest' -import { transformAngularFile } from '../index.js' +import { compileForHmrSync, transformAngularFile } from '../index.js' const COMPONENT_SOURCE = ` import { Component } from '@angular/core'; @@ -90,3 +90,18 @@ describe('Vite plugin SSR behavior (Issue #109)', () => { expect(ssrResult.code).toContain('ɵɵdefineComponent') }) }) + +describe('Component style minification', () => { + it('should minify encapsulated HMR styles when enabled', () => { + const result = compileForHmrSync( + '
Hello
', + 'AppComponent', + 'app.component.ts', + ['.container { color: red; background: transparent; }'], + { minifyComponentStyles: true }, + ) + + expect(result.errors).toHaveLength(0) + expect(result.hmrModule).toContain('.container[_ngcontent-%COMP%]{color:red;background:0 0}') + }) +}) diff --git a/napi/angular-compiler/test/transform.test.ts b/napi/angular-compiler/test/transform.test.ts index 64d127f1a..721c81720 100644 --- a/napi/angular-compiler/test/transform.test.ts +++ b/napi/angular-compiler/test/transform.test.ts @@ -91,6 +91,26 @@ describe('transformAngularFile', () => { expect(result.errors).toHaveLength(0) expect(Object.keys(result.templateUpdates).length).toBeGreaterThan(0) }) + + it('should minify final component styles when enabled', async () => { + const source = ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '
Hello
', + styles: ['.container { color: red; background: transparent; }'], + }) + export class AppComponent {} + ` + + const result = await transformAngularFile(source, 'app.component.ts', { + minifyComponentStyles: true, + }) + + expect(result.errors).toHaveLength(0) + expect(result.code).toContain('.container[_ngcontent-%COMP%]{color:red;background:0 0}') + }) }) describe('extractComponentUrlsSync', () => { diff --git a/napi/angular-compiler/vite-plugin/index.ts b/napi/angular-compiler/vite-plugin/index.ts index 34c8aee52..bece0c6ae 100644 --- a/napi/angular-compiler/vite-plugin/index.ts +++ b/napi/angular-compiler/vite-plugin/index.ts @@ -61,6 +61,21 @@ export interface PluginOptions { /** Enable zoneless mode. */ zoneless?: boolean + /** + * Minify final component styles before emitting them into `styles: [...]`. + * + * When set to `"auto"` or left undefined, this follows Vite's resolved CSS + * minification settings for production builds: + * + * - `true`: always minify component styles + * - `false`: never minify component styles + * - `"auto"`/`undefined`: use `build.cssMinify` when set, otherwise fall back + * to `build.minify` + * + * In dev, `"auto"` defaults to `false`. + */ + minifyComponentStyles?: boolean | 'auto' + /** File replacements (for environment files). */ fileReplacements?: Array<{ replace: string; with: string }> @@ -87,6 +102,44 @@ export interface PluginOptions { // Match all TypeScript files - we'll filter by @Component/@Directive decorator in the handler const ANGULAR_TS_REGEX = /\.tsx?$/ const ANGULAR_COMPONENT_PREFIX = '@ng/component' +type InlineBuildMinifyOptions = { + cssMinify?: boolean | string + minify?: boolean | string +} + +function resolveMinifyComponentStyles( + option: PluginOptions['minifyComponentStyles'], + isBuild: boolean, + inlineBuild?: InlineBuildMinifyOptions, + outputMinify?: unknown, + resolvedBuild?: ResolvedConfig['build'], +): boolean { + if (typeof option === 'boolean') { + return option + } + + if (!isBuild) { + return false + } + + if (inlineBuild?.cssMinify !== undefined) { + return inlineBuild.cssMinify !== false + } + + if (inlineBuild?.minify !== undefined) { + return inlineBuild.minify !== false + } + + if (outputMinify !== undefined) { + return outputMinify !== false + } + + if (resolvedBuild?.cssMinify !== undefined) { + return resolvedBuild.cssMinify !== false + } + + return resolvedBuild?.minify !== false +} /** * Create the Angular Vite plugin. @@ -123,6 +176,8 @@ export function angular(options: PluginOptions = {}): Plugin[] { let resolvedConfig: ResolvedConfig let viteServer: ViteDevServer | undefined let watchMode = false + let inlineBuild: InlineBuildMinifyOptions | undefined + let outputMinify: unknown // Track component IDs for HMR const componentIds = new Map() @@ -136,6 +191,18 @@ export function angular(options: PluginOptions = {}): Plugin[] { // Track component files with pending HMR updates (set by fs.watch, checked by HMR endpoint) const pendingHmrUpdates = new Set() + function getMinifyComponentStyles(context?: { + environment?: { config?: { build?: ResolvedConfig['build'] } } + }): boolean { + return resolveMinifyComponentStyles( + options.minifyComponentStyles, + !watchMode, + inlineBuild, + outputMinify, + context?.environment?.config?.build ?? resolvedConfig?.build, + ) + } + /** * Resolve external template/style URLs and read their contents. */ @@ -206,8 +273,14 @@ export function angular(options: PluginOptions = {}): Plugin[] { function angularPlugin(): Plugin { return { name: '@oxc-angular/vite', - async config(_, { command }) { + async config(config, { command }) { watchMode = command === 'serve' + inlineBuild = config.build + ? { + cssMinify: config.build.cssMinify, + minify: config.build.minify, + } + : undefined return { optimizeDeps: { @@ -226,6 +299,10 @@ export function angular(options: PluginOptions = {}): Plugin[] { configResolved(config) { resolvedConfig = config }, + outputOptions(options) { + outputMinify = options.minify + return null + }, // Safety net: resolve @ng/component virtual modules in SSR context. // The browser serves these via HTTP middleware, but Vite's module runner // (used by Nitro/SSR) resolves through plugin hooks instead. @@ -401,7 +478,10 @@ export function angular(options: PluginOptions = {}): Plugin[] { } } - const result = compileForHmrSync(templateContent, className, resolvedId, styles) + const result = compileForHmrSync(templateContent, className, resolvedId, styles, { + angularVersion: pluginOptions.angularVersion, + minifyComponentStyles: getMinifyComponentStyles(), + }) res.setHeader('Content-Type', 'text/javascript') res.setHeader('Cache-Control', 'no-cache') @@ -498,6 +578,7 @@ export function angular(options: PluginOptions = {}): Plugin[] { jit: pluginOptions.jit, hmr: pluginOptions.liveReload && watchMode && !isSSR, angularVersion: pluginOptions.angularVersion, + minifyComponentStyles: getMinifyComponentStyles(this as any), } const result = await transformAngularFile(code, actualId, transformOptions, resources)