diff --git a/node/index.d.ts b/node/index.d.ts index 993e62b7..4027a334 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -14,7 +14,14 @@ export interface TransformOptions { /** Whether to enable various draft syntax. */ drafts?: Drafts, /** Whether to compile this file as a CSS module. */ - cssModules?: boolean + cssModules?: boolean, + /** + * Whether to analyze dependencies (e.g. `@import` and `url()`). + * When enabled, `@import` rules are removed, and `url()` dependencies + * are replaced with hashed placeholders that can be replaced with the final + * urls later (after bundling). Dependencies are returned as part of the result. + */ + analyzeDependencies?: boolean } export interface Drafts { @@ -28,7 +35,9 @@ export interface TransformResult { /** The generated source map, if enabled. */ map: Buffer | void, /** CSS module exports, if enabled. */ - exports: CSSModuleExports | void + exports: CSSModuleExports | void, + /** `@import` and `url()` dependencies, if enabled. */ + dependencies: Dependency[] | void } export type CSSModuleExports = { @@ -54,6 +63,42 @@ export interface DependencyCSSModuleExport { } } +export type Dependency = ImportDependency | UrlDependency; + +export interface ImportDependency { + type: 'import', + /** The url of the `@import` dependency. */ + url: string, + /** The media query for the `@import` rule. */ + media: string | null, + /** The `supports()` query for the `@import` rule. */ + supports: string | null, + /** The source location where the `@import` rule was found. */ + loc: SourceLocation +} + +export interface UrlDependency { + type: 'url', + /** The url of the dependency. */ + url: string, + /** The source location where the `url()` was found. */ + loc: SourceLocation +} + +export interface SourceLocation { + /** The start location of the dependency. */ + start: Location, + /** The end location (inclusive) of the dependency. */ + end: Location +} + +export interface Location { + /** The line number (1-based). */ + line: number, + /** The column number (0-based). */ + column: number +} + /** * Compiles a CSS file, including optionally minifying and lowering syntax to the given * targets. A source map may also be generated, but this is not enabled by default. @@ -66,10 +111,24 @@ export interface TransformAttributeOptions { /** Whether to enable minification. */ minify?: boolean, /** The browser targets for the generated code. */ - targets?: Targets + targets?: Targets, + /** + * Whether to analyze `url()` dependencies. + * When enabled, `url()` dependencies are replaced with hashed placeholders + * that can be replaced with the final urls later (after bundling). + * Dependencies are returned as part of the result. + */ + analyzeDependencies?: boolean +} + +export interface TransformAttributeResult { + /** The transformed code. */ + code: Buffer, + /** `@import` and `url()` dependencies, if enabled. */ + dependencies: Dependency[] | void } /** * Compiles a single CSS declaration list, such as an inline style attribute in HTML. */ -export declare function transformStyleAttribute(options: TransformAttributeOptions): Buffer; +export declare function transformStyleAttribute(options: TransformAttributeOptions): TransformAttributeResult; diff --git a/node/src/lib.rs b/node/src/lib.rs index f419510b..45d7e74e 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -6,6 +6,7 @@ use serde::{Serialize, Deserialize}; use parcel_css::stylesheet::{StyleSheet, StyleAttribute, ParserOptions, PrinterOptions}; use parcel_css::targets::Browsers; use parcel_css::css_modules::CssModuleExports; +use parcel_css::dependencies::Dependency; // --------------------------------------------- @@ -26,11 +27,12 @@ pub fn transform(config_val: JsValue) -> Result { #[cfg(target_arch = "wasm32")] #[wasm_bindgen(js_name = "transformStyleAttribute")] -pub fn transform_style_attribute(config_val: JsValue) -> Result, JsValue> { +pub fn transform_style_attribute(config_val: JsValue) -> Result { let config: AttrConfig = from_value(config_val).map_err(JsValue::from)?; let code = unsafe { std::str::from_utf8_unchecked(&config.code) }; let res = compile_attr(code, &config)?; - Ok(res) + let serializer = Serializer::new().serialize_maps_as_objects(true); + res.serialize(&serializer).map_err(JsValue::from) } // --------------------------------------------- @@ -57,7 +59,8 @@ struct TransformResult { code: Vec, #[serde(with = "serde_bytes")] map: Option>, - exports: Option + exports: Option, + dependencies: Option> } #[cfg(not(target_arch = "wasm32"))] @@ -83,7 +86,7 @@ fn transform_style_attribute(ctx: CallContext) -> napi::Result { let res = compile_attr(code, &config); match res { - Ok(res) => Ok(ctx.env.create_buffer_with_data(res)?.into_unknown()), + Ok(res) => ctx.env.to_js_value(&res), Err(err) => err.throw(ctx, None, code) } } @@ -109,7 +112,8 @@ struct Config { pub minify: Option, pub source_map: Option, pub drafts: Option, - pub css_modules: Option + pub css_modules: Option, + pub analyze_dependencies: Option } #[derive(Serialize, Debug, Deserialize, Default)] @@ -130,7 +134,8 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result(code: &'i str, config: &Config) -> Result, pub targets: Option, - pub minify: Option + pub minify: Option, + pub analyze_dependencies: Option } -fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result, CompileError<'i>> { +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct AttrResult { + #[serde(with = "serde_bytes")] + code: Vec, + dependencies: Option> +} + +fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result> { let mut attr = StyleAttribute::parse(&code)?; attr.minify(config.targets); // TODO: should this be conditional? - let res = attr.to_css(config.minify.unwrap_or(false), config.targets)?; - Ok(res.into_bytes()) + let res = attr.to_css(PrinterOptions { + minify: config.minify.unwrap_or(false), + source_map: false, + targets: config.targets, + analyze_dependencies: config.analyze_dependencies.unwrap_or(false) + })?; + Ok(AttrResult { + code: res.code.into_bytes(), + dependencies: res.dependencies + }) } enum CompileError<'i> { diff --git a/src/dependencies.rs b/src/dependencies.rs new file mode 100644 index 00000000..19143fbb --- /dev/null +++ b/src/dependencies.rs @@ -0,0 +1,102 @@ +use crate::rules::import::ImportRule; +use crate::values::url::Url; +use serde::Serialize; +use cssparser::SourceLocation; +use crate::printer::Printer; +use crate::traits::ToCss; +use crate::css_modules::hash; + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum Dependency { + Import(ImportDependency), + Url(UrlDependency) +} + +impl From<&ImportRule> for Dependency { + fn from(rule: &ImportRule) -> Dependency { + Dependency::Import(rule.into()) + } +} + +#[derive(Serialize)] +pub struct ImportDependency { + pub url: String, + pub supports: Option, + pub media: Option, + pub loc: SourceRange +} + +impl From<&ImportRule> for ImportDependency { + fn from(rule: &ImportRule) -> ImportDependency { + let supports = if let Some(supports) = &rule.supports { + let mut s = String::new(); + let mut printer = Printer::new("", &mut s, None, false, None); + supports.to_css(&mut printer).unwrap(); + Some(s) + } else { + None + }; + + let media = if !rule.media.media_queries.is_empty() { + let mut s = String::new(); + let mut printer = Printer::new("", &mut s, None, false, None); + rule.media.to_css(&mut printer).unwrap(); + Some(s) + } else { + None + }; + + ImportDependency { + url: rule.url.clone(), + supports, + media, + loc: SourceRange::new(rule.loc, 8, rule.url.len() + 2) // TODO: what about @import url(...)? + } + } +} + +#[derive(Serialize)] +pub struct UrlDependency { + pub url: String, + pub placeholder: String, + pub loc: SourceRange +} + +impl UrlDependency { + pub fn new(url: &Url, filename: &str) -> UrlDependency { + let placeholder = hash(&format!("{}_{}", filename, url.url)); + UrlDependency { + url: url.url.clone(), + placeholder, + loc: SourceRange::new(url.loc, 4, url.url.len()) + } + } +} + +#[derive(Serialize)] +pub struct SourceRange { + pub start: Location, + pub end: Location, +} + +#[derive(Serialize)] +pub struct Location { + pub line: u32, + pub column: u32 +} + +impl SourceRange { + fn new(loc: SourceLocation, offset: u32, len: usize) -> SourceRange { + SourceRange { + start: Location { + line: loc.line + 1, + column: loc.column + offset + }, + end: Location { + line: loc.line + 1, + column: loc.column + offset + (len as u32) - 1 + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 64954959..088bcd7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod prefixes; pub mod vendor_prefix; pub mod targets; pub mod css_modules; +pub mod dependencies; #[cfg(test)] mod tests { @@ -47,8 +48,8 @@ mod tests { fn attr_test(source: &str, expected: &str, minify: bool) { let mut attr = StyleAttribute::parse(source).unwrap(); attr.minify(None); - let res = attr.to_css(minify, None).unwrap(); - assert_eq!(res, expected); + let res = attr.to_css(PrinterOptions { minify, ..PrinterOptions::default() }).unwrap(); + assert_eq!(res.code, expected); } fn nesting_test(source: &str, expected: &str) { diff --git a/src/printer.rs b/src/printer.rs index feb25bea..785c700b 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -4,8 +4,10 @@ use parcel_sourcemap::{SourceMap, OriginalLocation}; use crate::vendor_prefix::VendorPrefix; use crate::targets::Browsers; use crate::css_modules::CssModule; +use crate::dependencies::Dependency; pub(crate) struct Printer<'a, W> { + pub filename: &'a str, dest: &'a mut W, source_map: Option<&'a mut SourceMap>, indent: u8, @@ -17,17 +19,20 @@ pub(crate) struct Printer<'a, W> { /// the vendor prefix of whatever is being printed. pub vendor_prefix: VendorPrefix, pub in_calc: bool, - pub css_module: Option> + pub css_module: Option>, + pub dependencies: Option<&'a mut Vec> } impl<'a, W: Write + Sized> Printer<'a, W> { pub fn new( + filename: &'a str, dest: &'a mut W, source_map: Option<&'a mut SourceMap>, minify: bool, targets: Option ) -> Printer<'a, W> { Printer { + filename, dest, source_map, indent: 0, @@ -37,7 +42,8 @@ impl<'a, W: Write + Sized> Printer<'a, W> { targets, vendor_prefix: VendorPrefix::empty(), in_calc: false, - css_module: None + css_module: None, + dependencies: None } } diff --git a/src/properties/transform.rs b/src/properties/transform.rs index 9a6037e8..95150dab 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -48,13 +48,13 @@ impl ToCss for TransformList { if let Some(matrix) = self.to_matrix() { // Generate based on the original transforms. let mut base = String::new(); - self.to_css_base(&mut Printer::new(&mut base, None, true, None))?; + self.to_css_base(&mut Printer::new(dest.filename, &mut base, None, true, None))?; // Decompose the matrix into transform functions if possible. // If the resulting length is shorter than the original, use it. if let Some(d) = matrix.decompose() { let mut decomposed = String::new(); - d.to_css_base(&mut Printer::new(&mut decomposed, None, true, None))?; + d.to_css_base(&mut Printer::new(dest.filename, &mut decomposed, None, true, None))?; if decomposed.len() < base.len() { base = decomposed; } @@ -63,9 +63,9 @@ impl ToCss for TransformList { // Also generate a matrix() or matrix3d() representation and compare that. let mut mat = String::new(); if let Some(matrix) = matrix.to_matrix2d() { - Transform::Matrix(matrix).to_css(&mut Printer::new(&mut mat, None, true, None))? + Transform::Matrix(matrix).to_css(&mut Printer::new(dest.filename, &mut mat, None, true, None))? } else { - Transform::Matrix3d(matrix).to_css(&mut Printer::new(&mut mat, None, true, None))? + Transform::Matrix3d(matrix).to_css(&mut Printer::new(dest.filename, &mut mat, None, true, None))? } if mat.len() < base.len() { diff --git a/src/properties/ui.rs b/src/properties/ui.rs index 20b72a90..1b15115e 100644 --- a/src/properties/ui.rs +++ b/src/properties/ui.rs @@ -4,6 +4,7 @@ use crate::values::color::CssColor; use crate::macros::{enum_property, shorthand_property}; use crate::printer::Printer; use smallvec::SmallVec; +use crate::values::url::Url; // https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize enum_property!(Resize, @@ -18,13 +19,13 @@ enum_property!(Resize, /// https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor #[derive(Debug, Clone, PartialEq)] pub struct CursorImage { - pub url: String, + pub url: Url, pub hotspot: Option<(f32, f32)> } impl Parse for CursorImage { fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { - let url = input.expect_url()?.as_ref().to_owned(); + let url = Url::parse(input)?; let hotspot = if let Ok(x) = input.try_parse(f32::parse) { let y = f32::parse(input)?; Some((x, y)) @@ -41,10 +42,7 @@ impl Parse for CursorImage { impl ToCss for CursorImage { fn to_css(&self, dest: &mut Printer) -> std::fmt::Result where W: std::fmt::Write { - { - use cssparser::ToCss; - Token::UnquotedUrl(CowRcStr::from(self.url.as_ref())).to_css(dest)?; - } + self.url.to_css(dest)?; if let Some((x, y)) = self.hotspot { dest.write_char(' ')?; diff --git a/src/rules/font_face.rs b/src/rules/font_face.rs index 3bbdffa3..8599b876 100644 --- a/src/rules/font_face.rs +++ b/src/rules/font_face.rs @@ -5,6 +5,7 @@ use crate::properties::font::{FontFamily, FontStyle, FontWeight, FontStretch}; use crate::values::size::Size2D; use crate::properties::custom::CustomProperty; use crate::macros::enum_property; +use crate::values::url::Url; #[derive(Debug, PartialEq)] pub struct FontFaceRule { @@ -56,13 +57,13 @@ impl ToCss for Source { #[derive(Debug, Clone, PartialEq)] pub struct UrlSource { - pub url: String, + pub url: Url, pub format: Option } impl Parse for UrlSource { fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { - let url = input.expect_url()?.as_ref().to_owned(); + let url = Url::parse(input)?; let format = if input.try_parse(|input| input.expect_function_matching("format")).is_ok() { Some(input.parse_nested_block(Format::parse)?) @@ -76,8 +77,7 @@ impl Parse for UrlSource { impl ToCss for UrlSource { fn to_css(&self, dest: &mut Printer) -> std::fmt::Result where W: std::fmt::Write { - use cssparser::ToCss; - Token::UnquotedUrl(CowRcStr::from(self.url.as_ref())).to_css(dest)?; + self.url.to_css(dest)?; if let Some(format) = &self.format { dest.whitespace()?; dest.write_str("format(")?; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 9e77b204..07a2a246 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -184,6 +184,14 @@ impl ToCssWithContext for CssRuleList { let mut last_without_block = false; for rule in &self.0 { + // Skip @import rules if collecting dependencies. + if let CssRule::Import(rule) = &rule { + if let Some(dependencies) = &mut dest.dependencies { + dependencies.push(rule.into()); + continue; + } + } + if first { first = false; } else { diff --git a/src/stylesheet.rs b/src/stylesheet.rs index e1a67517..aee96e32 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -8,6 +8,7 @@ use crate::targets::Browsers; use crate::declaration::{DeclarationHandler, DeclarationBlock}; use crate::css_modules::{hash, CssModule, CssModuleExports}; use std::collections::HashMap; +use crate::dependencies::Dependency; pub use crate::parser::ParserOptions; @@ -21,13 +22,15 @@ pub struct StyleSheet { pub struct PrinterOptions { pub minify: bool, pub source_map: bool, - pub targets: Option + pub targets: Option, + pub analyze_dependencies: bool } pub struct ToCssResult { pub code: String, pub source_map: Option, - pub exports: Option + pub exports: Option, + pub dependencies: Option> } impl StyleSheet { @@ -70,7 +73,15 @@ impl StyleSheet { None }; - let mut printer = Printer::new(&mut dest, source_map.as_mut(), options.minify, options.targets); + let mut printer = Printer::new(&self.filename, &mut dest, source_map.as_mut(), options.minify, options.targets); + + let mut dependencies = if options.analyze_dependencies { + Some(Vec::new()) + } else { + None + }; + + printer.dependencies = dependencies.as_mut(); if self.options.css_modules { let h = hash(&self.filename); @@ -86,7 +97,8 @@ impl StyleSheet { Ok(ToCssResult { code: dest, source_map, - exports: Some(exports) + exports: Some(exports), + dependencies }) } else { self.rules.to_css(&mut printer)?; @@ -94,7 +106,8 @@ impl StyleSheet { Ok(ToCssResult { code: dest, source_map, - exports: None + exports: None, + dependencies }) } } @@ -120,9 +133,19 @@ impl StyleAttribute { self.declarations.minify(&mut handler, &mut important_handler); } - pub fn to_css(&self, minify: bool, targets: Option) -> Result { + pub fn to_css(&self, options: PrinterOptions) -> Result { + assert_eq!(options.source_map, false, "Source maps are not supported for style attributes"); + let mut dest = String::new(); - let mut printer = Printer::new(&mut dest, None, minify, targets); + let mut printer = Printer::new("", &mut dest, None, options.minify, options.targets); + + let mut dependencies = if options.analyze_dependencies { + Some(Vec::new()) + } else { + None + }; + + printer.dependencies = dependencies.as_mut(); let declarations = &self.declarations.declarations; let len = declarations.len(); @@ -134,6 +157,11 @@ impl StyleAttribute { } } - Ok(dest) + Ok(ToCssResult { + code: dest, + source_map: None, + exports: None, + dependencies + }) } } diff --git a/src/traits.rs b/src/traits.rs index 8580aa5c..288c1b9b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -23,7 +23,7 @@ pub(crate) trait ToCss { #[inline] fn to_css_string(&self) -> String { let mut s = String::new(); - let mut printer = Printer::new(&mut s, None, false, None); + let mut printer = Printer::new("", &mut s, None, false, None); self.to_css(&mut printer).unwrap(); s } diff --git a/src/values/image.rs b/src/values/image.rs index 60bd3538..6a97433b 100644 --- a/src/values/image.rs +++ b/src/values/image.rs @@ -6,12 +6,13 @@ use crate::traits::{Parse, ToCss}; use crate::printer::Printer; use super::gradient::*; use super::resolution::Resolution; +use crate::values::url::Url; /// https://www.w3.org/TR/css-images-3/#typedef-image #[derive(Debug, Clone, PartialEq)] pub enum Image { None, - Url(String), + Url(Url), Gradient(Gradient), ImageSet(ImageSet) } @@ -61,8 +62,8 @@ impl Parse for Image { return Ok(Image::None) } - if let Ok(url) = input.try_parse(|input| input.expect_url()) { - return Ok(Image::Url(url.as_ref().into())) + if let Ok(url) = input.try_parse(Url::parse) { + return Ok(Image::Url(url)) } if let Ok(grad) = input.try_parse(Gradient::parse) { @@ -79,34 +80,11 @@ impl Parse for Image { impl ToCss for Image { fn to_css(&self, dest: &mut Printer) -> std::fmt::Result where W: std::fmt::Write { - use Image::*; - use cssparser::ToCss; match self { - None => dest.write_str("none"), - Url(url) => { - if dest.minify { - let mut buf = String::new(); - Token::UnquotedUrl(CowRcStr::from(url.as_ref())).to_css(&mut buf)?; - - // If the unquoted url is longer than it would be quoted (e.g. `url("...")`) - // then serialize as a string and choose the shorter version. - if buf.len() > url.len() + 7 { - let mut buf2 = String::new(); - serialize_string(url, &mut buf2)?; - if buf2.len() + 5 < buf.len() { - dest.write_str("url(")?; - dest.write_str(&buf2)?; - return dest.write_char(')') - } - } - - dest.write_str(&buf) - } else { - Token::UnquotedUrl(CowRcStr::from(url.as_ref())).to_css(dest) - } - } - Gradient(grad) => grad.to_css(dest), - ImageSet(image_set) => image_set.to_css(dest) + Image::None => dest.write_str("none"), + Image::Url(url) => url.to_css(dest), + Image::Gradient(grad) => grad.to_css(dest), + Image::ImageSet(image_set) => image_set.to_css(dest) } } } @@ -182,8 +160,12 @@ pub struct ImageSetOption { impl Parse for ImageSetOption { fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { + let loc = input.current_source_location(); let image = if let Ok(url) = input.try_parse(|input| input.expect_url_or_string()) { - Image::Url(url.as_ref().into()) + Image::Url(Url { + url: url.as_ref().into(), + loc + }) } else { Image::parse(input)? }; @@ -205,7 +187,7 @@ impl ImageSetOption { fn to_css(&self, dest: &mut Printer, is_prefixed: bool) -> std::fmt::Result where W: std::fmt::Write { match &self.image { // Prefixed syntax didn't allow strings, only url() - Image::Url(url) if !is_prefixed => serialize_string(&url, dest)?, + Image::Url(url) if !is_prefixed => serialize_string(&url.url, dest)?, _ => self.image.to_css(dest)? } diff --git a/src/values/mod.rs b/src/values/mod.rs index f88e2279..55c61036 100644 --- a/src/values/mod.rs +++ b/src/values/mod.rs @@ -15,3 +15,4 @@ pub mod number; pub mod gradient; pub mod resolution; pub mod ratio; +pub mod url; diff --git a/src/values/url.rs b/src/values/url.rs new file mode 100644 index 00000000..5d79e5f6 --- /dev/null +++ b/src/values/url.rs @@ -0,0 +1,62 @@ +use cssparser::*; +use crate::traits::{Parse, ToCss}; +use crate::printer::Printer; +use crate::dependencies::{Dependency, UrlDependency}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Url { + pub url: String, + pub loc: SourceLocation +} + +impl Parse for Url { + fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { + let loc = input.current_source_location(); + let url = input.expect_url()?.as_ref().to_owned(); + Ok(Url { url, loc }) + } +} + +impl ToCss for Url { + fn to_css(&self, dest: &mut Printer) -> std::fmt::Result where W: std::fmt::Write { + let dep = if dest.dependencies.is_some() { + Some(UrlDependency::new(self, dest.filename)) + } else { + None + }; + + let url = if let Some(dep) = &dep { + &dep.placeholder + } else { + &self.url + }; + + use cssparser::ToCss; + if dest.minify { + let mut buf = String::new(); + Token::UnquotedUrl(CowRcStr::from(url.as_ref())).to_css(&mut buf)?; + + // If the unquoted url is longer than it would be quoted (e.g. `url("...")`) + // then serialize as a string and choose the shorter version. + if buf.len() > url.len() + 7 { + let mut buf2 = String::new(); + serialize_string(&url, &mut buf2)?; + if buf2.len() + 5 < buf.len() { + dest.write_str("url(")?; + dest.write_str(&buf2)?; + return dest.write_char(')') + } + } + + dest.write_str(&buf)?; + } else { + Token::UnquotedUrl(CowRcStr::from(url.as_ref())).to_css(dest)?; + } + + if let Some(dependencies) = &mut dest.dependencies { + dependencies.push(Dependency::Url(dep.unwrap())) + } + + Ok(()) + } +} diff --git a/test.js b/test.js index 8b9e475b..699ae573 100644 --- a/test.js +++ b/test.js @@ -37,6 +37,9 @@ let res = css.transform({ opera: 10 << 16 | 5 << 8 }, code: Buffer.from(` + @import "foo.css"; + @import "bar.css" print; + @import "baz.css" supports(display: grid); .foo { composes: bar; @@ -46,13 +49,16 @@ let res = css.transform({ .bar { color: red; + background: url(test.jpg); } `), drafts: { nesting: true }, - cssModules: true + cssModules: true, + analyzeDependencies: true }); console.log(res.code.toString()); console.log(res.exports); +console.log(require('util').inspect(res.dependencies, { colors: true, depth: 50 }));