|
|
|
|
// @flow
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
Based off glamor's StyleSheet, thanks Sunil ❤️
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// usage
|
|
|
|
|
|
|
|
|
|
import { StyleSheet } from '@emotion/sheet'
|
|
|
|
|
|
|
|
|
|
let styleSheet = new StyleSheet({ key: '', container: document.head })
|
|
|
|
|
|
|
|
|
|
styleSheet.insert('#box { border: 1px solid red; }')
|
|
|
|
|
- appends a css rule into the stylesheet
|
|
|
|
|
|
|
|
|
|
styleSheet.flush()
|
|
|
|
|
- empties the stylesheet of all its contents
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// $FlowFixMe
|
|
|
|
|
function sheetForTag(tag: HTMLStyleElement): CSSStyleSheet {
|
|
|
|
|
if (tag.sheet) {
|
|
|
|
|
// $FlowFixMe
|
|
|
|
|
return tag.sheet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this weirdness brought to you by firefox
|
|
|
|
|
/* istanbul ignore next */
|
|
|
|
|
for (let i = 0; i < document.styleSheets.length; i++) {
|
|
|
|
|
if (document.styleSheets[i].ownerNode === tag) {
|
|
|
|
|
// $FlowFixMe
|
|
|
|
|
return document.styleSheets[i]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type Options = {
|
|
|
|
|
nonce?: string,
|
|
|
|
|
key: string,
|
|
|
|
|
container: Node,
|
|
|
|
|
speedy?: boolean,
|
|
|
|
|
prepend?: boolean,
|
|
|
|
|
insertionPoint?: HTMLElement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createStyleElement(options: {
|
|
|
|
|
key: string,
|
|
|
|
|
nonce: string | void
|
|
|
|
|
}): HTMLStyleElement {
|
|
|
|
|
let tag = document.createElement('style')
|
|
|
|
|
tag.setAttribute('data-emotion', options.key)
|
|
|
|
|
if (options.nonce !== undefined) {
|
|
|
|
|
tag.setAttribute('nonce', options.nonce)
|
|
|
|
|
}
|
|
|
|
|
tag.appendChild(document.createTextNode(''))
|
|
|
|
|
tag.setAttribute('data-s', '')
|
|
|
|
|
return tag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class StyleSheet {
|
|
|
|
|
isSpeedy: boolean
|
|
|
|
|
ctr: number
|
|
|
|
|
tags: HTMLStyleElement[]
|
|
|
|
|
// Using Node instead of HTMLElement since container may be a ShadowRoot
|
|
|
|
|
container: Node
|
|
|
|
|
key: string
|
|
|
|
|
nonce: string | void
|
|
|
|
|
prepend: boolean | void
|
|
|
|
|
before: Element | null
|
|
|
|
|
insertionPoint: HTMLElement | void
|
|
|
|
|
constructor(options: Options) {
|
|
|
|
|
this.isSpeedy =
|
|
|
|
|
options.speedy === undefined
|
|
|
|
|
? process.env.NODE_ENV === 'production'
|
|
|
|
|
: options.speedy
|
|
|
|
|
this.tags = []
|
|
|
|
|
this.ctr = 0
|
|
|
|
|
this.nonce = options.nonce
|
|
|
|
|
// key is the value of the data-emotion attribute, it's used to identify different sheets
|
|
|
|
|
this.key = options.key
|
|
|
|
|
this.container = options.container
|
|
|
|
|
this.prepend = options.prepend
|
|
|
|
|
this.insertionPoint = options.insertionPoint
|
|
|
|
|
this.before = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_insertTag = (tag: HTMLStyleElement) => {
|
|
|
|
|
let before
|
|
|
|
|
if (this.tags.length === 0) {
|
|
|
|
|
if (this.insertionPoint) {
|
|
|
|
|
before = this.insertionPoint.nextSibling
|
|
|
|
|
} else if (this.prepend) {
|
|
|
|
|
before = this.container.firstChild
|
|
|
|
|
} else {
|
|
|
|
|
before = this.before
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
before = this.tags[this.tags.length - 1].nextSibling
|
|
|
|
|
}
|
|
|
|
|
this.container.insertBefore(tag, before)
|
|
|
|
|
this.tags.push(tag)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hydrate(nodes: HTMLStyleElement[]) {
|
|
|
|
|
nodes.forEach(this._insertTag)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
insert(rule: string) {
|
|
|
|
|
// the max length is how many rules we have per style tag, it's 65000 in speedy mode
|
|
|
|
|
// it's 1 in dev because we insert source maps that map a single rule to a location
|
|
|
|
|
// and you can only have one source map per style tag
|
|
|
|
|
if (this.ctr % (this.isSpeedy ? 65000 : 1) === 0) {
|
|
|
|
|
this._insertTag(createStyleElement(this))
|
|
|
|
|
}
|
|
|
|
|
const tag = this.tags[this.tags.length - 1]
|
|
|
|
|
|
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
|
|
const isImportRule =
|
|
|
|
|
rule.charCodeAt(0) === 64 && rule.charCodeAt(1) === 105
|
|
|
|
|
|
|
|
|
|
if (isImportRule && (this: any)._alreadyInsertedOrderInsensitiveRule) {
|
|
|
|
|
// this would only cause problem in speedy mode
|
|
|
|
|
// but we don't want enabling speedy to affect the observable behavior
|
|
|
|
|
// so we report this error at all times
|
|
|
|
|
console.error(
|
|
|
|
|
`You're attempting to insert the following rule:\n` +
|
|
|
|
|
rule +
|
|
|
|
|
'\n\n`@import` rules must be before all other types of rules in a stylesheet but other rules have already been inserted. Please ensure that `@import` rules are before all other rules.'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
;(this: any)._alreadyInsertedOrderInsensitiveRule =
|
|
|
|
|
(this: any)._alreadyInsertedOrderInsensitiveRule || !isImportRule
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.isSpeedy) {
|
|
|
|
|
const sheet = sheetForTag(tag)
|
|
|
|
|
try {
|
|
|
|
|
// this is the ultrafast version, works across browsers
|
|
|
|
|
// the big drawback is that the css won't be editable in devtools
|
|
|
|
|
sheet.insertRule(rule, sheet.cssRules.length)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (
|
|
|
|
|
process.env.NODE_ENV !== 'production' &&
|
|
|
|
|
!/:(-moz-placeholder|-moz-focus-inner|-moz-focusring|-ms-input-placeholder|-moz-read-write|-moz-read-only|-ms-clear|-ms-expand|-ms-reveal){/.test(
|
|
|
|
|
rule
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
console.error(
|
|
|
|
|
`There was a problem inserting the following rule: "${rule}"`,
|
|
|
|
|
e
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
tag.appendChild(document.createTextNode(rule))
|
|
|
|
|
}
|
|
|
|
|
this.ctr++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flush() {
|
|
|
|
|
// $FlowFixMe
|
|
|
|
|
this.tags.forEach(tag => tag.parentNode && tag.parentNode.removeChild(tag))
|
|
|
|
|
this.tags = []
|
|
|
|
|
this.ctr = 0
|
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
|
|
|
;(this: any)._alreadyInsertedOrderInsensitiveRule = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|