Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: refactor styled-base Flow types to work again #1570

Merged
merged 8 commits into from Oct 27, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/red-chefs-camp.md
@@ -0,0 +1,6 @@
---
'@emotion/styled-base': patch
FezVrasta marked this conversation as resolved.
Show resolved Hide resolved
'@emotion/styled': patch
---

Fixed package's Flow types
1 change: 1 addition & 0 deletions .flowconfig
Expand Up @@ -23,4 +23,5 @@

[options]
suppress_comment=.*\\$FlowFixMe
suppress_comment=.*\\$FlowExpectError
sharedmemory.hash_table_pow=21
1 change: 1 addition & 0 deletions .flowconfig-ci
Expand Up @@ -24,5 +24,6 @@

[options]
suppress_comment=.*\\$FlowFixMe
suppress_comment=.*\\$FlowExpectError
server.max_workers=1
sharedmemory.hash_table_pow=21
21 changes: 21 additions & 0 deletions packages/styled-base/flow-tests/flow.js
@@ -0,0 +1,21 @@
// @flow
import * as React from 'react'
import createStyled from '../src'
import type { CreateStyledComponent } from '../src/utils'

export const valid: CreateStyledComponent = createStyled('div')

// $FlowExpectError: we can't cast a StyledComponent to string
export const invalid: string = createStyled('div')

const styled = createStyled('div')
type Props = { color: string }
const Div = styled<Props>({ color: props => props.color })

export const validProp = <Div color="red" />

// $FlowExpectError: color property should be a string
export const invalidProp = <Div color={2} />

// $FlowExpectError: we don't expose the private StyledComponent properties
export const invalidPropAccess = styled().__emotion_base
197 changes: 98 additions & 99 deletions packages/styled-base/src/index.js
Expand Up @@ -4,7 +4,8 @@ import type { ElementType } from 'react'
import {
getDefaultShouldForwardProp,
type StyledOptions,
type CreateStyled
type CreateStyled,
type PrivateStyledComponent
} from './utils'
import { withEmotionCache, ThemeContext } from '@emotion/core'
import { getRegisteredStyles, insertStyles } from '@emotion/utils'
Expand All @@ -17,12 +18,6 @@ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_liter

let isBrowser = typeof document !== 'undefined'

type StyledComponent = (
props: *
) => React.Node & {
withComponent(nextTag: ElementType, nextOptions?: StyledOptions): *
}

let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
if (process.env.NODE_ENV !== 'production') {
if (tag === undefined) {
Expand Down Expand Up @@ -55,7 +50,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
shouldForwardProp || getDefaultShouldForwardProp(baseTag)
const shouldUseAs = !defaultShouldForwardProp('as')

return function(): StyledComponent {
return function<P>(): PrivateStyledComponent<P> {
let args = arguments
let styles =
isReal && tag.__emotion_styles !== undefined
Expand All @@ -82,104 +77,107 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
}
}

const Styled: any = withEmotionCache((props, context, ref) => {
return (
<ThemeContext.Consumer>
{theme => {
const finalTag = (shouldUseAs && props.as) || baseTag

let className = ''
let classInterpolations = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
for (let key in props) {
mergedProps[key] = props[key]
// $FlowFixMe: we need to cast StatelessFunctionalComponent to our PrivateStyledComponent class
const Styled: PrivateStyledComponent<P> = withEmotionCache(
(props, context, ref) => {
return (
<ThemeContext.Consumer>
{theme => {
const finalTag = (shouldUseAs && props.as) || baseTag

let className = ''
let classInterpolations = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
for (let key in props) {
mergedProps[key] = props[key]
}
mergedProps.theme = theme
}

if (typeof props.className === 'string') {
className = getRegisteredStyles(
context.registered,
classInterpolations,
props.className
)
} else if (props.className != null) {
className = `${props.className} `
}
mergedProps.theme = theme
}

if (typeof props.className === 'string') {
className = getRegisteredStyles(
const serialized = serializeStyles(
styles.concat(classInterpolations),
context.registered,
classInterpolations,
props.className
mergedProps
)
} else if (props.className != null) {
className = `${props.className} `
}

const serialized = serializeStyles(
styles.concat(classInterpolations),
context.registered,
mergedProps
)
const rules = insertStyles(
context,
serialized,
typeof finalTag === 'string'
)
className += `${context.key}-${serialized.name}`
if (targetClassName !== undefined) {
className += ` ${targetClassName}`
}

const finalShouldForwardProp =
shouldUseAs && shouldForwardProp === undefined
? getDefaultShouldForwardProp(finalTag)
: defaultShouldForwardProp

let newProps = {}

for (let key in props) {
if (shouldUseAs && key === 'as') continue

if (
// $FlowFixMe
finalShouldForwardProp(key)
) {
newProps[key] = props[key]
const rules = insertStyles(
context,
serialized,
typeof finalTag === 'string'
)
className += `${context.key}-${serialized.name}`
if (targetClassName !== undefined) {
className += ` ${targetClassName}`
}
}

newProps.className = className
const finalShouldForwardProp =
shouldUseAs && shouldForwardProp === undefined
? getDefaultShouldForwardProp(finalTag)
: defaultShouldForwardProp

newProps.ref = ref || props.innerRef
if (process.env.NODE_ENV !== 'production' && props.innerRef) {
console.error(
'`innerRef` is deprecated and will be removed in a future major version of Emotion, please use the `ref` prop instead' +
(identifierName === undefined
? ''
: ` in the usage of \`${identifierName}\``)
)
}

const ele = React.createElement(finalTag, newProps)
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
let newProps = {}

for (let key in props) {
if (shouldUseAs && key === 'as') continue

if (
// $FlowFixMe
finalShouldForwardProp(key)
) {
newProps[key] = props[key]
}
}

newProps.className = className

newProps.ref = ref || props.innerRef
if (process.env.NODE_ENV !== 'production' && props.innerRef) {
console.error(
'`innerRef` is deprecated and will be removed in a future major version of Emotion, please use the `ref` prop instead' +
(identifierName === undefined
? ''
: ` in the usage of \`${identifierName}\``)
)
}
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
)
}
return ele
}}
</ThemeContext.Consumer>
)
})

const ele = React.createElement(finalTag, newProps)
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
}
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
)
}
return ele
}}
</ThemeContext.Consumer>
)
}
)

Styled.displayName =
identifierName !== undefined
Expand All @@ -204,7 +202,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
) {
return 'NO_COMPONENT_SELECTOR'
}
// $FlowFixMe
// $FlowFixMe: coherce undefined to string
FezVrasta marked this conversation as resolved.
Show resolved Hide resolved
return `.${targetClassName}`
}
})
Expand All @@ -220,6 +218,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
: options
)(...styles)
}

return Styled
}
}
Expand Down
44 changes: 30 additions & 14 deletions packages/styled-base/src/utils.js
@@ -1,9 +1,32 @@
// @flow
import * as React from 'react'
import type { ElementType } from 'react'
import isPropValid from '@emotion/is-prop-valid'

export type Interpolations = Array<any>

export type StyledOptions = {
label?: string,
shouldForwardProp?: string => boolean,
target?: string
}

export type StyledComponent<P> = React.StatelessFunctionalComponent<P> & {
defaultProps: any,
toString: () => string,
withComponent: (
nextTag: ElementType,
nextOptions?: StyledOptions
) => StyledComponent<P>
}

export type PrivateStyledComponent<P> = StyledComponent<P> & {
__emotion_real: StyledComponent<P>,
__emotion_base: any,
__emotion_styles: any,
__emotion_forwardProp: any
}

const testOmitPropsOnStringTag = isPropValid
const testOmitPropsOnComponent = (key: string) =>
key !== 'theme' && key !== 'innerRef'
Expand All @@ -17,19 +40,12 @@ export const getDefaultShouldForwardProp = (tag: React.ElementType) =>
? testOmitPropsOnStringTag
: testOmitPropsOnComponent

export type StyledOptions = {
label?: string,
shouldForwardProp?: string => boolean,
target?: string
}

type CreateStyledComponent = (...args: Interpolations) => *

type BaseCreateStyled = (
tag: React.ElementType,
options?: StyledOptions
) => CreateStyledComponent
export type CreateStyledComponent = <P>(
...args: Interpolations
) => StyledComponent<P>

export type CreateStyled = BaseCreateStyled & {
[key: string]: CreateStyledComponent
export type CreateStyled = {
(tag: React.ElementType, options?: StyledOptions): CreateStyledComponent,
[key: string]: CreateStyledComponent,
bind: () => CreateStyled
}
13 changes: 13 additions & 0 deletions packages/styled/flow-tests/flow.js
@@ -0,0 +1,13 @@
// @flow
import * as React from 'react'
import styled from '../src'

type Props = { color: string }
const Foo = styled.div<Props>({
color: 'red'
})

export const valid = <Foo color="red" />

// $FlowExpectError: color must be string
export const invalid = <Foo color={2} />
2 changes: 1 addition & 1 deletion packages/styled/src/index.js
Expand Up @@ -5,7 +5,7 @@ import { tags } from './tags'
// bind it to avoid mutating the original function
const newStyled = styled.bind()

tags.forEach(tagName => {
tags.forEach((tagName: string) => {
FezVrasta marked this conversation as resolved.
Show resolved Hide resolved
newStyled[tagName] = newStyled(tagName)
})

Expand Down
2 changes: 1 addition & 1 deletion site/src/components/Title.js
Expand Up @@ -3,7 +3,7 @@ import styled from '@emotion/styled'
import { constants, mq, colors } from '../utils/style'
import * as markdownComponents from '../utils/markdown-styles'

export default styled(markdownComponents.h1)(
export default styled(markdownComponents.h1)<{}>(
mq({
paddingTop: 0,
marginTop: 0,
Expand Down