use css-tree to generate CSS AST and walk it

pull/294/head
Rich Harris 8 years ago
parent 96d7814bce
commit eb95fd1ebb

@ -49,6 +49,7 @@
"babel-register": "^6.18.0",
"codecov": "^1.0.1",
"console-group": "^0.3.2",
"css-tree": "^1.0.0-alpha16",
"eslint": "^3.12.2",
"eslint-plugin-import": "^2.2.0",
"estree-walker": "^0.3.0",

@ -2,7 +2,7 @@ import deindent from '../../utils/deindent.js';
import getBuilders from './utils/getBuilders.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import namespaces from '../../utils/namespaces.js';
import processCss from '../shared/css/process.js';
import processCss from '../shared/processCss.js';
import visitors from './visitors/index.js';
import Generator from '../Generator.js';
import * as shared from '../../shared/index.js';
@ -221,7 +221,7 @@ export default function dom ( parsed, source, options, names ) {
let addedCss = false;
function addCss () {
var style = createElement( 'style' );
style.textContent = ${JSON.stringify( processCss( parsed ) )};
style.textContent = ${JSON.stringify( processCss( parsed, generator.code ) )};
appendNode( style, document.head );
addedCss = true;

@ -1,6 +1,6 @@
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import processCss from '../shared/css/process.js';
import processCss from '../shared/processCss.js';
import visitors from './visitors/index.js';
import Generator from '../Generator.js';
@ -94,7 +94,7 @@ export default function ssr ( parsed, source, options, names ) {
builders.renderCss.addBlock( deindent`
components.push({
filename: ${name}.filename,
css: ${JSON.stringify( processCss( parsed ) )},
css: ${JSON.stringify( processCss( parsed, generator.code ) )},
map: null // TODO
});
` );

@ -1,5 +0,0 @@
import transform from './transform.js';
export default function process ( parsed ) {
return transform( parsed.css.content.styles, parsed.hash );
}

@ -1,58 +0,0 @@
// largely borrowed from Ractive https://github.com/ractivejs/ractive/blob/2ec648aaf5296bb88c21812e947e0e42fcc456e3/src/Ractive/config/custom/css/transform.js
const selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g;
const commentsPattern = /\/\*[\s\S]*?\*\//g;
const selectorUnitPattern = /((?:(?:\[[^\]+]\])|(?:[^\s\+\>~:]))+)((?:::?[^\s\+\>\~\(:]+(?:\([^\)]+\))?)*\s*[\s\+\>\~]?)\s*/g;
const excludePattern = /^(?:@|\d+%)/;
function transformSelector ( selector, parent ) {
const selectorUnits = [];
let match;
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
const base = selectorUnits.map( unit => unit.str );
const transformed = [];
let i = selectorUnits.length;
while ( i-- ) {
const appended = base.slice();
// Pseudo-selectors should go after the attribute selector
const unit = selectorUnits[i];
appended[i] = unit.base + parent + unit.modifiers || '';
const prepended = base.slice();
prepended[i] = parent + ' ' + prepended[i];
transformed.push( appended.join( ' ' ), prepended.join( ' ' ) );
}
return transformed.join( ', ' );
}
export default function transformCss ( css, hash ) {
const attr = `[svelte-${hash}]`;
return 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( selector => selector.trim() );
const transformed = selectors
.map( selector => transformSelector( selector, attr ) )
.join( ', ' ) + ' ';
return match.replace( $1, transformed );
});
}

@ -0,0 +1,51 @@
import parse from 'css-tree/lib/parser/index.js';
import walk from 'css-tree/lib/utils/walk.js';
const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed, code ) {
const css = parsed.css.content.styles;
const offset = parsed.css.content.start;
const ast = parse( css, {
positions: true
});
const attr = `[svelte-${parsed.hash}]`;
walk.rules( ast, rule => {
rule.selector.children.each( selector => {
const start = selector.loc.start.offset;
const end = selector.loc.end.offset;
const selectorString = css.slice( start, end );
const firstToken = selector.children.head;
let transformed;
if ( firstToken.data.type === 'TypeSelector' ) {
const insert = firstToken.data.loc.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}`;
}
code.overwrite( start + offset, end + offset, transformed );
});
});
// remove comments. TODO would be nice if this was exposed in css-tree
let match;
while ( match = commentsPattern.exec( css ) ) {
const start = match.index + offset;
const end = start + match[0].length;
code.remove( start, end );
}
return code.slice( parsed.css.content.start, parsed.css.content.end );
}
Loading…
Cancel
Save