Skip to content

Commit 87eeca7

Browse files
authored
Support for hashing CSS variables (parcel-bundler#183)
1 parent a1f8478 commit 87eeca7

38 files changed

+627
-124
lines changed

node/index.d.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Targets} from './targets';
1+
import type { Targets } from './targets';
22

33
export interface TransformOptions {
44
/** The filename being transformed. Used for error messages and source maps. */
@@ -14,7 +14,7 @@ export interface TransformOptions {
1414
/** Whether to enable various draft syntax. */
1515
drafts?: Drafts,
1616
/** Whether to compile this file as a CSS module. */
17-
cssModules?: boolean,
17+
cssModules?: boolean | CSSModulesConfig,
1818
/**
1919
* Whether to analyze dependencies (e.g. `@import` and `url()`).
2020
* When enabled, `@import` rules are removed, and `url()` dependencies
@@ -59,10 +59,19 @@ export interface TransformResult {
5959
map: Buffer | void,
6060
/** CSS module exports, if enabled. */
6161
exports: CSSModuleExports | void,
62+
/** CSS module references, if `dashedIdents` is enabled. */
63+
references: CSSModuleReferences,
6264
/** `@import` and `url()` dependencies, if enabled. */
6365
dependencies: Dependency[] | void
6466
}
6567

68+
export interface CSSModulesConfig {
69+
/** The pattern to use when renaming class names and other identifiers. Default is `[hash]_[local]`. */
70+
pattern: string,
71+
/** Whether to rename dashed identifiers, e.g. custom properties. */
72+
dashedIdents: boolean
73+
}
74+
6675
export type CSSModuleExports = {
6776
/** Maps exported (i.e. original) names to local names. */
6877
[name: string]: CSSModuleExport
@@ -77,6 +86,11 @@ export interface CSSModuleExport {
7786
composes: CSSModuleReference[]
7887
}
7988

89+
export type CSSModuleReferences = {
90+
/** Maps placeholder names to references. */
91+
[name: string]: DependencyCSSModuleReference,
92+
};
93+
8094
export type CSSModuleReference = LocalCSSModuleReference | GlobalCSSModuleReference | DependencyCSSModuleReference;
8195

8296
export interface LocalCSSModuleReference {
@@ -158,14 +172,14 @@ export interface TransformAttributeOptions {
158172
* that can be replaced with the final urls later (after bundling).
159173
* Dependencies are returned as part of the result.
160174
*/
161-
analyzeDependencies?: boolean
175+
analyzeDependencies?: boolean
162176
}
163177

164178
export interface TransformAttributeResult {
165179
/** The transformed code. */
166180
code: Buffer,
167181
/** `@import` and `url()` dependencies, if enabled. */
168-
dependencies: Dependency[] | void
182+
dependencies: Dependency[] | void
169183
}
170184

171185
/**

node/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
44

55
use parcel_css::bundler::{BundleErrorKind, Bundler, FileProvider, SourceProvider};
6-
use parcel_css::css_modules::CssModuleExports;
6+
use parcel_css::css_modules::{CssModuleExports, CssModuleReferences};
77
use parcel_css::dependencies::Dependency;
88
use parcel_css::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind};
99
use parcel_css::stylesheet::{
@@ -67,6 +67,7 @@ struct TransformResult {
6767
#[serde(with = "serde_bytes")]
6868
map: Option<Vec<u8>>,
6969
exports: Option<CssModuleExports>,
70+
references: Option<CssModuleReferences>,
7071
dependencies: Option<Vec<Dependency>>,
7172
}
7273

@@ -88,6 +89,7 @@ impl TransformResult {
8889
},
8990
)?;
9091
obj.set_named_property("exports", ctx.env.to_js_value(&self.exports)?)?;
92+
obj.set_named_property("references", ctx.env.to_js_value(&self.references)?)?;
9193
obj.set_named_property("dependencies", ctx.env.to_js_value(&self.dependencies)?)?;
9294
Ok(obj.into_unknown())
9395
}
@@ -194,7 +196,9 @@ enum CssModulesOption {
194196
#[derive(Debug, Deserialize)]
195197
#[serde(rename_all = "camelCase")]
196198
struct CssModulesConfig {
197-
pattern: String,
199+
pattern: Option<String>,
200+
#[serde(default)]
201+
dashed_idents: bool,
198202
}
199203

200204
#[derive(Debug, Deserialize)]
@@ -255,7 +259,10 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
255259
CssModulesOption::Bool(true) => Some(parcel_css::css_modules::Config::default()),
256260
CssModulesOption::Bool(false) => None,
257261
CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config {
258-
pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(),
262+
pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| {
263+
parcel_css::css_modules::Pattern::parse(pattern).unwrap()
264+
}),
265+
dashed_idents: c.dashed_idents,
259266
}),
260267
}
261268
} else {
@@ -296,6 +303,7 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
296303
code: res.code.into_bytes(),
297304
map,
298305
exports: res.exports,
306+
references: res.references,
299307
dependencies: res.dependencies,
300308
})
301309
}
@@ -316,7 +324,10 @@ fn compile_bundle<'i>(fs: &'i FileProvider, config: &BundleConfig) -> Result<Tra
316324
CssModulesOption::Bool(true) => Some(parcel_css::css_modules::Config::default()),
317325
CssModulesOption::Bool(false) => None,
318326
CssModulesOption::Config(c) => Some(parcel_css::css_modules::Config {
319-
pattern: parcel_css::css_modules::Pattern::parse(&c.pattern).unwrap(),
327+
pattern: c.pattern.as_ref().map_or(Default::default(), |pattern| {
328+
parcel_css::css_modules::Pattern::parse(pattern).unwrap()
329+
}),
330+
dashed_idents: c.dashed_idents,
320331
}),
321332
}
322333
} else {
@@ -351,6 +362,7 @@ fn compile_bundle<'i>(fs: &'i FileProvider, config: &BundleConfig) -> Result<Tra
351362
code: res.code.into_bytes(),
352363
map,
353364
exports: res.exports,
365+
references: res.references,
354366
dependencies: res.dependencies,
355367
})
356368
}

src/context.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashSet;
2+
13
use crate::compat::Feature;
24
use crate::declaration::DeclarationBlock;
35
use crate::properties::custom::UnparsedProperty;
@@ -25,24 +27,26 @@ pub(crate) enum DeclarationContext {
2527
}
2628

2729
#[derive(Debug)]
28-
pub(crate) struct PropertyHandlerContext<'i> {
30+
pub(crate) struct PropertyHandlerContext<'i, 'o> {
2931
pub targets: Option<Browsers>,
3032
pub is_important: bool,
3133
supports: Vec<SupportsEntry<'i>>,
3234
ltr: Vec<Property<'i>>,
3335
rtl: Vec<Property<'i>>,
3436
pub context: DeclarationContext,
37+
pub unused_symbols: &'o HashSet<String>,
3538
}
3639

37-
impl<'i> PropertyHandlerContext<'i> {
38-
pub fn new(targets: Option<Browsers>) -> Self {
40+
impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
41+
pub fn new(targets: Option<Browsers>, unused_symbols: &'o HashSet<String>) -> Self {
3942
PropertyHandlerContext {
4043
targets,
4144
is_important: false,
4245
supports: Vec::new(),
4346
ltr: Vec::new(),
4447
rtl: Vec::new(),
4548
context: DeclarationContext::None,
49+
unused_symbols,
4650
}
4751
}
4852

src/css_modules.rs

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! will be updated accordingly. A map of the original names to compiled (hashed) names will be returned.
1010
1111
use crate::error::PrinterErrorKind;
12-
use crate::properties::css_modules::{Composes, ComposesFrom};
12+
use crate::properties::css_modules::{Composes, Specifier};
1313
use crate::selector::Selectors;
1414
use data_encoding::{Encoding, Specification};
1515
use lazy_static::lazy_static;
@@ -25,8 +25,11 @@ use std::path::Path;
2525
/// Configuration for CSS modules.
2626
#[derive(Default, Clone, Debug)]
2727
pub struct Config<'i> {
28-
/// The class name pattern to use. Default is `[hash]_[local]`.
28+
/// The name pattern to use when renaming class names and other identifiers.
29+
/// Default is `[hash]_[local]`.
2930
pub pattern: Pattern<'i>,
31+
/// Whether to rename dashed identifiers, e.g. custom properties.
32+
pub dashed_idents: bool,
3033
}
3134

3235
/// A CSS modules class name pattern.
@@ -96,8 +99,14 @@ impl<'i> Pattern<'i> {
9699
Ok(())
97100
}
98101

99-
fn write_to_string(&self, hash: &str, path: &Path, local: &str) -> Result<String, std::fmt::Error> {
100-
let mut res = String::new();
102+
#[inline]
103+
fn write_to_string(
104+
&self,
105+
mut res: String,
106+
hash: &str,
107+
path: &Path,
108+
local: &str,
109+
) -> Result<String, std::fmt::Error> {
101110
self.write(hash, path, local, |s| res.write_str(s))?;
102111
Ok(res)
103112
}
@@ -158,6 +167,9 @@ pub struct CssModuleExport {
158167
/// A map of exported names to values.
159168
pub type CssModuleExports = HashMap<String, CssModuleExport>;
160169

170+
/// A map of placeholders to references.
171+
pub type CssModuleReferences = HashMap<String, CssModuleReference>;
172+
161173
lazy_static! {
162174
static ref ENCODER: Encoding = {
163175
let mut spec = Specification::new();
@@ -173,21 +185,44 @@ pub(crate) struct CssModule<'a, 'b, 'c> {
173185
pub path: &'c Path,
174186
pub hash: String,
175187
pub exports: &'a mut CssModuleExports,
188+
pub references: &'a mut HashMap<String, CssModuleReference>,
176189
}
177190

178191
impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
179-
pub fn new(config: &'a Config<'b>, filename: &'c str, exports: &'a mut CssModuleExports) -> Self {
192+
pub fn new(
193+
config: &'a Config<'b>,
194+
filename: &'c str,
195+
exports: &'a mut CssModuleExports,
196+
references: &'a mut HashMap<String, CssModuleReference>,
197+
) -> Self {
180198
Self {
181199
config,
182200
path: Path::new(filename),
183201
hash: hash(filename, matches!(config.pattern.segments[0], Segment::Hash)),
184202
exports,
203+
references,
185204
}
186205
}
187206

188207
pub fn add_local(&mut self, exported: &str, local: &str) {
189208
self.exports.entry(exported.into()).or_insert_with(|| CssModuleExport {
190-
name: self.config.pattern.write_to_string(&self.hash, &self.path, local).unwrap(),
209+
name: self
210+
.config
211+
.pattern
212+
.write_to_string(String::new(), &self.hash, &self.path, local)
213+
.unwrap(),
214+
composes: vec![],
215+
is_referenced: false,
216+
});
217+
}
218+
219+
pub fn add_dashed(&mut self, local: &str) {
220+
self.exports.entry(local.into()).or_insert_with(|| CssModuleExport {
221+
name: self
222+
.config
223+
.pattern
224+
.write_to_string("--".into(), &self.hash, &self.path, &local[2..])
225+
.unwrap(),
191226
composes: vec![],
192227
is_referenced: false,
193228
});
@@ -200,14 +235,57 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
200235
}
201236
std::collections::hash_map::Entry::Vacant(entry) => {
202237
entry.insert(CssModuleExport {
203-
name: self.config.pattern.write_to_string(&self.hash, &self.path, name).unwrap(),
238+
name: self
239+
.config
240+
.pattern
241+
.write_to_string(String::new(), &self.hash, &self.path, name)
242+
.unwrap(),
204243
composes: vec![],
205244
is_referenced: true,
206245
});
207246
}
208247
}
209248
}
210249

250+
pub fn reference_dashed(&mut self, name: &str, from: &Option<Specifier>) -> Option<String> {
251+
let (reference, key) = match from {
252+
Some(Specifier::Global) => return Some(name[2..].into()),
253+
Some(Specifier::File(file)) => (
254+
CssModuleReference::Dependency {
255+
name: name.to_string(),
256+
specifier: file.to_string(),
257+
},
258+
file.as_ref(),
259+
),
260+
None => {
261+
// Local export. Mark as used.
262+
match self.exports.entry(name.into()) {
263+
std::collections::hash_map::Entry::Occupied(mut entry) => {
264+
entry.get_mut().is_referenced = true;
265+
}
266+
std::collections::hash_map::Entry::Vacant(entry) => {
267+
entry.insert(CssModuleExport {
268+
name: self
269+
.config
270+
.pattern
271+
.write_to_string("--".into(), &self.hash, &self.path, name)
272+
.unwrap(),
273+
composes: vec![],
274+
is_referenced: true,
275+
});
276+
}
277+
}
278+
return None;
279+
}
280+
};
281+
282+
let hash = hash(&format!("{}_{}_{}", self.hash, name, key), false);
283+
let name = format!("--{}", hash);
284+
285+
self.references.insert(name.clone(), reference);
286+
Some(hash)
287+
}
288+
211289
pub fn handle_composes(
212290
&mut self,
213291
selectors: &SelectorList<Selectors>,
@@ -223,13 +301,13 @@ impl<'a, 'b, 'c> CssModule<'a, 'b, 'c> {
223301
name: self
224302
.config
225303
.pattern
226-
.write_to_string(&self.hash, &self.path, name.0.as_ref())
304+
.write_to_string(String::new(), &self.hash, &self.path, name.0.as_ref())
227305
.unwrap(),
228306
},
229-
Some(ComposesFrom::Global) => CssModuleReference::Global {
307+
Some(Specifier::Global) => CssModuleReference::Global {
230308
name: name.0.as_ref().into(),
231309
},
232-
Some(ComposesFrom::File(file)) => CssModuleReference::Dependency {
310+
Some(Specifier::File(file)) => CssModuleReference::Dependency {
233311
name: name.0.to_string(),
234312
specifier: file.to_string(),
235313
},

src/declaration.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl<'i> DeclarationBlock<'i> {
157157
&mut self,
158158
handler: &mut DeclarationHandler<'i>,
159159
important_handler: &mut DeclarationHandler<'i>,
160-
context: &mut PropertyHandlerContext<'i>,
160+
context: &mut PropertyHandlerContext<'i, '_>,
161161
) {
162162
macro_rules! handle {
163163
($decls: expr, $handler: expr, $important: literal) => {
@@ -494,7 +494,17 @@ impl<'i> DeclarationHandler<'i> {
494494
}
495495
}
496496

497-
pub fn handle_property(&mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i>) -> bool {
497+
pub fn handle_property(
498+
&mut self,
499+
property: &Property<'i>,
500+
context: &mut PropertyHandlerContext<'i, '_>,
501+
) -> bool {
502+
if !context.unused_symbols.is_empty()
503+
&& matches!(property, Property::Custom(custom) if context.unused_symbols.contains(custom.name.as_ref()))
504+
{
505+
return true;
506+
}
507+
498508
self.background.handle_property(property, &mut self.decls, context)
499509
|| self.border.handle_property(property, &mut self.decls, context)
500510
|| self.outline.handle_property(property, &mut self.decls, context)
@@ -522,7 +532,7 @@ impl<'i> DeclarationHandler<'i> {
522532
|| self.prefix.handle_property(property, &mut self.decls, context)
523533
}
524534

525-
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i>) {
535+
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
526536
self.background.finalize(&mut self.decls, context);
527537
self.border.finalize(&mut self.decls, context);
528538
self.outline.finalize(&mut self.decls, context);

0 commit comments

Comments
 (0)