Skip to content

Commit

Permalink
Stylis v4 tryout
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Mar 25, 2020
1 parent ff9746b commit 7c2a21a
Show file tree
Hide file tree
Showing 44 changed files with 2,226 additions and 3,764 deletions.
150 changes: 61 additions & 89 deletions packages/cache/src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// @flow
import { StyleSheet } from '@emotion/sheet'
import { type EmotionCache, type SerializedStyles } from '@emotion/utils'
import Stylis from '@emotion/stylis'
import {
serialize,
compile,
middleware,
rulesheet,
stringify,
compat,
prefixer
} from '@emotion/stylis'
import weakMemoize from '@emotion/weak-memoize'
import { Sheet, removeLabel, ruleSheet } from './stylis-plugins'
import { removeLabel, createUnsafeSelectorsAlarm } from './stylis-plugins'
import type { StylisPlugin } from './types'

let isBrowser = typeof document !== 'undefined'
Expand All @@ -23,8 +31,6 @@ export type Options = {
prepend?: boolean
}

let rootServerStylisCache = {}

let getServerStylisCache = isBrowser
? undefined
: weakMemoize(() => {
Expand All @@ -42,11 +48,11 @@ let getServerStylisCache = isBrowser
}
})

const defaultStylisPlugins = [compat, prefixer]
let movedStyles = false

let createCache = (options: Options): EmotionCache => {
let key = options.key
let stylisOptions

if (!key) {
throw new Error(
Expand All @@ -66,13 +72,7 @@ let createCache = (options: Options): EmotionCache => {
})
}

if (options.prefix !== undefined) {
stylisOptions = {
prefix: options.prefix
}
}

let stylis = new Stylis(stylisOptions)
const stylisPlugins = options.stylisPlugins || defaultStylisPlugins

if (process.env.NODE_ENV !== 'production') {
// $FlowFixMe
Expand Down Expand Up @@ -115,46 +115,77 @@ let createCache = (options: Options): EmotionCache => {
) => string | void

if (isBrowser) {
stylis.use(options.stylisPlugins)(ruleSheet)
let currentSheet
const omnipresentPlugins = [
removeLabel,
stringify,
rulesheet(rule => {
currentSheet.insert(rule)
})
]
if (process.env.NODE_ENV !== 'production') {
omnipresentPlugins.unshift(
createUnsafeSelectorsAlarm({
get compat() {
return cache.compat
}
})
)
}
const serializer = middleware(stylisPlugins.concat(omnipresentPlugins))
const stylis = styles => serialize(compile(styles), serializer)

insert = (
selector: string,
serialized: SerializedStyles,
sheet: StyleSheet,
shouldCache: boolean
): void => {
let name = serialized.name
Sheet.current = sheet
currentSheet = sheet
if (
process.env.NODE_ENV !== 'production' &&
serialized.map !== undefined
) {
let map = serialized.map
Sheet.current = {
insert: (rule: string) => {
sheet.insert(rule + map)
currentSheet = {
insert: rule => {
sheet.insert(rule + serialized.map)
}
}
}
stylis(selector, serialized.styles)

if (selector === '') {
stylis(serialized.styles)
} else {
stylis(`${selector}{${serialized.styles}}`)
}

if (shouldCache) {
cache.inserted[name] = true
cache.inserted[serialized.name] = true
}
}
} else {
stylis.use(removeLabel)
let serverStylisCache = rootServerStylisCache
if (options.stylisPlugins || options.prefix !== undefined) {
stylis.use(options.stylisPlugins)
// $FlowFixMe
serverStylisCache = getServerStylisCache(
options.stylisPlugins || rootServerStylisCache
)(options.prefix)
const omnipresentPlugins = [removeLabel, stringify]
if (process.env.NODE_ENV !== 'production') {
omnipresentPlugins.unshift(
createUnsafeSelectorsAlarm({
get compat() {
return cache.compat
}
})
)
}
const serializer = middleware(stylisPlugins.concat(omnipresentPlugins))
const stylis = styles => serialize(compile(styles), serializer)

let serverStylisCache = getServerStylisCache(stylisPlugins)
let getRules = (selector: string, serialized: SerializedStyles): string => {
let name = serialized.name
if (serverStylisCache[name] === undefined) {
serverStylisCache[name] = stylis(selector, serialized.styles)
if (selector === '') {
serverStylisCache[name] = stylis(serialized.styles)
} else {
serverStylisCache[name] = stylis(`${selector}{${serialized.styles}}`)
}
}
return serverStylisCache[name]
}
Expand Down Expand Up @@ -200,65 +231,6 @@ let createCache = (options: Options): EmotionCache => {
}
}

if (process.env.NODE_ENV !== 'production') {
// https://esbench.com/bench/5bf7371a4cd7e6009ef61d0a
const commentStart = /\/\*/g
const commentEnd = /\*\//g

stylis.use((context, content) => {
switch (context) {
case -1: {
while (commentStart.test(content)) {
commentEnd.lastIndex = commentStart.lastIndex

if (commentEnd.test(content)) {
commentStart.lastIndex = commentEnd.lastIndex
continue
}

throw new Error(
'Your styles have an unterminated comment ("/*" without corresponding "*/").'
)
}

commentStart.lastIndex = 0
break
}
}
})

stylis.use((context, content, selectors) => {
switch (context) {
case -1: {
const flag =
'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason'
const unsafePseudoClasses = content.match(
/(:first|:nth|:nth-last)-child/g
)

if (unsafePseudoClasses && cache.compat !== true) {
unsafePseudoClasses.forEach(unsafePseudoClass => {
const ignoreRegExp = new RegExp(
`${unsafePseudoClass}.*\\/\\* ${flag} \\*\\/`
)
const ignore = ignoreRegExp.test(content)

if (unsafePseudoClass && !ignore) {
console.error(
`The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${
unsafePseudoClass.split('-child')[0]
}-of-type".`
)
}
})
}

break
}
}
})
}

const cache: EmotionCache = {
key,
sheet: new StyleSheet({
Expand Down
120 changes: 39 additions & 81 deletions packages/cache/src/stylis-plugins.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,47 @@
// @flow
// https://github.com/thysultan/stylis.js/tree/master/plugins/rule-sheet
// inlined to avoid umd wrapper and peerDep warnings/installing stylis
// since we use stylis after closure compiler
const last = arr => (arr.length ? arr[arr.length - 1] : null)

import type { StylisPlugin } from './types'

const delimiter = '/*|*/'
const needle = delimiter + '}'

function toSheet(block) {
if (block) {
Sheet.current.insert(block + '}')
export let removeLabel = element => {
if (element.type === 'decl') {
var value = element.value
if (
// charcode for l
value.charCodeAt(0) === 108 &&
// charcode for b
value.charCodeAt(2) === 98
) {
// this ignores label
element.return = ''
element.value = ''
}
}
}

export let Sheet: { current: { +insert: string => void } } = {
current: (null: any)
}
const ignoreFlag =
'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason'

export let ruleSheet: StylisPlugin = (
context,
content,
selectors,
parents,
line,
column,
length,
ns,
depth,
at
) => {
switch (context) {
// property
case 1: {
switch (content.charCodeAt(0)) {
case 64: {
// @import
Sheet.current.insert(content + ';')
return ''
}
// charcode for l
case 108: {
// charcode for b
// this ignores label
if (content.charCodeAt(2) === 98) {
return ''
}
}
}
break
}
// selector
case 2: {
if (ns === 0) return content + delimiter
break
}
// at-rule
case 3: {
switch (ns) {
// @font-face, @page
case 102:
case 112: {
Sheet.current.insert(selectors[0] + content)
return ''
}
default: {
return content + (at === 0 ? delimiter : '')
}
}
}
case -2: {
content.split(needle).forEach(toSheet)
}
}
}
const isIgnoringComment = element =>
!!element &&
element.type === 'comm' &&
element.children.indexOf(ignoreFlag) > -1

export let removeLabel: StylisPlugin = (context, content) => {
if (
context === 1 &&
// charcode for l
content.charCodeAt(0) === 108 &&
// charcode for b
content.charCodeAt(2) === 98
// this ignores label
) {
return ''
export let createUnsafeSelectorsAlarm = cache => (element, index, children) => {
if (element.type !== 'rule') return

const unsafePseudoClasses = element.value.match(
/(:first|:nth|:nth-last)-child/g
)

if (unsafePseudoClasses && cache.compat !== true) {
const prevElement = index > 0 ? children[index - 1] : null
if (prevElement && isIgnoringComment(last(prevElement.children))) {
return
}
unsafePseudoClasses.forEach(unsafePseudoClass => {
console.error(
`The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${
unsafePseudoClass.split('-child')[0]
}-of-type".`
)
})
}
}
31 changes: 27 additions & 4 deletions packages/cache/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
// Definitions by: Junyoung Clare Jang <https://github.com/Ailrun>
// TypeScript Version: 2.2
import { Plugin as StylisPlugin, Prefix } from '@emotion/stylis'
import { EmotionCache } from '@emotion/utils'

export { StylisPlugin, Prefix, EmotionCache }
export { EmotionCache }

type StylisElement = {
type: string
value: string
props: string[]
root: StylisElement | null
children: StylisElement[]
line: number
column: number
length: number
return: string
}
type StylisPluginCallback = (
element: StylisElement,
index: number,
children: StylisElement[],
callback: StylisPluginCallback
) => string | undefined

type StylisPlugin = (
element: StylisElement,
index: number,
children: StylisElement[],
callback: StylisPluginCallback
) => string | undefined

export interface Options {
nonce?: string
stylisPlugins?: StylisPlugin | Array<StylisPlugin>
prefix?: Prefix
stylisPlugins?: StylisPlugin[]
key: string
container?: HTMLElement
speedy?: boolean
Expand Down
Loading

0 comments on commit 7c2a21a

Please sign in to comment.