Skip to content

Commit a19d019

Browse files
authored
Convert @emotion/styled's source code to TypeScript (#3284)
* Convert `@emotion/styled`'s source code to TypeScript * fixed entrypoint extension * fix types reference * add changeset * more localized cast * remove redundant cast * organize imports
1 parent 5974e33 commit a19d019

19 files changed

+352
-393
lines changed

.changeset/metal-cups-allow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@emotion/styled': minor
3+
---
4+
5+
Source code has been migrated to TypeScript. From now on type declarations will be emitted based on that, instead of being hand-written.

packages/serialize/src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,11 @@ export function serializeStyles(
398398
strings as Interpolation
399399
)
400400
} else {
401-
const asTemplateStringsArr = strings as TemplateStringsArray
402-
if (isDevelopment && asTemplateStringsArr[0] === undefined) {
401+
const templateStringsArr = strings as TemplateStringsArray
402+
if (isDevelopment && templateStringsArr[0] === undefined) {
403403
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
404404
}
405-
styles += asTemplateStringsArr[0]
405+
styles += templateStringsArr[0]
406406
}
407407
// we start at 1 since we've already handled the first arg
408408
for (let i = 1; i < args.length; i++) {

packages/styled/base/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"main": "dist/emotion-styled-base.cjs.js",
33
"module": "dist/emotion-styled-base.esm.js",
44
"umd:main": "dist/emotion-styled-base.umd.min.js",
5-
"types": "../types/base",
5+
"types": "dist/emotion-styled-base.cjs.d.ts",
66
"preconstruct": {
77
"umdName": "emotionStyledBase"
88
}

packages/styled/package.json

+10-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "styled API for emotion",
55
"main": "dist/emotion-styled.cjs.js",
66
"module": "dist/emotion-styled.esm.js",
7-
"types": "types/index.d.ts",
7+
"types": "dist/emotion-styled.cjs.d.ts",
88
"license": "MIT",
99
"repository": "https://github.com/emotion-js/emotion/tree/main/packages/styled",
1010
"scripts": {
@@ -40,7 +40,6 @@
4040
"src",
4141
"dist",
4242
"base",
43-
"types/*.d.ts",
4443
"macro.*"
4544
],
4645
"umd:main": "dist/emotion-styled.umd.min.js",
@@ -164,22 +163,22 @@
164163
},
165164
"imports": {
166165
"#is-development": {
167-
"development": "./src/conditions/true.js",
168-
"default": "./src/conditions/false.js"
166+
"development": "./src/conditions/true.ts",
167+
"default": "./src/conditions/false.ts"
169168
},
170169
"#is-browser": {
171-
"edge-light": "./src/conditions/false.js",
172-
"workerd": "./src/conditions/false.js",
173-
"worker": "./src/conditions/false.js",
174-
"browser": "./src/conditions/true.js",
175-
"default": "./src/conditions/is-browser.js"
170+
"edge-light": "./src/conditions/false.ts",
171+
"workerd": "./src/conditions/false.ts",
172+
"worker": "./src/conditions/false.ts",
173+
"browser": "./src/conditions/true.ts",
174+
"default": "./src/conditions/is-browser.ts"
176175
}
177176
},
178177
"preconstruct": {
179178
"umdName": "emotionStyled",
180179
"entrypoints": [
181-
"./index.js",
182-
"./base.js"
180+
"./index.ts",
181+
"./base.tsx"
183182
],
184183
"exports": {
185184
"extra": {

packages/styled/src/base.d.ts

-2
This file was deleted.

packages/styled/src/base.js packages/styled/src/base.tsx

+56-42
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
1-
import * as React from 'react'
2-
import {
3-
getDefaultShouldForwardProp,
4-
composeShouldForwardProps
5-
/*
6-
type StyledOptions,
7-
type CreateStyled,
8-
type PrivateStyledComponent,
9-
type StyledElementType
10-
*/
11-
} from './utils'
12-
import { withEmotionCache, ThemeContext } from '@emotion/react'
13-
import isDevelopment from '#is-development'
141
import isBrowser from '#is-browser'
2+
import isDevelopment from '#is-development'
3+
import { Theme, ThemeContext, withEmotionCache } from '@emotion/react'
4+
import { Interpolation, serializeStyles } from '@emotion/serialize'
5+
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
156
import {
7+
EmotionCache,
168
getRegisteredStyles,
179
insertStyles,
18-
registerStyles
10+
registerStyles,
11+
SerializedStyles
1912
} from '@emotion/utils'
20-
import { serializeStyles } from '@emotion/serialize'
21-
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
13+
import * as React from 'react'
14+
import { CreateStyled, ElementType, StyledOptions } from './types'
15+
import { composeShouldForwardProps, getDefaultShouldForwardProp } from './utils'
16+
export type {
17+
ArrayInterpolation,
18+
ComponentSelector,
19+
CSSObject,
20+
FunctionInterpolation,
21+
Interpolation
22+
} from '@emotion/serialize'
2223

2324
const ILLEGAL_ESCAPE_SEQUENCE_ERROR = `You have illegal escape sequence in your template literal, most likely inside content's property value.
2425
Because you write your CSS inside a JavaScript string you actually have to do double escaping, so for example "content: '\\00d7';" should become "content: '\\\\00d7';".
2526
You can read more about this here:
2627
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences`
2728

28-
const Insertion = ({ cache, serialized, isStringTag }) => {
29+
const Insertion = ({
30+
cache,
31+
serialized,
32+
isStringTag
33+
}: {
34+
cache: EmotionCache
35+
serialized: SerializedStyles
36+
isStringTag: boolean
37+
}) => {
2938
registerStyles(cache, serialized, isStringTag)
3039

3140
const rules = useInsertionEffectAlwaysWithSyncFallback(() =>
@@ -52,10 +61,7 @@ const Insertion = ({ cache, serialized, isStringTag }) => {
5261
return null
5362
}
5463

55-
let createStyled /*: CreateStyled */ = (
56-
tag /*: any */,
57-
options /* ?: StyledOptions */
58-
) => {
64+
const createStyled = (tag: ElementType, options?: StyledOptions) => {
5965
if (isDevelopment) {
6066
if (tag === undefined) {
6167
throw new Error(
@@ -66,8 +72,8 @@ let createStyled /*: CreateStyled */ = (
6672
const isReal = tag.__emotion_real === tag
6773
const baseTag = (isReal && tag.__emotion_base) || tag
6874

69-
let identifierName
70-
let targetClassName
75+
let identifierName: string | undefined
76+
let targetClassName: string | undefined
7177
if (options !== undefined) {
7278
identifierName = options.label
7379
targetClassName = options.target
@@ -78,9 +84,11 @@ let createStyled /*: CreateStyled */ = (
7884
shouldForwardProp || getDefaultShouldForwardProp(baseTag)
7985
const shouldUseAs = !defaultShouldForwardProp('as')
8086

81-
/* return function<Props>(): PrivateStyledComponent<Props> { */
8287
return function () {
83-
let args = arguments
88+
// eslint-disable-next-line prefer-rest-params
89+
let args = arguments as any as Array<
90+
TemplateStringsArray | Interpolation<Theme>
91+
>
8492
let styles =
8593
isReal && tag.__emotion_styles !== undefined
8694
? tag.__emotion_styles.slice(0)
@@ -89,29 +97,35 @@ let createStyled /*: CreateStyled */ = (
8997
if (identifierName !== undefined) {
9098
styles.push(`label:${identifierName};`)
9199
}
92-
if (args[0] == null || args[0].raw === undefined) {
100+
if (
101+
args[0] == null ||
102+
(args[0] as TemplateStringsArray).raw === undefined
103+
) {
104+
// eslint-disable-next-line prefer-spread
93105
styles.push.apply(styles, args)
94106
} else {
95-
if (isDevelopment && args[0][0] === undefined) {
107+
const templateStringsArr = args[0] as TemplateStringsArray
108+
if (isDevelopment && templateStringsArr[0] === undefined) {
96109
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
97110
}
98-
styles.push(args[0][0])
111+
styles.push(templateStringsArr[0])
99112
let len = args.length
100113
let i = 1
101114
for (; i < len; i++) {
102-
if (isDevelopment && args[0][i] === undefined) {
115+
if (isDevelopment && templateStringsArr[i] === undefined) {
103116
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
104117
}
105-
styles.push(args[i], args[0][i])
118+
styles.push(args[i], templateStringsArr[i])
106119
}
107120
}
108121

109-
const Styled /*: PrivateStyledComponent<Props> */ = withEmotionCache(
110-
(props, cache, ref) => {
111-
const FinalTag = (shouldUseAs && props.as) || baseTag
122+
const Styled: ElementType = withEmotionCache(
123+
(props: Record<string, unknown>, cache, ref) => {
124+
const FinalTag =
125+
(shouldUseAs && (props.as as React.ElementType)) || baseTag
112126

113127
let className = ''
114-
let classInterpolations = []
128+
let classInterpolations: Interpolation<Theme>[] = []
115129
let mergedProps = props
116130
if (props.theme == null) {
117131
mergedProps = {}
@@ -146,7 +160,7 @@ let createStyled /*: CreateStyled */ = (
146160
? getDefaultShouldForwardProp(FinalTag)
147161
: defaultShouldForwardProp
148162

149-
let newProps = {}
163+
let newProps: Record<string, unknown> = {}
150164

151165
for (let key in props) {
152166
if (shouldUseAs && key === 'as') continue
@@ -196,20 +210,20 @@ let createStyled /*: CreateStyled */ = (
196210
return `.${targetClassName}`
197211
}
198212
})
199-
200-
Styled.withComponent = (
201-
nextTag /*: StyledElementType<Props> */,
202-
nextOptions /* ?: StyledOptions */
213+
;(Styled as any).withComponent = (
214+
nextTag: ElementType,
215+
nextOptions: StyledOptions
203216
) => {
204-
return createStyled(nextTag, {
217+
const newStyled = createStyled(nextTag, {
205218
...options,
206219
...nextOptions,
207220
shouldForwardProp: composeShouldForwardProps(Styled, nextOptions, true)
208-
})(...styles)
221+
})
222+
return (newStyled as any)(...styles)
209223
}
210224

211225
return Styled
212226
}
213227
}
214228

215-
export default createStyled
229+
export default createStyled as CreateStyled
File renamed without changes.

packages/styled/src/index.d.ts

-2
This file was deleted.

packages/styled/src/index.js

-11
This file was deleted.

packages/styled/src/index.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Theme } from '@emotion/react'
2+
import styled from './base'
3+
import { ReactJSXIntrinsicElements } from './jsx-namespace'
4+
import { tags } from './tags'
5+
import {
6+
CreateStyledComponent,
7+
CreateStyled as BaseCreateStyled
8+
} from './types'
9+
export type {
10+
ArrayInterpolation,
11+
ComponentSelector,
12+
CSSObject,
13+
FunctionInterpolation,
14+
Interpolation
15+
} from '@emotion/serialize'
16+
export type {
17+
CreateStyledComponent,
18+
FilteringStyledOptions,
19+
StyledComponent,
20+
StyledOptions
21+
} from './types'
22+
23+
export type StyledTags = {
24+
[Tag in keyof ReactJSXIntrinsicElements]: CreateStyledComponent<
25+
{
26+
theme?: Theme
27+
as?: React.ElementType
28+
},
29+
ReactJSXIntrinsicElements[Tag]
30+
>
31+
}
32+
33+
export interface CreateStyled extends BaseCreateStyled, StyledTags {}
34+
35+
// bind it to avoid mutating the original function
36+
const newStyled = styled.bind(null) as CreateStyled
37+
38+
tags.forEach(tagName => {
39+
;(newStyled as any)[tagName] = newStyled(tagName as keyof typeof newStyled)
40+
})
41+
42+
export default newStyled

packages/styled/types/jsx-namespace.d.ts packages/styled/src/jsx-namespace.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ type IsPreReact19 = 2 extends Parameters<React.FunctionComponent<any>>['length']
77
? true
88
: false
99

10-
export type ReactJSXIntrinsicElements = true extends IsPreReact19
11-
? /** @ts-ignore */
12-
JSX.IntrinsicElements
13-
: /** @ts-ignore */
14-
React.JSX.IntrinsicElements
10+
// prettier-ignore
11+
/** @ts-ignore */
12+
export type ReactJSXIntrinsicElements = true extends IsPreReact19 ? JSX.IntrinsicElements : React.JSX.IntrinsicElements

packages/styled/src/tags.js packages/styled/src/tags.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,4 @@ export const tags = [
135135
'svg',
136136
'text',
137137
'tspan'
138-
]
138+
] as const

0 commit comments

Comments
 (0)