Skip to content

Commit 676bf1f

Browse files
committed
Add option to preserve @import dependencies with analyzeDependencies
1 parent bfa8b8c commit 676bf1f

File tree

7 files changed

+120
-33
lines changed

7 files changed

+120
-33
lines changed

node/index.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface TransformOptions {
2323
* are replaced with hashed placeholders that can be replaced with the final
2424
* urls later (after bundling). Dependencies are returned as part of the result.
2525
*/
26-
analyzeDependencies?: boolean,
26+
analyzeDependencies?: boolean | DependencyOptions,
2727
/**
2828
* Replaces user action pseudo classes with class names that can be applied from JavaScript.
2929
* This is useful for polyfills, for example.
@@ -43,6 +43,11 @@ export interface TransformOptions {
4343
errorRecovery?: boolean
4444
}
4545

46+
export interface DependencyOptions {
47+
/** Whether to preserve `@import` rules rather than removing them. */
48+
preserveImports?: boolean
49+
}
50+
4651
export type BundleOptions = Omit<TransformOptions, 'code'>;
4752

4853
export interface BundleAsyncOptions extends BundleOptions {

node/src/lib.rs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
44

55
use lightningcss::bundler::{BundleErrorKind, Bundler, FileProvider, SourceProvider};
66
use lightningcss::css_modules::{CssModuleExports, CssModuleReferences, PatternParseError};
7-
use lightningcss::dependencies::Dependency;
7+
use lightningcss::dependencies::{Dependency, DependencyOptions};
88
use lightningcss::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind};
99
use lightningcss::stylesheet::{
1010
MinifyOptions, ParserOptions, PrinterOptions, PseudoClasses, StyleAttribute, StyleSheet,
@@ -466,12 +466,25 @@ struct Config {
466466
pub input_source_map: Option<String>,
467467
pub drafts: Option<Drafts>,
468468
pub css_modules: Option<CssModulesOption>,
469-
pub analyze_dependencies: Option<bool>,
469+
pub analyze_dependencies: Option<AnalyzeDependenciesOption>,
470470
pub pseudo_classes: Option<OwnedPseudoClasses>,
471471
pub unused_symbols: Option<HashSet<String>>,
472472
pub error_recovery: Option<bool>,
473473
}
474474

475+
#[derive(Debug, Deserialize)]
476+
#[serde(untagged)]
477+
enum AnalyzeDependenciesOption {
478+
Bool(bool),
479+
Config(AnalyzeDependenciesConfig),
480+
}
481+
482+
#[derive(Debug, Deserialize)]
483+
#[serde(rename_all = "camelCase")]
484+
struct AnalyzeDependenciesConfig {
485+
preserve_imports: bool,
486+
}
487+
475488
#[derive(Debug, Deserialize)]
476489
#[serde(untagged)]
477490
enum CssModulesOption {
@@ -495,7 +508,7 @@ struct BundleConfig {
495508
pub source_map: Option<bool>,
496509
pub drafts: Option<Drafts>,
497510
pub css_modules: Option<CssModulesOption>,
498-
pub analyze_dependencies: Option<bool>,
511+
pub analyze_dependencies: Option<AnalyzeDependenciesOption>,
499512
pub pseudo_classes: Option<OwnedPseudoClasses>,
500513
pub unused_symbols: Option<HashSet<String>>,
501514
pub error_recovery: Option<bool>,
@@ -586,7 +599,17 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult<'i>, Co
586599
minify: config.minify.unwrap_or_default(),
587600
source_map: source_map.as_mut(),
588601
targets: config.targets,
589-
analyze_dependencies: config.analyze_dependencies.unwrap_or_default(),
602+
analyze_dependencies: if let Some(d) = &config.analyze_dependencies {
603+
match d {
604+
AnalyzeDependenciesOption::Bool(b) if *b => Some(DependencyOptions { remove_imports: true }),
605+
AnalyzeDependenciesOption::Config(c) => Some(DependencyOptions {
606+
remove_imports: !c.preserve_imports,
607+
}),
608+
_ => None,
609+
}
610+
} else {
611+
None
612+
},
590613
pseudo_classes: config.pseudo_classes.as_ref().map(|p| p.into()),
591614
})?
592615
};
@@ -672,7 +695,17 @@ fn compile_bundle<'i, P: SourceProvider>(
672695
minify: config.minify.unwrap_or_default(),
673696
source_map: source_map.as_mut(),
674697
targets: config.targets,
675-
analyze_dependencies: config.analyze_dependencies.unwrap_or_default(),
698+
analyze_dependencies: if let Some(d) = &config.analyze_dependencies {
699+
match d {
700+
AnalyzeDependenciesOption::Bool(b) if *b => Some(DependencyOptions { remove_imports: true }),
701+
AnalyzeDependenciesOption::Config(c) => Some(DependencyOptions {
702+
remove_imports: !c.preserve_imports,
703+
}),
704+
_ => None,
705+
}
706+
} else {
707+
None
708+
},
676709
pseudo_classes: config.pseudo_classes.as_ref().map(|p| p.into()),
677710
})?
678711
};
@@ -701,16 +734,15 @@ fn compile_bundle<'i, P: SourceProvider>(
701734
})
702735
}
703736

704-
#[derive(Serialize, Debug, Deserialize)]
737+
#[derive(Debug, Deserialize)]
705738
#[serde(rename_all = "camelCase")]
706739
struct AttrConfig {
707740
#[serde(with = "serde_bytes")]
708741
pub code: Vec<u8>,
709742
pub targets: Option<Browsers>,
710743
#[serde(default)]
711744
pub minify: bool,
712-
#[serde(default)]
713-
pub analyze_dependencies: bool,
745+
pub analyze_dependencies: Option<AnalyzeDependenciesOption>,
714746
#[serde(default)]
715747
pub error_recovery: bool,
716748
}
@@ -764,7 +796,17 @@ fn compile_attr<'i>(
764796
minify: config.minify,
765797
source_map: None,
766798
targets: config.targets,
767-
analyze_dependencies: config.analyze_dependencies,
799+
analyze_dependencies: if let Some(d) = &config.analyze_dependencies {
800+
match d {
801+
AnalyzeDependenciesOption::Bool(b) if *b => Some(DependencyOptions { remove_imports: true }),
802+
AnalyzeDependenciesOption::Config(c) => Some(DependencyOptions {
803+
remove_imports: !c.preserve_imports,
804+
}),
805+
_ => None,
806+
}
807+
} else {
808+
None
809+
},
768810
pseudo_classes: None,
769811
})?
770812
};

src/dependencies.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ use crate::values::url::Url;
1616
use cssparser::SourceLocation;
1717
use serde::Serialize;
1818

19+
/// Options for `analyze_dependencies` in `PrinterOptions`.
20+
#[derive(Default)]
21+
pub struct DependencyOptions {
22+
/// Whether to remove `@import` rules.
23+
pub remove_imports: bool,
24+
}
25+
1926
/// A dependency.
2027
#[derive(Serialize, Debug)]
2128
#[serde(tag = "type", rename_all = "lowercase")]
@@ -31,6 +38,8 @@ pub enum Dependency {
3138
pub struct ImportDependency {
3239
/// The url to import.
3340
pub url: String,
41+
/// The placeholder that the URL was replaced with.
42+
pub placeholder: String,
3443
/// An optional `supports()` condition.
3544
pub supports: Option<String>,
3645
/// A media query.
@@ -56,8 +65,11 @@ impl ImportDependency {
5665
None
5766
};
5867

68+
let placeholder = hash(&format!("{}_{}", filename, rule.url), false);
69+
5970
ImportDependency {
6071
url: rule.url.as_ref().to_owned(),
72+
placeholder,
6173
supports,
6274
media,
6375
loc: SourceRange::new(

src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19253,7 +19253,7 @@ mod tests {
1925319253
stylesheet.minify(MinifyOptions::default()).unwrap();
1925419254
let res = stylesheet
1925519255
.to_css(PrinterOptions {
19256-
analyze_dependencies: true,
19256+
analyze_dependencies: Some(Default::default()),
1925719257
minify: true,
1925819258
..PrinterOptions::default()
1925919259
})
@@ -19267,15 +19267,18 @@ mod tests {
1926719267
assert_eq!(dep.url, url);
1926819268
assert_eq!(dep.placeholder, placeholder);
1926919269
}
19270-
_ => unreachable!(),
19270+
Dependency::Import(dep) => {
19271+
assert_eq!(dep.url, url);
19272+
assert_eq!(dep.placeholder, placeholder);
19273+
}
1927119274
}
1927219275
}
1927319276
}
1927419277

1927519278
fn dep_error_test(source: &str, error: PrinterErrorKind) {
1927619279
let stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();
1927719280
let res = stylesheet.to_css(PrinterOptions {
19278-
analyze_dependencies: true,
19281+
analyze_dependencies: Some(Default::default()),
1927919282
..PrinterOptions::default()
1928019283
});
1928119284
match res {
@@ -19354,6 +19357,12 @@ mod tests {
1935419357
".foo{--foo:url(\"Zn9-2q\")}",
1935519358
vec![("#foo", "Zn9-2q")],
1935619359
);
19360+
19361+
dep_test(
19362+
"@import \"test.css\"; .foo { color: red }",
19363+
"@import \"hHsogW\";.foo{color:red}",
19364+
vec![("test.css", "hHsogW")],
19365+
);
1935719366
}
1935819367

1935919368
#[test]

src/printer.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! CSS serialization and source map generation.
22
33
use crate::css_modules::CssModule;
4-
use crate::dependencies::Dependency;
4+
use crate::dependencies::{Dependency, DependencyOptions};
55
use crate::error::{Error, ErrorLocation, PrinterError, PrinterErrorKind};
66
use crate::rules::Location;
77
use crate::targets::Browsers;
@@ -22,10 +22,10 @@ pub struct PrinterOptions<'a> {
2222
/// If true, the dependencies are returned as part of the
2323
/// [ToCssResult](super::stylesheet::ToCssResult).
2424
///
25-
/// When enabled, `@import` rules are removed, and `url()` dependencies
25+
/// When enabled, `@import` and `url()` dependencies
2626
/// are replaced with hashed placeholders that can be replaced with the final
2727
/// urls later (after bundling).
28-
pub analyze_dependencies: bool,
28+
pub analyze_dependencies: Option<DependencyOptions>,
2929
/// A mapping of pseudo classes to replace with class names that can be applied
3030
/// from JavaScript. Useful for polyfills, for example.
3131
pub pseudo_classes: Option<PseudoClasses<'a>>,
@@ -74,6 +74,7 @@ pub struct Printer<'a, 'b, 'c, W> {
7474
pub(crate) in_calc: bool,
7575
pub(crate) css_module: Option<CssModule<'a, 'b, 'c>>,
7676
pub(crate) dependencies: Option<Vec<Dependency>>,
77+
pub(crate) remove_imports: bool,
7778
pub(crate) pseudo_classes: Option<PseudoClasses<'a>>,
7879
}
7980

@@ -98,11 +99,12 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
9899
vendor_prefix: VendorPrefix::empty(),
99100
in_calc: false,
100101
css_module: None,
101-
dependencies: if options.analyze_dependencies {
102+
dependencies: if options.analyze_dependencies.is_some() {
102103
Some(Vec::new())
103104
} else {
104105
None
105106
},
107+
remove_imports: matches!(&options.analyze_dependencies, Some(d) if d.remove_imports),
106108
pseudo_classes: options.pseudo_classes,
107109
}
108110
}
@@ -209,11 +211,11 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
209211
self.loc = loc;
210212

211213
if let Some(map) = &mut self.source_map {
212-
let mut original = OriginalLocation {
213-
original_line: loc.line,
214-
original_column: loc.column - 1,
215-
source: loc.source_index,
216-
name: None,
214+
let mut original = OriginalLocation {
215+
original_line: loc.line,
216+
original_column: loc.column - 1,
217+
source: loc.source_index,
218+
name: None,
217219
};
218220

219221
// Remap using input source map if possible.
@@ -235,7 +237,7 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
235237

236238
found_mapping = true;
237239
}
238-
}
240+
}
239241

240242
if !found_mapping {
241243
return;

src/rules/import.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use super::layer::LayerName;
44
use super::supports::SupportsCondition;
55
use super::Location;
6+
use crate::dependencies::{Dependency, ImportDependency};
67
use crate::error::PrinterError;
78
use crate::media_query::MediaList;
89
use crate::printer::Printer;
@@ -32,9 +33,23 @@ impl<'i> ToCss for ImportRule<'i> {
3233
where
3334
W: std::fmt::Write,
3435
{
36+
let dep = if dest.dependencies.is_some() {
37+
Some(ImportDependency::new(self, dest.filename()))
38+
} else {
39+
None
40+
};
41+
3542
dest.add_mapping(self.loc);
3643
dest.write_str("@import ")?;
37-
serialize_string(&self.url, dest)?;
44+
if let Some(dep) = dep {
45+
serialize_string(&dep.placeholder, dest)?;
46+
47+
if let Some(dependencies) = &mut dest.dependencies {
48+
dependencies.push(Dependency::Import(dep))
49+
}
50+
} else {
51+
serialize_string(&self.url, dest)?;
52+
}
3853

3954
if let Some(layer) = &self.layer {
4055
dest.write_str(" layer")?;

src/rules/mod.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -532,15 +532,17 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for CssRuleList<'i> {
532532

533533
// Skip @import rules if collecting dependencies.
534534
if let CssRule::Import(rule) = &rule {
535-
let dep = if dest.dependencies.is_some() {
536-
Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
537-
} else {
538-
None
539-
};
540-
541-
if let Some(dependencies) = &mut dest.dependencies {
542-
dependencies.push(dep.unwrap());
543-
continue;
535+
if dest.remove_imports {
536+
let dep = if dest.dependencies.is_some() {
537+
Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
538+
} else {
539+
None
540+
};
541+
542+
if let Some(dependencies) = &mut dest.dependencies {
543+
dependencies.push(dep.unwrap());
544+
continue;
545+
}
544546
}
545547
}
546548

0 commit comments

Comments
 (0)