Skip to content

chore: use zimmerframe for CSS analysis/transformation #10482

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

Merged
merged 30 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b97060c
fix type
Rich-Harris Feb 14, 2024
a25753c
parse selectors properly the first time
Rich-Harris Feb 14, 2024
7c84a83
partial fix
Rich-Harris Feb 14, 2024
07df4d3
fix
Rich-Harris Feb 14, 2024
aedc4cb
start moving CSS validation into analysis phase
Rich-Harris Feb 14, 2024
0d0cc80
finish moving validation
Rich-Harris Feb 14, 2024
ab60b6a
fix tests
Rich-Harris Feb 14, 2024
2353cd7
regenerate types
Rich-Harris Feb 14, 2024
8d58de7
start porting scoping logic etc
Rich-Harris Feb 15, 2024
5fa9410
move Style to Css.StyleSheet
Rich-Harris Feb 15, 2024
2b873a4
some encouraging progress
Rich-Harris Feb 15, 2024
e9fe2c3
more progress
Rich-Harris Feb 15, 2024
a983714
more progress
Rich-Harris Feb 15, 2024
88f1aa6
fix a bunch of cases
Rich-Harris Feb 15, 2024
a704197
more fixes
Rich-Harris Feb 15, 2024
2c5e0fa
tweak
Rich-Harris Feb 15, 2024
523c9da
almost there
Rich-Harris Feb 15, 2024
d2b6ad2
keyframes
Rich-Harris Feb 15, 2024
96c6bbb
fix
Rich-Harris Feb 15, 2024
edbabb8
all CSS tests passing
Rich-Harris Feb 15, 2024
e844edc
legacy stuff
Rich-Harris Feb 15, 2024
097b97f
all tests passing
Rich-Harris Feb 15, 2024
c664975
merge main
Rich-Harris Feb 15, 2024
26696ec
delete old code
Rich-Harris Feb 15, 2024
3f73102
regenerate types
Rich-Harris Feb 15, 2024
1e718f9
defer analysis, address TODO
Rich-Harris Feb 15, 2024
33f8b0a
unused
Rich-Harris Feb 15, 2024
6924d57
tidy up
Rich-Harris Feb 15, 2024
00360b2
rename stuff
Rich-Harris Feb 15, 2024
0e7fc47
read selectors before combinators
Rich-Harris Feb 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
549 changes: 0 additions & 549 deletions packages/svelte/src/compiler/css/Stylesheet.js

This file was deleted.

34 changes: 26 additions & 8 deletions packages/svelte/src/compiler/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,7 @@ export function convert(source, ast) {
},
instance,
module,
css: ast.css
? walk(ast.css, null, {
_(node) {
// @ts-ignore
delete node.parent;
}
})
: undefined
css: ast.css ? visit(ast.css) : undefined
};
},
AnimateDirective(node) {
Expand Down Expand Up @@ -192,6 +185,24 @@ export function convert(source, ast) {
ClassDirective(node) {
return { ...node, type: 'Class' };
},
ComplexSelector(node, { visit }) {
const children = [];

for (const child of node.children) {
if (child.combinator) {
children.push(child.combinator);
}

children.push(...child.selectors);
}

return {
type: 'Selector',
start: node.start,
end: node.end,
children
};
},
Component(node, { visit }) {
return {
type: 'InlineComponent',
Expand Down Expand Up @@ -389,6 +400,13 @@ export function convert(source, ast) {
SpreadAttribute(node) {
return { ...node, type: 'Spread' };
},
StyleSheet(node, context) {
return {
...node,
...context.next(),
type: 'Style'
};
},
SvelteBody(node, { visit }) {
return {
type: 'Body',
Expand Down
148 changes: 109 additions & 39 deletions packages/svelte/src/compiler/phases/1-parse/read/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
* @param {import('../index.js').Parser} parser
* @param {number} start
* @param {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute | import('#compiler').Directive>} attributes
* @returns {import('#compiler').Style}
* @returns {import('#compiler').Css.StyleSheet}
*/
export default function read_style(parser, start, attributes) {
const content_start = parser.index;
Expand All @@ -28,7 +28,7 @@ export default function read_style(parser, start, attributes) {
parser.read(/^<\/style\s*>/);

return {
type: 'Style',
type: 'StyleSheet',
start,
end: parser.index,
attributes,
Expand All @@ -37,8 +37,7 @@ export default function read_style(parser, start, attributes) {
start: content_start,
end: content_end,
styles: parser.template.slice(content_start, content_end)
},
parent: null
}
};
}

Expand Down Expand Up @@ -187,42 +186,66 @@ function read_selector_list(parser, inside_pseudo_class = false) {
function read_selector(parser, inside_pseudo_class = false) {
const list_start = parser.index;

/** @type {Array<import('#compiler').Css.SimpleSelector | import('#compiler').Css.Combinator>} */
/** @type {import('#compiler').Css.RelativeSelector[]} */
const children = [];

/**
* @param {import('#compiler').Css.Combinator | null} combinator
* @param {number} start
* @returns {import('#compiler').Css.RelativeSelector}
*/
function create_selector(combinator, start) {
return {
type: 'RelativeSelector',
combinator,
selectors: [],
start,
end: -1,
metadata: {
is_global: false,
is_host: false,
is_root: false,
scoped: false
}
};
}

/** @type {import('#compiler').Css.RelativeSelector} */
let relative_selector = create_selector(null, parser.index);

while (parser.index < parser.template.length) {
const start = parser.index;
let start = parser.index;

if (parser.eat('*')) {
let name = '*';
if (parser.match('|')) {

if (parser.eat('|')) {
// * is the namespace (which we ignore)
parser.index++;
name = read_identifier(parser);
}

children.push({
relative_selector.selectors.push({
type: 'TypeSelector',
name,
start,
end: parser.index
});
} else if (parser.eat('#')) {
children.push({
relative_selector.selectors.push({
type: 'IdSelector',
name: read_identifier(parser),
start,
end: parser.index
});
} else if (parser.eat('.')) {
children.push({
relative_selector.selectors.push({
type: 'ClassSelector',
name: read_identifier(parser),
start,
end: parser.index
});
} else if (parser.eat('::')) {
children.push({
relative_selector.selectors.push({
type: 'PseudoElementSelector',
name: read_identifier(parser),
start,
Expand All @@ -247,7 +270,7 @@ function read_selector(parser, inside_pseudo_class = false) {
error(parser.index, 'invalid-css-global-selector');
}

children.push({
relative_selector.selectors.push({
type: 'PseudoClassSelector',
name,
args,
Expand Down Expand Up @@ -276,7 +299,7 @@ function read_selector(parser, inside_pseudo_class = false) {
parser.allow_whitespace();
parser.eat(']', true);

children.push({
relative_selector.selectors.push({
type: 'AttributeSelector',
start,
end: parser.index,
Expand All @@ -288,37 +311,28 @@ function read_selector(parser, inside_pseudo_class = false) {
} else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
// nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator

children.push({
relative_selector.selectors.push({
type: 'Nth',
value: /**@type {string} */ (parser.read(REGEX_NTH_OF)),
start,
end: parser.index
});
} else if (parser.match_regex(REGEX_COMBINATOR_WHITESPACE)) {
parser.allow_whitespace();
const start = parser.index;
children.push({
type: 'Combinator',
name: /** @type {string} */ (parser.read(REGEX_COMBINATOR)),
start,
end: parser.index
});
parser.allow_whitespace();
} else if (parser.match_regex(REGEX_PERCENTAGE)) {
children.push({
relative_selector.selectors.push({
type: 'Percentage',
value: /** @type {string} */ (parser.read(REGEX_PERCENTAGE)),
start,
end: parser.index
});
} else {
} else if (!parser.match_regex(REGEX_COMBINATOR)) {
let name = read_identifier(parser);
if (parser.match('|')) {

if (parser.eat('|')) {
// we ignore the namespace when trying to find matching element classes
parser.index++;
name = read_identifier(parser);
}
children.push({

relative_selector.selectors.push({
type: 'TypeSelector',
name,
start,
Expand All @@ -330,29 +344,85 @@ function read_selector(parser, inside_pseudo_class = false) {
parser.allow_whitespace();

if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
// rewind, so we know whether to continue building the selector list
parser.index = index;

relative_selector.end = index;
children.push(relative_selector);

return {
type: 'Selector',
type: 'ComplexSelector',
start: list_start,
end: index,
children
children,
metadata: {
used: false
}
};
}

if (parser.index !== index && !parser.match_regex(REGEX_COMBINATOR)) {
children.push({
type: 'Combinator',
name: ' ',
start: index,
end: parser.index
});
parser.index = index;
const combinator = read_combinator(parser);

if (combinator) {
if (relative_selector.selectors.length === 0) {
if (!inside_pseudo_class) {
error(start, 'invalid-css-selector');
}
} else {
relative_selector.end = index;
children.push(relative_selector);
}

// ...and start a new one
relative_selector = create_selector(combinator, combinator.start);

parser.allow_whitespace();

if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
error(parser.index, 'invalid-css-selector');
}
}
}

error(parser.template.length, 'unexpected-eof');
}

/**
* @param {import('../index.js').Parser} parser
* @returns {import('#compiler').Css.Combinator | null}
*/
function read_combinator(parser) {
const start = parser.index;
parser.allow_whitespace();

const index = parser.index;
const name = parser.read(REGEX_COMBINATOR);

if (name) {
const end = parser.index;
parser.allow_whitespace();

return {
type: 'Combinator',
name,
start: index,
end
};
}

if (parser.index !== start) {
return {
type: 'Combinator',
name: ' ',
start,
end: parser.index
};
}

return null;
}

/**
* @param {import('../index.js').Parser} parser
* @returns {import('#compiler').Css.Block}
Expand Down
Loading