glamor
Version:
inline css for component systems
232 lines (202 loc) • 7.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.StyleSheet = StyleSheet;
var _objectAssign = require('object-assign');
var _objectAssign2 = _interopRequireDefault(_objectAssign);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
/*
high performance StyleSheet for css-in-js systems
- uses multiple style tags behind the scenes for millions of rules
- uses `insertRule` for appending in production for *much* faster performance
- 'polyfills' on server side
// usage
import StyleSheet from 'glamor/lib/sheet'
let styleSheet = new StyleSheet()
styleSheet.inject()
- 'injects' the stylesheet into the page (or into memory if on server)
styleSheet.insert('#box { border: 1px solid red; }')
- appends a css rule into the stylesheet
styleSheet.flush()
- empties the stylesheet of all its contents
*/
function last(arr) {
return arr[arr.length - 1];
}
function sheetForTag(tag) {
if (tag.sheet) {
return tag.sheet;
}
// this weirdness brought to you by firefox
for (var i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].ownerNode === tag) {
return document.styleSheets[i];
}
}
}
var isBrowser = typeof window !== 'undefined';
var isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV; //(x => (x === 'development') || !x)(process.env.NODE_ENV)
var isTest = process.env.NODE_ENV === 'test';
var oldIE = function () {
if (isBrowser) {
var div = document.createElement('div');
div.innerHTML = '<!--[if lt IE 10]><i></i><![endif]-->';
return div.getElementsByTagName('i').length === 1;
}
}();
function makeStyleTag() {
var tag = document.createElement('style');
tag.type = 'text/css';
tag.setAttribute('data-glamor', '');
tag.appendChild(document.createTextNode(''));
(document.head || document.getElementsByTagName('head')[0]).appendChild(tag);
return tag;
}
function StyleSheet() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref$speedy = _ref.speedy,
speedy = _ref$speedy === undefined ? !isDev && !isTest : _ref$speedy,
_ref$maxLength = _ref.maxLength,
maxLength = _ref$maxLength === undefined ? isBrowser && oldIE ? 4000 : 65000 : _ref$maxLength;
this.isSpeedy = speedy; // the big drawback here is that the css won't be editable in devtools
this.sheet = undefined;
this.tags = [];
this.maxLength = maxLength;
this.ctr = 0;
}
(0, _objectAssign2.default)(StyleSheet.prototype, {
getSheet: function getSheet() {
return sheetForTag(last(this.tags));
},
inject: function inject() {
var _this = this;
if (this.injected) {
throw new Error('already injected stylesheet!');
}
if (isBrowser) {
this.tags[0] = makeStyleTag();
} else {
// server side 'polyfill'. just enough behavior to be useful.
this.sheet = {
cssRules: [],
insertRule: function insertRule(rule) {
// enough 'spec compliance' to be able to extract the rules later
// in other words, just the cssText field
_this.sheet.cssRules.push({ cssText: rule });
}
};
}
this.injected = true;
},
speedy: function speedy(bool) {
if (this.ctr !== 0) {
throw new Error('cannot change speedy mode after inserting any rule to sheet. Either call speedy(' + bool + ') earlier in your app, or call flush() before speedy(' + bool + ')');
}
this.isSpeedy = !!bool;
},
_insert: function _insert(rule) {
// this weirdness for perf, and chrome's weird bug
// https://stackoverflow.com/questions/20007992/chrome-suddenly-stopped-accepting-insertrule
try {
var sheet = this.getSheet();
sheet.insertRule(rule, rule.indexOf('@import') !== -1 ? 0 : sheet.cssRules.length);
} catch (e) {
if (isDev) {
// might need beter dx for this
console.warn('whoops, illegal rule inserted', rule); //eslint-disable-line no-console
}
}
},
insert: function insert(rule) {
if (isBrowser) {
// this is the ultrafast version, works across browsers
if (this.isSpeedy && this.getSheet().insertRule) {
this._insert(rule);
}
// more browser weirdness. I don't even know
// else if(this.tags.length > 0 && this.tags::last().styleSheet) {
// this.tags::last().styleSheet.cssText+= rule
// }
else {
if (rule.indexOf('@import') !== -1) {
var tag = last(this.tags);
tag.insertBefore(document.createTextNode(rule), tag.firstChild);
} else {
last(this.tags).appendChild(document.createTextNode(rule));
}
}
} else {
// server side is pretty simple
this.sheet.insertRule(rule, rule.indexOf('@import') !== -1 ? 0 : this.sheet.cssRules.length);
}
this.ctr++;
if (isBrowser && this.ctr % this.maxLength === 0) {
this.tags.push(makeStyleTag());
}
return this.ctr - 1;
},
// commenting this out till we decide on v3's decision
// _replace(index, rule) {
// // this weirdness for perf, and chrome's weird bug
// // https://stackoverflow.com/questions/20007992/chrome-suddenly-stopped-accepting-insertrule
// try {
// let sheet = this.getSheet()
// sheet.deleteRule(index) // todo - correct index here
// sheet.insertRule(rule, index)
// }
// catch(e) {
// if(isDev) {
// // might need beter dx for this
// console.warn('whoops, problem replacing rule', rule) //eslint-disable-line no-console
// }
// }
// }
// replace(index, rule) {
// if(isBrowser) {
// if(this.isSpeedy && this.getSheet().insertRule) {
// this._replace(index, rule)
// }
// else {
// let _slot = Math.floor((index + this.maxLength) / this.maxLength) - 1
// let _index = (index % this.maxLength) + 1
// let tag = this.tags[_slot]
// tag.replaceChild(document.createTextNode(rule), tag.childNodes[_index])
// }
// }
// else {
// let rules = this.sheet.cssRules
// this.sheet.cssRules = [ ...rules.slice(0, index), { cssText: rule }, ...rules.slice(index + 1) ]
// }
// }
delete: function _delete(index) {
// we insert a blank rule when 'deleting' so previously returned indexes remain stable
return this.replace(index, '');
},
flush: function flush() {
if (isBrowser) {
this.tags.forEach(function (tag) {
return tag.parentNode.removeChild(tag);
});
this.tags = [];
this.sheet = null;
this.ctr = 0;
// todo - look for remnants in document.styleSheets
} else {
// simpler on server
this.sheet.cssRules = [];
}
this.injected = false;
},
rules: function rules() {
if (!isBrowser) {
return this.sheet.cssRules;
}
var arr = [];
this.tags.forEach(function (tag) {
return arr.splice.apply(arr, [arr.length, 0].concat(_toConsumableArray(Array.from(sheetForTag(tag).cssRules))));
});
return arr;
}
});