diff --git a/Cargo.lock b/Cargo.lock index 282c23ad761..72961081c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,18 @@ dependencies = [ "regex", ] +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.7.4" @@ -32,6 +44,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.43" @@ -87,6 +108,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -102,12 +129,24 @@ dependencies = [ "generic-array", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "bumpalo" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + [[package]] name = "byteorder" version = "1.4.3" @@ -119,6 +158,9 @@ name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -132,6 +174,58 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudflare-zlib" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfcefb5df07f146eb15756342a135eb7d76b8bb609eff9c111f7539d060f94d" +dependencies = [ + "cloudflare-zlib-sys", +] + +[[package]] +name = "cloudflare-zlib-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2040b6d1edfee6d75f172d81e2d2a7807534f3f294ce18184c70e7bb0105cd6f" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "cpufeatures" version = "0.1.5" @@ -141,6 +235,83 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + +[[package]] +name = "crc" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c2722795460108a7872e1cd933a85d6ec38abc4baecad51028f702da28889f" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "darling" version = "0.10.2" @@ -161,7 +332,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.9.3", "syn", ] @@ -192,6 +363,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + [[package]] name = "digest" version = "0.9.0" @@ -225,6 +406,18 @@ dependencies = [ "syn", ] +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "fnv" version = "1.0.7" @@ -300,6 +493,12 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "hashbrown" version = "0.11.2" @@ -338,6 +537,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f7280c75fb2e2fc47080ec80ccc481376923acb04501957fc38f935c3de5088" +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.7.0" @@ -346,6 +560,7 @@ checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg", "hashbrown", + "rayon", ] [[package]] @@ -370,6 +585,15 @@ dependencies = [ "syn", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -397,6 +621,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.49" @@ -441,6 +674,24 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" +[[package]] +name = "libdeflate-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c81cf7b5510a30d8a1149dcca5fe85715475a05092c786e660edc72dbf24e4" +dependencies = [ + "cc", +] + +[[package]] +name = "libdeflater" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11c0c8321257b64709e8ee6811d0b4a2ce030806e7ce1f36094bfa2c1de1540" +dependencies = [ + "libdeflate-sys", +] + [[package]] name = "libmimalloc-sys" version = "0.1.22" @@ -471,6 +722,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + [[package]] name = "mimalloc" version = "0.1.26" @@ -480,6 +740,37 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mozjpeg-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2d207e7338a9a58abfc8a9d247bc9cc42a1b3eaa0a4e7014272825a00015a2" +dependencies = [ + "cc", + "dunce", + "libc", + "nasm-rs", +] + [[package]] name = "napi" version = "1.7.6" @@ -515,6 +806,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43563506c466587478849d80f46383d859b91bbec586580dadeb3639588f2f7e" +[[package]] +name = "nasm-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbff86bd2ee8cb407e8608e2c3504412a967c06286ef7e5cf7c1b9db756f0a9" +dependencies = [ + "rayon", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -552,6 +852,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -601,6 +923,33 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "oxipng" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ac0770862c1f005398661faea0a1e8d6bb73ca2671a2eed7448d910388f97b" +dependencies = [ + "bit-vec", + "byteorder", + "clap", + "cloudflare-zlib", + "crc 2.0.0", + "crossbeam-channel", + "filetime", + "image", + "indexmap", + "itertools", + "libdeflater", + "log", + "miniz_oxide 0.4.4", + "rayon", + "rgb", + "rustc_version 0.4.0", + "stderrlog", + "wild", + "zopfli", +] + [[package]] name = "parcel-fs-search" version = "0.1.0" @@ -620,6 +969,18 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "parcel-image" +version = "0.1.0" +dependencies = [ + "libc", + "mozjpeg-sys", + "napi", + "napi-build", + "napi-derive", + "oxipng", +] + [[package]] name = "parcel-js-swc-core" version = "0.1.0" @@ -735,6 +1096,18 @@ dependencies = [ "syn", ] +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -822,6 +1195,40 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.5.4" @@ -845,13 +1252,31 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +[[package]] +name = "rgb" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0" +dependencies = [ + "bytemuck", +] + [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.4", ] [[package]] @@ -875,6 +1300,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "semver" version = "0.9.0" @@ -885,6 +1316,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + [[package]] name = "semver-parser" version = "0.7.0" @@ -978,7 +1415,7 @@ dependencies = [ "if_chain", "lazy_static", "regex", - "rustc_version", + "rustc_version 0.2.3", "serde", "serde_json", "url", @@ -1018,6 +1455,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stderrlog" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38" +dependencies = [ + "atty", + "chrono", + "log", + "termcolor", + "thread_local", +] + [[package]] name = "string_cache" version = "0.8.1" @@ -1056,6 +1506,12 @@ dependencies = [ "syn", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.9.3" @@ -1193,7 +1649,7 @@ dependencies = [ "fxhash", "indexmap", "once_cell", - "semver", + "semver 0.9.0", "serde", "serde_json", "st-map", @@ -1513,6 +1969,34 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tinyvec" version = "1.3.1" @@ -1528,6 +2012,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + [[package]] name = "typenum" version = "1.13.0" @@ -1579,6 +2069,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.3" @@ -1662,6 +2158,15 @@ version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +[[package]] +name = "wild" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" +dependencies = [ + "glob", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1698,3 +2203,15 @@ name = "xxhash-rust" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575e15bedf6e57b5c2d763ffc6c3c760143466cbd09d762d539680ab5992ded" + +[[package]] +name = "zopfli" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4079b79464426ade2a1b0177fb0ce8396ba6b4084267407e333573c666073964" +dependencies = [ + "adler32", + "byteorder", + "crc 1.8.1", + "typed-arena", +] diff --git a/Cargo.toml b/Cargo.toml index 64c1cb178a8..7e966f6ac6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ members = [ "packages/transformers/js/wasm", "packages/utils/fs-search", "packages/utils/hash", + "packages/optimizers/image" ] diff --git a/packages/configs/default/index.json b/packages/configs/default/index.json index 8241705e16e..e7c390a7c8a 100644 --- a/packages/configs/default/index.json +++ b/packages/configs/default/index.json @@ -54,7 +54,8 @@ "*.css": ["@parcel/optimizer-cssnano"], "*.html": ["@parcel/optimizer-htmlnano"], "*.{js,mjs,cjs}": ["@parcel/optimizer-terser"], - "*.svg": ["@parcel/optimizer-svgo"] + "*.svg": ["@parcel/optimizer-svgo"], + "*.{jpg,jpeg,png}": ["@parcel/optimizer-image"] }, "packagers": { "*.html": "@parcel/packager-html", diff --git a/packages/configs/default/package.json b/packages/configs/default/package.json index c8ea8a451fe..3ee1167632f 100644 --- a/packages/configs/default/package.json +++ b/packages/configs/default/package.json @@ -22,6 +22,7 @@ "@parcel/namer-default": "2.0.0-rc.0", "@parcel/optimizer-cssnano": "2.0.0-rc.0", "@parcel/optimizer-htmlnano": "2.0.0-rc.0", + "@parcel/optimizer-image": "2.0.0-rc.0", "@parcel/optimizer-svgo": "2.0.0-rc.0", "@parcel/optimizer-terser": "2.0.0-rc.0", "@parcel/packager-css": "2.0.0-rc.0", diff --git a/packages/core/integration-tests/test/image.js b/packages/core/integration-tests/test/image.js index 60b113b9460..fa460566496 100644 --- a/packages/core/integration-tests/test/image.js +++ b/packages/core/integration-tests/test/image.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import {bundle, distDir, outputFS} from '@parcel/test-utils'; +import {bundle, distDir, inputFS, outputFS} from '@parcel/test-utils'; import path from 'path'; import sharp from 'sharp'; @@ -88,4 +88,50 @@ describe('image', function() { ); }); }); + + it('should optimise JPEGs', async function() { + let img = path.join(__dirname, '/integration/image/image.jpg'); + let b = await bundle(img, { + defaultTargetOptions: { + shouldOptimize: true, + }, + }); + + const imagePath = b.getBundles().find(b => b.type === 'jpg').filePath; + + let input = await inputFS.readFile(img); + let inputRaw = await sharp(input) + .toFormat('raw') + .toBuffer(); + let output = await outputFS.readFile(imagePath); + let outputRaw = await sharp(output) + .toFormat('raw') + .toBuffer(); + + assert(outputRaw.equals(inputRaw)); + assert(output.length < input.length); + }); + + it('should optimise PNGs', async function() { + let img = path.join(__dirname, '/integration/image/clock.png'); + let b = await bundle(img, { + defaultTargetOptions: { + shouldOptimize: true, + }, + }); + + const imagePath = b.getBundles().find(b => b.type === 'png').filePath; + + let input = await inputFS.readFile(img); + let inputRaw = await sharp(input) + .toFormat('raw') + .toBuffer(); + let output = await outputFS.readFile(imagePath); + let outputRaw = await sharp(output) + .toFormat('raw') + .toBuffer(); + + assert(outputRaw.equals(inputRaw)); + assert(output.length < input.length); + }); }); diff --git a/packages/core/integration-tests/test/integration/image/clock.png b/packages/core/integration-tests/test/integration/image/clock.png new file mode 100644 index 00000000000..faf7bf89635 Binary files /dev/null and b/packages/core/integration-tests/test/integration/image/clock.png differ diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/raw-url/foo.png b/packages/core/integration-tests/test/integration/scope-hoisting/es6/raw-url/foo.png index e69de29bb2d..8a1daa0121d 100644 Binary files a/packages/core/integration-tests/test/integration/scope-hoisting/es6/raw-url/foo.png and b/packages/core/integration-tests/test/integration/scope-hoisting/es6/raw-url/foo.png differ diff --git a/packages/optimizers/image/.gitignore b/packages/optimizers/image/.gitignore new file mode 100644 index 00000000000..1d0124e8753 --- /dev/null +++ b/packages/optimizers/image/.gitignore @@ -0,0 +1 @@ +*.node diff --git a/packages/optimizers/image/Cargo.toml b/packages/optimizers/image/Cargo.toml new file mode 100644 index 00000000000..5f5eb80737d --- /dev/null +++ b/packages/optimizers/image/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["Devon Govett "] +name = "parcel-image" +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = "1" +napi-derive = "1" +oxipng = "5.0.0" +mozjpeg-sys = "1.0.0" +libc = "0.2" + +[build-dependencies] +napi-build = "1" diff --git a/packages/optimizers/image/build.rs b/packages/optimizers/image/build.rs new file mode 100644 index 00000000000..1f866b6a3c3 --- /dev/null +++ b/packages/optimizers/image/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/packages/optimizers/image/native.js b/packages/optimizers/image/native.js new file mode 100644 index 00000000000..8058926be42 --- /dev/null +++ b/packages/optimizers/image/native.js @@ -0,0 +1,22 @@ +let parts = [process.platform, process.arch]; +if (process.platform === 'linux') { + const {MUSL, family} = require('detect-libc'); + if (family === MUSL) { + parts.push('musl'); + } else if (process.arch === 'arm') { + parts.push('gnueabihf'); + } else { + parts.push('gnu'); + } +} else if (process.platform === 'win32') { + parts.push('msvc'); +} + +let name = `./parcel-image.${parts.join('-')}.node`; +if (process.env.PARCEL_BUILD_ENV === 'production') { + module.exports = require(name); +} else if (require('fs').existsSync(require('path').join(__dirname, name))) { + module.exports = require(name); +} + +module.exports.init = Promise.resolve(); diff --git a/packages/optimizers/image/native.js.flow b/packages/optimizers/image/native.js.flow new file mode 100644 index 00000000000..398477b138a --- /dev/null +++ b/packages/optimizers/image/native.js.flow @@ -0,0 +1,3 @@ +// @flow + +declare export function optimize(type: string, buffer: Buffer): Buffer; diff --git a/packages/optimizers/image/package.json b/packages/optimizers/image/package.json new file mode 100644 index 00000000000..297e558bc86 --- /dev/null +++ b/packages/optimizers/image/package.json @@ -0,0 +1,43 @@ +{ + "name": "@parcel/optimizer-image", + "version": "2.0.0-rc.0", + "license": "MIT", + "main": "lib/ImageOptimizer.js", + "source": "src/ImageOptimizer.js", + "publishConfig": { + "access": "public" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "repository": { + "type": "git", + "url": "https://github.com/parcel-bundler/parcel.git" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.0.0-beta.1" + }, + "files": [ + "lib", + "native.js", + "*.node" + ], + "napi": { + "name": "parcel-image" + }, + "scripts": { + "build": "napi build --platform", + "build-release": "napi build --platform --release" + }, + "dependencies": { + "@parcel/plugin": "2.0.0-rc.0", + "@parcel/utils": "2.0.0-rc.0", + "detect-libc": "^1.0.3" + }, + "devDependencies": { + "@napi-rs/cli": "1.0.4", + "tiny-benchy": "^1.0.2" + } +} diff --git a/packages/optimizers/image/src/ImageOptimizer.js b/packages/optimizers/image/src/ImageOptimizer.js new file mode 100644 index 00000000000..98be1160f89 --- /dev/null +++ b/packages/optimizers/image/src/ImageOptimizer.js @@ -0,0 +1,14 @@ +// @flow +import {Optimizer} from '@parcel/plugin'; +import {blobToBuffer} from '@parcel/utils'; +import {optimize} from '../native'; + +export default (new Optimizer({ + async optimize({bundle, contents}) { + let buffer = await blobToBuffer(contents); + let optimized = optimize(bundle.type, buffer); + return { + contents: optimized.length < buffer.length ? optimized : buffer, + }; + }, +}): Optimizer); diff --git a/packages/optimizers/image/src/lib.rs b/packages/optimizers/image/src/lib.rs new file mode 100644 index 00000000000..d2cbd91df8a --- /dev/null +++ b/packages/optimizers/image/src/lib.rs @@ -0,0 +1,143 @@ +extern crate napi; +#[macro_use] +extern crate napi_derive; +extern crate libc; +extern crate mozjpeg_sys; +extern crate oxipng; + +use mozjpeg_sys::*; +use napi::{CallContext, Env, Error, JsBuffer, JsObject, JsString, Result}; +use oxipng::{optimize_from_memory, Deflaters, Headers, Options}; +use std::mem; +use std::ptr; +use std::slice; + +#[js_function(2)] +fn optimize(ctx: CallContext) -> Result { + let k = ctx.get::(0)?.into_utf8()?; + let kind = k.as_str()?; + let buf = ctx.get::(1)?.into_value()?; + let slice = buf.as_ref(); + + match kind { + "png" => { + let mut options = Options::default(); + options.deflate = Deflaters::Libdeflater; + options.strip = Headers::Safe; + match optimize_from_memory(slice, &options) { + Ok(res) => Ok(ctx.env.create_buffer_with_data(res)?.into_raw()), + Err(err) => Err(Error::from_reason(format!("{}", err))), + } + } + "jpg" | "jpeg" => unsafe { + match optimize_jpeg(slice) { + Ok(res) => Ok( + ctx + .env + .create_buffer_with_borrowed_data(res.as_ptr(), res.len(), res.as_mut_ptr(), finalize)? + .into_raw(), + ), + Err(err) => { + if let Some(msg) = err.downcast_ref::() { + Err(Error::from_reason(msg.to_string())) + } else { + Err(Error::from_reason("Unknown libjpeg error".into())) + } + } + } + }, + _ => Err(Error::from_reason(format!("Unknown image type {}", kind))), + } +} + +fn finalize(ptr: *mut u8, _env: Env) { + unsafe { + libc::free(ptr as *mut c_void); + } +} + +struct JPEGOptimizer { + srcinfo: jpeg_decompress_struct, + dstinfo: jpeg_compress_struct, +} + +impl JPEGOptimizer { + unsafe fn new() -> JPEGOptimizer { + JPEGOptimizer { + srcinfo: mem::zeroed(), + dstinfo: mem::zeroed(), + } + } +} + +impl Drop for JPEGOptimizer { + fn drop(&mut self) { + unsafe { + jpeg_destroy_decompress(&mut self.srcinfo); + jpeg_destroy_compress(&mut self.dstinfo); + } + } +} + +// This function losslessly optimizes jpegs. +// Based on the jpegtran.c example program in libjpeg. +unsafe fn optimize_jpeg(bytes: &[u8]) -> std::thread::Result<&mut [u8]> { + std::panic::catch_unwind(|| { + let mut info = JPEGOptimizer::new(); + let mut err = create_error_handler(); + info.srcinfo.common.err = &mut err; + jpeg_create_decompress(&mut info.srcinfo); + jpeg_mem_src(&mut info.srcinfo, bytes.as_ptr(), bytes.len() as c_ulong); + + info.dstinfo.optimize_coding = 1; + info.dstinfo.common.err = &mut err; + jpeg_create_compress(&mut info.dstinfo); + jpeg_read_header(&mut info.srcinfo, 1); + + let src_coef_arrays = jpeg_read_coefficients(&mut info.srcinfo); + jpeg_copy_critical_parameters(&mut info.srcinfo, &mut info.dstinfo); + + let mut buf = ptr::null_mut(); + let mut outsize: c_ulong = 0; + jpeg_mem_dest(&mut info.dstinfo, &mut buf, &mut outsize); + + jpeg_write_coefficients(&mut info.dstinfo, src_coef_arrays); + + jpeg_finish_compress(&mut info.dstinfo); + jpeg_finish_decompress(&mut info.srcinfo); + + slice::from_raw_parts_mut(buf, outsize as usize) + }) +} + +unsafe fn create_error_handler() -> jpeg_error_mgr { + let mut err: jpeg_error_mgr = mem::zeroed(); + jpeg_std_error(&mut err); + err.error_exit = Some(unwind_error_exit); + err.emit_message = Some(silence_message); + err +} + +extern "C" fn unwind_error_exit(cinfo: &mut jpeg_common_struct) { + let message = unsafe { + let err = cinfo.err.as_ref().unwrap(); + match err.format_message { + Some(fmt) => { + let buffer = mem::zeroed(); + fmt(cinfo, &buffer); + let len = buffer.iter().take_while(|&&c| c != 0).count(); + String::from_utf8_lossy(&buffer[..len]).into() + } + None => format!("libjpeg error: {}", err.msg_code), + } + }; + std::panic::resume_unwind(Box::new(message)) +} + +extern "C" fn silence_message(_cinfo: &mut jpeg_common_struct, _level: c_int) {} + +#[module_exports] +fn init(mut exports: JsObject, _env: Env) -> Result<()> { + exports.create_named_method("optimize", optimize)?; + Ok(()) +}