Skip to content

Commit 6636ea3

Browse files
authored
Merge pull request #607 from sveltejs/gh-583
add cascade option, to prevent components inheriting styles
2 parents b5b484b + 7b99d47 commit 6636ea3

File tree

18 files changed

+203
-23
lines changed

18 files changed

+203
-23
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"glob": "^7.1.1",
6767
"jsdom": "^9.9.1",
6868
"locate-character": "^2.0.0",
69-
"magic-string": "^0.19.0",
69+
"magic-string": "^0.21.1",
7070
"mocha": "^3.2.0",
7171
"node-resolve": "^1.3.3",
7272
"nyc": "^10.0.0",

src/generators/Generator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ export default class Generator {
2929
transitions: Set<string>;
3030
importedComponents: Map<string, string>;
3131

32+
code: MagicString;
33+
3234
bindingGroups: string[];
3335
expectedProperties: Set<string>;
36+
cascade: boolean;
3437
css: string;
3538
cssId: string;
3639
usesRefs: boolean;
@@ -59,7 +62,8 @@ export default class Generator {
5962
this.expectedProperties = new Set();
6063

6164
this.code = new MagicString( source );
62-
this.css = parsed.css ? processCss( parsed, this.code ) : null;
65+
this.cascade = options.cascade !== false; // TODO remove this option in v2
66+
this.css = parsed.css ? processCss( parsed, this.code, this.cascade ) : null;
6367
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
6468
this.usesRefs = false;
6569

src/generators/dom/visitors/Element/Element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
4646
block.mount( name, state.parentNode );
4747

4848
// add CSS encapsulation attribute
49-
if ( generator.cssId && state.isTopLevel ) {
49+
if ( generator.cssId && ( !generator.cascade || state.isTopLevel ) ) {
5050
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
5151
}
5252

src/generators/server-side-rendering/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class SsrGenerator extends Generator {
1313
super( parsed, source, name, options );
1414
this.bindings = [];
1515
this.renderCode = '';
16+
this.elementDepth = 0;
1617
}
1718

1819
append ( code: string ) {

src/generators/server-side-rendering/visitors/Element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function visitElement ( generator: SsrGenerator, block: Block, no
5050
}
5151
});
5252

53-
if ( generator.cssId && !generator.elementDepth ) {
53+
if ( generator.cssId && ( !generator.cascade || generator.elementDepth === 0 ) ) {
5454
openingTag += ` ${generator.cssId}`;
5555
}
5656

src/generators/shared/processCss.ts

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import MagicString from 'magic-string';
12
import { Parsed, Node } from '../../interfaces';
23

34
const commentsPattern = /\/\*[\s\S]*?\*\//g;
45

5-
export default function processCss ( parsed: Parsed, code ) {
6+
export default function processCss ( parsed: Parsed, code: MagicString, cascade: boolean ) {
67
const css = parsed.css.content.styles;
78
const offset = parsed.css.content.start;
89

@@ -14,9 +15,13 @@ export default function processCss ( parsed: Parsed, code ) {
1415
if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {
1516
node.expression.children.forEach( ( expression: Node ) => {
1617
if ( expression.type === 'Identifier' ) {
17-
const newName = `svelte-${parsed.hash}-${expression.name}`;
18-
code.overwrite( expression.start, expression.end, newName );
19-
keyframes.set( expression.name, newName );
18+
if ( expression.name.startsWith( '-global-' ) ) {
19+
code.remove( expression.start, expression.start + 8 );
20+
} else {
21+
const newName = `svelte-${parsed.hash}-${expression.name}`;
22+
code.overwrite( expression.start, expression.end, newName );
23+
keyframes.set( expression.name, newName );
24+
}
2025
}
2126
});
2227
} else if ( node.children ) {
@@ -30,26 +35,63 @@ export default function processCss ( parsed: Parsed, code ) {
3035

3136
function transform ( rule: Node ) {
3237
rule.selector.children.forEach( ( selector: Node ) => {
33-
const start = selector.start - offset;
34-
const end = selector.end - offset;
38+
if ( cascade ) {
39+
// TODO disable cascading (without :global(...)) in v2
40+
const start = selector.start - offset;
41+
const end = selector.end - offset;
3542

36-
const selectorString = css.slice( start, end );
43+
const selectorString = css.slice( start, end );
3744

38-
const firstToken = selector.children[0];
45+
const firstToken = selector.children[0];
3946

40-
let transformed;
47+
let transformed;
4148

42-
if ( firstToken.type === 'TypeSelector' ) {
43-
const insert = firstToken.end - offset;
44-
const head = css.slice( start, insert );
45-
const tail = css.slice( insert, end );
49+
if ( firstToken.type === 'TypeSelector' ) {
50+
const insert = firstToken.end - offset;
51+
const head = css.slice( start, insert );
52+
const tail = css.slice( insert, end );
4653

47-
transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`;
48-
} else {
49-
transformed = `${attr}${selectorString}, ${attr} ${selectorString}`;
54+
transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`;
55+
} else {
56+
transformed = `${attr}${selectorString}, ${attr} ${selectorString}`;
57+
}
58+
59+
code.overwrite( selector.start, selector.end, transformed );
5060
}
5161

52-
code.overwrite( start + offset, end + offset, transformed );
62+
else {
63+
let shouldTransform = true;
64+
let c = selector.start;
65+
66+
selector.children.forEach( ( child: Node ) => {
67+
if ( child.type === 'WhiteSpace' || child.type === 'Combinator' ) {
68+
code.appendLeft( c, attr );
69+
shouldTransform = true;
70+
return;
71+
}
72+
73+
if ( !shouldTransform ) return;
74+
75+
if ( child.type === 'PseudoClassSelector' ) {
76+
// `:global(xyz)` > xyz
77+
if ( child.name === 'global' ) {
78+
const first = child.children[0];
79+
const last = child.children[child.children.length - 1];
80+
code.remove( child.start, first.start ).remove( last.end, child.end );
81+
} else {
82+
code.prependRight( c, attr );
83+
}
84+
85+
shouldTransform = false;
86+
}
87+
88+
c = child.end;
89+
});
90+
91+
if ( shouldTransform ) {
92+
code.appendLeft( c, attr );
93+
}
94+
}
5395
});
5496

5597
rule.block.children.forEach( ( block: Node ) => {

src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface CompileOptions {
4040

4141
dev?: boolean;
4242
shared?: boolean | string;
43+
cascade?: boolean;
4344

4445
onerror?: (error: Error) => void
4546
onwarn?: (warning: Warning) => void

test/css/index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import assert from 'assert';
22
import * as fs from 'fs';
3-
import { svelte, exists } from '../helpers.js';
3+
import { svelte } from '../helpers.js';
4+
5+
function tryRequire ( file ) {
6+
try {
7+
return require( file ).default;
8+
} catch ( err ) {
9+
if ( err.code !== 'MODULE_NOT_FOUND' ) throw err;
10+
return null;
11+
}
12+
}
413

514
describe( 'css', () => {
615
fs.readdirSync( 'test/css/samples' ).forEach( dir => {
@@ -14,9 +23,10 @@ describe( 'css', () => {
1423
}
1524

1625
( solo ? it.only : it )( dir, () => {
26+
const config = tryRequire( `./samples/${dir}/_config.js` ) || {};
1727
const input = fs.readFileSync( `test/css/samples/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
1828

19-
const actual = svelte.compile( input ).css;
29+
const actual = svelte.compile( input, config ).css;
2030
fs.writeFileSync( `test/css/samples/${dir}/_actual.css`, actual );
2131
const expected = fs.readFileSync( `test/css/samples/${dir}/expected.css`, 'utf-8' );
2232

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
@keyframes why {
3+
0% { color: red; }
4+
100% { color: blue; }
5+
}
6+
7+
[svelte-2486527405].animated, [svelte-2486527405] .animated {
8+
animation: why 2s;
9+
}
10+
11+
[svelte-2486527405].also-animated, [svelte-2486527405] .also-animated {
12+
animation: not-defined-here 2s;
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div class='animated'>animated</div>
2+
<div class='also-animated'>also animated</div>
3+
4+
<style>
5+
@keyframes -global-why {
6+
0% { color: red; }
7+
100% { color: blue; }
8+
}
9+
10+
.animated {
11+
animation: why 2s;
12+
}
13+
14+
.also-animated {
15+
animation: not-defined-here 2s;
16+
}
17+
</style>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
cascade: false
3+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
div {
3+
color: red;
4+
}
5+
6+
div.foo {
7+
color: blue;
8+
}
9+
10+
.foo {
11+
font-weight: bold;
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<div>red</div>
2+
<div class='foo'>bold/blue</div>
3+
4+
<style>
5+
:global(div) {
6+
color: red;
7+
}
8+
9+
:global(div.foo) {
10+
color: blue;
11+
}
12+
13+
:global(.foo) {
14+
font-weight: bold;
15+
}
16+
</style>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
@keyframes svelte-776829126-why {
3+
0% { color: red; }
4+
100% { color: blue; }
5+
}
6+
7+
[svelte-776829126].animated, [svelte-776829126] .animated {
8+
animation: svelte-776829126-why 2s;
9+
}
10+
11+
[svelte-776829126].also-animated, [svelte-776829126] .also-animated {
12+
animation: not-defined-here 2s;
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div class='animated'>animated</div>
2+
<div class='also-animated'>also animated</div>
3+
4+
<style>
5+
@keyframes why {
6+
0% { color: red; }
7+
100% { color: blue; }
8+
}
9+
10+
.animated {
11+
animation: why 2s;
12+
}
13+
14+
.also-animated {
15+
animation: not-defined-here 2s;
16+
}
17+
</style>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
cascade: false
3+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
div[svelte-4161687011] {
3+
color: red;
4+
}
5+
6+
div.foo[svelte-4161687011] {
7+
color: blue;
8+
}
9+
10+
.foo[svelte-4161687011] {
11+
font-weight: bold;
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<div>red</div>
2+
<div class='foo'>bold/blue</div>
3+
4+
<style>
5+
div {
6+
color: red;
7+
}
8+
9+
div.foo {
10+
color: blue;
11+
}
12+
13+
.foo {
14+
font-weight: bold;
15+
}
16+
</style>

0 commit comments

Comments
 (0)