You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
5.0 KiB

3 years ago
// @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
}
}
}