From c4614ae150b047d763a6b3be88596d7eddee4d0f Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 29 Oct 2019 16:58:40 +0100 Subject: [PATCH 01/11] chore: tell Prettier we are using Flow this gets rid of a lot of warnings and wrong auto formatting --- .prettierrc.yaml | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc.yaml b/.prettierrc.yaml index c00fa2d41..614f548d8 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -4,3 +4,4 @@ overrides: - files: "docs/*.md" options: printWidth: 60 + parser: flow diff --git a/package.json b/package.json index 33d6626bc..438c0db3d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ ], "parser": "babel-eslint", "rules": { - "prettier/prettier": "error", + "prettier/prettier": ["error", {"parser": "flow"}], "react/prop-types": 0, "react/no-unused-prop-types": 0, "standard/computed-property-even-spacing": 0, From 712b476c9ac3dc35a2642b3ee0144a4d973f42e3 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 29 Oct 2019 17:00:18 +0100 Subject: [PATCH 02/11] fix: make CreateStyled callable function polymorphic This makes it possible to type styled functional components and enables a workaround for lack of tagged templates support of Flow --- packages/styled-base/flow-tests/flow.js | 30 ++++++++++++++++++------- packages/styled-base/src/utils.js | 12 +++++----- packages/styled/flow-tests/flow.js | 23 ++++++++++++++----- packages/styled/src/index.js | 1 + 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/styled-base/flow-tests/flow.js b/packages/styled-base/flow-tests/flow.js index 110dd2b49..fcde9401e 100644 --- a/packages/styled-base/flow-tests/flow.js +++ b/packages/styled-base/flow-tests/flow.js @@ -2,17 +2,23 @@ // @flow import * as React from 'react' import createStyled from '../src' -import type { CreateStyledComponent, StyledComponent } from '../src/utils' +import type { + CreateStyledComponent, + StyledComponent, + Interpolations +} from '../src/utils' -export const valid: CreateStyledComponent = createStyled('div') +type Props = { color: string } + +// It returns the expected type +export const valid: ( + ...args: Interpolations +) => StyledComponent = 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 } -// prettier-ignore -const Div = styled({ color: props => props.color }) +const Div = createStyled.div < Props > { color: props => props.color } const validProp =
@@ -20,8 +26,16 @@ const validProp =
const invalidProp =
// $FlowExpectError: we don't expose the private StyledComponent properties -const invalidPropAccess = styled().__emotion_base +const invalidPropAccess = createStyled().__emotion_base // We allow styled components not to specify their props types // NOTE: this is allowed only if you don't attempt to export it! -const untyped: StyledComponent = styled({}) +const untyped: StyledComponent = createStyled.div({}) + +// Style a functional component +const styledFn = + createStyled < + Props > + (props =>
)` + color: red; +` diff --git a/packages/styled-base/src/utils.js b/packages/styled-base/src/utils.js index 6e4ac85fd..3b12f6c5a 100644 --- a/packages/styled-base/src/utils.js +++ b/packages/styled-base/src/utils.js @@ -1,6 +1,5 @@ // @flow -import * as React from 'react' -import type { ElementType } from 'react' +import type { ElementType, StatelessFunctionalComponent } from 'react' import isPropValid from '@emotion/is-prop-valid' export type Interpolations = Array @@ -11,7 +10,7 @@ export type StyledOptions = { target?: string } -export type StyledComponent

= React.StatelessFunctionalComponent

& { +export type StyledComponent

= StatelessFunctionalComponent

& { defaultProps: any, toString: () => string, withComponent: ( @@ -31,7 +30,7 @@ const testOmitPropsOnStringTag = isPropValid const testOmitPropsOnComponent = (key: string) => key !== 'theme' && key !== 'innerRef' -export const getDefaultShouldForwardProp = (tag: React.ElementType) => +export const getDefaultShouldForwardProp = (tag: ElementType) => typeof tag === 'string' && // 96 is one less than the char code // for "a" so this is checking that @@ -45,7 +44,10 @@ export type CreateStyledComponent =

( ) => StyledComponent

export type CreateStyled = { - (tag: React.ElementType, options?: StyledOptions): CreateStyledComponent, +

( + tag: ElementType, + options?: StyledOptions + ): (...args: Interpolations) => StyledComponent

, [key: string]: CreateStyledComponent, bind: () => CreateStyled } diff --git a/packages/styled/flow-tests/flow.js b/packages/styled/flow-tests/flow.js index 0754fdc58..37d01b91f 100644 --- a/packages/styled/flow-tests/flow.js +++ b/packages/styled/flow-tests/flow.js @@ -1,13 +1,26 @@ +/* eslint-disable no-unused-vars */ // @flow import * as React from 'react' import styled from '../src' type Props = { color: string } -const Foo = styled.div({ - color: 'red' -}) +const Foo = + styled.div < + Props > + { + color: 'red' + } -export const valid = +const valid = // $FlowExpectError: color must be string -export const invalid = +const invalid = + +// components defined using the root method should be identical +// to the ones generated using the shortcuts +const root: typeof Foo = + styled < + Props > + 'div'` + colors: red; +` diff --git a/packages/styled/src/index.js b/packages/styled/src/index.js index eea895068..3916bef12 100644 --- a/packages/styled/src/index.js +++ b/packages/styled/src/index.js @@ -6,6 +6,7 @@ import { tags } from './tags' const newStyled = styled.bind() tags.forEach(tagName => { + // $FlowFixMe: we can ignore this because its exposed type is defined by the CreateStyled type newStyled[tagName] = newStyled(tagName) }) From fb7c4ff456ba6e13e83d393c2b390cfdeb21ab4c Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 29 Oct 2019 17:00:40 +0100 Subject: [PATCH 03/11] chore: update type annotation to use new style --- site/src/components/Title.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/Title.js b/site/src/components/Title.js index 03d5e3fb1..a6197aad5 100644 --- a/site/src/components/Title.js +++ b/site/src/components/Title.js @@ -6,7 +6,7 @@ import * as markdownComponents from '../utils/markdown-styles' type Props = { children: React$Node } -export default styled(markdownComponents.h1)( +export default styled(markdownComponents.h1)( mq({ paddingTop: 0, marginTop: 0, From 3288cf94fc87851e547acafe01799927d3d1b49d Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 29 Oct 2019 17:11:28 +0100 Subject: [PATCH 04/11] fix: wrong formatting --- .prettierrc.yaml | 2 ++ packages/styled-base/flow-tests/flow.js | 7 ++----- packages/styled/flow-tests/flow.js | 14 ++++---------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 614f548d8..afa0c8ad2 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -4,4 +4,6 @@ overrides: - files: "docs/*.md" options: printWidth: 60 +- files: "*.js" + options: parser: flow diff --git a/packages/styled-base/flow-tests/flow.js b/packages/styled-base/flow-tests/flow.js index fcde9401e..12b670f4d 100644 --- a/packages/styled-base/flow-tests/flow.js +++ b/packages/styled-base/flow-tests/flow.js @@ -18,7 +18,7 @@ export const valid: ( // $FlowExpectError: we can't cast a StyledComponent to string export const invalid: string = createStyled('div') -const Div = createStyled.div < Props > { color: props => props.color } +const Div = createStyled.div({ color: props => props.color }) const validProp =

@@ -33,9 +33,6 @@ const invalidPropAccess = createStyled().__emotion_base const untyped: StyledComponent = createStyled.div({}) // Style a functional component -const styledFn = - createStyled < - Props > - (props =>
)` +const styledFn = createStyled(props =>
)` color: red; ` diff --git a/packages/styled/flow-tests/flow.js b/packages/styled/flow-tests/flow.js index 37d01b91f..c0fdee630 100644 --- a/packages/styled/flow-tests/flow.js +++ b/packages/styled/flow-tests/flow.js @@ -4,12 +4,9 @@ import * as React from 'react' import styled from '../src' type Props = { color: string } -const Foo = - styled.div < - Props > - { - color: 'red' - } +const Foo = styled.div({ + color: 'red' +}) const valid = @@ -18,9 +15,6 @@ const invalid = // components defined using the root method should be identical // to the ones generated using the shortcuts -const root: typeof Foo = - styled < - Props > - 'div'` +const root: typeof Foo = styled('div')` colors: red; ` From 8bd3286d709df2e59652315f3c235ec5c671a154 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 30 Oct 2019 19:18:21 +0100 Subject: [PATCH 05/11] style: fix prettier issues --- packages/utils/src/types.js | 3 --- playgrounds/razzle/src/index.js | 3 +-- site/src/pages/404.js | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/utils/src/types.js b/packages/utils/src/types.js index 903dca8cf..8a0bcd27c 100644 --- a/packages/utils/src/types.js +++ b/packages/utils/src/types.js @@ -1,9 +1,6 @@ // @flow -/*:: import { StyleSheet } from '@emotion/sheet' -*/ - export type RegisteredCache = { [string]: string } export type Interpolation = any diff --git a/playgrounds/razzle/src/index.js b/playgrounds/razzle/src/index.js index f4b87635e..ea2e04c71 100644 --- a/playgrounds/razzle/src/index.js +++ b/playgrounds/razzle/src/index.js @@ -12,8 +12,7 @@ server.listen(process.env.PORT || 3000, error => { } console.log('🚀 started') -}) - +}) // eslint-disable-next-line // $FlowFixMe if (module.hot) { console.log('✅ Server-side HMR Enabled!') diff --git a/site/src/pages/404.js b/site/src/pages/404.js index 86f092961..84df26030 100644 --- a/site/src/pages/404.js +++ b/site/src/pages/404.js @@ -11,5 +11,4 @@ const NotFoundPage = () => { ) } - export default NotFoundPage From da722bda60730b5dd28d19a18bf507351fb9454d Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 30 Oct 2019 19:19:58 +0100 Subject: [PATCH 06/11] chore: changeset --- .changeset/strange-pumas-suffer.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/strange-pumas-suffer.md diff --git a/.changeset/strange-pumas-suffer.md b/.changeset/strange-pumas-suffer.md new file mode 100644 index 000000000..e84c3ad0d --- /dev/null +++ b/.changeset/strange-pumas-suffer.md @@ -0,0 +1,6 @@ +--- +'@emotion/styled-base': patch +'@emotion/styled': patch +--- + +StyledComponent Flow type is now polymorphic, that means you can now define the component prop types to get better type safety. From 300cc9495f4bfa508970ff3623d5ee6c0d76e1db Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 30 Oct 2019 19:22:49 +0100 Subject: [PATCH 07/11] fix: just import the @emotion/sheet type --- packages/utils/src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/types.js b/packages/utils/src/types.js index 8a0bcd27c..173b5063e 100644 --- a/packages/utils/src/types.js +++ b/packages/utils/src/types.js @@ -1,5 +1,5 @@ // @flow -import { StyleSheet } from '@emotion/sheet' +import type { StyleSheet } from '@emotion/sheet' export type RegisteredCache = { [string]: string } From 22d78a23db919ef1abb162221cc6e1d9958fced2 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 30 Oct 2019 19:28:39 +0100 Subject: [PATCH 08/11] style: make prettier happy --- playgrounds/razzle/src/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/playgrounds/razzle/src/index.js b/playgrounds/razzle/src/index.js index ea2e04c71..5f0c00988 100644 --- a/playgrounds/razzle/src/index.js +++ b/playgrounds/razzle/src/index.js @@ -12,8 +12,7 @@ server.listen(process.env.PORT || 3000, error => { } console.log('🚀 started') -}) // eslint-disable-next-line -// $FlowFixMe +}) // $FlowFixMe if (module.hot) { console.log('✅ Server-side HMR Enabled!') // $FlowFixMe From e1ac9d83cbcfa4ed83b7b99332e8933844d8fbbd Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Thu, 31 Oct 2019 13:14:36 +0100 Subject: [PATCH 09/11] docs: added Flow types documentation page --- docs/docs.yaml | 1 + docs/flow.mdx | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 docs/flow.mdx diff --git a/docs/docs.yaml b/docs/docs.yaml index b25bd6cdc..615c10b4e 100644 --- a/docs/docs.yaml +++ b/docs/docs.yaml @@ -29,6 +29,7 @@ - source-maps - testing - typescript + - flow # This loads the READMEs instead of files in docs/ - title: Packages diff --git a/docs/flow.mdx b/docs/flow.mdx new file mode 100644 index 000000000..ae2835e40 --- /dev/null +++ b/docs/flow.mdx @@ -0,0 +1,115 @@ +--- +title: 'Flow' +--- + +Emotion is built with Flow, thus it exports type definitions for most of its packages, +included `@emotion/styled`. + +## @emotion/styled + +The styled package can be used to define styled components in two ways, by calling `styled()`, +or by using the `styled.*` shortcuts. + +Unfortunately, Flow doesn't currently support generic types on tagged templates, this means if +you'd like to explictly type a styled component props, you will have to use one of the following +alternatives: + +```jsx +import styled from '@emotion/styled' + +// Option A +const A = styled('div')` + color: red; +` + +// Option B +const B = styled.div({ + color: 'red', +}) +``` + +Styled components are annotated the same way normal React components are: + +```jsx +import styled from '@emotion/styled' + +type Props = { a: string } +const Link = styled('a')` + color: red; +` + +const App = () => Click me +``` + +Just like for normal React components, you don't need to provide type annotations +for your styled components if you don't plan to export them from your module: + +```jsx +import styled from '@emotion/styled' + +const Internal = styled.div` + color: red; +` +``` + +Be aware, Flow infers the return type of your components by referencing their return type, +this means you will need to annotate the properties of the root component in the case below: + +```jsx + +const Container = styled.div` + ^^^^^^^^^^^ Missing type annotation for P. P is a type parameter declared in function type [1] and was implicitly instantiated at +encaps tag [2]. + color: red; +` + +export const App = () => +``` + +You can use `React$ElementConfig` to obtain the props type of a HTML tag, or of +any existing React component: + +```jsx +import type { ElementConfig } from 'react' + +type Props = ElementConfig<'div'> +const Container = styled('div')` + color: red; +` + +export const App = () => +``` + + +```jsx +import type { ElementConfig } from 'react' +import styled from '@emotion/styled' + +const Container = styled>('div')` + background-color: yellow; +` + +const App = () => ( + {() => 10} + ^^^^^^^^^^ Cannot create Container element because in property children: + • Either inexact function [1] is incompatible with exact React.Element [2]. + • Or function [1] is incompatible with React.Portal [3]. + • Or property @@iterator is missing in function [1] but exists in $Iterable [4]. +) +``` + +Alternatively, you can define the return type of your component, so that +Flow doesn't need to infer it reading the props type of the internal component: + +```jsx +import type { Node } from 'react' + +const Container = styled.div` + color: red; +` + +export const App = (): Node => +``` + + + From 87ecc7863b4a984c3e3a0ff8747630c28fe38cfe Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Fri, 1 Nov 2019 06:25:16 +1000 Subject: [PATCH 10/11] Update flow.mdx --- docs/flow.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/flow.mdx b/docs/flow.mdx index ae2835e40..60ccc5579 100644 --- a/docs/flow.mdx +++ b/docs/flow.mdx @@ -2,8 +2,8 @@ title: 'Flow' --- -Emotion is built with Flow, thus it exports type definitions for most of its packages, -included `@emotion/styled`. +Emotion is built with Flow, so it exports type definitions for most of its packages, +including `@emotion/styled`. ## @emotion/styled From 87e720a2d6a5a9d64a086c2f33d36b89f8890ae7 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Mon, 4 Nov 2019 13:53:34 +0100 Subject: [PATCH 11/11] refactor: rename P to Props # Conflicts: # packages/styled-base/src/utils.js --- packages/styled-base/src/index.js | 4 ++-- packages/styled-base/src/utils.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/styled-base/src/index.js b/packages/styled-base/src/index.js index ca79488e3..568649278 100644 --- a/packages/styled-base/src/index.js +++ b/packages/styled-base/src/index.js @@ -50,7 +50,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => { shouldForwardProp || getDefaultShouldForwardProp(baseTag) const shouldUseAs = !defaultShouldForwardProp('as') - return function

(): PrivateStyledComponent

{ + return function(): PrivateStyledComponent { let args = arguments let styles = isReal && tag.__emotion_styles !== undefined @@ -78,7 +78,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => { } // $FlowFixMe: we need to cast StatelessFunctionalComponent to our PrivateStyledComponent class - const Styled: PrivateStyledComponent

= withEmotionCache( + const Styled: PrivateStyledComponent = withEmotionCache( (props, context, ref) => { return ( diff --git a/packages/styled-base/src/utils.js b/packages/styled-base/src/utils.js index 3b12f6c5a..c06191fd0 100644 --- a/packages/styled-base/src/utils.js +++ b/packages/styled-base/src/utils.js @@ -10,17 +10,17 @@ export type StyledOptions = { target?: string } -export type StyledComponent

= StatelessFunctionalComponent

& { +export type StyledComponent = StatelessFunctionalComponent & { defaultProps: any, toString: () => string, withComponent: ( nextTag: ElementType, nextOptions?: StyledOptions - ) => StyledComponent

+ ) => StyledComponent } -export type PrivateStyledComponent

= StyledComponent

& { - __emotion_real: StyledComponent

, +export type PrivateStyledComponent = StyledComponent & { + __emotion_real: StyledComponent, __emotion_base: any, __emotion_styles: any, __emotion_forwardProp: any @@ -39,15 +39,15 @@ export const getDefaultShouldForwardProp = (tag: ElementType) => ? testOmitPropsOnStringTag : testOmitPropsOnComponent -export type CreateStyledComponent =

( +export type CreateStyledComponent = ( ...args: Interpolations -) => StyledComponent

+) => StyledComponent export type CreateStyled = { -

( + ( tag: ElementType, options?: StyledOptions - ): (...args: Interpolations) => StyledComponent

, + ): (...args: Interpolations) => StyledComponent, [key: string]: CreateStyledComponent, bind: () => CreateStyled }