Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cbcb511

Browse files
authored
perf: deprecate mime_guess in favor of a lighter guessing function. (#1698)
I wrapped the `mime`, and `infer` crate to [`mime_more`](https://github.com/7086cmd/mime_more) (benchmark result also in README). It also provides lighter mime guessing functions, with the benchmark from using `mime_guess` in about 289 ns to 37 ns and less rare extensions. Also, the behavior of the Data URL is aligned with esbuild (except the percent decoding).
1 parent e81f853 commit cbcb511

7 files changed

Lines changed: 132 additions & 23 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ lightningcss = { version = "1.0.0-alpha.57" }
117117
memchr = "2.7.2"
118118
mimalloc = "0.1.42"
119119
mime = "0.3.17"
120-
mime_guess = "2.0.5"
121120
napi = { version = "3.0.0-alpha", features = ["async"] }
122121
napi-build = { version = "2.1.3" }
123122
napi-derive = { version = "3.0.0-alpha", default-features = false, features = ["type-def"] }
124123
oxc_resolver = { version = "1.9.0" }
124+
phf = "0.11.2"
125125
rayon = "1.10.0"
126126
regex = "1.10.5"
127127
rustc-hash = "2.0.0"

crates/rolldown_utils/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ futures = { workspace = true }
1919
indexmap = { workspace = true }
2020
infer = { workspace = true }
2121
mime = { workspace = true }
22-
mime_guess = { workspace = true }
2322
oxc = { workspace = true }
23+
phf = { workspace = true }
2424
regex = { workspace = true }
2525
rustc-hash = { workspace = true }
2626
sugar_path = { workspace = true }

crates/rolldown_utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ pub mod debug;
77
pub mod ecma_script;
88
pub mod futures;
99
pub mod indexmap;
10+
pub mod light_guess;
1011
pub mod mime;
1112
pub mod path_buf_ext;
1213
pub mod path_ext;
1314
pub mod percent_encoding;
1415
pub mod rayon;
1516
pub mod rustc_hash;
1617
pub mod xxhash;
18+
1719
pub use bitset::BitSet;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Port from https://github.com/7086cmd/mime_more/blob/main/src/light_guess.rs
2+
use mime::Mime;
3+
use phf::{phf_map, Map};
4+
use std::str::FromStr;
5+
6+
pub static MIME_TYPES: Map<&'static str, &'static str> = phf_map! {
7+
// Text
8+
"txt" => "text/plain",
9+
"css" => "text/css",
10+
"htm" => "text/html",
11+
"html" => "text/html",
12+
"js" => "text/javascript",
13+
"mjs" => "text/javascript",
14+
"jsx" => "text/javascript",
15+
"json" => "application/json",
16+
"yaml" => "text/x-yaml",
17+
"yml" => "text/x-yaml",
18+
"toml" => "text/x-toml",
19+
"markdown" => "text/markdown",
20+
"md" => "text/markdown",
21+
"xml" => "text/xml",
22+
"csv" => "text/csv",
23+
"tsv" => "text/tab-separated-values",
24+
// Images
25+
"bmp" => "image/bmp",
26+
"avif" => "image/avif",
27+
"gif" => "image/gif",
28+
"ico" => "image/x-icon",
29+
"icns" => "image/x-icns",
30+
"jpg" => "image/jpeg",
31+
"jpeg" => "image/jpeg",
32+
"png" => "image/png",
33+
"svg" => "image/svg+xml",
34+
"webp" => "image/webp",
35+
// Fonts
36+
"otf" => "font/otf",
37+
"ttf" => "font/ttf",
38+
"ttc" => "font/collection",
39+
"woff" => "font/woff",
40+
"woff2" => "font/woff2",
41+
// Audios
42+
"aac" => "audio/aac",
43+
"midi" => "audio/midi",
44+
"mid" => "audio/midi",
45+
"mp3" => "audio/mpeg",
46+
"ogg" => "audio/ogg",
47+
"oga" => "audio/ogg",
48+
"wav" => "audio/wav",
49+
"weba" => "audio/webm",
50+
"flac" => "audio/flac",
51+
"m3u8" => "audio/x-mpegurl",
52+
"m4a" => "audio/m4a",
53+
// Videos
54+
"avi" => "video/x-msvideo",
55+
"mpeg" => "video/mpeg",
56+
"ogv" => "video/ogg",
57+
"ivf" => "video/x-ivf",
58+
"webm" => "video/webm",
59+
"mp4" => "video/mp4",
60+
"flv" => "video/x-flv",
61+
"ts" => "audio/vnd.dlna.mpeg-tts", // Though I write TypeScript, this is not TypeScript
62+
"mov" => "video/quicktime",
63+
"wmv" => "video/x-ms-wmv",
64+
// Other
65+
"pdf" => "application/pdf",
66+
"wasm" => "application/wasm",
67+
"webmanifest" => "application/manifest+json",
68+
};
69+
70+
/// Adapted from:
71+
/// - https://github.com/rolldown/rolldown/pull/1406/files#diff-4b612e077c82ae0e05e50eb0d419e02c05a04b83c6ac5440c0d0c9d0c38af942
72+
/// - https://github.com/evanw/esbuild/blob/fc37c2fa9de2ad77476a6d4a8f1516196b90187e/internal/helpers/mime.go#L5
73+
///
74+
/// Thanks to @ikkz and @evanw for the inspiration.
75+
pub fn mime_type_by_extension(ext: &str) -> Option<&'static str> {
76+
MIME_TYPES.get(ext).copied()
77+
}
78+
79+
pub fn try_from_ext(ext: &str) -> anyhow::Result<Mime> {
80+
mime_type_by_extension(ext)
81+
.ok_or_else(|| anyhow::Error::msg(format!("No mime type found for extension: {ext}")))
82+
.and_then(|mime| Ok(Mime::from_str(mime)?))
83+
}
84+
85+
pub fn try_from_path(path: &std::path::Path) -> anyhow::Result<Mime> {
86+
if let Some(ext) = path.extension().and_then(|ext| ext.to_str()) {
87+
try_from_ext(ext)
88+
} else {
89+
anyhow::bail!("No extension found for path: {:?}", path);
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
97+
#[test]
98+
fn normal_extensions() {
99+
assert_eq!(mime_type_by_extension("txt").unwrap(), "text/plain");
100+
assert_eq!(mime_type_by_extension("css").unwrap(), "text/css");
101+
assert_eq!(mime_type_by_extension("html").unwrap(), "text/html");
102+
assert_eq!(mime_type_by_extension("json").unwrap(), "application/json");
103+
assert_eq!(mime_type_by_extension("png").unwrap(), "image/png");
104+
assert_eq!(mime_type_by_extension("svg").unwrap(), "image/svg+xml");
105+
assert_eq!(mime_type_by_extension("woff2").unwrap(), "font/woff2");
106+
assert_eq!(mime_type_by_extension("aac").unwrap(), "audio/aac");
107+
assert_eq!(mime_type_by_extension("avi").unwrap(), "video/x-msvideo");
108+
assert_eq!(mime_type_by_extension("pdf").unwrap(), "application/pdf");
109+
assert_eq!(mime_type_by_extension("wasm").unwrap(), "application/wasm");
110+
assert_eq!(mime_type_by_extension("webmanifest").unwrap(), "application/manifest+json");
111+
}
112+
113+
#[test]
114+
fn unknown_extensions() {
115+
assert!(mime_type_by_extension("unknown").is_none());
116+
}
117+
118+
#[test]
119+
fn try_from_exts() {
120+
assert!(matches!(try_from_ext("png").unwrap().subtype(), mime::PNG));
121+
assert!(matches!(try_from_ext("svg").unwrap().subtype(), mime::SVG));
122+
assert!(matches!(try_from_ext("woff2").unwrap().type_(), mime::FONT));
123+
}
124+
}

crates/rolldown_utils/src/mime.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::light_guess;
12
use mime::Mime;
23
use std::{path::Path, str::FromStr};
34

@@ -6,7 +7,7 @@ fn is_texture(data: &[u8]) -> bool {
67
}
78

89
pub fn guess_mime(path: &Path, data: &[u8]) -> anyhow::Result<Mime> {
9-
if let Some(guessed) = mime_guess::from_path(path).first() {
10+
if let Ok(guessed) = light_guess::try_from_path(path) {
1011
return Ok(guessed);
1112
}
1213

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"/CHANGELOG.md",
2222
"/packages/rolldown/src/rollup.d.ts",
2323
"/crates/rolldown/tests/esbuild/splitting/ignore-test.md",
24+
"/crates/rolldown_utils/src/light_guess.rs",
2425
"/pnpm-lock.yaml",
2526
"*.wasm"
2627
],

0 commit comments

Comments
 (0)