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
67 changes: 63 additions & 4 deletions node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 = {
Expand All @@ -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.
Expand All @@ -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;
46 changes: 35 additions & 11 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

// ---------------------------------------------

Expand All @@ -26,11 +27,12 @@ pub fn transform(config_val: JsValue) -> Result<JsValue, JsValue> {

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = "transformStyleAttribute")]
pub fn transform_style_attribute(config_val: JsValue) -> Result<Vec<u8>, JsValue> {
pub fn transform_style_attribute(config_val: JsValue) -> Result<JsValue, JsValue> {
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)
}

// ---------------------------------------------
Expand All @@ -57,7 +59,8 @@ struct TransformResult {
code: Vec<u8>,
#[serde(with = "serde_bytes")]
map: Option<Vec<u8>>,
exports: Option<CssModuleExports>
exports: Option<CssModuleExports>,
dependencies: Option<Vec<Dependency>>
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -83,7 +86,7 @@ fn transform_style_attribute(ctx: CallContext) -> napi::Result<JsUnknown> {
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)
}
}
Expand All @@ -109,7 +112,8 @@ struct Config {
pub minify: Option<bool>,
pub source_map: Option<bool>,
pub drafts: Option<Drafts>,
pub css_modules: Option<bool>
pub css_modules: Option<bool>,
pub analyze_dependencies: Option<bool>
}

#[derive(Serialize, Debug, Deserialize, Default)]
Expand All @@ -130,7 +134,8 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
let res = stylesheet.to_css(PrinterOptions {
minify: config.minify.unwrap_or(false),
source_map: config.source_map.unwrap_or(false),
targets: config.targets
targets: config.targets,
analyze_dependencies: config.analyze_dependencies.unwrap_or(false)
})?;

let map = if let Some(mut source_map) = res.source_map {
Expand All @@ -154,23 +159,42 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
Ok(TransformResult {
code: res.code.into_bytes(),
map,
exports: res.exports
exports: res.exports,
dependencies: res.dependencies
})
}

#[derive(Serialize, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AttrConfig {
#[serde(with = "serde_bytes")]
pub code: Vec<u8>,
pub targets: Option<Browsers>,
pub minify: Option<bool>
pub minify: Option<bool>,
pub analyze_dependencies: Option<bool>
}

fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result<Vec<u8>, CompileError<'i>> {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct AttrResult {
#[serde(with = "serde_bytes")]
code: Vec<u8>,
dependencies: Option<Vec<Dependency>>
}

fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result<AttrResult, CompileError<'i>> {
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> {
Expand Down
102 changes: 102 additions & 0 deletions src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub media: Option<String>,
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
}
}
}
}
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod prefixes;
pub mod vendor_prefix;
pub mod targets;
pub mod css_modules;
pub mod dependencies;

#[cfg(test)]
mod tests {
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 8 additions & 2 deletions src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<CssModule<'a>>
pub css_module: Option<CssModule<'a>>,
pub dependencies: Option<&'a mut Vec<Dependency>>
}

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<Browsers>
) -> Printer<'a, W> {
Printer {
filename,
dest,
source_map,
indent: 0,
Expand All @@ -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
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/properties/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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() {
Expand Down
Loading