From 52192c553f3a4117972b0a7f4c4acb49731ad042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kol=C3=A1rik?= Date: Fri, 10 Feb 2017 00:39:01 +0100 Subject: [PATCH] Better CSS at-rules handling (fixes #2854) --- src/Ractive/config/custom/css/transform.js | 48 +++++++++++----------- src/utils/cleanCss.js | 6 ++- test/browser-tests/render/css.js | 12 ++++++ 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/Ractive/config/custom/css/transform.js b/src/Ractive/config/custom/css/transform.js index 9fa2e022c0..09264f8237 100644 --- a/src/Ractive/config/custom/css/transform.js +++ b/src/Ractive/config/custom/css/transform.js @@ -1,7 +1,7 @@ import cleanCss from '../../../../utils/cleanCss'; -const selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g; -const commentsPattern = /\/\*[\s\S]*?\*\//g; +const selectorsPattern = /(?:^|\}|\{)\s*([^\{\}\0]+)\s*(?=\{)/g; +const keyframesDeclarationPattern = /@keyframes\s+[^\{\}]+\s*\{(?:[^{}]+|\{[^{}]+})*}/gi; const selectorUnitPattern = /((?:(?:\[[^\]]+\])|(?:[^\s\+\>~:]))+)((?:::?[^\s\+\>\~\(:]+(?:\([^\)]+\))?)*\s*[\s\+\>\~]?)\s*/g; const excludePattern = /^(?:@|\d+%)/; const dataRvcGuidPattern = /\[data-ractive-css~="\{[a-z0-9-]+\}"]/g; @@ -18,15 +18,13 @@ function transformSelector ( selector, parent ) { const selectorUnits = []; let match; - cleanCss( selector, ( selector, reconstruct ) => { - while ( match = selectorUnitPattern.exec( selector ) ) { - selectorUnits.push({ - str: reconstruct( match[0] ), - base: reconstruct( match[1] ), - modifiers: reconstruct( match[2] ) - }); - } - }); + while ( match = selectorUnitPattern.exec( selector ) ) { + selectorUnits.push({ + str: match[0], + base: match[1], + modifiers: match[2] + }); + } // For each simple selector within the selector, we need to create a version // that a) combines with the id, and b) is inside the id @@ -59,19 +57,21 @@ export default function transformCss ( css, id ) { if ( dataRvcGuidPattern.test( css ) ) { transformed = css.replace( dataRvcGuidPattern, dataAttr ); } else { - transformed = css - .replace( commentsPattern, '' ) - .replace( selectorsPattern, ( match, $1 ) => { - // don't transform at-rules and keyframe declarations - if ( excludePattern.test( $1 ) ) return match; - - const selectors = $1.split( ',' ).map( trim ); - const transformed = selectors - .map( selector => transformSelector( selector, dataAttr ) ) - .join( ', ' ) + ' '; - - return match.replace( $1, transformed ); - }); + transformed = cleanCss( css, ( css, reconstruct ) => { + css = css.replace( selectorsPattern, ( match, $1 ) => { + // don't transform at-rules and keyframe declarations + if ( excludePattern.test( $1 ) ) return match; + + const selectors = $1.split( ',' ).map( trim ); + const transformed = selectors + .map( selector => transformSelector( selector, dataAttr ) ) + .join( ', ' ) + ' '; + + return match.replace( $1, transformed ); + }); + + return reconstruct( css ); + }, [ keyframesDeclarationPattern ]); } return transformed; diff --git a/src/utils/cleanCss.js b/src/utils/cleanCss.js index 3f2fba0c88..8b56b1f118 100644 --- a/src/utils/cleanCss.js +++ b/src/utils/cleanCss.js @@ -5,10 +5,14 @@ const value = /\0(\d+)/g; // Removes comments and strings from the given CSS to make it easier to parse. // Callback receives the cleaned CSS and a function which can be used to put // the removed strings back in place after parsing is done. -export default function ( css, callback ) { +export default function ( css, callback, additionalReplaceRules = [] ) { const values = []; const reconstruct = css => css.replace( value, ( match, n ) => values[ n ] ); css = css.replace( escape, match => `\0${values.push( match ) - 1}`).replace( remove, '' ); + additionalReplaceRules.forEach( ( pattern ) => { + css = css.replace( pattern, match => `\0${values.push( match ) - 1}` ); + }); + return callback( css, reconstruct ); } diff --git a/test/browser-tests/render/css.js b/test/browser-tests/render/css.js index 3425d7ec19..ea8a52eece 100644 --- a/test/browser-tests/render/css.js +++ b/test/browser-tests/render/css.js @@ -369,4 +369,16 @@ export default function() { t.equal( cmp.toHTML(), '
' ); t.ok( ~cmp.toCSS().indexOf( 'my-nifty-cmp' ) ); }); + + test( `using 'from' and 'to' in keyframe declarations works (#2854)`, t => { + const cmp = new (Ractive.extend({ + el: fixture, + template: '

foo

bar

', + css: '.blue { color: blue } @keyframes someAnimation { from { transform: scale3d(1.5,1.5,1) rotate(0deg); } } .red { color: red }' + })); + + t.equal( getHexColor( cmp.find( '.blue' ) ), hexCodes.blue ); + t.equal( getHexColor( cmp.find( '.red' ) ), hexCodes.red ); + t.ok( ~cmp.toCSS().indexOf( 'someAnimation { from { transform: scale3d(1.5,1.5,1) rotate(0deg); } }' ) ); + }); }