diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 1a1528d0c4..c33f440ebc 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -13,6 +13,7 @@ import annotateWithScopes from '../utils/annotateWithScopes'; import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; +import extractSelectors from './extractSelectors'; import { Node, Parsed, CompileOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; @@ -40,6 +41,8 @@ export default class Generator { cssId: string; usesRefs: boolean; + selectors: any[]; // TODO how to indicate it takes `(Node[]) => boolean` functions? + importedNames: Set; aliases: Map; usedNames: Set; @@ -76,6 +79,8 @@ export default class Generator { this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.usesRefs = false; + this.selectors = extractSelectors(parsed.css); + // allow compiler to deconflict user's `import { get } from 'whatever'` and // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; this.importedNames = new Set(); @@ -211,6 +216,16 @@ export default class Generator { }; } + cssAppliesTo(node: Node, stack: Node[]) { + 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; + } + } + } + findDependencies( contextDependencies: Map, indexes: Map, diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 8921c6cabe..65d697fa0b 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -73,7 +73,7 @@ export default function dom( const { block, state } = preprocess(generator, namespace, parsed.html); parsed.html.children.forEach((node: Node) => { - visit(generator, block, state, node); + visit(generator, block, state, node, []); }); const builders = { diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts index 91601fa8fd..82fc23c03b 100644 --- a/src/generators/dom/visit.ts +++ b/src/generators/dom/visit.ts @@ -2,13 +2,15 @@ import visitors from './visitors/index'; import { DomGenerator } from './index'; import Block from './Block'; import { Node } from '../../interfaces'; +import { State } from './interfaces'; export default function visit( generator: DomGenerator, block: Block, - state, - node: Node + state: State, + node: Node, + elementStack: Node[] ) { const visitor = visitors[node.type]; - visitor(generator, block, state, node); + visitor(generator, block, state, node, elementStack); } diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 96fdd4e31e..d126f09dc2 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -40,7 +40,8 @@ export default function visitComponent( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const hasChildren = node.children.length > 0; const name = block.getUniqueName( @@ -123,7 +124,7 @@ export default function visitComponent( const childBlock = node._block; node.children.forEach((child: Node) => { - visit(generator, childBlock, childState, child); + visit(generator, childBlock, childState, child, elementStack); }); const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 4be9059e52..dc98ad1f37 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -9,7 +9,8 @@ export default function visitEachBlock( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const each_block = generator.getUniqueName(`each_block`); const create_each_block = node._block.name; @@ -118,12 +119,12 @@ export default function visitEachBlock( } node.children.forEach((child: Node) => { - visit(generator, node._block, node._state, child); + visit(generator, node._block, node._state, child, elementStack); }); if (node.else) { node.else.children.forEach((child: Node) => { - visit(generator, node.else._block, node.else._state, child); + visit(generator, node.else._block, node.else._state, child, elementStack); }); } } diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 5e16abb3eb..bc2e811d3b 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -35,14 +35,15 @@ export default function visitElement( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { if (node.name in meta) { return meta[node.name](generator, block, node); } if (generator.components.has(node.name) || node.name === ':Self') { - return visitComponent(generator, block, state, node); + return visitComponent(generator, block, state, node, elementStack); } const childState = node._state; @@ -67,7 +68,7 @@ export default function visitElement( } // add CSS encapsulation attribute - if (generator.cssId && (!generator.cascade || state.isTopLevel)) { + if (generator.cssId && (generator.cascade ? state.isTopLevel : generator.cssAppliesTo(node, elementStack))) { block.builders.hydrate.addLine( `${generator.helper( 'setAttribute' @@ -172,7 +173,7 @@ export default function visitElement( } node.children.forEach((child: Node) => { - visit(generator, block, childState, child); + visit(generator, block, childState, child, elementStack.concat(node)); }); if (node.lateUpdate) { diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index fc2c54ca97..deb625e808 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -19,7 +19,8 @@ function getBranches( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const branches = [ { @@ -31,11 +32,11 @@ function getBranches( }, ]; - visitChildren(generator, block, state, node); + visitChildren(generator, block, state, node, elementStack); if (isElseIf(node.else)) { branches.push( - ...getBranches(generator, block, state, node.else.children[0]) + ...getBranches(generator, block, state, node.else.children[0], elementStack) ); } else { branches.push({ @@ -47,7 +48,7 @@ function getBranches( }); if (node.else) { - visitChildren(generator, block, state, node.else); + visitChildren(generator, block, state, node.else, elementStack); } } @@ -58,10 +59,11 @@ function visitChildren( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { node.children.forEach((child: Node) => { - visit(generator, node._block, node._state, child); + visit(generator, node._block, node._state, child, elementStack); }); } @@ -69,7 +71,8 @@ export default function visitIfBlock( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const name = generator.getUniqueName(`if_block`); const anchor = node.needsAnchor @@ -77,7 +80,7 @@ export default function visitIfBlock( : (node.next && node.next._state.name) || 'null'; const params = block.params.join(', '); - const branches = getBranches(generator, block, state, node); + const branches = getBranches(generator, block, state, node, elementStack); const hasElse = isElseBranch(branches[branches.length - 1]); const if_name = hasElse ? '' : `if ( ${name} ) `; diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts new file mode 100644 index 0000000000..bcb6bf031b --- /dev/null +++ b/src/generators/extractSelectors.ts @@ -0,0 +1,64 @@ +import { Node } from '../interfaces'; + +export default function extractSelectors(css: Node) :Node[] { + if (!css) return []; + + const selectors = []; + + function processRule(rule: Node) { + if (rule.type !== 'Rule') { + // TODO @media etc + throw new Error(`not supported: ${rule.type}`); + } + + const selectors = rule.selector.children; + selectors.forEach(processSelector); + } + + function processSelector(selector: Node) { + selectors.push({ + used: false, + appliesTo: (node: Node, stack: Node[]) => { + let i = selector.children.length; + let j = stack.length; + + while (i--) { + if (!node) return false; + + const part = selector.children[i]; + + if (part.type === 'ClassSelector') { + if (!classMatches(node, part.name)) return false; + } + + else if (part.type === 'TypeSelector') { + if (node.name !== part.name) return false; + } + + else if (part.type === 'WhiteSpace') { + node = stack[--j]; + } + + else { + throw new Error(`TODO ${part.type}`); + } + } + + return true; + } + }); + } + + css.children.forEach(processRule); + + return selectors; +} + +function classMatches(node: Node, className: string) { + const attr = node.attributes.find((attr: Node) => attr.name === 'class'); + if (!attr) return false; + if (attr.value.length > 1) return true; + if (attr.value[0].type !== 'Text') return true; + + return attr.value[0].data.split(' ').indexOf(className) !== -1; +} \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/_config.js b/test/css/samples/omit-scoping-attribute-class-dynamic/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/_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-class-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css new file mode 100644 index 0000000000..45b763d4de --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css @@ -0,0 +1,4 @@ + + .foo[svelte-2643270928] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html new file mode 100644 index 0000000000..a51f22d968 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

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

this is styled

+

this is unstyled

+ + + + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-static/_config.js b/test/css/samples/omit-scoping-attribute-class-static/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/_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-class-static/expected.css b/test/css/samples/omit-scoping-attribute-class-static/expected.css new file mode 100644 index 0000000000..bf4e3cb73c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.css @@ -0,0 +1,4 @@ + + .foo[svelte-633959357] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-class-static/expected.html b/test/css/samples/omit-scoping-attribute-class-static/expected.html new file mode 100644 index 0000000000..b972aa4cb1 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

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

this is styled

+

this is unstyled

+ + \ 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.solo/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/_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-descendant.solo/expected.css b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css new file mode 100644 index 0000000000..b596c30221 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 0000000000..650f7868f8 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html @@ -0,0 +1 @@ +

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 new file mode 100644 index 0000000000..c9de601104 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/input.html @@ -0,0 +1,9 @@ +
+

this is styled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute/_config.js b/test/css/samples/omit-scoping-attribute/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/_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/expected.css b/test/css/samples/omit-scoping-attribute/expected.css new file mode 100644 index 0000000000..f8adbd21a7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/expected.css @@ -0,0 +1,4 @@ + + p[svelte-1997768937] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute/expected.html b/test/css/samples/omit-scoping-attribute/expected.html new file mode 100644 index 0000000000..580168ced2 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/expected.html @@ -0,0 +1 @@ +

this is styled

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

this is styled

+
+ + \ No newline at end of file