Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implementing the base @scope #425

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions c/lightningcss.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct ParseOptions {
const char *filename;
bool nesting;
bool custom_media;
bool scope;
bool css_modules;
const char *css_modules_pattern;
bool css_modules_dashed_idents;
Expand Down
2 changes: 2 additions & 0 deletions c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub struct ParseOptions {
filename: *const c_char,
nesting: bool,
custom_media: bool,
scope: bool,
css_modules: bool,
css_modules_pattern: *const c_char,
css_modules_dashed_idents: bool,
Expand Down Expand Up @@ -263,6 +264,7 @@ pub extern "C" fn lightningcss_stylesheet_parse(
},
nesting: options.nesting,
custom_media: options.custom_media,
scope: options.scope,
css_modules: if options.css_modules {
let pattern = if !options.css_modules_pattern.is_null() {
let pattern =
Expand Down
4 changes: 4 additions & 0 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ struct Drafts {
nesting: bool,
#[serde(default)]
custom_media: bool,
#[serde(default)]
scope: bool,
}

fn compile<'i>(
Expand Down Expand Up @@ -605,6 +607,7 @@ fn compile<'i>(
filename: filename.clone(),
nesting: matches!(drafts, Some(d) if d.nesting),
custom_media: matches!(drafts, Some(d) if d.custom_media),
scope: matches!(drafts, Some(d) if d.scope),
css_modules: if let Some(css_modules) = &config.css_modules {
match css_modules {
CssModulesOption::Bool(true) => Some(lightningcss::css_modules::Config::default()),
Expand Down Expand Up @@ -714,6 +717,7 @@ fn compile_bundle<
let parser_options = ParserOptions {
nesting: matches!(drafts, Some(d) if d.nesting),
custom_media: matches!(drafts, Some(d) if d.custom_media),
scope: matches!(drafts, Some(d) if d.custom_media),
css_modules: if let Some(css_modules) = &config.css_modules {
match css_modules {
CssModulesOption::Bool(true) => Some(lightningcss::css_modules::Config::default()),
Expand Down
1 change: 1 addition & 0 deletions node/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
CssRule::LayerStatement(..) => "layer-statement",
CssRule::Property(..) => "property",
CssRule::Container(..) => "container",
CssRule::Scope(..) => "scope",
CssRule::MozDocument(..) => "moz-document",
CssRule::Nesting(..) => "nesting",
CssRule::Viewport(..) => "viewport",
Expand Down
51 changes: 51 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,33 @@ mod tests {
assert_eq!(res.code, expected);
}

fn at_scope_minify_test(source: &str, expected: &str) {
let mut stylesheet = StyleSheet::parse(
&source,
ParserOptions {
scope: true,
..ParserOptions::default()
},
)
.unwrap();
stylesheet
.minify(MinifyOptions {
targets: Some(Browsers {
chrome: Some(95 << 16),
..Browsers::default()
}),
..MinifyOptions::default()
})
.unwrap();
let res = stylesheet
.to_css(PrinterOptions {
minify: true,
..PrinterOptions::default()
})
.unwrap();
assert_eq!(res.code, expected);
}

fn error_test(source: &str, error: ParserError) {
let res = StyleSheet::parse(&source, ParserOptions::default());
match res {
Expand Down Expand Up @@ -21823,6 +21850,30 @@ mod tests {
);
}

#[test]
fn test_at_scope() {
at_scope_minify_test(
r#"
@scope {
.foo {
display: flex;
}
}
"#,
"@scope{.foo{display:flex}}",
);
at_scope_minify_test(
r#"
@scope {
:scope {
display: flex;
color: lightblue;
}
}"#,
"@scope{:scope{color:#add8e6;display:flex}}",
);
}

#[test]
fn test_custom_media() {
custom_media_test(
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ struct CliArgs {
/// Enable parsing custom media queries
#[clap(long, value_parser)]
custom_media: bool,
/// Enable parsing @scope
#[clap(long, value_parser)]
scope: bool,
/// Enable CSS modules in output.
/// If no filename is provided, <output_file>.json will be used.
/// If no --output-file is specified, code and exports will be printed to stdout as JSON.
Expand Down
15 changes: 15 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::rules::container::{ContainerName, ContainerRule};
use crate::rules::font_palette_values::FontPaletteValuesRule;
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
use crate::rules::property::PropertyRule;
use crate::rules::scope::ScopeRule;
use crate::rules::viewport::ViewportRule;

use crate::rules::{
counter_style::CounterStyleRule,
custom_media::CustomMediaRule,
Expand Down Expand Up @@ -45,6 +47,8 @@ pub struct ParserOptions<'o, 'i> {
pub nesting: bool,
/// Whether to enable the [custom media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) draft syntax.
pub custom_media: bool,
/// Whether to enable the [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) draft syntax.
pub scope: bool,
/// Whether the enable [CSS modules](https://github.com/css-modules/css-modules).
pub css_modules: Option<crate::css_modules::Config<'o>>,
/// The source index to assign to all parsed rules. Impacts the source map when
Expand Down Expand Up @@ -137,6 +141,10 @@ impl<'a, 'o, 'b, 'i, T> TopLevelRuleParser<'a, 'o, 'i, T> {
pub enum AtRulePrelude<'i, T> {
/// A @font-face rule prelude.
FontFace,

/// A @scope rule prelude.
Scope,

/// A @font-feature-values rule prelude, with its FamilyName list.
FontFeatureValues, //(Vec<FamilyName>),
/// A @font-palette-values rule prelude, with its name.
Expand Down Expand Up @@ -423,6 +431,9 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
"font-face" => {
Ok(AtRulePrelude::FontFace)
},
"scope" if self.options.scope => {
Ok(AtRulePrelude::Scope)
},
// "font-feature-values" => {
// if !cfg!(feature = "gecko") {
// // Support for this rule is not fully implemented in Servo yet.
Expand Down Expand Up @@ -563,6 +574,10 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
rules: self.parse_nested_rules(input)?,
loc,
})),
AtRulePrelude::Scope => Ok(CssRule::Scope(ScopeRule {
rules: self.parse_nested_rules(input)?,
loc,
})),
AtRulePrelude::Viewport(vendor_prefix) => {
Ok(CssRule::Viewport(ViewportRule {
vendor_prefix,
Expand Down
10 changes: 10 additions & 0 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub mod namespace;
pub mod nesting;
pub mod page;
pub mod property;
pub mod scope;
pub mod style;
pub mod supports;
pub mod unknown;
Expand Down Expand Up @@ -88,6 +89,7 @@ use media::MediaRule;
use namespace::NamespaceRule;
use nesting::NestingRule;
use page::PageRule;
use scope::ScopeRule;
use std::collections::{HashMap, HashSet};
use style::StyleRule;
use supports::SupportsRule;
Expand Down Expand Up @@ -163,6 +165,8 @@ pub enum CssRule<'i, R = DefaultAtRule> {
Property(PropertyRule<'i>),
/// A `@container` rule.
Container(ContainerRule<'i, R>),
/// A `@scope` rule.
Scope(ScopeRule<'i, R>),
/// A placeholder for a rule that was removed.
Ignored,
/// An unknown at-rule.
Expand Down Expand Up @@ -299,6 +303,10 @@ impl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRul
let rule = ContainerRule::deserialize(deserializer)?;
Ok(CssRule::Container(rule))
}
"scope" => {
let rule = ScopeRule::deserialize(deserializer)?;
Ok(CssRule::Scope(rule))
}
"ignored" => Ok(CssRule::Ignored),
"unknown" => {
let rule = UnknownAtRule::deserialize(deserializer)?;
Expand Down Expand Up @@ -337,6 +345,7 @@ impl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {
CssRule::LayerBlock(layer) => layer.to_css(dest),
CssRule::Property(property) => property.to_css(dest),
CssRule::Container(container) => container.to_css(dest),
CssRule::Scope(scope) => scope.to_css(dest),
CssRule::Unknown(unknown) => unknown.to_css(dest),
CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
kind: PrinterErrorKind::FmtError,
Expand Down Expand Up @@ -641,6 +650,7 @@ impl<'i, T> CssRuleList<'i, T> {
continue;
}
}
CssRule::Scope(scope) => scope.minify(context)?,
CssRule::Nesting(nesting) => {
if nesting.minify(context, parent_is_unused)? {
continue;
Expand Down
54 changes: 54 additions & 0 deletions src/rules/scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! The `@scope` rule.

use super::Location;
use super::{CssRuleList, MinifyContext};
use crate::error::{MinifyError, PrinterError};
use crate::parser::DefaultAtRule;
use crate::printer::Printer;
use crate::traits::ToCss;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;

/// A [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) rule.
///
/// @scope (<scope-start>) [to (<scope-end>)]? {
/// <stylesheet>
/// }
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct ScopeRule<'i, R = DefaultAtRule> {
// TODO: support (<scope-start>) [to (<scope-end>)]?
/// Nested rules within the `@scope` rule.
#[cfg_attr(feature = "serde", serde(borrow))]
pub rules: CssRuleList<'i, R>,
/// The location of the rule in the source file.
#[cfg_attr(feature = "visitor", skip_visit)]
pub loc: Location,
}

impl<'i, T> ScopeRule<'i, T> {
pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) -> Result<(), MinifyError> {
self.rules.minify(context, false)
}
}

impl<'i, T: ToCss> ToCss for ScopeRule<'i, T> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
#[cfg(feature = "sourcemap")]
dest.add_mapping(self.loc);
dest.write_str("@scope")?;
dest.whitespace()?;
dest.write_char('{')?;
dest.indent();
dest.newline()?;
self.rules.to_css(dest)?;
dest.dedent();
dest.newline()?;
dest.write_char('}')
}
}
3 changes: 2 additions & 1 deletion website/pages/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ You can also configure Lightning CSS in the `package.json` in the root of your p
"cssModules": true,
"drafts": {
"nesting": true,
"customMedia": true
"customMedia": true,
"scope": true,
},
"pseudoClasses": {
"focusVisible": "focus-ring"
Expand Down
1 change: 1 addition & 0 deletions website/playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ <h3>Options</h3>
<h3>Draft syntax</h3>
<label><input id="nesting" type="checkbox" checked> Nesting</label>
<label><input id="customMedia" type="checkbox" checked> Custom media queries</label>
<label><input id="scope" type="checkbox" checked> @scope</label>
<h3>Targets</h3>
<div class="targets">
<label><span>Chrome: </span><input id="chrome" type="number" value="95"></label>
Expand Down
8 changes: 7 additions & 1 deletion website/playground/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ function reflectPlaygroundState(playgroundState) {
customMedia.checked = playgroundState.customMedia;
}

if (typeof playgroundState.scope !== 'undefined') {
scope.checked = playgroundState.scope;
}

if (typeof playgroundState.visitorEnabled !== 'undefined') {
visitorEnabled.checked = playgroundState.visitorEnabled;
}
Expand All @@ -134,6 +138,7 @@ function savePlaygroundState() {
minify: minify.checked,
nesting: nesting.checked,
customMedia: customMedia.checked,
scope: scope.checked,
cssModules: cssModules.checked,
analyzeDependencies: analyzeDependencies.checked,
targets: getTargets(),
Expand Down Expand Up @@ -182,7 +187,8 @@ function update() {
targets: Object.keys(targets).length === 0 ? null : targets,
drafts: {
nesting: nesting.checked,
customMedia: customMedia.checked
customMedia: customMedia.checked,
scope: scope.checked,
},
cssModules: cssModules.checked,
analyzeDependencies: analyzeDependencies.checked,
Expand Down