improve compressed output for selectors and colors

This commit is contained in:
connorskees 2023-01-18 05:55:55 +00:00
parent 3b7f4dd039
commit d0d9459d8e
8 changed files with 312 additions and 13 deletions

View File

@ -552,8 +552,9 @@ pub(crate) struct StyleSheet {
pub body: Vec<AstStmt>,
pub url: PathBuf,
pub is_plain_css: bool,
pub uses: Vec<AstUseRule>,
pub forwards: Vec<AstForwardRule>,
/// Array of indices into body
pub uses: Vec<usize>,
pub forwards: Vec<usize>,
}
impl StyleSheet {

View File

@ -1,8 +1,8 @@
use codemap::{Span, Spanned};
use crate::{
ast::{AstForwardRule, Mixin},
builtin::modules::{ForwardedModule, Module, Modules},
ast::{AstForwardRule, Configuration, Mixin},
builtin::modules::{ForwardedModule, Module, ModuleScope, Modules},
common::Identifier,
error::SassResult,
selector::ExtensionStore,
@ -42,6 +42,122 @@ impl Environment {
}
}
pub fn for_import(&self) -> Self {
Self {
scopes: self.scopes.new_closure(),
modules: Arc::new(RefCell::new(Modules::new())),
global_modules: Vec::new(),
content: self.content.as_ref().map(Arc::clone),
forwarded_modules: Arc::clone(&self.forwarded_modules),
}
}
pub fn to_dummy_module(&self, span: Span) -> Module {
Module::Environment {
scope: ModuleScope::new(),
upstream: Vec::new(),
extension_store: ExtensionStore::new(span),
env: self.clone(),
}
}
pub fn import_forwards(&mut self, _env: Module) {
// if (module is _EnvironmentModule) {
// var forwarded = module._environment._forwardedModules;
// if (forwarded == null) return;
// // Omit modules from [forwarded] that are already globally available and
// // forwarded in this module.
// var forwardedModules = _forwardedModules;
// if (forwardedModules != null) {
// forwarded = {
// for (var entry in forwarded.entries)
// if (!forwardedModules.containsKey(entry.key) ||
// !_globalModules.containsKey(entry.key))
// entry.key: entry.value,
// };
// } else {
// forwardedModules = _forwardedModules ??= {};
// }
// var forwardedVariableNames =
// forwarded.keys.expand((module) => module.variables.keys).toSet();
// var forwardedFunctionNames =
// forwarded.keys.expand((module) => module.functions.keys).toSet();
// var forwardedMixinNames =
// forwarded.keys.expand((module) => module.mixins.keys).toSet();
// if (atRoot) {
// // Hide members from modules that have already been imported or
// // forwarded that would otherwise conflict with the @imported members.
// for (var entry in _importedModules.entries.toList()) {
// var module = entry.key;
// var shadowed = ShadowedModuleView.ifNecessary(module,
// variables: forwardedVariableNames,
// mixins: forwardedMixinNames,
// functions: forwardedFunctionNames);
// if (shadowed != null) {
// _importedModules.remove(module);
// if (!shadowed.isEmpty) _importedModules[shadowed] = entry.value;
// }
// }
// for (var entry in forwardedModules.entries.toList()) {
// var module = entry.key;
// var shadowed = ShadowedModuleView.ifNecessary(module,
// variables: forwardedVariableNames,
// mixins: forwardedMixinNames,
// functions: forwardedFunctionNames);
// if (shadowed != null) {
// forwardedModules.remove(module);
// if (!shadowed.isEmpty) forwardedModules[shadowed] = entry.value;
// }
// }
// _importedModules.addAll(forwarded);
// forwardedModules.addAll(forwarded);
// } else {
// (_nestedForwardedModules ??=
// List.generate(_variables.length - 1, (_) => []))
// .last
// .addAll(forwarded.keys);
// }
// // Remove existing member definitions that are now shadowed by the
// // forwarded modules.
// for (var variable in forwardedVariableNames) {
// _variableIndices.remove(variable);
// _variables.last.remove(variable);
// _variableNodes.last.remove(variable);
// }
// for (var function in forwardedFunctionNames) {
// _functionIndices.remove(function);
// _functions.last.remove(function);
// }
// for (var mixin in forwardedMixinNames) {
// _mixinIndices.remove(mixin);
// _mixins.last.remove(mixin);
// }
// }
// todo!()
}
pub fn to_implicit_configuration(&self) -> Configuration {
// var configuration = <String, ConfiguredValue>{};
// for (var i = 0; i < _variables.length; i++) {
// var values = _variables[i];
// var nodes = _variableNodes[i];
// for (var entry in values.entries) {
// // Implicit configurations are never invalid, making [configurationSpan]
// // unnecessary, so we pass null here to avoid having to compute it.
// configuration[entry.key] =
// ConfiguredValue.implicit(entry.value, nodes[entry.key]!);
// }
// }
// return Configuration.implicit(configuration);
todo!()
}
pub fn forward_module(&mut self, module: Arc<RefCell<Module>>, rule: AstForwardRule) {
let view = ForwardedModule::if_necessary(module, rule);
(*self.forwarded_modules).borrow_mut().push(view);

View File

@ -767,8 +767,9 @@ impl<'a> Visitor<'a> {
|| path_buf.extension() == Some(OsStr::new("css"))
{
let extension = path_buf.extension().unwrap();
try_path!(path.with_extension(format!(".import{}", extension.to_str().unwrap())));
try_path!(path);
try_path!(path_buf.with_extension(format!(".import{}", extension.to_str().unwrap())));
try_path!(path_buf);
// todo: consider load paths
return None;
}
@ -882,9 +883,62 @@ impl<'a> Visitor<'a> {
return Ok(());
}
// todo:
let loads_user_defined_modules = true;
// this todo should be unreachable, as we currently do not push
// to stylesheet.uses or stylesheet.forwards
todo!()
// let mut children = Vec::new();
let env = self.env.for_import();
self.with_environment::<SassResult<()>, _>(env.clone(), |visitor| {
let old_parent = visitor.parent;
let old_configuration = Arc::clone(&visitor.configuration);
if loads_user_defined_modules {
visitor.parent = Some(CssTree::ROOT);
}
// This configuration is only used if it passes through a `@forward`
// rule, so we avoid creating unnecessary ones for performance reasons.
if !stylesheet.forwards.is_empty() {
visitor.configuration = Arc::new(RefCell::new(env.to_implicit_configuration()));
}
visitor.visit_stylesheet(stylesheet)?;
if loads_user_defined_modules {
visitor.parent = old_parent;
}
visitor.configuration = old_configuration;
Ok(())
})?;
// Create a dummy module with empty CSS and no extensions to make forwarded
// members available in the current import context and to combine all the
// CSS from modules used by [stylesheet].
let module = env.to_dummy_module(self.span_before);
self.env.import_forwards(module);
if loads_user_defined_modules {
// if (module.transitivelyContainsCss) {
// // If any transitively used module contains extensions, we need to
// // clone all modules' CSS. Otherwise, it's possible that they'll be
// // used or imported from another location that shouldn't have the same
// // extensions applied.
// await _combineCss(module,
// clone: module.transitivelyContainsExtensions)
// .accept(this);
// }
// var visitor = _ImportedCssVisitor(this);
// for (var child in children) {
// child.accept(visitor);
// }
}
Ok(())
}
fn visit_static_import_rule(&mut self, static_import: AstPlainCssImport) -> SassResult<()> {

View File

@ -202,6 +202,17 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
Ok(Some(parser.parse_statement()?))
})?;
for (idx, child) in style_sheet.body.iter().enumerate() {
match child {
AstStmt::VariableDecl(_) | AstStmt::LoudComment(_) | AstStmt::SilentComment(_) => {
continue
}
AstStmt::Use(..) => style_sheet.uses.push(idx),
AstStmt::Forward(..) => style_sheet.forwards.push(idx),
_ => break,
}
}
Ok(style_sheet)
}

View File

@ -285,7 +285,7 @@ impl<'a> Serializer<'a> {
} else {
self.buffer.push(b',');
if complex.line_break {
self.buffer.push(b'\n');
self.write_newline();
} else {
self.write_optional_space();
}
@ -294,6 +294,12 @@ impl<'a> Serializer<'a> {
}
}
fn write_newline(&mut self) {
if !self.options.is_compressed() {
self.buffer.push(b'\n');
}
}
fn write_comma_separator(&mut self) {
self.buffer.push(b',');
self.write_optional_space();
@ -399,12 +405,15 @@ impl<'a> Serializer<'a> {
self.write_float(color.red().0);
self.buffer.extend_from_slice(b",");
self.write_optional_space();
self.write_float(color.green().0);
self.buffer.extend_from_slice(b",");
self.write_optional_space();
self.write_float(color.blue().0);
if !is_opaque {
self.buffer.extend_from_slice(b",");
self.write_optional_space();
self.write_float(color.alpha().0);
}

View File

@ -26,7 +26,6 @@ test!(
grass::Options::default().style(grass::OutputStyle::Compressed)
);
test!(
#[ignore = "regress selector compression"]
compresses_selector_with_newline_after_comma,
"a,\nb {\n color: red;\n}\n",
"a,b{color:red}",
@ -100,7 +99,6 @@ test!(
grass::Options::default().style(grass::OutputStyle::Compressed)
);
test!(
#[ignore = "we do not support compressed colors"]
removes_leading_zero_in_number_under_1_in_rgba_alpha_channel,
"a {\n color: rgba(1, 1, 1, 0.5);\n}\n",
"a{color:rgba(1,1,1,.5)}",

View File

@ -483,6 +483,26 @@ fn chained_imports_in_directory() {
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test]
fn explicit_file_extension_import_inside_index_file() {
let input =
"@import \"explicit_file_extension_import_inside_index_file\";\na {\n color: $a;\n}";
tempfile!(
"_a.scss",
"$a: red;",
dir = "explicit_file_extension_import_inside_index_file"
);
tempfile!(
"_index.scss",
"@import \"a.scss\";",
dir = "explicit_file_extension_import_inside_index_file"
);
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
error!(
// note: dart-sass error is "expected more input."
missing_input_after_import,

View File

@ -702,4 +702,94 @@ fn use_variable_declared_in_two_modules() {
);
}
#[test]
fn import_module_using_same_builtin_module() {
let mut fs = TestFs::new();
fs.add_file(
"_a.scss",
r#"
@use "sass:meta";
"#,
);
fs.add_file(
"_b.scss",
r#"
$a: red;
"#,
);
let input = r#"
@use "sass:meta";
@import "a";
"#;
assert_eq!(
"",
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
);
}
#[test]
fn import_module_using_same_builtin_module_has_styles() {
let mut fs = TestFs::new();
fs.add_file(
"_a.scss",
r#"
@use "sass:meta";
a {
color: red;
}
"#,
);
fs.add_file(
"_b.scss",
r#"
$a: red;
"#,
);
let input = r#"
@use "sass:meta";
@import "a";
"#;
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
);
}
#[test]
#[ignore = "we don't hermetically evaluate @extend"]
fn use_module_with_extend() {
let mut fs = TestFs::new();
fs.add_file(
"_a.scss",
r#"
a {
@extend b;
}
"#,
);
let input = r#"
@use "a";
b {
color: red;
}
"#;
assert_err!(
input,
"Error: The target selector was not found.",
grass::Options::default().fs(&fs)
);
}
// todo: refactor these tests to use testfs where possible