add cascade option, to prevent components inheriting styles (#583)

pull/607/head
Rich Harris 8 years ago
parent 79d3c44785
commit 7b99d47215

@ -66,7 +66,7 @@
"glob": "^7.1.1",
"jsdom": "^9.9.1",
"locate-character": "^2.0.0",
"magic-string": "^0.19.0",
"magic-string": "^0.21.1",
"mocha": "^3.2.0",
"node-resolve": "^1.3.3",
"nyc": "^10.0.0",

@ -29,8 +29,11 @@ export default class Generator {
transitions: Set<string>;
importedComponents: Map<string, string>;
code: MagicString;
bindingGroups: string[];
expectedProperties: Set<string>;
cascade: boolean;
css: string;
cssId: string;
usesRefs: boolean;
@ -59,7 +62,8 @@ export default class Generator {
this.expectedProperties = new Set();
this.code = new MagicString( source );
this.css = parsed.css ? processCss( parsed, this.code ) : null;
this.cascade = options.cascade !== false; // TODO remove this option in v2
this.css = parsed.css ? processCss( parsed, this.code, this.cascade ) : null;
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
this.usesRefs = false;

@ -46,7 +46,7 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
block.mount( name, state.parentNode );
// add CSS encapsulation attribute
if ( generator.cssId && state.isTopLevel ) {
if ( generator.cssId && ( !generator.cascade || state.isTopLevel ) ) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
}

@ -13,6 +13,7 @@ export class SsrGenerator extends Generator {
super( parsed, source, name, options );
this.bindings = [];
this.renderCode = '';
this.elementDepth = 0;
}
append ( code: string ) {

@ -50,7 +50,7 @@ export default function visitElement ( generator: SsrGenerator, block: Block, no
}
});
if ( generator.cssId && !generator.elementDepth ) {
if ( generator.cssId && ( !generator.cascade || generator.elementDepth === 0 ) ) {
openingTag += ` ${generator.cssId}`;
}

@ -1,8 +1,9 @@
import MagicString from 'magic-string';
import { Parsed, Node } from '../../interfaces';
const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed: Parsed, code ) {
export default function processCss ( parsed: Parsed, code: MagicString, cascade: boolean ) {
const css = parsed.css.content.styles;
const offset = parsed.css.content.start;
@ -14,9 +15,13 @@ export default function processCss ( parsed: Parsed, code ) {
if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {
node.expression.children.forEach( ( expression: Node ) => {
if ( expression.type === 'Identifier' ) {
const newName = `svelte-${parsed.hash}-${expression.name}`;
code.overwrite( expression.start, expression.end, newName );
keyframes.set( expression.name, newName );
if ( expression.name.startsWith( '-global-' ) ) {
code.remove( expression.start, expression.start + 8 );
} else {
const newName = `svelte-${parsed.hash}-${expression.name}`;
code.overwrite( expression.start, expression.end, newName );
keyframes.set( expression.name, newName );
}
}
});
} else if ( node.children ) {
@ -30,26 +35,63 @@ export default function processCss ( parsed: Parsed, code ) {
function transform ( rule: Node ) {
rule.selector.children.forEach( ( selector: Node ) => {
const start = selector.start - offset;
const end = selector.end - offset;
if ( cascade ) {
// TODO disable cascading (without :global(...)) in v2
const start = selector.start - offset;
const end = selector.end - offset;
const selectorString = css.slice( start, end );
const selectorString = css.slice( start, end );
const firstToken = selector.children[0];
const firstToken = selector.children[0];
let transformed;
let transformed;
if ( firstToken.type === 'TypeSelector' ) {
const insert = firstToken.end - offset;
const head = css.slice( start, insert );
const tail = css.slice( insert, end );
if ( firstToken.type === 'TypeSelector' ) {
const insert = firstToken.end - offset;
const head = css.slice( start, insert );
const tail = css.slice( insert, end );
transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`;
} else {
transformed = `${attr}${selectorString}, ${attr} ${selectorString}`;
transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`;
} else {
transformed = `${attr}${selectorString}, ${attr} ${selectorString}`;
}
code.overwrite( selector.start, selector.end, transformed );
}
code.overwrite( start + offset, end + offset, transformed );
else {
let shouldTransform = true;
let c = selector.start;
selector.children.forEach( ( child: Node ) => {
if ( child.type === 'WhiteSpace' || child.type === 'Combinator' ) {
code.appendLeft( c, attr );
shouldTransform = true;
return;
}
if ( !shouldTransform ) return;
if ( child.type === 'PseudoClassSelector' ) {
// `:global(xyz)` > xyz
if ( child.name === 'global' ) {
const first = child.children[0];
const last = child.children[child.children.length - 1];
code.remove( child.start, first.start ).remove( last.end, child.end );
} else {
code.prependRight( c, attr );
}
shouldTransform = false;
}
c = child.end;
});
if ( shouldTransform ) {
code.appendLeft( c, attr );
}
}
});
rule.block.children.forEach( ( block: Node ) => {

@ -40,6 +40,7 @@ export interface CompileOptions {
dev?: boolean;
shared?: boolean | string;
cascade?: boolean;
onerror?: (error: Error) => void
onwarn?: (warning: Warning) => void

@ -1,6 +1,15 @@
import assert from 'assert';
import * as fs from 'fs';
import { svelte, exists } from '../helpers.js';
import { svelte } from '../helpers.js';
function tryRequire ( file ) {
try {
return require( file ).default;
} catch ( err ) {
if ( err.code !== 'MODULE_NOT_FOUND' ) throw err;
return null;
}
}
describe( 'css', () => {
fs.readdirSync( 'test/css/samples' ).forEach( dir => {
@ -14,9 +23,10 @@ describe( 'css', () => {
}
( solo ? it.only : it )( dir, () => {
const config = tryRequire( `./samples/${dir}/_config.js` ) || {};
const input = fs.readFileSync( `test/css/samples/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
const actual = svelte.compile( input ).css;
const actual = svelte.compile( input, config ).css;
fs.writeFileSync( `test/css/samples/${dir}/_actual.css`, actual );
const expected = fs.readFileSync( `test/css/samples/${dir}/expected.css`, 'utf-8' );

@ -0,0 +1,13 @@
@keyframes why {
0% { color: red; }
100% { color: blue; }
}
[svelte-2486527405].animated, [svelte-2486527405] .animated {
animation: why 2s;
}
[svelte-2486527405].also-animated, [svelte-2486527405] .also-animated {
animation: not-defined-here 2s;
}

@ -0,0 +1,17 @@
<div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style>
@keyframes -global-why {
0% { color: red; }
100% { color: blue; }
}
.animated {
animation: why 2s;
}
.also-animated {
animation: not-defined-here 2s;
}
</style>

@ -0,0 +1,3 @@
export default {
cascade: false
};

@ -0,0 +1,12 @@
div {
color: red;
}
div.foo {
color: blue;
}
.foo {
font-weight: bold;
}

@ -0,0 +1,16 @@
<div>red</div>
<div class='foo'>bold/blue</div>
<style>
:global(div) {
color: red;
}
:global(div.foo) {
color: blue;
}
:global(.foo) {
font-weight: bold;
}
</style>

@ -0,0 +1,13 @@
@keyframes svelte-776829126-why {
0% { color: red; }
100% { color: blue; }
}
[svelte-776829126].animated, [svelte-776829126] .animated {
animation: svelte-776829126-why 2s;
}
[svelte-776829126].also-animated, [svelte-776829126] .also-animated {
animation: not-defined-here 2s;
}

@ -0,0 +1,17 @@
<div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style>
@keyframes why {
0% { color: red; }
100% { color: blue; }
}
.animated {
animation: why 2s;
}
.also-animated {
animation: not-defined-here 2s;
}
</style>

@ -0,0 +1,3 @@
export default {
cascade: false
};

@ -0,0 +1,12 @@
div[svelte-4161687011] {
color: red;
}
div.foo[svelte-4161687011] {
color: blue;
}
.foo[svelte-4161687011] {
font-weight: bold;
}

@ -0,0 +1,16 @@
<div>red</div>
<div class='foo'>bold/blue</div>
<style>
div {
color: red;
}
div.foo {
color: blue;
}
.foo {
font-weight: bold;
}
</style>
Loading…
Cancel
Save