Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
styleh-components / src / styles / StyledComponent.tsx
Size: Mime:
import React from 'react'
// // import IS_PIPELINE_SERVER from 'config/env/IS_PIPELINE_SERVER'
import { EMPTY_STRING, isObj, isString, isArray } from 'exotic'
import { fliphash } from 'chain-able-boost'
// import persistance from '@skava/persistence'
import { toComponentName } from '@skava/identifier/dist/index'
//
import { fromStringToStyle } from '../deps/fromStringToStyleTag'
// import { transpileStyles } from './transpileStyles'

const persistance = new Map()

/**
 * @todo - these should be in an mst store to be rehydrated?
 *
 * {String}
 * @type {Map<StyleStringToParse, CSS>}
 */
const styleRegistry = new Map()
/**
 * {Element}
 * @type {Map<CSS, StyledComponent>}
 */
const styleBlockRegistry = new Map()
const globalStyleBlocks = new Set()
// @todo - set keys with fliphash
const metaRegistry = new Map()
const cssStringList = []

/**
 * @see styleRegistry
 * @description persistance [key, value]
 *
 * @param {StylesNextGen} styles next-gen styles
 * @param {CSS} css transpiled
 * @return {CSS} same-in-same-out
 */
function saveTranspiledStyles(styles, css) {
  styleRegistry.set(styles, css)

  const savedStyles = persistance.get('styles')

  if (isArray(savedStyles)) {
    savedStyles.push([styles, css])
    persistance.set('styles', savedStyles)
  }

  return css
}

/**
 * @todo should cache StyledComponents as well as Style
 *
 * @param {String | *} css
 * @return {true | false | JSX.Element<CSSStyleSheet>}
 */
function getElementFromRegistry(css) {
  if (css === '') {
    return false
  } else if (styleBlockRegistry.has(css)) {
    return styleBlockRegistry.get(css)
  } else {
    return false
  }
}

let hashed = 0
const HOT_RELOAD_STYLES = false
// let HOT_RELOAD_STYLES = !IS_PIPELINE_SERVER
// let HOT_RELOAD_STYLES = process.env.HOT === 'true'
// process.env.NODE_ENV === 'development'

// rehydrateFromPersistance()

export interface StyledEhComponentProps {
  styleseh: string
  className?: string
  key?: string
  'data-for-component': any
}

class StyledComponent extends React.Component<StyledEhComponentProps> {
  // static propTypes = {
  //   styleseh: string.isRequired,
  //   // or #id
  //   className: string,
  //   // component: Object.optional,
  // }
  /**
   * @type css only
   */
  css = EMPTY_STRING

  /**
   * @description currently only autogenerated, see types.className
   */
  selector = EMPTY_STRING

  /**
   * @description for persisting state on server side render
   */
  state = {
    css: EMPTY_STRING,
  }

  hasMounted: any

  shouldComponentUpdate(nextProps) {
    return (
      HOT_RELOAD_STYLES ||
      (nextProps && nextProps.styleseh !== this.props.styleseh)
    )
  }
  // @todo
  componentWillUnmount() {
    // not sure if we can change props on a component...
    // so we are using a meta registry
    if (metaRegistry.has(this.css) === true) {
      const meta = metaRegistry.get(this.css)
      meta.hasMounted = false
    }
  }
  // @todo
  // 1. we render a style
  // 2. we go to another page
  // 3. that style was unmounted
  // 4. we already rendered it
  // 5. we don't show it again <- needs to go in a globals
  componentDidMount() {
    if (metaRegistry.has(this.css) === true) {
      const meta = metaRegistry.get(this.css)
      meta.hasMounted = true
    }
  }
  // mucks with deep force
  // componentDidUpdate() {
  //   if (HOT_RELOAD_STYLES === true) {
  //     this.loadStylesIfNeeded(this.props.styleseh)
  //     // this.forceUpdate()
  //   }
  // }
  componentWillMount() {
    this.loadStylesIfNeeded(this.props.styleseh)
    return Promise.resolve(this.css)
  }

  /**
   * @description loads from registry if possible
   *
   * @param {String} styles styles to parse
   * @return {void}
   *
   * @see this.handleStyles
   */
  loadStylesIfNeeded(styles) {
    // if state was loaded server side
    if (this.css === EMPTY_STRING && this.css !== this.state.css) {
      this.css = this.state.css
    }

    // if observable already is already set up on our instance
    if (this.css !== EMPTY_STRING && !HOT_RELOAD_STYLES) {
      return
    }

    // load from registry if we've already transpiled
    if (styleRegistry.has(styles)) {
      this.css = styleRegistry.get(styles)
    }

    // gotta load em
    this.handleStyles(styles)
  }

  /**
   * @description transpile as needed, and updates state
   * @param {String} styles
   * @return {void}
   */
  handleStyles(styles) {
    // console.dir({ handlingStyles: css })
    // const css = transpileStyles(styles)
    const css = styles

    // save to global registry cache
    saveTranspiledStyles(this.props.styleseh, css)

    // set property, if observable
    this.css = css

    // if we can set state, do it
    if (this.hasMounted || HOT_RELOAD_STYLES) {
      this.setState({ css })
    }
  }

  // componentDidUpdate() {
  //   if (metaRegistry.has(this.css)) {
  //     // meta
  //     const meta = Object.seal({
  //       hasMounted: true,
  //       renderCount: 0,
  //       hashed,
  //     })
  //     metaRegistry.set(this.css, meta)
  //   }
  // }

  /* @lint dev */
  /* eslint-disable max-statements */
  /**
   * @see https://github.com/threepointone/glamor/blob/master/docs/server.md
   * @description if HOT_RELOAD_STYLESforce reload
   * @return {JSX.Element<CSSStyleSheet>}
   */
  render() {
    const found = getElementFromRegistry(this.css)

    // if (HOT_RELOAD_STYLES) {
    //   hashed += 1
    //   const attributes = Object.seal({
    //     key: hashed,
    //   })
    //   const rendered = fromStringToStyle(this.css, attributes)
    //   styleBlockRegistry.set(this.css, rendered)
    //   return rendered
    // }

    if (found === false) {
      hashed += 1
      // jsx blocks
      const attributes = Object.seal({
        key: hashed,
      })
      const rendered = fromStringToStyle(this.css, attributes)
      styleBlockRegistry.set(this.css, rendered)
      return rendered
    } else {
      return found
    }
  }
}

let logged = false
// Array.from(stylez.metaRegistry.entries()).filter((kv) => {
//   const [key, value] = kv
//   // console.log({key, value})
//   return value.duplicationCount > 1
// })

/**
 * @todo !!!!!!!!!!!! should be loading webworker
 *
 * @todo - processing vs memory
 * if we hash every time, more processing, less memory
 * no processing...
 */
function markDuplicationIfNeeded(css, named) {
  const isInStyleStrings = cssStringList.includes(css) === true
  const isInStyleRegistry = styleRegistry.has(css) === true

  if (metaRegistry.has(css) === true) {
    const meta = metaRegistry.get(css)

    if (meta.names.includes(named) === false) {
      // !!! USED IN A DIFF COMPONENT !!!
      meta.REQUIRES_SOLUTION = true
    } else {
      meta.names.push(named)
    }
    // if (meta.hasMounted === false) {
    //   meta.duplicationCount += 1
    // }
    meta.duplicationCount += 1
  } else {
    const hash = fliphash(css)

    // meta
    const meta = Object.seal({
      REQUIRES_SOLUTION: false,
      names: [named],
      hasMounted: true,
      renderCount: 0,
      duplicationCount: 1,
      hashed,
      hash,
      isInStyleStrings,
      isInStyleRegistry,
    })
    metaRegistry.set(css, meta)
  }

  // return metaRegistry.get(css)
}

/**
 * @see fromStringToStyle
 * @see toComponentName
 *
 * @param {String} css styles to transpile as a <style> tag
 * @param {React.ComponentClass} [Target=undefined]
 * @return {StyledComponent}
 */
function asStyledComponent(css, Target = undefined) {
  // const hash = fliphash(css)
  const name = toComponentName(Target)
  markDuplicationIfNeeded(css, name)
  const meta = metaRegistry.get(css)
  const { isInStyleRegistry, isInStyleStrings, REQUIRES_SOLUTION } = meta

  /**
   * @note it is pulling from registry in the render, but not hoisting
   * @todo if we provide a context per page & large widget, should be easy hoist
   * OR provide `namespace` in the  styled.namespace
   *
   * @todo would also work to use for snapshots to dedupe
   * @todo can use chain includesCount for how many times we have the style to more easily dedupe
   */
  if (isInStyleRegistry && REQUIRES_SOLUTION === false) {
    if (logged === false) {
      console.warn(`
      already had these styles connected.
      update state with an observable,
      or refresh
      (should connect HMR.accept to this module)
    `)
      logged = true
    }

    // else
    return ''
  } else if (isInStyleStrings && REQUIRES_SOLUTION === false) {
    /**
     * @todo - this is when we would hoist original
     */
    if (logged === false) {
      console.warn(`already transpiled these styles`)
      logged = true
    }
    globalStyleBlocks.add(css)
    return ''
  }

  cssStringList.push(css)

  // @todo - should add fromStyleToKey
  const node = (
    <StyledComponent key={css} styleseh={css} data-for-component={name} />
  )
  return node
}

// if (typeof window === 'object') {
//   window.React = React
//   window.stylez = {
//     styleRegistry,
//     styleBlockRegistry,
//     globalStyleBlocks,
//     metaRegistry,
//     cssStringList,
//   }
// }

export { StyledComponent, asStyledComponent }
export default asStyledComponent