Skip to content
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

Apply CSS scoping classes directly to AST (WIP) #1228

Merged
merged 4 commits into from
Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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>