browserslist/lib.rs
1//! **oxc-browserslist** is a Rust-based implementation of [Browserslist](https://github.com/browserslist/browserslist).
2//!
3//! ## Introduction
4//!
5//! This library bundles Can I Use data, Electron versions list and Node.js releases list,
6//! so it won't and doesn't need to access any data files.
7//!
8//! Except several non-widely/non-frequently used features,
9//! this library works as same as the JavaScript-based
10//! implementation [Browserslist](https://github.com/browserslist/browserslist).
11//!
12//! ## Usage
13//!
14//! It provides a simple API for querying which accepts a sequence of strings and options [`Opts`],
15//! then returns the result.
16//!
17//! ```
18//! use browserslist::{Distrib, Opts, resolve, Error};
19//!
20//! let distribs = resolve(&["ie <= 6"], &Opts::default()).unwrap();
21//! assert_eq!(distribs[0].name(), "ie");
22//! assert_eq!(distribs[0].version(), "6");
23//! assert_eq!(distribs[1].name(), "ie");
24//! assert_eq!(distribs[1].version(), "5.5");
25//!
26//! assert_eq!(
27//! resolve(&["yuru 1.0"], &Opts::default()),
28//! Err(Error::BrowserNotFound(String::from("yuru")))
29//! );
30//! ```
31//!
32//! The result isn't a list of strings, instead, it's a tuple struct called [`Distrib`].
33//! If you need to retrieve something like JavaScript-based implementation of
34//! [Browserslist](https://github.com/browserslist/browserslist),
35//! you can convert them to strings:
36//!
37//! ```
38//! use browserslist::{Distrib, Opts, resolve, Error};
39//!
40//! let distribs = resolve(&["ie <= 6"], &Opts::default()).unwrap();
41//! assert_eq!(
42//! distribs.into_iter().map(|d| d.to_string()).collect::<Vec<_>>(),
43//! vec![String::from("ie 6"), String::from("ie 5.5")]
44//! );
45//! ```
46//!
47//! ## WebAssembly
48//!
49//! This crate can be compiled as WebAssembly, without configuring any features manually.
50//!
51//! Please note that browser and Deno can run WebAssembly,
52//! but those environments aren't Node.js,
53//! so you will receive an error when querying `current node` in those environments.
54
55pub use error::Error;
56pub use opts::Opts;
57use parser::parse_browserslist_query;
58pub use queries::Distrib;
59pub use semver::Version;
60#[cfg(all(feature = "wasm_bindgen", target_arch = "wasm32"))]
61pub use wasm::browserslist;
62
63mod data;
64mod date;
65mod error;
66mod generated;
67mod opts;
68mod parser;
69mod queries;
70mod semver;
71#[cfg(all(feature = "wasm_bindgen", target_arch = "wasm32"))]
72mod wasm;
73
74/// Resolve browserslist queries.
75///
76/// ```
77/// use browserslist::{Distrib, Opts, resolve};
78///
79/// let distribs = resolve(&["ie <= 6"], &Opts::default()).unwrap();
80/// assert_eq!(distribs[0].name(), "ie");
81/// assert_eq!(distribs[0].version(), "6");
82/// assert_eq!(distribs[1].name(), "ie");
83/// assert_eq!(distribs[1].version(), "5.5");
84/// ```
85pub fn resolve<S>(queries: &[S], opts: &Opts) -> Result<Vec<Distrib>, Error>
86where
87 S: AsRef<str>,
88{
89 match queries.len() {
90 1 => _resolve(queries[0].as_ref(), opts),
91 _ => {
92 // Pre-calculate capacity to avoid reallocations
93 let total_len: usize = queries.iter().map(|q| q.as_ref().len() + 1).sum();
94 let mut s = String::with_capacity(total_len);
95 for (i, q) in queries.iter().enumerate() {
96 if i > 0 {
97 s.push(',');
98 }
99 s.push_str(q.as_ref());
100 }
101 _resolve(&s, opts)
102 }
103 }
104}
105
106// reduce generic monomorphization
107fn _resolve(query: &str, opts: &Opts) -> Result<Vec<Distrib>, Error> {
108 let queries = parse_browserslist_query(query)?;
109 let mut distribs = vec![];
110 for (i, current) in queries.1.into_iter().enumerate() {
111 if i == 0 && current.negated {
112 return handle_first_negated_error(current.raw.to_string());
113 }
114
115 let dist = queries::query(current.atom, opts)?;
116 apply_query_operation(&mut distribs, dist, current.negated, current.is_and);
117 }
118
119 sort_and_dedup_distribs(&mut distribs);
120 Ok(distribs)
121}
122
123// Separate function to reduce _resolve size and improve inlining decisions
124fn apply_query_operation(
125 distribs: &mut Vec<Distrib>,
126 dist: Vec<Distrib>,
127 negated: bool,
128 is_and: bool,
129) {
130 if negated {
131 distribs.retain(|d| !dist.contains(d));
132 } else if is_and {
133 distribs.retain(|d| dist.contains(d));
134 } else {
135 distribs.extend(dist);
136 }
137}
138
139// Optimized sorting that parses versions only once
140fn sort_and_dedup_distribs(distribs: &mut Vec<Distrib>) {
141 if distribs.is_empty() {
142 return;
143 }
144
145 // Use sort_by_cached_key to parse each version only once
146 distribs.sort_by_cached_key(|d| {
147 let version = d.version().parse::<semver::Version>().unwrap_or_default();
148 (d.name().to_string(), std::cmp::Reverse(version))
149 });
150
151 // Dedup in place
152 distribs.dedup();
153}
154
155// Cold path for error handling
156#[cold]
157fn handle_first_negated_error(raw: String) -> Result<Vec<Distrib>, Error> {
158 Err(Error::NotAtFirst(raw))
159}