chore: tidy up CSS stuff (#10478)

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/10487/head
Rich Harris 9 months ago committed by GitHub
parent 643edc34db
commit 87d4b12620
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -47,7 +47,7 @@ export class Parser {
throw new TypeError('Template must be a string');
}
this.template = template.trimRight();
this.template = template.trimEnd();
let match_lang;
@ -147,9 +147,9 @@ export class Parser {
/**
* @param {string} str
* @param {boolean} [required]
* @param {boolean} required
*/
eat(str, required) {
eat(str, required = false) {
if (this.match(str)) {
this.index += str.length;
return true;

@ -1,6 +1,7 @@
import { get_possible_values } from './gather_possible_values.js';
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../patterns.js';
import { error } from '../../../errors.js';
import { Stylesheet } from './Stylesheet.js';
const NO_MATCH = 'NO_MATCH';
const POSSIBLE_MATCH = 'POSSIBLE_MATCH';
@ -22,7 +23,7 @@ export default class Selector {
/** @type {import('#compiler').Css.Selector} */
node;
/** @type {import('./Stylesheet.js').default} */
/** @type {import('./Stylesheet.js').Stylesheet} */
stylesheet;
/** @type {Block[]} */
@ -31,39 +32,28 @@ export default class Selector {
/** @type {Block[]} */
local_blocks;
/** @type {boolean} */
used;
used = false;
/**
* @param {import('#compiler').Css.Selector} node
* @param {import('./Stylesheet.js').default} stylesheet
* @param {import('./Stylesheet.js').Stylesheet} stylesheet
*/
constructor(node, stylesheet) {
this.node = node;
this.stylesheet = stylesheet;
this.blocks = group_selectors(node);
// take trailing :global(...) selectors out of consideration
let i = this.blocks.length;
while (i > 0) {
if (!this.blocks[i - 1].global) break;
i -= 1;
}
this.local_blocks = this.blocks.slice(0, i);
const host_only = this.blocks.length === 1 && this.blocks[0].host;
const root_only = this.blocks.length === 1 && this.blocks[0].root;
this.used = this.local_blocks.length === 0 || host_only || root_only;
const i = this.blocks.findLastIndex((block) => !block.can_ignore());
this.local_blocks = this.blocks.slice(0, i + 1);
// if we have a `:root {...}` or `:global(...) {...}` selector, we need to mark
// this selector as `used` even if the component doesn't contain any nodes
this.used = this.local_blocks.length === 0;
}
/** @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */
apply(node) {
/** @type {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }>} */
const to_encapsulate = [];
apply_selector(this.local_blocks.slice(), node, to_encapsulate);
if (to_encapsulate.length > 0) {
to_encapsulate.forEach(({ node, block }) => {
this.stylesheet.nodes_with_css_class.add(node);
block.should_encapsulate = true;
});
if (apply_selector(this.local_blocks.slice(), node, this.stylesheet)) {
this.used = true;
}
}
@ -130,6 +120,13 @@ export default class Selector {
/** @param {import('../../types.js').ComponentAnalysis} analysis */
validate(analysis) {
this.validate_global_placement();
this.validate_global_with_multiple_selectors();
this.validate_global_compound_selector();
this.validate_invalid_combinator_without_selector(analysis);
}
validate_global_placement() {
let start = 0;
let end = this.blocks.length;
for (; start < end; start += 1) {
@ -143,9 +140,6 @@ export default class Selector {
error(this.blocks[i].selectors[0], 'invalid-css-global-placement');
}
}
this.validate_global_with_multiple_selectors();
this.validate_global_compound_selector();
this.validate_invalid_combinator_without_selector(analysis);
}
validate_global_with_multiple_selectors() {
@ -207,10 +201,10 @@ export default class Selector {
/**
* @param {Block[]} blocks
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} node
* @param {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }>} to_encapsulate
* @param {Stylesheet} stylesheet
* @returns {boolean}
*/
function apply_selector(blocks, node, to_encapsulate) {
function apply_selector(blocks, node, stylesheet) {
const block = blocks.pop();
if (!block) return false;
if (!node) {
@ -224,11 +218,22 @@ function apply_selector(blocks, node, to_encapsulate) {
return false;
}
if (applies === UNKNOWN_SELECTOR) {
to_encapsulate.push({ node, block });
/**
* Mark both the compound selector and the node it selects as encapsulated,
* for transformation in a later step
* @param {Block} block
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node
*/
function mark(block, node) {
block.should_encapsulate = true;
stylesheet.nodes_with_css_class.add(node);
return true;
}
if (applies === UNKNOWN_SELECTOR) {
return mark(block, node);
}
if (block.combinator) {
if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') {
for (const ancestor_block of blocks) {
@ -236,31 +241,29 @@ function apply_selector(blocks, node, to_encapsulate) {
continue;
}
if (ancestor_block.host) {
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
/** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} */
let parent = node;
let matched = false;
while ((parent = get_element_parent(parent))) {
if (block_might_apply_to_node(ancestor_block, parent) !== NO_MATCH) {
to_encapsulate.push({ node: parent, block: ancestor_block });
mark(ancestor_block, parent);
matched = true;
}
}
if (to_encapsulate.length) {
to_encapsulate.push({ node, block });
return true;
if (matched) {
return mark(block, node);
}
}
if (blocks.every((block) => block.global)) {
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
return false;
} else if (block.combinator.name === '>') {
const has_global_parent = blocks.every((block) => block.global);
if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block });
return true;
if (has_global_parent || apply_selector(blocks, get_element_parent(node), stylesheet)) {
return mark(block, node);
}
return false;
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
@ -274,23 +277,22 @@ function apply_selector(blocks, node, to_encapsulate) {
if (siblings.size === 0 && get_element_parent(node) !== null) {
return false;
}
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
for (const possible_sibling of siblings.keys()) {
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
to_encapsulate.push({ node, block });
if (apply_selector(blocks.slice(), possible_sibling, stylesheet)) {
mark(block, node);
has_match = true;
}
}
return has_match;
}
// TODO other combinators
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
const regex_backslash_and_following_character = /\\(.)/g;
@ -815,6 +817,11 @@ class Block {
this.selectors.push(selector);
this.end = selector.end;
}
can_ignore() {
return this.global || this.host || this.root;
}
get global() {
return (
this.selectors.length >= 1 &&

@ -52,19 +52,19 @@ class Rule {
/** @type {import('./Selector.js').default[]} */
selectors;
/** @type {Declaration[]} */
declarations;
/** @type {import('#compiler').Css.Rule} */
node;
/** @type {Atrule | undefined} */
/** @type {Stylesheet | Atrule} */
parent;
/** @type {Declaration[]} */
declarations;
/**
* @param {import('#compiler').Css.Rule} node
* @param {any} stylesheet
* @param {Atrule | undefined} parent
* @param {Stylesheet | Atrule} parent
*/
constructor(node, stylesheet, parent) {
this.node = node;
@ -81,16 +81,24 @@ class Rule {
this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
}
/** @param {boolean} dev */
is_used(dev) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
/** @returns {boolean} */
is_empty() {
if (this.declarations.length > 0) return false;
return true;
}
/** @returns {boolean} */
is_used() {
if (this.parent instanceof Atrule && is_keyframes_node(this.parent.node)) {
return true;
}
// keep empty rules in dev, because it's convenient to
// see them in devtools
if (this.declarations.length === 0) return dev;
for (const selector of this.selectors) {
if (selector.used) return true;
}
return this.selectors.some((s) => s.used);
return false;
}
/**
@ -99,7 +107,7 @@ class Rule {
* @param {Map<string, string>} keyframes
*/
transform(code, id, keyframes) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) {
if (this.parent instanceof Atrule && is_keyframes_node(this.parent.node)) {
return;
}
@ -127,19 +135,16 @@ class Rule {
* @param {boolean} dev
*/
prune(code, dev) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) {
if (this.parent instanceof Atrule && is_keyframes_node(this.parent.node)) {
return;
}
// keep empty rules in dev, because it's convenient to
// see them in devtools
if (this.declarations.length === 0) {
if (!dev) {
code.prependRight(this.node.start, '/* (empty) ');
code.appendLeft(this.node.end, '*/');
escape_comment_close(this.node, code);
}
if (!dev && this.is_empty()) {
code.prependRight(this.node.start, '/* (empty) ');
code.appendLeft(this.node.end, '*/');
escape_comment_close(this.node, code);
return;
}
@ -268,8 +273,11 @@ class Atrule {
}
}
/** @param {boolean} _dev */
is_used(_dev) {
is_empty() {
return false; // TODO
}
is_used() {
return true; // TODO
}
@ -326,7 +334,7 @@ class Atrule {
}
}
export default class Stylesheet {
export class Stylesheet {
/** @type {import('#compiler').Style | null} */
ast;
@ -375,53 +383,35 @@ export default class Stylesheet {
this.has_styles = true;
const state = {
/** @type {Atrule | undefined} */
atrule: undefined
/** @type {Stylesheet | Atrule | Rule} */
current: this
};
walk(/** @type {import('#compiler').Css.Node} */ (ast), state, {
Atrule: (node, context) => {
const atrule = new Atrule(node);
if (context.state.atrule) {
context.state.atrule.children.push(atrule);
} else {
this.children.push(atrule);
}
if (is_keyframes_node(node)) {
if (!node.prelude.startsWith('-global-')) {
this.keyframes.set(node.prelude, `${this.id}-${node.prelude}`);
}
} else if (node.block) {
/** @type {Declaration[]} */
const declarations = [];
for (const child of node.block.children) {
if (child.type === 'Declaration') {
declarations.push(new Declaration(child));
}
}
if (declarations.length > 0) {
push_array(atrule.declarations, declarations);
}
}
context.next({
...context.state,
atrule
});
// @ts-expect-error temporary, until nesting is implemented
context.state.current.children.push(atrule);
context.next({ current: atrule });
},
Declaration: (node, context) => {
const declaration = new Declaration(node);
/** @type {Atrule | Rule} */ (context.state.current).declarations.push(declaration);
},
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);
}
// @ts-expect-error temporary, until nesting is implemented
const rule = new Rule(node, this, context.state.current);
context.next();
// @ts-expect-error temporary, until nesting is implemented
context.state.current.children.push(rule);
context.next({ current: rule });
}
});
}

@ -13,7 +13,7 @@ import * as b from '../../utils/builders.js';
import { ReservedKeywords, Runes, SVGElements } from '../constants.js';
import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
import { merge } from '../visitors.js';
import Stylesheet from './css/Stylesheet.js';
import { Stylesheet } from './css/Stylesheet.js';
import { validation_legacy, validation_runes, validation_runes_js } from './validation.js';
import { warn } from '../../warnings.js';
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';

@ -7,7 +7,7 @@ import type {
SvelteOptions
} from '#compiler';
import type { Identifier, LabeledStatement, Program } from 'estree';
import type Stylesheet from './2-analyze/css/Stylesheet.js';
import { Stylesheet } from './2-analyze/css/Stylesheet.js';
import type { Scope, ScopeRoot } from './scope.js';
export interface Js {

@ -99,4 +99,4 @@ export interface Declaration extends BaseNode {
}
// for zimmerframe
export type Node = Style | Rule | Atrule;
export type Node = Style | Rule | Atrule | Declaration;

@ -1,5 +1,6 @@
div.svelte-xyz {
@apply --funky-div;
color: red;
}
/* (empty) div.svelte-xyz {

@ -3,6 +3,7 @@
<style>
div {
@apply --funky-div;
color: red;
}
div {

@ -1,3 +1,4 @@
div.svelte-xyz {
@apply --funky-div;
color: red;
}

@ -3,5 +3,6 @@
<style>
div {
@apply --funky-div;
color: red;
}
</style>
</style>

Loading…
Cancel
Save