Merge pull request #1228 from sveltejs/scope-class-redo

Apply CSS scoping classes directly to AST (WIP)
pull/1231/head
Rich Harris 7 years ago committed by GitHub
commit 83c1e18233
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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);
@ -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;
});

@ -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));
}
@ -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;
@ -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)}`;
@ -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) {
@ -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;
}
@ -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 };

@ -179,6 +179,7 @@ export default class Generator {
}
this.walkTemplate();
if (!this.customElement) this.stylesheet.reify();
}
addSourcemapLocations(node: Node) {

@ -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';
@ -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

@ -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);
@ -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
@ -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}>`;
@ -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(

@ -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 += '>';

@ -0,0 +1,7 @@
export default {
cascade: false,
data: {
dynamic: 'x'
}
};

@ -0,0 +1 @@
.foo.svelte-xyz{color:red}.bar.svelte-xyz{font-style:italic}

@ -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>

@ -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>
Loading…
Cancel
Save