Working CSS nesting implementation

pull/9549/head
Albert Marashi 3 years ago
parent 4ded50fa78
commit a3d4eafbec

@ -189,17 +189,17 @@ function read_selector(parser) {
while (parser.index < parser.template.length) {
const start = parser.index;
if (parser.eat('&')){
if (parser.eat('*')) {
children.push({
type: 'NestedSelector',
name: '&',
type: 'TypeSelector',
name: '*',
start,
end: parser.index
});
} else if (parser.eat('*')) {
} else if (parser.eat('&')){
children.push({
type: 'TypeSelector',
name: '*',
type: 'NestedSelector',
name: '&',
start,
end: parser.index
});
@ -392,9 +392,9 @@ function read_declaration(parser) {
* @returns {import('#compiler').Css.Declaration | import('#compiler').Css.Rule}
*/
function read_declaration_or_rule(parser) {
// We will only allow css nesting using & selector
// We will only allow css nesting using & selector for now
// due to complexities with https://bugs.chromium.org/p/chromium/issues/detail?id=1427259
// as most browsers as of 17/11/2023 do not support nesting without & selector prefix
// as most browsers as of 17/11/2023 do not support nesting without & selector
if (parser.match('&')) {
return read_rule(parser)
} else {

@ -117,14 +117,13 @@ export default class Selector {
}
}
this.blocks.forEach((block, index) => {
console.log(block)
if (block.global) {
remove_global_pseudo_class(block.selectors[0]);
}
if (block.should_encapsulate) {
encapsulate_block(
block,
index === this.blocks.length - 1
index === this.blocks.filter(block => block.nested !== true).length - 1
? attr.repeat(amount_class_specificity_to_increase + 1)
: attr
);
@ -314,10 +313,6 @@ function block_might_apply_to_node(block, node) {
const name = selector.name.replace(regex_backslash_and_following_character, '$1');
// if(name === "&") {
// return POSSIBLE_MATCH;
// }
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return NO_MATCH;
}
@ -806,6 +801,7 @@ class Block {
this.combinator = combinator;
this.host = false;
this.root = false;
this.nested = false;
this.selectors = [];
this.start = -1;
this.end = -1;
@ -845,6 +841,7 @@ function group_selectors(selector) {
block = new Block(child);
blocks.push(block);
} else {
if (child.type === "NestedSelector") block.nested = true;
block.add(child);
}
});

@ -6,6 +6,7 @@ import hash from '../utils/hash.js';
// import { extract_ignores_above_position } from '../utils/extract_svelte_ignore.js';
import { push_array } from '../utils/push_array.js';
import { create_attribute } from '../../nodes.js';
import assert from 'assert';
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
@ -61,12 +62,17 @@ class Rule {
/** @type {Atrule | Rule | undefined} */
parent;
/** @type {Rule[]} */
nested_rules;
/**
* @param {import('#compiler').Css.Rule} node
* @param {any} stylesheet
* @param {Stylesheet} stylesheet
* @param {Atrule | Rule | undefined} parent
*/
constructor(node, stylesheet, parent) {
// console.log('...............')
// console.log(JSON.stringify(node, null, 4))
this.node = node;
this.parent = parent;
this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet));
@ -82,9 +88,13 @@ class Rule {
/** @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */
apply(node) {
this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
this.nested_rules.forEach((rule) => rule.apply(node));
}
/** @param {boolean} dev */
/**
* @param {boolean} dev
* @returns {boolean}
*/
is_used(dev) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
@ -93,7 +103,7 @@ class Rule {
// see them in devtools
if (this.declarations.length === 0) return dev;
return this.selectors.some((s) => s.used);
return [this.selectors.some((s) => s.used), this.nested_rules.some(r => r.is_used(dev))].some(Boolean);
}
/**
@ -120,6 +130,9 @@ class Rule {
this.selectors.forEach((selector) => {
selector.validate(analysis);
});
this.nested_rules.forEach((rule) => {
rule.validate(analysis);
});
}
/** @param {(selector: import('./Selector.js').default) => void} handler */
@ -127,6 +140,9 @@ class Rule {
this.selectors.forEach((selector) => {
if (!selector.used) handler(selector);
});
this.nested_rules.forEach((rule) => {
rule.warn_on_unused_selector(handler);
});
}
/** @returns number */
@ -134,6 +150,7 @@ class Rule {
return Math.max(
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
);
// do we need to check nested rules?
}
/**
@ -147,7 +164,7 @@ class Rule {
// keep empty rules in dev, because it's convenient to
// see them in devtools
if (this.declarations.length === 0) {
if (this.declarations.length === 0 && this.nested_rules.length === 0) {
if (!dev) {
code.prependRight(this.node.start, '/* (empty) ');
code.appendLeft(this.node.end, '*/');
@ -198,6 +215,10 @@ class Rule {
code.appendLeft(last, '*/');
}
}
this.nested_rules.forEach((rule) => {
rule.prune(code, dev);
});
}
}
@ -390,9 +411,12 @@ export default class Stylesheet {
const state = {
/** @type {Atrule | undefined} */
atrule: undefined
atrule: undefined,
};
/** @type {import('#compiler').Css.Node}*/
let prev_node;
walk(/** @type {import('#compiler').Css.Node} */ (ast), state, {
Atrule: (node, context) => {
const atrule = new Atrule(node);
@ -429,12 +453,17 @@ export default class Stylesheet {
},
Rule: (node, context) => {
const rule = new Rule(node, this, context.state.atrule);
if (context.state.atrule) {
context.state.atrule.children.push(rule);
} else {
this.children.push(rule);
}
if (rule.nested_rules.length > 0) {
// Skip nested rules as they are instantiated in the Rule constructor
return node
}
context.next();
}
});

@ -10,5 +10,11 @@ button.svelte-xyz {
color: green;
}
& .bar.svelte-xyz {
& .hello.svelte-xyz {
color: orange;
}
}
color: black;
}

@ -1,6 +1,8 @@
<button>
<div class="foo"/>
<div class="bar"/>
<div class="bar">
<div class="hello"/>
</div>
</button>
<style>
button {
@ -15,6 +17,12 @@ button {
color: green;
}
& .bar {
& .hello {
color: orange;
}
}
color: black;
}
</style>
Loading…
Cancel
Save