diff --git a/.eslintrc.json b/.eslintrc.json index cda1d4a..52462e8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,6 +8,7 @@ "node": true }, "rules": { + "indent": ["error", 4], "react/jsx-indent": [ 2, 4 ] } } diff --git a/examples/webpack.config.js b/examples/webpack.config.js index b872134..23b2beb 100644 --- a/examples/webpack.config.js +++ b/examples/webpack.config.js @@ -1,16 +1,16 @@ module.exports = { - output: { - path: __dirname + '/', - filename: 'bundle.js' - }, - entry: [ - './src/examples' - ], - module: { - loaders: [{ - test: /\.js$/, - loaders: ['babel'], - exclude: /node_modules/ - }] - } + output: { + path: __dirname + '/', + filename: 'bundle.js' + }, + entry: [ + './src/examples' + ], + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/ + }] + } } diff --git a/src/generate.js b/src/generate.js index 6f259cf..4485415 100644 --- a/src/generate.js +++ b/src/generate.js @@ -1,8 +1,8 @@ import prefixAll from 'inline-style-prefixer/static'; import { - objectToPairs, kebabifyStyleName, recursiveMerge, stringifyValue, - importantify, flatten + prefixLocally, objectToPairs, kebabifyStyleName, recursiveMerge, + stringifyValue, importantify, flatten } from './util'; /** * Generate CSS for a selector and some styles. @@ -65,21 +65,32 @@ export const generateCSS = (selector, styleTypes, stringHandlers, declarations[key] = merged[key]; } }); - - return ( - generateCSSRuleset(selector, declarations, stringHandlers, - useImportant) + - Object.keys(pseudoStyles).map(pseudoSelector => { - return generateCSSRuleset(selector + pseudoSelector, - pseudoStyles[pseudoSelector], - stringHandlers, useImportant); - }).join("") + - Object.keys(mediaQueries).map(mediaQuery => { - const ruleset = generateCSS(selector, [mediaQueries[mediaQuery]], - stringHandlers, useImportant); - return `${mediaQuery}{${ruleset}}`; - }).join("") - ); + const genericRules = generateCSSRuleset(selector, declarations, stringHandlers, useImportant); + const pseudoRules = Object.keys(pseudoStyles) + .reduce((reduction, pseudoSelector) => { + const ruleset = generateCSS(selector + pseudoSelector, + [pseudoStyles[pseudoSelector]], + stringHandlers, useImportant); + const safeSelectors = [':visited', ':focus', ':active', ':hover']; + const safeRuleset = safeSelectors.includes(pseudoSelector) ? ruleset : + ruleset.map(set => ({...set, isDangerous: true})); + reduction.push(...safeRuleset); + return reduction; + },[]); + const mediaRules = Object.keys(mediaQueries) + .reduce((reduction, mediaQuery) => { + const ruleset = generateCSS(selector, [mediaQueries[mediaQuery]], + stringHandlers, useImportant); + const wrappedRuleset = ruleset.map(set => { + return { + ...set, + rule: `${mediaQuery}{${set.rule}}` + } + }); + reduction.push(...wrappedRuleset); + return reduction; + },[]); + return [...genericRules, ...pseudoRules, ...mediaRules]; }; /** @@ -122,7 +133,7 @@ const runStringHandlers = (declarations, stringHandlers) => { * that is output. * @param {bool} useImportant: A boolean saying whether to append "!important" * to each of the CSS declarations. - * @returns {string} A string of raw CSS. + * @returns {Array} Array with 0-to-1 objects: rule: A string of raw CSS, isDangerous: boolean * * Examples: * @@ -140,45 +151,54 @@ export const generateCSSRuleset = (selector, declarations, stringHandlers, const handledDeclarations = runStringHandlers( declarations, stringHandlers); - const prefixedDeclarations = prefixAll(handledDeclarations); - - const prefixedRules = flatten( - objectToPairs(prefixedDeclarations).map(([key, value]) => { - if (Array.isArray(value)) { - // inline-style-prefix-all returns an array when there should be - // multiple rules, we will flatten to single rules - - const prefixedValues = []; - const unprefixedValues = []; - - value.forEach(v => { - if (v.indexOf('-') === 0) { - prefixedValues.push(v); - } else { - unprefixedValues.push(v); - } - }); - - prefixedValues.sort(); - unprefixedValues.sort(); - - return prefixedValues - .concat(unprefixedValues) - .map(v => [key, v]); - } - return [[key, value]]; - }) - ); - - const rules = prefixedRules.map(([key, value]) => { - const stringValue = stringifyValue(key, value); - const ret = `${kebabifyStyleName(key)}:${stringValue};`; - return useImportant === false ? ret : importantify(ret); - }).join(""); - - if (rules) { - return `${selector}{${rules}}`; + let rules; + if (typeof window === 'undefined') { + // prefix all if we're on the server + const prefixedDeclarations = prefixAll(handledDeclarations); + const prefixedRules = flatten( + objectToPairs(prefixedDeclarations).map(([key, value]) => { + if (Array.isArray(value)) { + // inline-style-prefix-all returns an array when there should be + // multiple rules, we will flatten to single rules + + const prefixedValues = []; + const unprefixedValues = []; + + value.forEach(v => { + if (v.indexOf('-') === 0) { + prefixedValues.push(v); + } else { + unprefixedValues.push(v); + } + }); + + prefixedValues.sort(); + unprefixedValues.sort(); + + return prefixedValues + .concat(unprefixedValues) + .map(v => [key, v]); + } + return [[key, value]]; + }) + ); + const ruleString = prefixedRules.map(([key, value]) => { + const stringValue = stringifyValue(key, value); + const ret = `${kebabifyStyleName(key)}:${stringValue};`; + return useImportant === false ? ret : importantify(ret); + }).join(""); + rules = {isDangerous: false, ruleString}; + } else { + rules = prefixLocally(handledDeclarations, useImportant); + } + if (rules.ruleString) { + return [{ + // make it easy to detect empty blocks later + rule: `${selector}{${rules.ruleString}}`, + // protect against pseudo elements like ::moz-input-placeholder + isDangerous: rules.isDangerous + }]; } else { - return ""; + return []; } }; diff --git a/src/index.js b/src/index.js index 4bb0253..f289bca 100644 --- a/src/index.js +++ b/src/index.js @@ -30,8 +30,8 @@ const StyleSheetServer = { reset(); startBuffering(); const html = renderFunc(); - const cssContent = flushToString(); - + const cssRules = flushToString(); + const cssContent = cssRules.map(c => c.rule).join(''); return { html: html, css: { diff --git a/src/inject.js b/src/inject.js index f31d9d7..226829f 100644 --- a/src/inject.js +++ b/src/inject.js @@ -9,33 +9,46 @@ import {flattenDeep, hashObject} from './util'; // faster. let styleTag = null; +// This is the buffer of style rules which have not yet been flushed. +const injectionBuffer = []; + +// externalized try/catch block allows the engine to optimize the caller +const tryInsertRule = (rule) => { + try { + styleTag.sheet.insertRule(rule, styleTag.sheet.cssRules.length); + } catch(e) { + // user-defined vendor-prefixed styles go here + } + +}; // Inject a string of styles into a