Skip to content

Commit

Permalink
Merge pull request #1228 from sveltejs/scope-class-redo
Browse files Browse the repository at this point in the history
Apply CSS scoping classes directly to AST (WIP)
  • Loading branch information
Rich-Harris authored Mar 13, 2018
2 parents dc4fdcf + b35aab3 commit 83c1e18
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 69 deletions.
7 changes: 5 additions & 2 deletions src/css/Selector.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import MagicString from 'magic-string';
import Stylesheet from './Stylesheet';
import { gatherPossibleValues, UNKNOWN } from './gatherPossibleValues';
import { Validator } from '../validate/index';
import { Node } from '../interfaces';

export default class Selector {
node: Node;
stylesheet: Stylesheet;
blocks: Block[];
localBlocks: Block[];
used: boolean;

constructor(node: Node) {
constructor(node: Node, stylesheet: Stylesheet) {
this.node = node;
this.stylesheet = stylesheet;

this.blocks = groupSelectors(node);

Expand All @@ -31,7 +34,7 @@ export default class Selector {

if (toEncapsulate.length > 0) {
toEncapsulate.filter((_, i) => i === 0 || i === toEncapsulate.length - 1).forEach(({ node, block }) => {
node._needsCssAttribute = true;
this.stylesheet.nodesWithCssClass.add(node);
block.shouldEncapsulate = true;
});

Expand Down
20 changes: 15 additions & 5 deletions src/css/Stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ class Rule {
node: Node;
parent: Atrule;

constructor(node: Node, parent?: Atrule) {
constructor(node: Node, stylesheet, parent?: Atrule) {
this.node = node;
this.parent = parent;
this.selectors = node.selector.children.map((node: Node) => new Selector(node));
this.selectors = node.selector.children.map((node: Node) => new Selector(node, stylesheet));
this.declarations = node.block.children.map((node: Node) => new Declaration(node));
}

Expand Down Expand Up @@ -274,6 +274,8 @@ export default class Stylesheet {
children: (Rule|Atrule)[];
keyframes: Map<string, string>;

nodesWithCssClass: Set<Node>;

constructor(source: string, parsed: Parsed, filename: string, cascade: boolean, dev: boolean) {
this.source = source;
this.parsed = parsed;
Expand All @@ -284,6 +286,8 @@ export default class Stylesheet {
this.children = [];
this.keyframes = new Map();

this.nodesWithCssClass = new Set();

if (parsed.css && parsed.css.children.length) {
this.id = `svelte-${hash(parsed.css.content.styles)}`;

Expand Down Expand Up @@ -322,7 +326,7 @@ export default class Stylesheet {
}

if (node.type === 'Rule') {
const rule = new Rule(node, currentAtrule);
const rule = new Rule(node, this, currentAtrule);
stack.push(rule);

if (currentAtrule) {
Expand Down Expand Up @@ -353,7 +357,7 @@ export default class Stylesheet {
}

if (this.cascade) {
if (stack.length === 0) node._needsCssAttribute = true;
if (stack.length === 0) this.nodesWithCssClass.add(node);
return;
}

Expand All @@ -363,6 +367,12 @@ export default class Stylesheet {
}
}

reify() {
this.nodesWithCssClass.forEach((node: Node) => {
node.addCssClass();
});
}

render(cssOutputFilename: string, shouldTransformSelectors: boolean) {
if (!this.hasStyles) {
return { css: null, cssMap: null };
Expand Down Expand Up @@ -438,4 +448,4 @@ export default class Stylesheet {
child.warnOnUnusedSelector(handler);
});
}
}
}
1 change: 1 addition & 0 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export default class Generator {
}

this.walkTemplate();
if (!this.customElement) this.stylesheet.reify();
}

addSourcemapLocations(node: Node) {
Expand Down
23 changes: 4 additions & 19 deletions src/generators/nodes/Attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,6 @@ export default class Attribute {
shouldCache = true;
}

if (node._needsCssAttribute && name === 'class') {
value = `(${value}) + " ${this.generator.stylesheet.id}"`;
}

const isSelectValueAttribute =
name === 'value' && node.name === 'select';

Expand Down Expand Up @@ -227,21 +223,10 @@ export default class Attribute {
);
}
} else {
const isScopedClassAttribute = (
name === 'class' &&
this.parent._needsCssAttribute &&
!this.generator.customElement
);

const value = isScopedClassAttribute && this.value !== true
? this.value.length === 0
? `'${this.generator.stylesheet.id}'`
: stringify(this.value[0].data.concat(` ${this.generator.stylesheet.id}`))
: this.value === true
? 'true'
: this.value.length === 0
? `''`
: stringify(this.value[0].data);
const value =
this.value === true
? 'true'
: this.value.length === 0 ? `''` : stringify(this.value[0].data);

const statement = (
isLegacyInputType
Expand Down
55 changes: 28 additions & 27 deletions src/generators/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ export default class Element extends Node {
this.name.replace(/[^a-zA-Z0-9_$]/g, '_')
);

this.generator.stylesheet.apply(this);

if (this.children.length) {
if (this.name === 'pre' || this.name === 'textarea') stripWhitespace = false;
this.initChildren(block, stripWhitespace, nextSibling);
Expand Down Expand Up @@ -212,22 +210,11 @@ export default class Element extends Node {
block.builders.unmount.addLine(`@detachNode(${name});`);
}

// add CSS encapsulation attribute
if (this._needsCssAttribute && !this.generator.customElement) {
if (!this.attributes.find(a => a.type === 'Attribute' && a.name === 'class')) {
block.builders.hydrate.addLine(
this.namespace
? `@setAttribute(${name}, "class", "${this.generator.stylesheet.id}");`
: `${name}.className = "${this.generator.stylesheet.id}";`
);
}

// TODO move this into a class as well?
if (this._cssRefAttribute) {
block.builders.hydrate.addLine(
`@setAttribute(${name}, "svelte-ref-${this._cssRefAttribute}", "");`
)
}
// TODO move this into a class as well?
if (this._cssRefAttribute) {
block.builders.hydrate.addLine(
`@setAttribute(${name}, "svelte-ref-${this._cssRefAttribute}", "");`
)
}

// insert static children with textContent or innerHTML
Expand Down Expand Up @@ -438,17 +425,9 @@ export default class Element extends Node {
}

node.attributes.forEach((attr: Node) => {
const value = node._needsCssAttribute && attr.name === 'class'
? attr.value.concat({ type: 'Text', data: ` ${generator.stylesheet.id}` })
: attr.value;

open += ` ${fixAttributeCasing(attr.name)}${stringifyAttributeValue(value)}`
open += ` ${fixAttributeCasing(attr.name)}${stringifyAttributeValue(attr.value)}`
});

if (node._needsCssAttribute && !node.attributes.find(a => a.name === 'class')) {
open += ` class="${generator.stylesheet.id}"`;
}

if (isVoidElementName(node.name)) return open + '>';

return `${open}>${node.children.map(toHTML).join('')}</${node.name}>`;
Expand Down Expand Up @@ -688,6 +667,28 @@ export default class Element extends Node {

return `@appendNode(${this.var}, ${name}._slotted${this.generator.legacy ? `["default"]` : `.default`});`;
}

addCssClass() {
const classAttribute = this.attributes.find(a => a.name === 'class');
if (classAttribute && classAttribute.value !== true) {
if (classAttribute.value.length === 1 && classAttribute.value[0].type === 'Text') {
classAttribute.value[0].data += ` ${this.generator.stylesheet.id}`;
} else {
(<Node[]>classAttribute.value).push(
new Node({ type: 'Text', data: ` ${this.generator.stylesheet.id}` })
);
}
} else {
this.attributes.push(
new Attribute({
generator: this.generator,
name: 'class',
value: [new Node({ type: 'Text', data: `${this.generator.stylesheet.id}` })],
parent: this,
})
);
}
}
}

function getRenderStatement(
Expand Down
19 changes: 3 additions & 16 deletions src/generators/server-side-rendering/visitors/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,12 @@ export default function visitElement(
block.contextualise(attribute.value[0].expression);
openingTag += '${' + attribute.value[0].metadata.snippet + ' ? " ' + attribute.name + '" : "" }';
} else {
const value = attribute.name === 'class' && node._needsCssAttribute
? attribute.value.concat({
type: 'Text',
data: ` ${generator.stylesheet.id}`
})
: attribute.value;

openingTag += ` ${attribute.name}="${stringifyAttributeValue(block, value)}"`;
openingTag += ` ${attribute.name}="${stringifyAttributeValue(block, attribute.value)}"`;
}
});

if (node._needsCssAttribute && !node.attributes.find(a => a.type === 'Attribute' && a.name === 'class')) {
openingTag += ` class="${generator.stylesheet.id}"`;
}

if (node._needsCssAttribute) {
if (node._cssRefAttribute) {
openingTag += ` svelte-ref-${node._cssRefAttribute}`;
}
if (node._cssRefAttribute) {
openingTag += ` svelte-ref-${node._cssRefAttribute}`;
}

openingTag += '>';
Expand Down
7 changes: 7 additions & 0 deletions test/css/samples/cascade-false-nested/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
cascade: false,

data: {
dynamic: 'x'
}
};
1 change: 1 addition & 0 deletions test/css/samples/cascade-false-nested/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.foo.svelte-xyz{color:red}.bar.svelte-xyz{font-style:italic}
2 changes: 2 additions & 0 deletions test/css/samples/cascade-false-nested/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<span class="foo svelte-xyz"><span class="bar svelte-xyz">text</span></span>
<span class="foo svelte-xyz"><span class="bar svelte-xyz">x</span></span>
16 changes: 16 additions & 0 deletions test/css/samples/cascade-false-nested/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<span class='foo'>
<span class='bar'>text</span>
</span>

<span class='foo'>
<span class='bar'>{{dynamic}}</span>
</span>

<style>
.foo {
color: red;
}
.bar {
font-style: italic;
}
</style>

0 comments on commit 83c1e18

Please sign in to comment.