lightningcss/
dependencies.rs

1//! Dependency analysis.
2//!
3//! Dependencies in CSS can be analyzed using the `analyze_dependencies` option
4//! when printing a style sheet. These include other style sheets referenved via
5//! the `@import` rule, as well as `url()` references. See [PrinterOptions](PrinterOptions).
6//!
7//! When dependency analysis is enabled, `@import` rules are removed, and `url()`
8//! dependencies are replaced with hashed placeholders that can be substituted with
9//! the final urls later (e.g. after bundling and content hashing).
10
11use crate::css_modules::hash;
12use crate::printer::PrinterOptions;
13use crate::rules::import::ImportRule;
14use crate::traits::ToCss;
15use crate::values::url::Url;
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use cssparser::SourceLocation;
19#[cfg(any(feature = "serde", feature = "nodejs"))]
20use serde::Serialize;
21
22/// Options for `analyze_dependencies` in `PrinterOptions`.
23#[derive(Default)]
24pub struct DependencyOptions {
25  /// Whether to remove `@import` rules.
26  pub remove_imports: bool,
27}
28
29/// A dependency.
30#[derive(Debug)]
31#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
32#[cfg_attr(
33  any(feature = "serde", feature = "nodejs"),
34  serde(tag = "type", rename_all = "lowercase")
35)]
36pub enum Dependency {
37  /// An `@import` dependency.
38  Import(ImportDependency),
39  /// A `url()` dependency.
40  Url(UrlDependency),
41}
42
43/// An `@import` dependency.
44#[derive(Debug)]
45#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
46pub struct ImportDependency {
47  /// The url to import.
48  pub url: String,
49  /// The placeholder that the URL was replaced with.
50  pub placeholder: String,
51  /// An optional `supports()` condition.
52  pub supports: Option<String>,
53  /// A media query.
54  pub media: Option<String>,
55  /// The location of the dependency in the source file.
56  pub loc: SourceRange,
57}
58
59impl ImportDependency {
60  /// Creates a new dependency from an `@import` rule.
61  pub fn new(rule: &ImportRule, filename: &str) -> ImportDependency {
62    let supports = if let Some(supports) = &rule.supports {
63      let s = supports.to_css_string(PrinterOptions::default()).unwrap();
64      Some(s)
65    } else {
66      None
67    };
68
69    let media = if !rule.media.media_queries.is_empty() {
70      let s = rule.media.to_css_string(PrinterOptions::default()).unwrap();
71      Some(s)
72    } else {
73      None
74    };
75
76    let placeholder = hash(&format!("{}_{}", filename, rule.url), false);
77
78    ImportDependency {
79      url: rule.url.as_ref().to_owned(),
80      placeholder,
81      supports,
82      media,
83      loc: SourceRange::new(
84        filename,
85        Location {
86          line: rule.loc.line + 1,
87          column: rule.loc.column,
88        },
89        8,
90        rule.url.len() + 2,
91      ), // TODO: what about @import url(...)?
92    }
93  }
94}
95
96/// A `url()` dependency.
97#[derive(Debug)]
98#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
99pub struct UrlDependency {
100  /// The url of the dependency.
101  pub url: String,
102  /// The placeholder that the URL was replaced with.
103  pub placeholder: String,
104  /// The location of the dependency in the source file.
105  pub loc: SourceRange,
106}
107
108impl UrlDependency {
109  /// Creates a new url dependency.
110  pub fn new(url: &Url, filename: &str) -> UrlDependency {
111    let placeholder = hash(&format!("{}_{}", filename, url.url), false);
112    UrlDependency {
113      url: url.url.to_string(),
114      placeholder,
115      loc: SourceRange::new(filename, url.loc, 4, url.url.len()),
116    }
117  }
118}
119
120/// Represents the range of source code where a dependency was found.
121#[derive(Debug)]
122#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
123#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(rename_all = "camelCase"))]
124pub struct SourceRange {
125  /// The filename in which the dependency was found.
126  pub file_path: String,
127  /// The starting line and column position of the dependency.
128  pub start: Location,
129  /// The ending line and column position of the dependency.
130  pub end: Location,
131}
132
133/// A line and column position within a source file.
134#[derive(Debug, Clone, Copy, PartialEq)]
135#[cfg_attr(feature = "visitor", derive(Visit))]
136#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
137#[cfg_attr(any(feature = "serde"), derive(serde::Deserialize))]
138#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
139#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
140pub struct Location {
141  /// The line number, starting from 1.
142  pub line: u32,
143  /// The column number, starting from 1.
144  pub column: u32,
145}
146
147impl From<SourceLocation> for Location {
148  fn from(loc: SourceLocation) -> Location {
149    Location {
150      line: loc.line + 1,
151      column: loc.column,
152    }
153  }
154}
155
156impl SourceRange {
157  fn new(filename: &str, loc: Location, offset: u32, len: usize) -> SourceRange {
158    SourceRange {
159      file_path: filename.into(),
160      start: Location {
161        line: loc.line,
162        column: loc.column + offset,
163      },
164      end: Location {
165        line: loc.line,
166        column: loc.column + offset + (len as u32) - 1,
167      },
168    }
169  }
170}