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 (#1501)

* Restructured types to greatly reduce number of types created during compilation

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

* Fixing tests

* Fixed a bunch of tests and improved TypeScript docs

* Updated a bunch of the TypeScript docs

* Removed WithTheme type, not sure it's usage and there is no tests

* Few small cleanups around styled-base

* Fixed tests in a few more packages

* Fixed serialise tests after changes in #1236

* Removed failing redundant test in sheet typescript tests.

It is failing with the same compilation error as the previous line, but formatting is causing the test failure

* Removed line with expected error, I am not sure the reason it should be failing.

* Need to bump the typescript version for styled-base for the union type test

* TypeScript tests passing

* Upgrade build image version to get newer version of yarn

* fix: styled component with static API

* Added changes in https://github.com/JakeGinnivan/emotion/pull/1/files to other functions with similar signatures

* fix: type issue where styled component passed in

* Add some additional tests around theming and fix them

* Restrict css function to css interpolation

* Fixed emotion-theming linting issue

* Reversed some incorrect type changes, withComponent has to include the previous components props otherwise styles on the original component may fail at runtime

* Fixed some accidently formatted package.json files

* Allowed theming of CreateStyled and StyledTags

* Restructured generic type params to make it explicit about what component props should transfer with withComponent and which shouldn't

Added tests

* Cleaned up some tests and added additional assertions

* Default the type of SpecificComponentProps in StyledComponent

* Reverted changes around ThemeProvider and added tests

* Added changeset

* Fixed ThemeProvider after revert

* Update tslint rules to fix error

* Fixed linting issues

* Fixed import path for css and clarified docs

* Added comment about fragment shorthand without babel being a typescript limitation

* Removed breaking change around some of the internal types

It's implementation detail user doesn't need to know

* Reverted changes around removing function interpolation from the  return types of function interpolation

* Renamed Omit to DistributiveOmit

To make it clear it's different to the inbuilt Omit

* Removed duplicate intersected type

* Renamed all usages of SFC to FC

* Fixed poor grammar

* Ignore lint rule rather than exporting type

* Updated generic constraints

* Reverted TypeScript version bump in create-emotion types

* Sync docs and test code

* Constrained Theme to extend {}

* Add tests for broken examples in #1298

* Fix typo

* Add test which verifies #1226 is fixed by type changes
  • Loading branch information
Jake Ginnivan authored and Andarist committed Nov 4, 2019
1 parent e67a5be commit a72e6dc
Show file tree
Hide file tree
Showing 48 changed files with 616 additions and 498 deletions.
31 changes: 31 additions & 0 deletions .changeset/long-apes-admire/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"releases": [
{ "name": "@emotion/css", "type": "minor" },
{ "name": "emotion-theming", "type": "minor" },
{ "name": "@emotion/serialize", "type": "minor" },
{ "name": "@emotion/styled-base", "type": "minor" },
{ "name": "@emotion/styled", "type": "minor" }
],
"dependents": [
{
"name": "babel-plugin-emotion",
"type": "patch",
"dependencies": ["@emotion/serialize"]
},
{
"name": "@emotion/core",
"type": "patch",
"dependencies": [
"@emotion/css",
"emotion-theming",
"@emotion/serialize",
"@emotion/styled"
]
},
{
"name": "create-emotion",
"type": "patch",
"dependencies": ["@emotion/serialize"]
}
]
}
31 changes: 31 additions & 0 deletions .changeset/long-apes-admire/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
TypeScript types have been restructured. These changes:

- Reduce build times when using emotion
- In many cases remove the need for manually specifying generic parameters for your emotion components.

If you encounter build issues after upgrade, try removing any manually specified generic types and let them be inferred. Otherwise refer to the breaking changes list below.

## Improvements

- useTheme added to EmotionTheming interface and can now create your own closed variation of withTheme. More information in the docs under the theming section.
- Union types as props are better supported and should be inferred properly
- Build times should be reduced significantly in larger projects.

## Breaking changes

- withTheme can now have the Theme type specified when calling it. For example `withTheme<MyTheme>(MyComponent)`

**Breaking change:** Generic argument changed, if you were specifying the ComponentType you will need to remove the generic parameter. Recommend following example setup in the TypeScript docs under theming section

- `css` function has been restricted to prevent passing of invalid types
- `CreateStyled` functions no longer take a second `ExtraProps` argument. Instead move it to after the create styled call. For example

`styled<typeof MyComponent, ExtraProps>(MyComponent)({})`
to
`styled(MyComponent)<ExtraProps>({})`

- `StyledComponent` type no longer supports the third generic `Theme` parameter. Instead add the `theme` prop to the first `Props` argument. For example:

`StyledComponent<Props, {}, MyTheme>`
to
`StyledComponent<Props & { theme?: MyTheme }>`
9 changes: 5 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
flow:
docker:
- image: circleci/node:10.6.0-browsers
- image: circleci/node:10.16.3-browsers
working_directory: ~/repo
steps:
- checkout
Expand All @@ -25,7 +25,7 @@ jobs:

test:
docker:
- image: circleci/node:10.6.0
- image: circleci/node:10.16.3
working_directory: ~/repo
steps:
- checkout
Expand All @@ -48,7 +48,7 @@ jobs:

test_dist:
docker:
- image: circleci/node:10.6.0
- image: circleci/node:10.16.3
working_directory: ~/repo
steps:
- checkout
Expand All @@ -66,7 +66,7 @@ jobs:

lint_and_typescript:
docker:
- image: circleci/node:10.6.0
- image: circleci/node:10.16.3
working_directory: ~/repo
steps:
- checkout
Expand All @@ -77,6 +77,7 @@ jobs:
- v4-dependencies-
- run: yarn install --pure-lockfile
- run: yarn lint:check
- run: yarn test:typescript

workflows:
version: 2
Expand Down
117 changes: 68 additions & 49 deletions docs/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,21 @@ To make the css prop work with pure TypeScript (without babel plugin) you need t

```tsx
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { jsx } from '@emotion/core'

<div css={{ background: 'black' }} />
```

As a result you may be not able to use react fragment shorthand syntax - `<></>`, but still you can use `<Fragment></Fragment>`.
This is a limitation of the TypeScript compiler not being able to independently specify jsx pragma and jsxFrag pragma.

You can still use the css helper and pass the className yourself (ensure you are importing from the `@emotion` package, not `@emotion/core`).

```tsx
import { css } from '@emotion'

<div className={css({ background: 'black' })} />
```

## @emotion/styled

Expand Down Expand Up @@ -103,28 +114,20 @@ type ImageProps = {
width: number
}

const Image0 = styled('div')`
width: ${(props: ImageProps) => props.width};
background: url(${(props: ImageProps) => props.src})
center center;
// Using a css block
const Image0 = styled.div<ImageProps>`
width: ${props => props.width};
background: url(${props => props.src}) center center;
background-size: contain;
`
const Image0 = styled('div')<ImageProps>`
width: ${props => props.width};
background: url(${props => props.src}) center center;
background-size: contain;
`

// Or with object styles

const Image1 = styled('div')(
{
backgroundSize: 'contain'
},
(props: ImageProps) => ({
width: props.width,
background: `url(${props.src}) center center`
})
)

// Or with a generic type

const Image2 = styled('div')<ImageProps>(
const Image1 = styled('div')<ImageProps>(
{
backgroundSize: 'contain'
},
Expand All @@ -133,36 +136,28 @@ const Image2 = styled('div')<ImageProps>(
background: `url(${props.src}) center center`
})
)

// TS 2.9+ only
const Image3 = styled.div<ImageProps>`
width: ${(props: ImageProps) => props.width};
background: url(${(props: ImageProps) => props.src})
center center;
background-size: contain;
`
```

- For TypeScript <2.9, the generic type version only works with object styles due to https://github.com/Microsoft/TypeScript/issues/11947.

### React Components

Emotion can also style React components and will infer component props as expected.

```tsx
import React, { SFC } from 'react'
import React, { FC } from 'react'
import styled from '@emotion/styled'

type ComponentProps = {
interface ComponentProps {
className?: string
label: string
}

const Component: SFC<ComponentProps> = ({
const Component: FC<ComponentProps> = ({
label,
className
}) => <div className={className}>{label}</div>

const StyledComponent0 = styled(Component)`
color: red;
color: ${props => label === 'Important' ? 'red' : 'green'};
`

const StyledComponent1 = styled(Component)({
Expand All @@ -171,7 +166,7 @@ const StyledComponent1 = styled(Component)({

const App = () => (
<div>
<StyledComponent0 label="Yea! No need to re-type this label prop." />
<StyledComponent0 label="Important" />
<StyledComponent1 label="Yea! No need to re-type this label prop." />
</div>
)
Expand All @@ -180,37 +175,32 @@ const App = () => (
### Passing props when styling a React component

```tsx
import React, { SFC } from 'react'
import React, { FC } from 'react'
import styled from '@emotion/styled'

type ComponentProps = {
interface ComponentProps {
className?: string
label: string
}

const Component: SFC<ComponentProps> = ({
const Component: FC<ComponentProps> = ({
label,
className
}) => <div className={className}>{label}</div>

type StyledComponentProps = {
interface StyledComponentProps {
bgColor: string
}

const StyledComponent0 = styled(Component)`
const StyledComponent0 = styled(Component)<StyledComponentProps>`
color: red;
background: ${(props: StyledComponentProps) =>
props.bgColor};
background: ${props => props.label ? props.bgColor : 'white'};
`
const StyledComponent1 = styled(Component)<
StyledComponentProps
>(
{
color: 'red'
},
// or
const StyledComponent1 = styled(Component)<StyledComponentProps>(
props => ({
background: props.bgColor
color: 'red'
background: props.label ? props.bgColor : 'white'
})
)

Expand All @@ -237,6 +227,7 @@ _styled.tsx_

```tsx
import styled, { CreateStyled } from '@emotion/styled'
import * as emotionTheming from 'emotion-theming'

type Theme = {
color: {
Expand All @@ -248,6 +239,10 @@ type Theme = {
}

export default styled as CreateStyled<Theme>

// You can also create themed versions of all the other theme helpers and hooks
const { ThemeProvider, withTheme, useTheme } = emotionTheming as emotionTheming.EmotionTheming<Theme>
export { ThemeProvider, withTheme, useTheme }
```

_Button.tsx_
Expand All @@ -263,3 +258,27 @@ const Button = styled('button')`

export default Button
```

### TypeScript < 2.9

For Typescript <2.9, the generic type version only works with object styles due to https://github.com/Microsoft/TypeScript/issues/11947.

You can work around this by specifying the prop types in your style callback:

``` ts
const StyledComponent0 = styled(Component)`
color: red;
background: ${(props: StyledComponentProps) =>
props.bgColor};
`
```

NOTE: This approach you will have to perform the intersection with the component props yourself to get at the component props

``` ts
const StyledComponent0 = styled(Component)`
color: red;
background: ${(props: StyledComponentProps & ComponentProps) =>
props.bgColor};
`
```
3 changes: 3 additions & 0 deletions packages/babel-plugin-emotion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"lib",
"dist"
],
"scripts": {
"test:typescript": "exit 0"
},
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/hash": "0.7.3",
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-plugin-jsx-pragmatic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"module": "dist/babel-plugin-jsx-pragmatic.esm.js",
"license": "MIT",
"repository": "https://github.com/emotion-js/emotion/tree/master/packages/babel-plugin-jsx-pragmatic",
"scripts": {
"test:typescript": "exit 0"
},
"dependencies": {
"@babel/plugin-syntax-jsx": "^7.2.0"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-preset-css-prop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"module": "dist/babel-preset-css-prop.esm.js",
"license": "MIT",
"repository": "https://github.com/emotion-js/emotion/tree/master/packages/babel-preset-css-prop",
"scripts": {
"test:typescript": "exit 0"
},
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/runtime": "^7.5.5",
Expand Down
10 changes: 6 additions & 4 deletions packages/core/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ComponentClass,
Context,
Provider,
SFC,
FC,
ReactElement,
ReactNode,
Ref,
Expand All @@ -30,7 +30,7 @@ export const ThemeContext: Context<object>
export const CacheProvider: Provider<EmotionCache>
export function withEmotionCache<Props, RefType = any>(
func: (props: Props, context: EmotionCache, ref: Ref<RefType>) => ReactNode
): SFC<Props & ClassAttributes<RefType>>
): FC<Props & ClassAttributes<RefType>>

export const jsx: typeof createElement

Expand All @@ -45,7 +45,9 @@ export interface GlobalProps<Theme> {
* @desc
* JSX generic are supported only after TS@2.9
*/
export function Global<Theme = any>(props: GlobalProps<Theme>): ReactElement
export function Global<Theme extends {} = any>(
props: GlobalProps<Theme>
): ReactElement

export function keyframes(
template: TemplateStringsArray,
Expand Down Expand Up @@ -75,7 +77,7 @@ export interface ClassNamesProps<Theme> {
* @desc
* JSX generic are supported only after TS@2.9
*/
export function ClassNames<Theme = any>(
export function ClassNames<Theme extends {} = any>(
props: ClassNamesProps<Theme>
): ReactElement

Expand Down
2 changes: 0 additions & 2 deletions packages/core/types/tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
ClassNames,
ClassNamesContent,
Global,
Interpolation,
CacheProvider,
css,
jsx,
keyframes,
Expand Down

0 comments on commit a72e6dc

Please sign in to comment.