Skip to content

Commit

Permalink
Restructured types to greatly reduce number of types created during c…
Browse files Browse the repository at this point in the history
…ompilation

BREAKING CHANGE: There are a few breaking changes, see below

* withTheme now infers types properly and may require removing the manually specified generic parameter
* The Theme generic parameter has been removed from a number of types exported from `@emotion/styled` and `@emotion/styled-base`.
* Introduced new CreateThemedStyled type which is exported from emotion-theming, use type to create your own themed `styled` export. See updated docs
* WithTheme should be imported from emotion-theming
* CreateStyledComponentExtrinsic, CreateStyledComponentIntrinsic and CreateStyledComponentBase all have been replaced with CreateStyledComponent
  • Loading branch information
Jake Ginnivan committed Sep 12, 2019
1 parent 95b37bb commit 9c9d70a
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 262 deletions.
5 changes: 3 additions & 2 deletions docs/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ However, you can define a theme type by creating another `styled` instance.
_styled.tsx_

```tsx
import styled, { CreateStyled } from '@emotion/styled'
import styled from '@emotion/styled'
import { CreateThemedStyled } from 'emotion-theming'

type Theme = {
color: {
Expand All @@ -247,7 +248,7 @@ type Theme = {
// ...
}

export default styled as CreateStyled<Theme>
export default styled as CreateThemedStyled<Theme>
```

_Button.tsx_
Expand Down
52 changes: 41 additions & 11 deletions packages/emotion-theming/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

import * as React from 'react'

import { AddOptionalTo, PropsOf } from './helper'
import {
StyledComponent,
StyledOptions,
CreateStyledComponent
} from '@emotion/styled'

export interface ThemeProviderProps<Theme> {
theme: Partial<Theme> | ((outerTheme: Theme) => Theme)
Expand All @@ -14,17 +18,43 @@ export function ThemeProvider<Theme>(
props: ThemeProviderProps<Theme>
): React.ReactElement

/**
* @todo Add more constraint to C so that
* this function only accepts components with theme props.
*/
export function withTheme<C extends React.ComponentType<any>>(
component: C
): React.SFC<AddOptionalTo<PropsOf<C>, 'theme'>>
export function withTheme<P extends object>(
component: React.ComponentType<P & { theme: any }>
): // Return type needs to have an optional theme so it is composible
React.FC<Omit<P, 'theme'> & { theme?: any }>

export interface EmotionTheming<Theme> {
ThemeProvider(props: ThemeProviderProps<Theme>): React.ReactElement
withTheme<C extends React.ComponentType<any>>(
component: C
): React.SFC<AddOptionalTo<PropsOf<C>, 'theme'>>
withTheme<P extends object>(
component: React.ComponentType<P & { theme: Theme }>
): React.FC<Omit<P, 'theme'> & { theme?: any }>
}

/**
* @desc
* This function accepts `InnerProps`/`Tag` to infer the type of `tag`,
* and accepts `ExtraProps` for user who use string style
* to be able to declare extra props without using
* `` styled('button')<ExtraProps>`...` ``, which does not supported in
* styled-component VSCode extension.
* If your tool support syntax highlighting for `` styled('button')<ExtraProps>`...` ``
* it could be more efficient.
*/
export interface CreateThemedStyled<Theme extends object> {
<Props extends object, ExtraProps = {}>(
tag: React.ComponentType<Props>,
options?: StyledOptions
): CreateStyledComponent<Props, ExtraProps & { theme: Theme }>

<Tag extends keyof JSX.IntrinsicElements, ExtraProps = {}>(
tag: Tag,
options?: StyledOptions
): CreateStyledComponent<
JSX.IntrinsicElements[Tag],
ExtraProps & { theme: Theme }
>
}

export type WithTheme<P, T> = P extends { theme: infer Theme }
? P & { theme: Exclude<Theme, undefined> }
: P & { theme: T }
18 changes: 14 additions & 4 deletions packages/serialize/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import { RegisteredCache, SerializedStyles } from '@emotion/utils'
import * as CSS from 'csstype'

import { Equal } from './helper'

export { RegisteredCache, SerializedStyles }

export type CSSProperties = CSS.PropertiesFallback<number | string>
Expand Down Expand Up @@ -63,7 +61,19 @@ export interface ObjectInterpolation<MP>
extends CSSPropertiesWithMultiValues,
CSSPseudos<MP>,
CSSOthersObject<MP> {}
export type FunctionInterpolation<MP> = (mergedProps: MP) => Interpolation<MP>
export interface FunctionInterpolation<MP> {
(mergedProps: MP):
| null
| undefined
| boolean
| number
| string
| ComponentSelector
| Keyframes
| SerializedStyles
| ArrayInterpolation<MP>
| ObjectInterpolation<MP>
}

export type Interpolation<MP = undefined> =
| null
Expand All @@ -76,7 +86,7 @@ export type Interpolation<MP = undefined> =
| SerializedStyles
| ArrayInterpolation<MP>
| ObjectInterpolation<MP>
| Equal<MP, undefined, never, FunctionInterpolation<MP>>
| FunctionInterpolation<MP>

export function serializeStyles<MP>(
args: Array<TemplateStringsArray | Interpolation<MP>>,
Expand Down
109 changes: 20 additions & 89 deletions packages/styled-base/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
import { ComponentSelector, Interpolation } from '@emotion/serialize'
import * as React from 'react'

import { Omit, Overwrapped, PropsOf } from './helper'

export {
ArrayInterpolation,
CSSObject,
Expand All @@ -26,105 +24,38 @@ export {

export { ComponentSelector, Interpolation }

type JSXInEl = JSX.IntrinsicElements

export type WithTheme<P, T> = P extends { theme: infer Theme }
? P & { theme: Exclude<Theme, undefined> }
: P & { theme: T }

export interface StyledOptions {
label?: string
shouldForwardProp?(propName: string): boolean
target?: string
}

export interface StyledComponent<InnerProps, StyleProps, Theme extends object>
extends React.SFC<InnerProps & StyleProps & { theme?: Theme }>,
export interface StyledComponent<InnerProps, StyleProps>
extends React.SFC<InnerProps & StyleProps>,
ComponentSelector {
/**
* @desc this method is type-unsafe
*/
withComponent<NewTag extends keyof JSXInEl>(
withComponent<NewTag extends keyof JSX.IntrinsicElements>(
tag: NewTag
): StyledComponent<JSXInEl[NewTag], StyleProps, Theme>
withComponent<Tag extends React.ComponentType<any>>(
tag: Tag
): StyledComponent<PropsOf<Tag>, StyleProps, Theme>
): StyledComponent<JSX.IntrinsicElements[NewTag], StyleProps>
withComponent<Props extends object>(
tag: React.ComponentType<Props>
): StyledComponent<Props, StyleProps>
}

type ReactClassPropKeys = keyof React.ClassAttributes<any>

interface CreateStyledComponentBaseThemeless<InnerProps, ExtraProps> {
<
StyleProps extends Omit<
Overwrapped<InnerProps, StyleProps>,
ReactClassPropKeys
> = Omit<InnerProps & ExtraProps, ReactClassPropKeys>,
Theme extends object = object
>(
...styles: Array<Interpolation<WithTheme<StyleProps, Theme>>>
): StyledComponent<InnerProps, StyleProps, Theme>
<
StyleProps extends Omit<
Overwrapped<InnerProps, StyleProps>,
ReactClassPropKeys
> = Omit<InnerProps & ExtraProps, ReactClassPropKeys>,
Theme extends object = object
>(
export interface CreateStyledComponent<InnerProps, ExtraProps> {
<StyleProps extends object>(
...styles: Array<Interpolation<InnerProps & StyleProps & ExtraProps>>
): StyledComponent<InnerProps, StyleProps>
<StyleProps extends object>(
template: TemplateStringsArray,
...styles: Array<Interpolation<WithTheme<StyleProps, Theme>>>
): StyledComponent<InnerProps, StyleProps, Theme>
...styles: Array<Interpolation<InnerProps & StyleProps & ExtraProps>>
): StyledComponent<InnerProps, StyleProps>
}

interface CreateStyledComponentBaseThemed<
InnerProps,
ExtraProps,
StyledInstanceTheme extends object
> {
<
StyleProps extends Omit<
Overwrapped<InnerProps, StyleProps>,
ReactClassPropKeys
> = Omit<InnerProps & ExtraProps, ReactClassPropKeys>
>(
...styles: Array<Interpolation<WithTheme<StyleProps, StyledInstanceTheme>>>
): StyledComponent<InnerProps, StyleProps, StyledInstanceTheme>
<
StyleProps extends Omit<
Overwrapped<InnerProps, StyleProps>,
ReactClassPropKeys
> = Omit<InnerProps & ExtraProps, ReactClassPropKeys>
>(
template: TemplateStringsArray,
...styles: Array<Interpolation<WithTheme<StyleProps, StyledInstanceTheme>>>
): StyledComponent<InnerProps, StyleProps, StyledInstanceTheme>
}

export type CreateStyledComponentBase<
InnerProps,
ExtraProps,
StyledInstanceTheme extends object
> =
// this "reversed" condition checks if StyledInstanceTheme was already parametrized when using CreateStyled
object extends StyledInstanceTheme
? CreateStyledComponentBaseThemeless<InnerProps, ExtraProps>
: CreateStyledComponentBaseThemed<
InnerProps,
ExtraProps,
StyledInstanceTheme
>

export type CreateStyledComponentIntrinsic<
Tag extends keyof JSXInEl,
ExtraProps,
Theme extends object
> = CreateStyledComponentBase<JSXInEl[Tag], ExtraProps, Theme>
export type CreateStyledComponentExtrinsic<
Tag extends React.ComponentType<any>,
ExtraProps,
Theme extends object
> = CreateStyledComponentBase<PropsOf<Tag>, ExtraProps, Theme>

/**
* @desc
* This function accepts `InnerProps`/`Tag` to infer the type of `tag`,
Expand All @@ -135,16 +66,16 @@ export type CreateStyledComponentExtrinsic<
* If your tool support syntax highlighting for `` styled('button')<ExtraProps>`...` ``
* it could be more efficient.
*/
export interface CreateStyled<Theme extends object = any> {
<Tag extends React.ComponentType<any>, ExtraProps = {}>(
tag: Tag,
export interface CreateStyled {
<Props extends object, ExtraProps = {}>(
component: React.ComponentType<Props>,
options?: StyledOptions
): CreateStyledComponentExtrinsic<Tag, ExtraProps, Theme>
): CreateStyledComponent<Props, ExtraProps>

<Tag extends keyof JSXInEl, ExtraProps = {}>(
<Tag extends keyof JSX.IntrinsicElements, ExtraProps = {}>(
tag: Tag,
options?: StyledOptions
): CreateStyledComponentIntrinsic<Tag, ExtraProps, Theme>
): CreateStyledComponent<JSX.IntrinsicElements[Tag], ExtraProps>
}

declare const styled: CreateStyled
Expand Down
Loading

0 comments on commit 9c9d70a

Please sign in to comment.