1use crate::context::{DeclarationContext, PropertyHandlerContext};
7use crate::css_modules::{hash, CssModule, CssModuleExports, CssModuleReferences};
8use crate::declaration::{DeclarationBlock, DeclarationHandler};
9use crate::dependencies::Dependency;
10use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind};
11use crate::parser::{DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};
12use crate::printer::Printer;
13use crate::rules::{CssRule, CssRuleList, MinifyContext};
14use crate::targets::{should_compile, Targets, TargetsWithSupportsScope};
15use crate::traits::{AtRuleParser, ToCss};
16use crate::values::string::CowArcStr;
17#[cfg(feature = "visitor")]
18use crate::visitor::{Visit, VisitTypes, Visitor};
19use cssparser::{Parser, ParserInput, StyleSheetParser};
20#[cfg(feature = "sourcemap")]
21use parcel_sourcemap::SourceMap;
22use std::collections::{HashMap, HashSet};
23
24pub use crate::parser::{ParserFlags, ParserOptions};
25pub use crate::printer::PrinterOptions;
26pub use crate::printer::PseudoClasses;
27
28#[derive(Debug, Clone)]
63#[cfg_attr(
64 feature = "serde",
65 derive(serde::Serialize, serde::Deserialize),
66 serde(rename_all = "camelCase")
67)]
68#[cfg_attr(
69 feature = "jsonschema",
70 derive(schemars::JsonSchema),
71 schemars(rename = "StyleSheet", bound = "T: schemars::JsonSchema")
72)]
73#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
74pub struct StyleSheet<'i, T = DefaultAtRule> {
75 #[cfg_attr(feature = "serde", serde(borrow))]
77 pub rules: CssRuleList<'i, T>,
78 pub sources: Vec<String>,
81 pub source_map_urls: Vec<Option<String>>,
83 pub license_comments: Vec<CowArcStr<'i>>,
85 #[cfg_attr(feature = "serde", serde(skip))]
88 pub(crate) content_hashes: Option<Vec<String>>,
89 #[cfg_attr(feature = "serde", serde(skip))]
90 options: ParserOptions<'i>,
92}
93
94#[derive(Default)]
97pub struct MinifyOptions {
98 pub targets: Targets,
100 pub unused_symbols: HashSet<String>,
103}
104
105#[derive(Debug)]
108pub struct ToCssResult {
109 pub code: String,
111 pub exports: Option<CssModuleExports>,
114 pub references: Option<CssModuleReferences>,
117 pub dependencies: Option<Vec<Dependency>>,
120}
121
122impl<'i, 'o> StyleSheet<'i, DefaultAtRule> {
123 pub fn parse(code: &'i str, options: ParserOptions<'i>) -> Result<Self, Error<ParserError<'i>>> {
125 Self::parse_with(code, options, &mut DefaultAtRuleParser)
126 }
127}
128
129impl<'i, T> StyleSheet<'i, T>
130where
131 T: ToCss + Clone,
132{
133 pub fn new(sources: Vec<String>, rules: CssRuleList<'i, T>, options: ParserOptions<'i>) -> StyleSheet<'i, T> {
135 StyleSheet {
136 sources,
137 source_map_urls: Vec::new(),
138 license_comments: Vec::new(),
139 content_hashes: None,
140 rules,
141 options,
142 }
143 }
144
145 pub fn parse_with<P: AtRuleParser<'i, AtRule = T>>(
147 code: &'i str,
148 mut options: ParserOptions<'i>,
149 at_rule_parser: &mut P,
150 ) -> Result<Self, Error<ParserError<'i>>> {
151 let mut input = ParserInput::new(&code);
152 let mut parser = Parser::new(&mut input);
153 let mut license_comments = Vec::new();
154
155 let mut content_hashes = None;
156 if let Some(config) = &options.css_modules {
157 if config.pattern.has_content_hash() {
158 content_hashes = Some(vec![hash(
159 &code,
160 matches!(config.pattern.segments[0], crate::css_modules::Segment::ContentHash),
161 )]);
162 }
163 }
164
165 let mut state = parser.state();
166 while let Ok(token) = parser.next_including_whitespace_and_comments() {
167 match token {
168 cssparser::Token::WhiteSpace(..) => {}
169 cssparser::Token::Comment(comment) if comment.starts_with('!') => {
170 license_comments.push((*comment).into());
171 }
172 cssparser::Token::Comment(comment) if comment.contains("cssmodules-pure-no-check") => {
173 if let Some(css_modules) = &mut options.css_modules {
174 css_modules.pure = false;
175 }
176 }
177 _ => break,
178 }
179 state = parser.state();
180 }
181 parser.reset(&state);
182
183 let mut rules = CssRuleList(vec![]);
184 let mut rule_parser = TopLevelRuleParser::new(&mut options, at_rule_parser, &mut rules);
185 let mut rule_list_parser = StyleSheetParser::new(&mut parser, &mut rule_parser);
186
187 while let Some(rule) = rule_list_parser.next() {
188 match rule {
189 Ok(()) => {}
190 Err((e, _)) => {
191 let options = &mut rule_list_parser.parser.options;
192 if options.error_recovery {
193 options.warn(e);
194 continue;
195 }
196
197 return Err(Error::from(e, options.filename.clone()));
198 }
199 }
200 }
201
202 Ok(StyleSheet {
203 sources: vec![options.filename.clone()],
204 source_map_urls: vec![parser.current_source_map_url().map(|s| s.to_owned())],
205 content_hashes,
206 rules,
207 license_comments,
208 options,
209 })
210 }
211
212 pub fn source_map_url(&self, source_index: usize) -> Option<&String> {
214 self.source_map_urls.get(source_index)?.as_ref()
215 }
216
217 #[cfg(feature = "sourcemap")]
219 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
220 pub fn source_map(&self, source_index: usize) -> Option<SourceMap> {
221 SourceMap::from_data_url("/", self.source_map_url(source_index)?).ok()
222 }
223
224 pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error<MinifyErrorKind>> {
226 let context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
227 let mut handler = DeclarationHandler::default();
228 let mut important_handler = DeclarationHandler::default();
229
230 let custom_media = if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA)
233 && should_compile!(options.targets, CustomMediaQueries)
234 {
235 let mut custom_media = HashMap::new();
236 for rule in &self.rules.0 {
237 if let CssRule::CustomMedia(rule) = rule {
238 custom_media.insert(rule.name.0.clone(), rule.clone());
239 }
240 }
241 Some(custom_media)
242 } else {
243 None
244 };
245
246 let mut ctx = MinifyContext {
247 targets: TargetsWithSupportsScope::new(options.targets),
248 handler: &mut handler,
249 important_handler: &mut important_handler,
250 handler_context: context,
251 unused_symbols: &options.unused_symbols,
252 custom_media,
253 css_modules: self.options.css_modules.is_some(),
254 pure_css_modules: self.options.css_modules.as_ref().map(|c| c.pure).unwrap_or_default(),
255 };
256
257 self.rules.minify(&mut ctx, false).map_err(|e| Error {
258 kind: e.kind,
259 loc: Some(ErrorLocation::new(
260 e.loc,
261 self.sources[e.loc.source_index as usize].clone(),
262 )),
263 })?;
264
265 Ok(())
266 }
267
268 pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, Error<PrinterErrorKind>> {
270 let mut dest = String::with_capacity(1);
272 let project_root = options.project_root.clone();
273 let mut printer = Printer::new(&mut dest, options);
274
275 #[cfg(feature = "sourcemap")]
276 {
277 printer.sources = Some(&self.sources);
278 }
279
280 #[cfg(feature = "sourcemap")]
281 if printer.source_map.is_some() {
282 printer.source_maps = self.sources.iter().enumerate().map(|(i, _)| self.source_map(i)).collect();
283 }
284
285 for comment in &self.license_comments {
286 printer.write_str("/*")?;
287 printer.write_str_with_newlines(comment)?;
288 printer.write_str_with_newlines("*/\n")?;
289 }
290
291 if let Some(config) = &self.options.css_modules {
292 let mut references = HashMap::new();
293 printer.css_module = Some(CssModule::new(
294 config,
295 &self.sources,
296 project_root,
297 &mut references,
298 &self.content_hashes,
299 ));
300
301 self.rules.to_css(&mut printer)?;
302 printer.newline()?;
303
304 Ok(ToCssResult {
305 dependencies: printer.dependencies,
306 exports: Some(std::mem::take(
307 &mut printer.css_module.unwrap().exports_by_source_index[0],
308 )),
309 code: dest,
310 references: Some(references),
311 })
312 } else {
313 self.rules.to_css(&mut printer)?;
314 printer.newline()?;
315
316 Ok(ToCssResult {
317 dependencies: printer.dependencies,
318 code: dest,
319 exports: None,
320 references: None,
321 })
322 }
323 }
324}
325
326#[cfg(feature = "visitor")]
327#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
328impl<'i, T, V> Visit<'i, T, V> for StyleSheet<'i, T>
329where
330 T: Visit<'i, T, V>,
331 V: ?Sized + Visitor<'i, T>,
332{
333 const CHILD_TYPES: VisitTypes = VisitTypes::all();
334
335 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
336 visitor.visit_stylesheet(self)
337 }
338
339 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
340 self.rules.visit(visitor)
341 }
342}
343
344#[cfg_attr(feature = "visitor", derive(Visit))]
370#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
371pub struct StyleAttribute<'i> {
372 pub declarations: DeclarationBlock<'i>,
374 #[cfg_attr(feature = "visitor", skip_visit)]
375 sources: Vec<String>,
376}
377
378impl<'i> StyleAttribute<'i> {
379 pub fn parse(code: &'i str, options: ParserOptions<'i>) -> Result<StyleAttribute<'i>, Error<ParserError<'i>>> {
381 let mut input = ParserInput::new(&code);
382 let mut parser = Parser::new(&mut input);
383 Ok(StyleAttribute {
384 declarations: DeclarationBlock::parse(&mut parser, &options).map_err(|e| Error::from(e, "".into()))?,
385 sources: vec![options.filename],
386 })
387 }
388
389 pub fn minify(&mut self, options: MinifyOptions) {
391 let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
392 let mut handler = DeclarationHandler::default();
393 let mut important_handler = DeclarationHandler::default();
394 context.context = DeclarationContext::StyleAttribute;
395 self.declarations.minify(&mut handler, &mut important_handler, &mut context);
396 }
397
398 pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, PrinterError> {
400 #[cfg(feature = "sourcemap")]
401 assert!(
402 options.source_map.is_none(),
403 "Source maps are not supported for style attributes"
404 );
405
406 let mut dest = String::with_capacity(1);
408 let mut printer = Printer::new(&mut dest, options);
409 printer.sources = Some(&self.sources);
410
411 self.declarations.to_css(&mut printer)?;
412
413 Ok(ToCssResult {
414 dependencies: printer.dependencies,
415 code: dest,
416 exports: None,
417 references: None,
418 })
419 }
420}