Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

really try insertRule #157

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"node": true
},
"rules": {
"indent": ["error", 4],
"react/jsx-indent": [ 2, 4 ]
}
}
28 changes: 14 additions & 14 deletions examples/webpack.config.js
Original file line number Diff line number Diff line change
@@ -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/
}]
}
}
134 changes: 77 additions & 57 deletions src/generate.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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];
};

/**
Expand Down Expand Up @@ -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:
*
Expand All @@ -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 [];
}
};
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
100 changes: 57 additions & 43 deletions src/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <style> tag in the head of the document. This
// will automatically create a style tag and then continue to use it for
// multiple injections. It will also use a style tag with the `data-aphrodite`
// tag on it if that exists in the DOM. This could be used for e.g. reusing the
// same style tag that server-side rendering inserts.
const injectStyleTag = (cssContents) => {
if (styleTag == null) {
// Try to find a style tag with the `data-aphrodite` attribute first.
styleTag = document.querySelector("style[data-aphrodite]");

// If that doesn't work, generate a new style tag.
if (styleTag == null) {
// Taken from
// http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
const head = document.head || document.getElementsByTagName('head')[0];
styleTag = document.createElement('style');

styleTag.type = 'text/css';
styleTag.setAttribute("data-aphrodite", "");
head.appendChild(styleTag);
const injectStyleTag = (cssRules) => {
// Try to find a style tag with the `data-aphrodite` attribute first (SSR)
styleTag = styleTag || document.querySelector("style[data-aphrodite]");
if (styleTag) {
for (let i = 0; i < cssRules.length; i++) {
const {isDangerous, rule} = cssRules[i];
if (isDangerous) {
tryInsertRule(rule);
} else {
styleTag.sheet.insertRule(rule, styleTag.sheet.cssRules.length);
}
}
}

if (styleTag.styleSheet) {
styleTag.styleSheet.cssText += cssContents;
} else {
styleTag.appendChild(document.createTextNode(cssContents));
// If that doesn't work, generate a new style tag.
// Taken from
// http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
const head = document.head || document.getElementsByTagName('head')[0];
styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.setAttribute("data-aphrodite", "");
const cssContent = cssRules.map(c => c.rule).join('');
styleTag.appendChild(document.createTextNode(cssContent));
head.appendChild(styleTag);
}
};

Expand All @@ -49,9 +62,11 @@ const stringHandlers = {
fontFamily: function fontFamily(val) {
if (Array.isArray(val)) {
return val.map(fontFamily).join(",");
} else if (typeof val === "object") {
injectStyleOnce(val.src, "@font-face", [val], false);
return `"${val.fontFamily}"`;
} else if (val && typeof val === "object") {
const {fontFamily, fontStyle, fontWeight} = val;
const key = `${fontFamily}-${fontWeight || 400}${fontStyle}`;
injectStyleOnce(key, "@font-face", [val], false);
return `"${fontFamily}"`;
} else {
return val;
}
Expand Down Expand Up @@ -87,28 +102,28 @@ const stringHandlers = {
// TODO(emily): this probably makes debugging hard, allow a custom
// name?
const name = `keyframe_${hashObject(val)}`;

// Since keyframes need 3 layers of nesting, we use `generateCSS` to
// build the inner layers and wrap it in `@keyframes` ourselves.
let finalVal = `@keyframes ${name}{`;
Object.keys(val).forEach(key => {
finalVal += generateCSS(key, [val[key]], stringHandlers, false);
});
finalVal += '}';

injectGeneratedCSSOnce(name, finalVal);
let anyIsDangerous = false;
const rules = Object.keys(val).reduce((reduction,key) => {
const {isDangerous, rule} = generateCSS(key, [val[key]], stringHandlers, false)[0];
anyIsDangerous = anyIsDangerous || isDangerous;
return reduction + rule;
},'');
const finalVal = {
rule: `@keyframes ${name}{${rules}}`,
isDangerous: anyIsDangerous
};
injectGeneratedCSSOnce(name, [finalVal]);

return name;
},
}
};

// This is a map from Aphrodite's generated class names to `true` (acting as a
// set of class names)
let alreadyInjected = {};

// This is the buffer of styles which have not yet been flushed.
let injectionBuffer = "";

// A flag to tell if we are already buffering styles. This could happen either
// because we scheduled a flush call already, so newly added styles will
// already be flushed, or because we are statically buffering on the server.
Expand All @@ -129,8 +144,7 @@ const injectGeneratedCSSOnce = (key, generatedCSS) => {
isBuffering = true;
asap(flushToStyleTag);
}

injectionBuffer += generatedCSS;
injectionBuffer.push(...generatedCSS);
alreadyInjected[key] = true;
}
}
Expand All @@ -145,7 +159,7 @@ export const injectStyleOnce = (key, selector, definitions, useImportant) => {
};

export const reset = () => {
injectionBuffer = "";
injectionBuffer.length = 0;
alreadyInjected = {};
isBuffering = false;
styleTag = null;
Expand All @@ -161,15 +175,15 @@ export const startBuffering = () => {

export const flushToString = () => {
isBuffering = false;
const ret = injectionBuffer;
injectionBuffer = "";
const ret = injectionBuffer.slice();
injectionBuffer.length = 0;
return ret;
};

export const flushToStyleTag = () => {
const cssContent = flushToString();
if (cssContent.length > 0) {
injectStyleTag(cssContent);
const cssRules = flushToString();
if (cssRules.length > 0) {
injectStyleTag(cssRules);
}
};

Expand Down
Loading