rofrischmann/fela

View on GitHub
packages/fela-monolithic/src/index.js

Summary

Maintainability
D
1 day
Test Coverage
import { objectReduce } from 'fast-loops'
import { cssifyObject } from 'css-in-js-utils'
import isPlainObject from 'isobject'
import {
  isSupport,
  isMediaQuery,
  isNestedSelector,
  isUndefinedValue,
  normalizeNestedProperty,
  processStyleWithPlugins,
  generateCombinedMediaQuery,
  generateCSSSelector,
  RULE_TYPE,
} from 'fela-utils'

import generateMonolithicClassName from './generateMonolithicClassName'

function useMonolithicRenderer(renderer, prettySelectors = false) {
  renderer.prettySelectors = prettySelectors

  renderer._renderStyleToCache = (
    className,
    style,
    pseudo = '',
    media = '',
    support = ''
  ) => {
    const ruleSet = objectReduce(
      style,
      (ruleset, value, property) => {
        if (isPlainObject(value)) {
          if (isNestedSelector(property)) {
            renderer._renderStyleToCache(
              className,
              value,
              pseudo + normalizeNestedProperty(property),
              media,
              support
            )
          } else if (isMediaQuery(property)) {
            const combinedMediaQuery = generateCombinedMediaQuery(
              media,
              property.slice(6).trim()
            )

            renderer._renderStyleToCache(
              className,
              value,
              pseudo,
              combinedMediaQuery,
              support
            )
          } else if (isSupport(property)) {
            const combinedSupport = generateCombinedMediaQuery(
              support,
              property.slice(9).trim()
            )
            renderer._renderStyleToCache(
              className,
              value,
              pseudo,
              media,
              combinedSupport
            )
          } else {
            console.warn(`The object key "${property}" is not a valid nested key in Fela. 
Maybe you forgot to add a plugin to resolve it? 
Check http://fela.js.org/docs/basics/Rules.html#styleobject for more information.`)
          }
        } else if (!isUndefinedValue(value)) {
          ruleset[property] = value
        }

        return ruleset
      },
      {}
    )

    if (Object.keys(ruleSet).length > 0) {
      const css = cssifyObject(ruleSet)
      const selector = generateCSSSelector(
        className,
        pseudo,
        renderer.specificityPrefix
      )

      const change = {
        type: RULE_TYPE,
        className,
        selector,
        declaration: css,
        media,
        pseudo,
        support,
      }

      const declarationReference = selector + media + support
      renderer.cache[declarationReference] = change
      renderer._emitChange(change)
    }
  }

  renderer._renderStyleToClassNames = ({ _className, ...style }, rule) => {
    if (Object.keys(style).length < 1) {
      return ''
    }

    const localRulePrefix =
      renderer.prettySelectors && (rule.ruleName || rule.name)
        ? `${rule.ruleName || rule.name}_`
        : ''

    const className = generateMonolithicClassName(
      style,
      (renderer.selectorPrefix || '') + (rule.selectorPrefix || localRulePrefix)
    )

    if (!renderer.cache.hasOwnProperty(className)) {
      renderer._renderStyleToCache(className, style)
      renderer.cache[className] = {}
    }

    return (_className ? _className + ' ' : '') + className
  }

  renderer.renderRule = (rule, props = {}) =>
    renderer._renderStyle(rule(props, renderer), props, rule)

  renderer._renderStyle = (style = {}, props = {}, rule) => {
    const processedStyle = processStyleWithPlugins(
      renderer,
      style,
      RULE_TYPE,
      props
    )

    return renderer._renderStyleToClassNames(processedStyle, rule || {})
  }

  renderer.subscribe((event) => {
    if (event.type === 'REHYDRATATION_FINISHED') {
      // Repair cache for monolithic usage
      renderer.cache = Object.keys(renderer.cache).reduce((acc, key) => {
        const item = renderer.cache[key]
        if (item.type === 'RULE') {
          return Object.assign(acc, {
            [item.className + item.media + item.support]: item,
          })
        }
        return Object.assign(acc, { [key]: item })
      }, {})
    }
  })

  return renderer
}

export default function monolithic(options = {}) {
  return (renderer) =>
    useMonolithicRenderer(
      renderer,
      process.env.NODE_ENV !== 'production' && options.prettySelectors
    )
}