From ab4000740637c436b072bfa74c643f5f47220d02 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 27 Jun 2017 10:45:10 -0400 Subject: [PATCH] move CSS analysis into preprocess --- src/generators/Generator.ts | 14 +-- src/generators/dom/preprocess.ts | 31 +++++-- .../dom/visitors/Element/Element.ts | 3 +- src/generators/extractSelectors.ts | 88 ++++++++++++++----- .../expected.css | 4 - .../expected.html | 1 - .../input.html | 9 -- .../_config.js | 0 .../expected.css | 4 + .../expected.html | 1 + .../input.html | 11 +++ .../_config.js | 3 + .../expected.css | 4 + .../expected.html | 1 + .../input.html | 11 +++ 15 files changed, 135 insertions(+), 50 deletions(-) delete mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.css delete mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.html delete mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/input.html rename test/css/samples/{omit-scoping-attribute-descendant.solo => omit-scoping-attribute-descendant}/_config.js (100%) create mode 100644 test/css/samples/omit-scoping-attribute-descendant/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant/input.html create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/input.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index c33f440ebc..aa3771fea5 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -216,13 +216,17 @@ export default class Generator { }; } - cssAppliesTo(node: Node, stack: Node[]) { + applyCss(node: Node, stack: Node[]) { + if (!this.cssId) return; + + if (this.cascade) { + if (stack.length === 0) node._needsCssAttribute = true; + return; + } + for (let i = 0; i < this.selectors.length; i += 1) { const selector = this.selectors[i]; - if (selector.appliesTo(node, stack)) { - selector.used = true; - return true; - } + selector.apply(node, stack); } } diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 55a057b9e4..e4ea6f8f9f 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -40,6 +40,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean ) => { const dependencies = block.findDependencies(node.expression); @@ -55,6 +56,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean ) => { const dependencies = block.findDependencies(node.expression); @@ -66,7 +68,14 @@ const preprocessors = { node._state = getChildState(state, { basename, name }); }, - Text: (generator: DomGenerator, block: Block, state: State, node: Node, stripWhitespace: boolean) => { + Text: ( + generator: DomGenerator, + block: Block, + state: State, + node: Node, + elementStack: Node[], + stripWhitespace: boolean + ) => { node._state = getChildState(state); if (!/\S/.test(node.data)) { @@ -83,6 +92,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -102,7 +112,7 @@ const preprocessors = { node._state = getChildState(state); blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, node); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); if (node._block.dependencies.size > 0) { dynamic = true; @@ -127,6 +137,7 @@ const preprocessors = { node.else._block, node.else._state, node.else, + elementStack, stripWhitespace, nextSibling ); @@ -154,6 +165,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -202,7 +214,7 @@ const preprocessors = { }); generator.blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; @@ -219,6 +231,7 @@ const preprocessors = { node.else._block, node.else._state, node.else, + elementStack, stripWhitespace, nextSibling ); @@ -231,6 +244,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -315,6 +329,8 @@ const preprocessors = { : state.namespace, allUsedContexts: [], }); + + generator.applyCss(node, elementStack); } if (node.children.length) { @@ -328,12 +344,12 @@ const preprocessors = { }); generator.blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; } else { if (node.name === 'pre' || node.name === 'textarea') stripWhitespace = false; - preprocessChildren(generator, block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, block, node._state, node, elementStack.concat(node), stripWhitespace, nextSibling); } } }, @@ -344,6 +360,7 @@ function preprocessChildren( block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) { @@ -373,7 +390,7 @@ function preprocessChildren( cleaned.forEach((child: Node, i: number) => { const preprocessor = preprocessors[child.type]; - if (preprocessor) preprocessor(generator, block, state, child, stripWhitespace, cleaned[i + 1] || nextSibling); + if (preprocessor) preprocessor(generator, block, state, child, elementStack, stripWhitespace, cleaned[i + 1] || nextSibling); if (lastChild) { lastChild.next = child; @@ -432,7 +449,7 @@ export default function preprocess( }; generator.blocks.push(block); - preprocessChildren(generator, block, state, node, true, null); + preprocessChildren(generator, block, state, node, [], true, null); block.hasUpdateMethod = block.dependencies.size > 0; return { block, state }; diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 2eb31dc2d0..bb7de853bb 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -81,7 +81,8 @@ export default function visitElement( } // add CSS encapsulation attribute - if (generator.cssId && (generator.cascade ? state.isTopLevel : generator.cssAppliesTo(node, elementStack))) { + // TODO add a helper for this, rather than repeating it + if (node._needsCssAttribute) { block.builders.hydrate.addLine( `@setAttribute( ${name}, '${generator.cssId}', '' );` ); diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index bcb6bf031b..50a021ea6b 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -6,6 +6,15 @@ export default function extractSelectors(css: Node) :Node[] { const selectors = []; function processRule(rule: Node) { + if (rule.type === 'Atrule') { + if (rule.name === 'keyframes') return; + if (rule.name == 'media') { + rule.block.children.forEach(processRule); + return; + } + console.log(rule); + throw new Error('nope'); + } if (rule.type !== 'Rule') { // TODO @media etc throw new Error(`not supported: ${rule.type}`); @@ -18,40 +27,73 @@ export default function extractSelectors(css: Node) :Node[] { function processSelector(selector: Node) { selectors.push({ used: false, - appliesTo: (node: Node, stack: Node[]) => { - let i = selector.children.length; - let j = stack.length; + apply: (node: Node, stack: Node[]) => { + const applies = selectorAppliesTo(selector.children, node, stack.slice()); - while (i--) { - if (!node) return false; + if (applies) { + node._needsCssAttribute = true; + if (selector.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + } + } + }); + } - const part = selector.children[i]; + css.children.forEach(processRule); - if (part.type === 'ClassSelector') { - if (!classMatches(node, part.name)) return false; - } + return selectors; +} + +function isDescendantSelector(selector: Node) { + return selector.type === 'WhiteSpace'; // TODO or '>' +} + +function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { + let i = parts.length; + let j = stack.length; - else if (part.type === 'TypeSelector') { - if (node.name !== part.name) return false; - } + while (i--) { + if (!node) return; + const part = parts[i]; - else if (part.type === 'WhiteSpace') { - node = stack[--j]; - } + if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') { + continue; + } + + if (part.type === 'ClassSelector') { + if (!classMatches(node, part.name)) return false; + } - else { - throw new Error(`TODO ${part.type}`); - } + else if (part.type === 'TypeSelector') { + if (node.name !== part.name) return false; + } + + else if (part.type === 'WhiteSpace') { + parts = parts.slice(0, i); + + while (stack.length) { + if (selectorAppliesTo(parts, stack.pop(), stack)) { + return true; } + } - return true; + return false; + } + + else if (part.type === 'Combinator') { + if (part.name === '>') { + return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack); } - }); - } - css.children.forEach(processRule); + console.log(part); + return true; + } - return selectors; + else { + throw new Error(`TODO ${part.type}`); + } + } + + return true; } function classMatches(node: Node, className: string) { diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css deleted file mode 100644 index b596c30221..0000000000 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css +++ /dev/null @@ -1,4 +0,0 @@ - - div[svelte-1941127328] p[svelte-1941127328] { - color: red; - } diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html deleted file mode 100644 index 650f7868f8..0000000000 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html +++ /dev/null @@ -1 +0,0 @@ -

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/input.html b/test/css/samples/omit-scoping-attribute-descendant.solo/input.html deleted file mode 100644 index c9de601104..0000000000 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/input.html +++ /dev/null @@ -1,9 +0,0 @@ -
-

this is styled

-
- - \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/_config.js b/test/css/samples/omit-scoping-attribute-descendant/_config.js similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant.solo/_config.js rename to test/css/samples/omit-scoping-attribute-descendant/_config.js diff --git a/test/css/samples/omit-scoping-attribute-descendant/expected.css b/test/css/samples/omit-scoping-attribute-descendant/expected.css new file mode 100644 index 0000000000..548ef810ef --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/expected.css @@ -0,0 +1,4 @@ + + div[svelte-2557028325] > p[svelte-2557028325] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant/expected.html b/test/css/samples/omit-scoping-attribute-descendant/expected.html new file mode 100644 index 0000000000..c3cd49d71e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/expected.html @@ -0,0 +1 @@ +

this is not styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant/input.html b/test/css/samples/omit-scoping-attribute-descendant/input.html new file mode 100644 index 0000000000..8af2cc4dd1 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/input.html @@ -0,0 +1,11 @@ +
+
+

this is not styled

+
+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/_config.js b/test/css/samples/omit-scoping-attribute-whitespace/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.css b/test/css/samples/omit-scoping-attribute-whitespace/expected.css new file mode 100644 index 0000000000..fedf7985e1 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.css @@ -0,0 +1,4 @@ + + div[svelte-4240344240] p[svelte-4240344240] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.html b/test/css/samples/omit-scoping-attribute-whitespace/expected.html new file mode 100644 index 0000000000..06092e315c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.html @@ -0,0 +1 @@ +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/input.html b/test/css/samples/omit-scoping-attribute-whitespace/input.html new file mode 100644 index 0000000000..6d00e91267 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/input.html @@ -0,0 +1,11 @@ +
+
+

this is styled

+
+
+ + \ No newline at end of file