Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ An extremely fast CSS parser, transformer, and minifier written in Rust. Use it
- Removing default property sub-values which will be inferred by browsers.
- Many micro-optimizations, e.g. converting to shorter units, removing unnecessary quotation marks, etc.
- **Vendor prefixing** – Lightning CSS accepts a list of browser targets, and automatically adds (and removes) vendor prefixes.
- **Browserslist configuration** – Lightning CSS supports opt-in browserslist configuration discovery to resolve browser targets and integrate with your existing tools and config setup.
- **Syntax lowering** – Lightning CSS parses modern CSS syntax, and generates more compatible output where needed, based on browser targets.
- CSS Nesting (draft spec)
- Custom media queries (draft spec)
Expand Down Expand Up @@ -197,6 +198,46 @@ To see all of the available options, use the `--help` argument:
npx lightningcss --help
```

#### Browserslist configuration

If the `--browserslist` option is provided, then `lightningcss` finds browserslist configuration,
selects queries by environment and loads the resulting queries as targets.

Configuration discovery and targets resolution is modeled after the original `browserslist` nodeJS package.
The configuration is resolved in the following order:

- If a `BROWSERSLIST` environment variable is present, then load targets from its value. This is analog to the `--targets` CLI option.
_Example:_ `BROWSERSLIST="firefox ESR" lightningcss [OPTIONS] <INPUT_FILE>`
- If a `BROWSERSLIST_CONFIG` environment variable is present, then resolve the file at the provided path.
Then parse and use targets from `package.json` or any browserslist configuration file pointed to by the environment variable.
_Example:_ `BROWSERSLIST_CONFIG="../config/browserslist" lightningcss [OPTIONS] <INPUT_FILE>`
- If none of the above apply, then find, parse and use targets from the first `browserslist`, `.browserslistrc`
or `package.json` configuration file in any parent directory.

Browserslist configuration files may contain sections denoted by angular brackets `[]`.
Use these to specify different targets for different environments.
Targets which are not placed in a section are added to `defaults` and used if no section applies matches the environment.

_Example:_

```
# Defaults, applied when no other section matches the provided environment.
firefox ESR

[staging]
# Targets applied only to the staging environment.
samsung >= 4
```

When using parsed configuration from `browserslist`, `.browserslistrc` or `package.json` configuration files,
the environment determined by

- the `BROWSERSLIST_ENV` environment variable if present,
- otherwise the `NODE_ENV` environment variable if present,
- otherwise `production` is used.

If no targets are found for the resulting environment, then the `defaults` configuration section is used.

### Error recovery

By default, Lightning CSS is strict, and will error when parsing an invalid rule or declaration. However, sometimes you may encounter a third party library that you can't easily modify, which unintentionally contains invalid syntax, or IE-specific hacks. In these cases, you can enable the `errorRecovery` option (or `--error-recovery` CLI flag). This will skip over invalid rules and declarations, omitting them in the output, and producing a warning instead of an error. You should also open an issue or PR to fix the issue in the library if possible.
Expand All @@ -207,7 +248,7 @@ By default, Lightning CSS is strict, and will error when parsing an invalid rule
<img width="680" alt="performance and build size charts" src="https://user-images.githubusercontent.com/19409/189022693-6956b044-422b-4f56-9628-d59c6f791095.png#gh-dark-mode-only">

```
$ node bench.js bootstrap-4.css
$ node bench.js bootstrap-4.css
cssnano: 544.809ms
159636 bytes

Expand All @@ -229,7 +270,7 @@ lightningcss: 1.973ms
23666 bytes


$ node bench.js tailwind.css
$ node bench.js tailwind.css
cssnano: 2.198s
1925626 bytes

Expand Down
17 changes: 13 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::Parser;
use clap::{ArgGroup, Parser};
use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, StyleSheet};
use lightningcss::targets::Browsers;
Expand All @@ -13,6 +13,10 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[clap(group(
ArgGroup::new("targets-resolution")
.args(&["targets", "browserslist"]),
))]
struct CliArgs {
/// Target CSS file
#[clap(value_parser)]
Expand Down Expand Up @@ -46,6 +50,8 @@ struct CliArgs {
#[clap(short, long, value_parser)]
targets: Vec<String>,
#[clap(long, value_parser)]
browserslist: bool,
#[clap(long, value_parser)]
error_recovery: bool,
}

Expand Down Expand Up @@ -124,11 +130,14 @@ pub fn main() -> Result<(), std::io::Error> {
StyleSheet::parse(&source, options).unwrap()
};

let targets = if cli_args.targets.is_empty() {
None
} else {
let targets = if !cli_args.targets.is_empty() {
Browsers::from_browserslist(cli_args.targets).unwrap()
} else if cli_args.browserslist {
Browsers::load_browserslist().unwrap()
} else {
None
};

stylesheet
.minify(MinifyOptions {
targets,
Expand Down
33 changes: 31 additions & 2 deletions src/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,40 @@ impl Browsers {
) -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::{resolve, Opts};

let res = resolve(query, &Opts::new())?;
Self::from_distribs(resolve(query, &Opts::new())?)
}

/// Finds browserslist configuration, selects queries by environment and loads the resulting queries into LightningCSS targets.
///
/// Configuration resolution is modeled after the original `browserslist` nodeJS package.
/// The configuration is resolved in the following order:
///
/// - If a `BROWSERSLIST` environment variable is present, then load targets from its value. This is analog to the `--targets` CLI option.
/// Example: `BROWSERSLIST="firefox ESR" lightningcss [OPTIONS] <INPUT_FILE>`
/// - If a `BROWSERSLIST_CONFIG` environment variable is present, then resolve the file at the provided path.
/// Then parse and use targets from `package.json` or any browserslist configuration file pointed to by the environment variable.
/// Example: `BROWSERSLIST_CONFIG="../config/browserslist" lightningcss [OPTIONS] <INPUT_FILE>`
/// - If none of the above apply, then find, parse and use targets from the first `browserslist`, `.browserslistrc`
/// or `package.json` configuration file in any parent directory.
///
/// When using parsed configuration from `browserslist`, `.browserslistrc` or `package.json` configuration files,
/// the environment determined by:
///
/// - the `BROWSERSLIST_ENV` environment variable if present,
/// - otherwise the `NODE_ENV` environment varialbe if present,
/// - otherwise `production` is used.
///
/// If no targets are found for the resulting environment, then the `defaults` configuration section is used.
pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::{execute, Opts};

Self::from_distribs(execute(&Opts::new())?)
}

fn from_distribs(distribs: Vec<browserslist::Distrib>) -> Result<Option<Browsers>, browserslist::Error> {
let mut browsers = Browsers::default();
let mut has_any = false;
for distrib in res {
for distrib in distribs {
macro_rules! browser {
($browser: ident) => {{
if let Some(v) = parse_version(distrib.version()) {
Expand Down
Loading