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
22 changes: 18 additions & 4 deletions node/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Targets} from './targets';
import type { Targets } from './targets';

export interface TransformOptions {
/** The filename being transformed. Used for error messages and source maps. */
Expand All @@ -14,7 +14,7 @@ export interface TransformOptions {
/** Whether to enable various draft syntax. */
drafts?: Drafts,
/** Whether to compile this file as a CSS module. */
cssModules?: boolean,
cssModules?: boolean | CSSModulesConfig,
/**
* Whether to analyze dependencies (e.g. `@import` and `url()`).
* When enabled, `@import` rules are removed, and `url()` dependencies
Expand Down Expand Up @@ -59,10 +59,19 @@ export interface TransformResult {
map: Buffer | void,
/** CSS module exports, if enabled. */
exports: CSSModuleExports | void,
/** CSS module references, if `dashedIdents` is enabled. */
references: CSSModuleReferences,
/** `@import` and `url()` dependencies, if enabled. */
dependencies: Dependency[] | void
}

export interface CSSModulesConfig {
/** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */
pattern: string,
/** Whether to rename dashed identifiers, e.g. custom properties. */
dashedIdents: boolean
}

export type CSSModuleExports = {
/** Maps exported (i.e. original) names to local names. */
[name: string]: CSSModuleExport
Expand All @@ -77,6 +86,11 @@ export interface CSSModuleExport {
composes: CSSModuleReference[]
}

export type CSSModuleReferences = {
/** Maps placeholder names to references. */
[name: string]: DependencyCSSModuleReference,
};

export type CSSModuleReference = LocalCSSModuleReference | GlobalCSSModuleReference | DependencyCSSModuleReference;

export interface LocalCSSModuleReference {
Expand Down Expand Up @@ -158,14 +172,14 @@ export interface TransformAttributeOptions {
* that can be replaced with the final urls later (after bundling).
* Dependencies are returned as part of the result.
*/
analyzeDependencies?: boolean
analyzeDependencies?: boolean
}

export interface TransformAttributeResult {
/** The transformed code. */
code: Buffer,
/** `@import` and `url()` dependencies, if enabled. */
dependencies: Dependency[] | void
dependencies: Dependency[] | void
}

/**
Expand Down
20 changes: 16 additions & 4 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;

use parcel_css::bundler::{BundleErrorKind, Bundler, FileProvider, SourceProvider};
use parcel_css::css_modules::CssModuleExports;
use parcel_css::css_modules::{CssModuleExports, CssModuleReferences};
use parcel_css::dependencies::Dependency;
use parcel_css::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind};
use parcel_css::stylesheet::{
Expand Down Expand Up @@ -67,6 +67,7 @@ struct TransformResult {
#[serde(with = "serde_bytes")]
map: Option<Vec<u8>>,
exports: Option<CssModuleExports>,
references: Option<CssModuleReferences>,
dependencies: Option<Vec<Dependency>>,
}

Expand All @@ -88,6 +89,7 @@ impl TransformResult {
},
)?;
obj.set_named_property("exports", ctx.env.to_js_value(&self.exports)?)?;
obj.set_named_property("references", ctx.env.to_js_value(&self.references)?)?;
obj.set_named_property("dependencies", ctx.env.to_js_value(&self.dependencies)?)?;
Ok(obj.into_unknown())
}
Expand Down Expand Up @@ -194,7 +196,9 @@ enum CssModulesOption {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CssModulesConfig {
pattern: String,
pattern: Option<String>,
#[serde(default)]
dashed_idents: bool,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -255,7 +259,10 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
CssModulesOption::Bool(true) => Some(parcel_css::css_modules::Config::default()),
CssModulesOption::Bool(false) => None,
CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config {
pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(),
pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| {
parcel_css::css_modules::Pattern::parse(pattern).unwrap()
}),
dashed_idents: c.dashed_idents,
}),
}
} else {
Expand Down Expand Up @@ -296,6 +303,7 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
code: res.code.into_bytes(),
map,
exports: res.exports,
references: res.references,
dependencies: res.dependencies,
})
}
Expand All @@ -316,7 +324,10 @@ fn compile_bundle<'i>(fs: &'i FileProvider, config: &BundleConfig) -> Result<Tra
CssModulesOption::Bool(true) => Some(parcel_css::css_modules::Config::default()),
CssModulesOption::Bool(false) => None,
CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config {
pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(),
pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| {
parcel_css::css_modules::Pattern::parse(pattern).unwrap()
}),
dashed_idents: c.dashed_idents,
}),
}
} else {
Expand Down Expand Up @@ -351,6 +362,7 @@ fn compile_bundle<'i>(fs: &'i FileProvider, config: &BundleConfig) -> Result<Tra
code: res.code.into_bytes(),
map,
exports: res.exports,
references: res.references,
dependencies: res.dependencies,
})
}
Expand Down
10 changes: 7 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use crate::compat::Feature;
use crate::declaration::DeclarationBlock;
use crate::properties::custom::UnparsedProperty;
Expand Down Expand Up @@ -25,24 +27,26 @@ pub(crate) enum DeclarationContext {
}

#[derive(Debug)]
pub(crate) struct PropertyHandlerContext<'i> {
pub(crate) struct PropertyHandlerContext<'i, 'o> {
pub targets: Option<Browsers>,
pub is_important: bool,
supports: Vec<SupportsEntry<'i>>,
ltr: Vec<Property<'i>>,
rtl: Vec<Property<'i>>,
pub context: DeclarationContext,
pub unused_symbols: &'o HashSet<String>,
}

impl<'i> PropertyHandlerContext<'i> {
pub fn new(targets: Option<Browsers>) -> Self {
impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
pub fn new(targets: Option<Browsers>, unused_symbols: &'o HashSet<String>) -> Self {
PropertyHandlerContext {
targets,
is_important: false,
supports: Vec::new(),
ltr: Vec::new(),
rtl: Vec::new(),
context: DeclarationContext::None,
unused_symbols,
}
}

Expand Down
98 changes: 88 additions & 10 deletions src/css_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! will be updated accordingly. A map of the original names to compiled (hashed) names will be returned.

use crate::error::PrinterErrorKind;
use crate::properties::css_modules::{Composes, ComposesFrom};
use crate::properties::css_modules::{Composes, Specifier};
use crate::selector::Selectors;
use data_encoding::{Encoding, Specification};
use lazy_static::lazy_static;
Expand All @@ -25,8 +25,11 @@ use std::path::Path;
/// Configuration for CSS modules.
#[derive(Default, Clone, Debug)]
pub struct Config<'i> {
/// The class name pattern to use. Default is `[hash]_[local]`.
/// The name pattern to use when renaming class names and other identifiers.
/// Default is `[hash]_[local]`.
pub pattern: Pattern<'i>,
/// Whether to rename dashed identifiers, e.g. custom properties.
pub dashed_idents: bool,
}

/// A CSS modules class name pattern.
Expand Down Expand Up @@ -96,8 +99,14 @@ impl<'i> Pattern<'i> {
Ok(())
}

fn write_to_string(&self, hash: &str, path: &Path, local: &str) -> Result<String, std::fmt::Error> {
let mut res = String::new();
#[inline]
fn write_to_string(
&self,
mut res: String,
hash: &str,
path: &Path,
local: &str,
) -> Result<String, std::fmt::Error> {
self.write(hash, path, local, |s| res.write_str(s))?;
Ok(res)
}
Expand Down Expand Up @@ -158,6 +167,9 @@ pub struct CssModuleExport {
/// A map of exported names to values.
pub type CssModuleExports = HashMap<String, CssModuleExport>;

/// A map of placeholders to references.
pub type CssModuleReferences = HashMap<String, CssModuleReference>;

lazy_static! {
static ref ENCODER: Encoding = {
let mut spec = Specification::new();
Expand All @@ -173,21 +185,44 @@ pub(crate) struct CssModule<'a, 'b, 'c> {
pub path: &'c Path,
pub hash: String,
pub exports: &'a mut CssModuleExports,
pub references: &'a mut HashMap<String, CssModuleReference>,
}

impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
pub fn new(config: &'a Config<'b>, filename: &'c str, exports: &'a mut CssModuleExports) -> Self {
pub fn new(
config: &'a Config<'b>,
filename: &'c str,
exports: &'a mut CssModuleExports,
references: &'a mut HashMap<String, CssModuleReference>,
) -> Self {
Self {
config,
path: Path::new(filename),
hash: hash(filename, matches!(config.pattern.segments[0], Segment::Hash)),
exports,
references,
}
}

pub fn add_local(&mut self, exported: &str, local: &str) {
self.exports.entry(exported.into()).or_insert_with(|| CssModuleExport {
name: self.config.pattern.write_to_string(&self.hash, &self.path, local).unwrap(),
name: self
.config
.pattern
.write_to_string(String::new(), &self.hash, &self.path, local)
.unwrap(),
composes: vec![],
is_referenced: false,
});
}

pub fn add_dashed(&mut self, local: &str) {
self.exports.entry(local.into()).or_insert_with(|| CssModuleExport {
name: self
.config
.pattern
.write_to_string("--".into(), &self.hash, &self.path, &local[2..])
.unwrap(),
composes: vec![],
is_referenced: false,
});
Expand All @@ -200,14 +235,57 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(CssModuleExport {
name: self.config.pattern.write_to_string(&self.hash, &self.path, name).unwrap(),
name: self
.config
.pattern
.write_to_string(String::new(), &self.hash, &self.path, name)
.unwrap(),
composes: vec![],
is_referenced: true,
});
}
}
}

pub fn reference_dashed(&mut self, name: &str, from: &Option<Specifier>) -> Option<String> {
let (reference, key) = match from {
Some(Specifier::Global) => return Some(name[2..].into()),
Some(Specifier::File(file)) => (
CssModuleReference::Dependency {
name: name.to_string(),
specifier: file.to_string(),
},
file.as_ref(),
),
None => {
// Local export. Mark as used.
match self.exports.entry(name.into()) {
std::collections::hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().is_referenced = true;
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(CssModuleExport {
name: self
.config
.pattern
.write_to_string("--".into(), &self.hash, &self.path, name)
.unwrap(),
composes: vec![],
is_referenced: true,
});
}
}
return None;
}
};

let hash = hash(&format!("{}_{}_{}", self.hash, name, key), false);
let name = format!("--{}", hash);

self.references.insert(name.clone(), reference);
Some(hash)
}

pub fn handle_composes(
&mut self,
selectors: &SelectorList<Selectors>,
Expand All @@ -223,13 +301,13 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
name: self
.config
.pattern
.write_to_string(&self.hash, &self.path, name.0.as_ref())
.write_to_string(String::new(), &self.hash, &self.path, name.0.as_ref())
.unwrap(),
},
Some(ComposesFrom::Global) => CssModuleReference::Global {
Some(Specifier::Global) => CssModuleReference::Global {
name: name.0.as_ref().into(),
},
Some(ComposesFrom::File(file)) => CssModuleReference::Dependency {
Some(Specifier::File(file)) => CssModuleReference::Dependency {
name: name.0.to_string(),
specifier: file.to_string(),
},
Expand Down
16 changes: 13 additions & 3 deletions src/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ impl<'i> DeclarationBlock<'i> {
&mut self,
handler: &mut DeclarationHandler<'i>,
important_handler: &mut DeclarationHandler<'i>,
context: &mut PropertyHandlerContext<'i>,
context: &mut PropertyHandlerContext<'i, '_>,
) {
macro_rules! handle {
($decls: expr, $handler: expr, $important: literal) => {
Expand Down Expand Up @@ -494,7 +494,17 @@ impl<'i> DeclarationHandler<'i> {
}
}

pub fn handle_property(&mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i>) -> bool {
pub fn handle_property(
&mut self,
property: &Property<'i>,
context: &mut PropertyHandlerContext<'i, '_>,
) -> bool {
if !context.unused_symbols.is_empty()
&& matches!(property, Property::Custom(custom) if context.unused_symbols.contains(custom.name.as_ref()))
{
return true;
}

self.background.handle_property(property, &mut self.decls, context)
|| self.border.handle_property(property, &mut self.decls, context)
|| self.outline.handle_property(property, &mut self.decls, context)
Expand Down Expand Up @@ -522,7 +532,7 @@ impl<'i> DeclarationHandler<'i> {
|| self.prefix.handle_property(property, &mut self.decls, context)
}

pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i>) {
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
self.background.finalize(&mut self.decls, context);
self.border.finalize(&mut self.decls, context);
self.outline.finalize(&mut self.decls, context);
Expand Down
Loading