From 7a752df55d9a1150a30740425d2436e3322b3d01 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Jul 2017 11:13:12 -0400 Subject: [PATCH] fix handling of modified :global(...) selectors --- src/generators/Generator.ts | 5 +- src/generators/Selector.ts | 34 +++++++++- src/generators/shared/processCss.ts | 62 +++++-------------- src/utils/css.ts | 22 +++++-- src/validate/css/index.ts | 14 ++--- .../samples/cascade-false-global/input.html | 2 +- 6 files changed, 77 insertions(+), 62 deletions(-) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 6eff2f2a3d..d1cee03e1a 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -81,7 +81,6 @@ export default class Generator { // styles this.cascade = options.cascade !== false; // TODO remove this option in v2 - this.css = parsed.css ? processCss(parsed, this.code, this.cascade) : null; this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.selectors = []; @@ -91,6 +90,10 @@ export default class Generator { this.selectors.push(new Selector(child)); }); }); + + this.css = processCss(this, this.code, this.cascade); + } else { + this.css = null; } // allow compiler to deconflict user's `import { get } from 'whatever'` and diff --git a/src/generators/Selector.ts b/src/generators/Selector.ts index 862bbea650..e7a23489c1 100644 --- a/src/generators/Selector.ts +++ b/src/generators/Selector.ts @@ -1,9 +1,10 @@ +import MagicString from 'magic-string'; import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css'; import { Node } from '../interfaces'; export default class Selector { node: Node; - blocks: Node[][]; + blocks: any; // TODO parts: Node[]; used: boolean; @@ -27,7 +28,7 @@ export default class Selector { this.parts = node.children.slice(0, i); - this.used = isGlobalSelector(this.blocks[0]); + this.used = this.blocks[0].global; } apply(node: Node, stack: Node[]) { @@ -42,6 +43,35 @@ export default class Selector { if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; } } + + transform(code: MagicString, attr: string) { + function encapsulateBlock(block) { + let i = block.selectors.length; + while (i--) { + const selector = block.selectors[i]; + if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') continue; + + if (selector.type === 'TypeSelector' && selector.name === '*') { + code.overwrite(selector.start, selector.end, attr); + } else { + code.appendLeft(selector.end, attr); + } + + return; + } + } + + this.blocks.forEach((block, i) => { + if (block.global) { + const selector = block.selectors[0]; + const first = selector.children[0]; + const last = selector.children[selector.children.length - 1]; + code.remove(selector.start, first.start).remove(last.end, selector.end); + } else if (i === 0 || i === this.blocks.length - 1) { + encapsulateBlock(block); + } + }); + } } function isDescendantSelector(selector: Node) { diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 15719e7b8f..5caec55239 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,18 +1,19 @@ import MagicString from 'magic-string'; import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; -import { Parsed, Node } from '../../interfaces'; +import Generator from '../Generator'; +import { Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; export default function processCss( - parsed: Parsed, + generator: Generator, code: MagicString, cascade: boolean ) { - const css = parsed.css.content.styles; - const offset = parsed.css.content.start; + const css = generator.parsed.css.content.styles; + const offset = generator.parsed.css.content.start; - const attr = `[svelte-${parsed.hash}]`; + const attr = `[svelte-${generator.parsed.hash}]`; const keyframes = new Map(); @@ -23,7 +24,7 @@ export default function processCss( if (expression.name.startsWith('-global-')) { code.remove(expression.start, expression.start + 8); } else { - const newName = `svelte-${parsed.hash}-${expression.name}`; + const newName = `svelte-${generator.parsed.hash}-${expression.name}`; code.overwrite(expression.start, expression.end, newName); keyframes.set(expression.name, newName); } @@ -36,23 +37,7 @@ export default function processCss( } } - parsed.css.children.forEach(walkKeyframes); - - function encapsulateBlock(block: Node[]) { - let i = block.length; - while (i--) { - const child = block[i]; - if (child.type === 'PseudoElementSelector' || child.type === 'PseudoClassSelector') continue; - - if (child.type === 'TypeSelector' && child.name === '*') { - code.overwrite(child.start, child.end, attr); - } else { - code.appendLeft(child.end, attr); - } - - return; - } - } + generator.parsed.css.children.forEach(walkKeyframes); function transform(rule: Node) { rule.selector.children.forEach((selector: Node) => { @@ -78,27 +63,6 @@ export default function processCss( } code.overwrite(selector.start, selector.end, transformed); - } else { - let shouldTransform = true; - let c = selector.start; - - // separate .foo > .bar > .baz into three separate blocks, so - // that we can transform only the first and last - let block: Node[] = []; - const blocks: Node[][] = groupSelectors(selector); - - blocks.forEach((block: Node[], i) => { - if (i === 0 || i === blocks.length - 1) { - encapsulateBlock(blocks[i]); - } - - if (isGlobalSelector(block)) { - const selector = block[0]; - const first = selector.children[0]; - const last = selector.children[selector.children.length - 1]; - code.remove(selector.start, first.start).remove(last.end, selector.end); - } - }); } }); @@ -119,7 +83,13 @@ export default function processCss( }); } - walkRules(parsed.css.children, transform); + walkRules(generator.parsed.css.children, transform); + + if (!cascade) { + generator.selectors.forEach(selector => { + selector.transform(code, attr); + }); + } // remove comments. TODO would be nice if this was exposed in css-tree let match; @@ -130,5 +100,5 @@ export default function processCss( code.remove(start, end); } - return code.slice(parsed.css.content.start, parsed.css.content.end); + return code.slice(generator.parsed.css.content.start, generator.parsed.css.content.end); } diff --git a/src/utils/css.ts b/src/utils/css.ts index ec547bd784..1e00d657e4 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -5,15 +5,27 @@ export function isGlobalSelector(block: Node[]) { } export function groupSelectors(selector: Node) { - let block: Node[] = []; - const blocks: Node[][] = [block]; + let block = { + global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global', + selectors: [], + combinator: null + }; - selector.children.forEach((child: Node) => { + const blocks = [block]; + + selector.children.forEach((child: Node, i: number) => { if (child.type === 'WhiteSpace' || child.type === 'Combinator') { - block = []; + const next = selector.children[i + 1]; + + block = { + global: next.type === 'PseudoClassSelector' && next.name === 'global', + selectors: [], + combinator: child + }; + blocks.push(block); } else { - block.push(child); + block.selectors.push(child); } }); diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts index 8b90eff09c..57c13a7716 100644 --- a/src/validate/css/index.ts +++ b/src/validate/css/index.ts @@ -8,12 +8,12 @@ export default function validateCss(validator: Validator, css: Node) { }); function validateSelector(selector: Node) { - const blocks: Node[][] = groupSelectors(selector); + const blocks = groupSelectors(selector); blocks.forEach((block) => { - let i = block.length; + let i = block.selectors.length; while (i-- > 1) { - const part = block[i]; + const part = block.selectors[i]; if (part.type === 'PseudoClassSelector' && part.name === 'global') { validator.error(`:global(...) must be the first element in a compound selector`, part.start); } @@ -24,16 +24,16 @@ export default function validateCss(validator: Validator, css: Node) { let end = blocks.length; for (; start < end; start += 1) { - if (!isGlobalSelector(blocks[start])) break; + if (!blocks[start].global) break; } for (; end > start; end -= 1) { - if (!isGlobalSelector(blocks[end - 1])) break; + if (!blocks[end - 1].global) break; } for (let i = start; i < end; i += 1) { - if (isGlobalSelector(blocks[i])) { - validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i][0].start); + if (blocks[i].global) { + validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i].selectors[0].start); } } } diff --git a/test/css/samples/cascade-false-global/input.html b/test/css/samples/cascade-false-global/input.html index 947edb6ab5..0350c826f5 100644 --- a/test/css/samples/cascade-false-global/input.html +++ b/test/css/samples/cascade-false-global/input.html @@ -6,7 +6,7 @@ color: red; } - :global(div.foo) { + :global(div).foo { color: blue; }