use crate as css;
use css::PrintErr;
use css::Printer;
use css::error::MinifyErr;
pub(super) type ArrayList<T> = ::bun_alloc::core_alloc::AllocVec<T, ::bun_alloc::core_alloc::Global>;
pub mod container;
pub mod counter_style;
pub mod custom_media;
pub mod document;
pub mod font_face;
pub mod font_palette_values;
pub mod import;
pub mod keyframes;
pub mod layer;
pub mod media;
pub mod namespace;
pub mod nesting;
pub mod page;
pub mod property;
pub mod scope;
pub mod starting_style;
pub mod style;
pub mod supports;
pub mod tailwind;
pub mod unknown;
pub mod viewport;
macro_rules! css_rule_variants {
( $( $(#[$doc:meta])* $Variant:ident($Payload:ty) ),+ $(,)? ) => {
pub enum CssRule<R> {
$( $(#[$doc])* $Variant($Payload), )+
Ignored,
Unknown(unknown::UnknownAtRule),
Custom(R),
}
impl<R> CssRule<R> {
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
match self {
$( CssRule::$Variant(x) => x.to_css(dest), )+
CssRule::Unknown(x) => x.to_css(dest),
CssRule::Custom(_x) => Err(dest.add_fmt_error()),
CssRule::Ignored => Ok(()),
}
}
pub fn deep_clone<'bump>(&self, bump: &'bump bun_alloc::Arena) -> Self
where
R: css::generics::DeepClone<'bump>,
{
#[allow(unused_imports)]
use css::generics::DeepClone as _;
match self {
$( CssRule::$Variant(x) => CssRule::$Variant(x.deep_clone(bump)), )+
CssRule::Unknown(x) => CssRule::Unknown(x.deep_clone(bump)),
CssRule::Custom(x) => CssRule::Custom(x.deep_clone(bump)),
CssRule::Ignored => CssRule::Ignored,
}
}
}
};
}
css_rule_variants! {
Media(media::MediaRule<R>),
Import(import::ImportRule),
Style(style::StyleRule<R>),
Keyframes(keyframes::KeyframesRule),
FontFace(font_face::FontFaceRule),
FontPaletteValues(font_palette_values::FontPaletteValuesRule),
Page(page::PageRule),
Supports(supports::SupportsRule<R>),
CounterStyle(counter_style::CounterStyleRule),
Namespace(namespace::NamespaceRule),
MozDocument(document::MozDocumentRule<R>),
Nesting(nesting::NestingRule<R>),
Viewport(viewport::ViewportRule),
CustomMedia(custom_media::CustomMediaRule),
LayerStatement(layer::LayerStatementRule),
LayerBlock(layer::LayerBlockRule<R>),
Property(property::PropertyRule),
Container(container::ContainerRule<R>),
Scope(scope::ScopeRule<R>),
StartingStyle(starting_style::StartingStyleRule<R>),
}
unsafe impl<R: Send> Send for CssRule<R> {}
unsafe impl<R: Sync> Sync for CssRule<R> {}
pub struct CssRuleList<R> {
pub v: Vec<CssRule<R>>,
}
impl<R> Default for CssRuleList<R> {
fn default() -> Self {
Self { v: Vec::new() }
}
}
pub(super) mod dc {
use bun_alloc::Arena;
#[inline]
pub(crate) fn decl_block<'bump>(
this: &crate::DeclarationBlock<'bump>,
bump: &'bump Arena,
) -> crate::DeclarationBlock<'bump> {
crate::DeclarationBlock {
important_declarations: bun_alloc::vec_from_iter_in(
this.important_declarations
.iter()
.map(|p| property(p, bump)),
bump,
),
declarations: bun_alloc::vec_from_iter_in(
this.declarations.iter().map(|p| property(p, bump)),
bump,
),
}
}
#[inline(always)]
unsafe fn arena_static(bump: &Arena) -> &'static Arena {
unsafe { &*core::ptr::from_ref(bump) }
}
#[inline]
pub(crate) fn decl_block_static(
this: &crate::DeclarationBlock<'static>,
bump: &Arena,
) -> crate::DeclarationBlock<'static> {
decl_block(this, unsafe { arena_static(bump) })
}
#[inline]
pub(crate) fn decl_block_empty_static(bump: &Arena) -> crate::DeclarationBlock<'static> {
crate::DeclarationBlock::new_in(unsafe { arena_static(bump) })
}
#[inline]
pub(crate) fn decl_handler_static<'a>(
h: &'a mut crate::DeclarationHandler<'_>,
) -> &'a mut crate::DeclarationHandler<'static> {
unsafe { &mut *core::ptr::from_mut(h).cast::<crate::DeclarationHandler<'static>>() }
}
#[inline]
pub(crate) fn media_list(
this: &crate::media_query::MediaList,
bump: &Arena,
) -> crate::media_query::MediaList {
this.deep_clone(bump)
}
#[inline]
pub(crate) fn selector_list(
this: &crate::selectors::SelectorList,
_bump: &Arena,
) -> crate::selectors::SelectorList {
this.deep_clone()
}
#[inline]
pub(crate) fn query_feature<F>(
this: &crate::media_query::QueryFeature<F>,
bump: &Arena,
) -> crate::media_query::QueryFeature<F>
where
F: crate::media_query::FeatureIdTrait,
{
this.deep_clone(bump)
}
#[inline]
pub(crate) fn property(
this: &crate::properties::Property,
bump: &Arena,
) -> crate::properties::Property {
this.deep_clone(bump)
}
}
pub(super) fn decl_block_to_css(
decls: &css::DeclarationBlock<'_>,
dest: &mut Printer,
) -> Result<(), PrintErr> {
dest.whitespace()?;
dest.write_char(b'{')?;
dest.indent();
let length = decls.len();
let mut i: usize = 0;
for decl in decls.declarations.iter() {
dest.newline()?;
decl.to_css(dest, false)?;
if i != length - 1 || !dest.minify {
dest.write_char(b';')?;
}
i += 1;
}
for decl in decls.important_declarations.iter() {
dest.newline()?;
decl.to_css(dest, true)?;
if i != length - 1 || !dest.minify {
dest.write_char(b';')?;
}
i += 1;
}
dest.dedent();
dest.newline()?;
dest.write_char(b'}')
}
#[inline]
pub(super) fn vendor_prefix_to_css(
prefix: css::VendorPrefix,
dest: &mut Printer,
) -> Result<(), PrintErr> {
use css::VendorPrefix as VP;
match prefix.bits() {
b if b == VP::WEBKIT.bits() => dest.write_str("-webkit-"),
b if b == VP::MOZ.bits() => dest.write_str("-moz-"),
b if b == VP::MS.bits() => dest.write_str("-ms-"),
b if b == VP::O.bits() => dest.write_str("-o-"),
_ => Ok(()),
}
}
#[inline]
pub(super) fn custom_ident_to_css(
ident: &css::css_values::ident::CustomIdent,
dest: &mut Printer,
) -> Result<(), PrintErr> {
let v = unsafe { crate::arena_str(ident.v) };
let enabled = dest
.css_module
.as_ref()
.is_some_and(|m| m.config.custom_idents);
dest.write_ident(v, enabled)
}
#[inline]
pub(super) fn dashed_ident_to_css(
ident: &css::css_values::ident::DashedIdent,
dest: &mut Printer,
) -> Result<(), PrintErr> {
let v = ident.v();
dest.write_str("--")?;
dest.serialize_name(&v[2..])
}
impl<R> media::MediaRule<R> {
pub fn minify(
&mut self,
context: &mut MinifyContext<'_, '_>,
parent_is_unused: bool,
) -> Result<bool, MinifyErr>
where
R: for<'b> css::generics::DeepClone<'b>,
{
self.rules.minify(context, parent_is_unused)?;
Ok(self.rules.v.is_empty() || self.query.never_matches())
}
}
impl<R> CssRule<R> {
pub(crate) fn is_deferred_to_final_prefix_pass(&self) -> bool {
match self {
CssRule::Style(style) => !style.vendor_prefix.is_empty(),
CssRule::Nesting(nesting) => !nesting.style.vendor_prefix.is_empty(),
_ => false,
}
}
}
impl<R> CssRuleList<R> {
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
let mut first = true;
let mut last_without_block = false;
for rule in self.v.iter() {
if matches!(rule, CssRule::Ignored) {
continue;
}
if dest.skip_prefixed_nested_rules && rule.is_deferred_to_final_prefix_pass() {
continue;
}
if let CssRule::Import(import_rule) = rule
&& dest.remove_imports
{
let dep = if dest.dependencies.is_some() {
Some(css::dependencies::Dependency::Import(
css::dependencies::ImportDependency::new(
dest.arena,
import_rule,
dest.filename(),
dest.local_names,
dest.symbols,
),
))
} else {
None
};
if let Some(deps) = dest.dependencies.as_mut() {
if let Some(d) = dep {
deps.push(d);
}
continue;
}
}
if first {
first = false;
} else {
if !dest.minify
&& !(last_without_block
&& matches!(
rule,
CssRule::Import(_) | CssRule::Namespace(_) | CssRule::LayerStatement(_)
))
{
dest.write_char(b'\n')?;
}
dest.newline()?;
}
rule.to_css(dest)?;
last_without_block = matches!(
rule,
CssRule::Import(_) | CssRule::Namespace(_) | CssRule::LayerStatement(_)
);
}
Ok(())
}
pub fn minify(
&mut self,
context: &mut MinifyContext<'_, '_>,
parent_is_unused: bool,
) -> Result<(), MinifyErr>
where
R: for<'b> css::generics::DeepClone<'b>,
{
let mut style_rules = StyleRuleKeyMap::default();
let mut rules: Vec<CssRule<R>> = Vec::new();
for rule in self.v.iter_mut() {
let mut moved_rule = false;
'arm: {
match rule {
CssRule::Keyframes(_keyframez) => {
}
CssRule::CustomMedia(_) => {
if context.custom_media.is_some() {
break 'arm;
}
}
CssRule::Media(med) => {
if let Some(CssRule::Media(last_rule)) = rules.last_mut()
&& last_rule.query.eql(&med.query)
{
last_rule.rules.v.append(&mut med.rules.v);
let _ = last_rule.minify(context, parent_is_unused)?;
break 'arm;
}
if med.minify(context, parent_is_unused)? {
break 'arm;
}
}
CssRule::Supports(supp) => {
if let Some(CssRule::Supports(last_rule)) = rules.last_mut()
&& last_rule.condition.eql(&supp.condition)
{
break 'arm;
}
supp.minify(context, parent_is_unused)?;
if supp.rules.v.is_empty() {
break 'arm;
}
}
CssRule::Container(cont) => {
cont.rules.minify(context, parent_is_unused)?;
}
CssRule::LayerBlock(lay) => {
lay.rules.minify(context, parent_is_unused)?;
if lay.rules.v.is_empty() {
break 'arm;
}
}
CssRule::LayerStatement(_lay) => {
}
CssRule::MozDocument(doc) => {
doc.rules.minify(context, parent_is_unused)?;
}
CssRule::Style(_sty) => {
{
minify_style_arm(
rule,
&mut rules,
&mut style_rules,
context,
parent_is_unused,
)?;
break 'arm;
}
}
CssRule::CounterStyle(_) => { }
CssRule::Scope(scpe) => {
scpe.rules.minify(context, parent_is_unused)?;
}
CssRule::Nesting(nst) => {
nst.style.charge_selector_expansion(context)?;
nst.style.minify_nested_rules(context, parent_is_unused)?;
}
CssRule::StartingStyle(rl) => {
rl.rules.minify(context, parent_is_unused)?;
}
CssRule::FontPaletteValues(_) => { }
CssRule::Property(_) => { }
_ => {}
}
rules.push(core::mem::replace(rule, CssRule::Ignored));
moved_rule = true;
style_rules.clear();
}
if moved_rule {
*rule = CssRule::Ignored;
}
}
self.v = rules;
Ok(())
}
pub fn deep_clone<'bump>(&self, bump: &'bump bun_alloc::Arena) -> Self
where
R: css::generics::DeepClone<'bump>,
{
Self {
v: self.v.iter().map(|r| r.deep_clone(bump)).collect(),
}
}
}
fn minify_style_arm<R: for<'b> css::generics::DeepClone<'b>>(
rule: &mut CssRule<R>,
rules: &mut Vec<CssRule<R>>,
style_rules: &mut StyleRuleKeyMap,
context: &mut MinifyContext<'_, '_>,
parent_is_unused: bool,
) -> Result<(), MinifyErr> {
use css::SmallList;
use css::selector::{self, Component, Selector, SelectorList};
let CssRule::Style(sty) = rule else {
unreachable!()
};
if parent_is_unused || sty.minify(context, parent_is_unused)? {
return Ok(());
}
let mut incompatible: SmallList<Selector, 1> = if sty.selectors.v.len() > 1
&& context.targets.should_compile_selectors()
&& !sty.is_compatible(context.targets)
{
if context
.targets
.is_compatible(css::compat::Feature::IsSelector)
&& !sty.selectors.any_has_pseudo_element()
&& sty.selectors.specifities_all_equal()
{
let component = Component::Is(core::mem::take(&mut sty.selectors.v).to_owned_slice());
let mut list = SmallList::<Selector, 1>::default();
list.append(Selector::from_component(component));
sty.selectors = SelectorList { v: list };
SmallList::default()
} else {
let mut incompatible = SmallList::<Selector, 1>::default();
let mut i: u32 = 0;
while i < sty.selectors.v.len() {
if selector::is_compatible(
&sty.selectors.v.slice()[i as usize..i as usize + 1],
context.targets,
) {
i += 1;
} else {
incompatible.append(sty.selectors.v.ordered_remove(i));
}
}
incompatible
}
} else {
SmallList::default()
};
sty.update_prefix(context);
let mut merged = false;
if let Some(CssRule::Style(last_style_rule)) = rules.last_mut()
&& merge_style_rules(sty, last_style_rule, context)
{
while rules.len() >= 2 {
let len = rules.len();
let (a, b) = rules.split_at_mut(len - 1);
if let (CssRule::Style(prev), CssRule::Style(last)) = (&mut a[len - 2], &mut b[0])
&& merge_style_rules(last, prev, context)
{
rules.pop();
continue;
}
break;
}
merged = true;
}
let supps = context.handler_context.get_supports_rules::<R>(sty);
let logical = context.handler_context.get_additional_rules::<R>(sty);
struct IncompatibleRuleEntry<R> {
rule: style::StyleRule<R>,
supports: Vec<CssRule<R>>,
logical: Vec<CssRule<R>>,
}
let mut incompatible_rules: SmallList<IncompatibleRuleEntry<R>, 1> =
SmallList::init_capacity(incompatible.len());
while incompatible.len() > 0 {
let sel = incompatible.ordered_remove(0);
let list = SelectorList {
v: SmallList::with_one(sel),
};
let mut clone = style::StyleRule::<R> {
selectors: list,
vendor_prefix: sty.vendor_prefix,
declarations: dc::decl_block_static(&sty.declarations, context.arena),
rules: sty.rules.deep_clone(context.arena),
loc: sty.loc,
};
clone.update_prefix(context);
let s = context.handler_context.get_supports_rules::<R>(&clone);
let l = context.handler_context.get_additional_rules::<R>(&clone);
incompatible_rules.append(IncompatibleRuleEntry {
rule: clone,
supports: s,
logical: l,
});
}
context.handler_context.reset();
let nested_rule: Option<style::StyleRule<R>> = if !sty.rules.v.is_empty()
&& sty.selectors.v.len() > 0
&& (!logical.is_empty() || !supps.is_empty() || !incompatible_rules.is_empty())
{
let mut rulesss = CssRuleList::<R>::default();
core::mem::swap(&mut sty.rules, &mut rulesss);
Some(style::StyleRule {
selectors: sty.selectors.deep_clone(),
declarations: dc::decl_block_empty_static(context.arena),
rules: rulesss,
vendor_prefix: sty.vendor_prefix,
loc: sty.loc,
})
} else {
None
};
if !merged && !sty.is_empty() {
let source_index = sty.loc.source_index;
let has_no_rules = sty.rules.v.is_empty();
let idx = rules.len();
rules.push(core::mem::replace(rule, CssRule::Ignored));
if has_no_rules {
let key = StyleRuleKey::new(rules, idx);
if idx > 0
&& let Some(i) = style_rules.remove_duplicate(rules, &key)
&& i < rules.len()
&& let CssRule::Style(other) = &rules[i]
&& (!context.css_modules || source_index == other.loc.source_index)
{
rules[i] = CssRule::Ignored;
}
style_rules.insert(key);
}
}
if !logical.is_empty() {
let mut log = CssRuleList { v: logical };
log.minify(context, parent_is_unused)?;
rules.append(&mut log.v);
}
rules.extend(supps);
while incompatible_rules.len() > 0 {
let entry = incompatible_rules.ordered_remove(0);
if !entry.rule.is_empty() {
rules.push(CssRule::Style(entry.rule));
}
if !entry.logical.is_empty() {
let mut log = CssRuleList { v: entry.logical };
log.minify(context, parent_is_unused)?;
rules.append(&mut log.v);
}
rules.extend(entry.supports);
}
if let Some(nested) = nested_rule {
rules.push(CssRule::Style(nested));
}
Ok(())
}
#[derive(Clone, Copy)]
pub(crate) struct StyleRuleKey {
index: usize,
hash: u64,
}
impl StyleRuleKey {
pub(crate) fn new<R>(list: &[CssRule<R>], index: usize) -> Self {
let hash = match &list[index] {
CssRule::Style(rule) => rule.hash_key(),
_ => 0,
};
Self { index, hash }
}
}
#[derive(Default)]
pub(crate) struct StyleRuleKeyMap {
buckets: bun_collections::HashMap<u64, Vec<usize>>,
}
impl StyleRuleKeyMap {
pub(crate) fn remove_duplicate<R>(
&mut self,
rules: &[CssRule<R>],
key: &StyleRuleKey,
) -> Option<usize> {
let bucket = self.buckets.get_mut(&key.hash)?;
let CssRule::Style(rule) = &rules[key.index] else {
return None;
};
let pos = bucket.iter().position(|&other_idx| {
match rules.get(other_idx) {
Some(CssRule::Style(other_rule)) => rule.is_duplicate(other_rule),
_ => false,
}
})?;
Some(bucket.swap_remove(pos))
}
pub(crate) fn insert(&mut self, key: StyleRuleKey) {
self.buckets.entry(key.hash).or_default().push(key.index);
}
pub(crate) fn clear(&mut self) {
self.buckets.clear();
}
}
pub(crate) fn merge_style_rules<R>(
sty: &mut style::StyleRule<R>,
last_style_rule: &mut style::StyleRule<R>,
context: &mut MinifyContext<'_, '_>,
) -> bool {
use css::VendorPrefix;
if sty.selectors.eql(&last_style_rule.selectors)
&& sty.is_compatible(context.targets)
&& last_style_rule.is_compatible(context.targets)
&& sty.rules.v.is_empty()
&& last_style_rule.rules.v.is_empty()
&& (!context.css_modules || sty.loc.source_index == last_style_rule.loc.source_index)
{
last_style_rule
.declarations
.declarations
.extend(sty.declarations.declarations.drain(..));
last_style_rule
.declarations
.important_declarations
.extend(sty.declarations.important_declarations.drain(..));
last_style_rule.declarations.minify(
dc::decl_handler_static(&mut *context.handler),
dc::decl_handler_static(&mut *context.important_handler),
&mut context.handler_context,
);
return true;
} else if sty.declarations.eql(&last_style_rule.declarations)
&& sty.rules.v.is_empty()
&& last_style_rule.rules.v.is_empty()
{
if !sty.vendor_prefix.is_empty()
&& !last_style_rule.vendor_prefix.is_empty()
&& css::selector::is_equivalent(
sty.selectors.v.slice(),
last_style_rule.selectors.v.slice(),
)
{
if sty.vendor_prefix.contains(VendorPrefix::NONE)
&& context.targets.should_compile_selectors()
{
last_style_rule.vendor_prefix = sty.vendor_prefix;
} else {
last_style_rule.vendor_prefix.insert(sty.vendor_prefix);
}
return true;
}
if sty.is_compatible(context.targets) && last_style_rule.is_compatible(context.targets) {
let moved = core::mem::take(&mut sty.selectors.v);
last_style_rule.selectors.v.reserve(moved.len());
for sel in moved {
last_style_rule.selectors.v.append_assume_capacity(sel);
}
if sty.vendor_prefix.contains(VendorPrefix::NONE)
&& context.targets.should_compile_selectors()
{
last_style_rule.vendor_prefix = sty.vendor_prefix;
} else {
last_style_rule.vendor_prefix.insert(sty.vendor_prefix);
}
return true;
}
}
false
}
pub use crate::Location;
pub struct StyleContext<'a> {
pub selectors: &'a crate::selectors::SelectorList,
pub parent: Option<&'a StyleContext<'a>>,
}
pub const MAX_SELECTOR_EXPANSION: u32 = 65_536;
pub struct MinifyContext<'a, 'bump> {
pub arena: &'bump bun_alloc::Arena,
pub targets: &'a css::targets::Targets,
pub handler: &'a mut css::DeclarationHandler<'bump>,
pub important_handler: &'a mut css::DeclarationHandler<'bump>,
pub handler_context: css::PropertyHandlerContext<'bump>,
pub unused_symbols: &'a bun_collections::ArrayHashMap<Box<[u8]>, ()>,
pub custom_media:
Option<bun_collections::ArrayHashMap<Box<[u8]>, custom_media::CustomMediaRule>>,
pub extra: &'a css::StylesheetExtra,
pub css_modules: bool,
pub err: Option<css::error::MinifyError>,
pub selector_expansion_multiplier: u32,
pub selector_expansion_total: u32,
}