Skip to content

Commit

Permalink
Convert @emotion/styled's source code to TypeScript (#3284)
Browse files Browse the repository at this point in the history
* Convert `@emotion/styled`'s source code to TypeScript

* fixed entrypoint extension

* fix types reference

* add changeset

* more localized cast

* remove redundant cast

* organize imports
  • Loading branch information
Andarist authored Dec 9, 2024
1 parent 5974e33 commit a19d019
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 393 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-cups-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@emotion/styled': minor
---

Source code has been migrated to TypeScript. From now on type declarations will be emitted based on that, instead of being hand-written.
6 changes: 3 additions & 3 deletions packages/serialize/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,11 @@ export function serializeStyles(
strings as Interpolation
)
} else {
const asTemplateStringsArr = strings as TemplateStringsArray
if (isDevelopment && asTemplateStringsArr[0] === undefined) {
const templateStringsArr = strings as TemplateStringsArray
if (isDevelopment && templateStringsArr[0] === undefined) {
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
}
styles += asTemplateStringsArr[0]
styles += templateStringsArr[0]
}
// we start at 1 since we've already handled the first arg
for (let i = 1; i < args.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion packages/styled/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"main": "dist/emotion-styled-base.cjs.js",
"module": "dist/emotion-styled-base.esm.js",
"umd:main": "dist/emotion-styled-base.umd.min.js",
"types": "../types/base",
"types": "dist/emotion-styled-base.cjs.d.ts",
"preconstruct": {
"umdName": "emotionStyledBase"
}
Expand Down
21 changes: 10 additions & 11 deletions packages/styled/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "styled API for emotion",
"main": "dist/emotion-styled.cjs.js",
"module": "dist/emotion-styled.esm.js",
"types": "types/index.d.ts",
"types": "dist/emotion-styled.cjs.d.ts",
"license": "MIT",
"repository": "https://github.com/emotion-js/emotion/tree/main/packages/styled",
"scripts": {
Expand Down Expand Up @@ -40,7 +40,6 @@
"src",
"dist",
"base",
"types/*.d.ts",
"macro.*"
],
"umd:main": "dist/emotion-styled.umd.min.js",
Expand Down Expand Up @@ -164,22 +163,22 @@
},
"imports": {
"#is-development": {
"development": "./src/conditions/true.js",
"default": "./src/conditions/false.js"
"development": "./src/conditions/true.ts",
"default": "./src/conditions/false.ts"
},
"#is-browser": {
"edge-light": "./src/conditions/false.js",
"workerd": "./src/conditions/false.js",
"worker": "./src/conditions/false.js",
"browser": "./src/conditions/true.js",
"default": "./src/conditions/is-browser.js"
"edge-light": "./src/conditions/false.ts",
"workerd": "./src/conditions/false.ts",
"worker": "./src/conditions/false.ts",
"browser": "./src/conditions/true.ts",
"default": "./src/conditions/is-browser.ts"
}
},
"preconstruct": {
"umdName": "emotionStyled",
"entrypoints": [
"./index.js",
"./base.js"
"./index.ts",
"./base.tsx"
],
"exports": {
"extra": {
Expand Down
2 changes: 0 additions & 2 deletions packages/styled/src/base.d.ts

This file was deleted.

98 changes: 56 additions & 42 deletions packages/styled/src/base.js → packages/styled/src/base.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import * as React from 'react'
import {
getDefaultShouldForwardProp,
composeShouldForwardProps
/*
type StyledOptions,
type CreateStyled,
type PrivateStyledComponent,
type StyledElementType
*/
} from './utils'
import { withEmotionCache, ThemeContext } from '@emotion/react'
import isDevelopment from '#is-development'
import isBrowser from '#is-browser'
import isDevelopment from '#is-development'
import { Theme, ThemeContext, withEmotionCache } from '@emotion/react'
import { Interpolation, serializeStyles } from '@emotion/serialize'
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
import {
EmotionCache,
getRegisteredStyles,
insertStyles,
registerStyles
registerStyles,
SerializedStyles
} from '@emotion/utils'
import { serializeStyles } from '@emotion/serialize'
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
import * as React from 'react'
import { CreateStyled, ElementType, StyledOptions } from './types'
import { composeShouldForwardProps, getDefaultShouldForwardProp } from './utils'
export type {
ArrayInterpolation,
ComponentSelector,
CSSObject,
FunctionInterpolation,
Interpolation
} from '@emotion/serialize'

const ILLEGAL_ESCAPE_SEQUENCE_ERROR = `You have illegal escape sequence in your template literal, most likely inside content's property value.
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';".
You can read more about this here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences`

const Insertion = ({ cache, serialized, isStringTag }) => {
const Insertion = ({
cache,
serialized,
isStringTag
}: {
cache: EmotionCache
serialized: SerializedStyles
isStringTag: boolean
}) => {
registerStyles(cache, serialized, isStringTag)

const rules = useInsertionEffectAlwaysWithSyncFallback(() =>
Expand All @@ -52,10 +61,7 @@ const Insertion = ({ cache, serialized, isStringTag }) => {
return null
}

let createStyled /*: CreateStyled */ = (
tag /*: any */,
options /* ?: StyledOptions */
) => {
const createStyled = (tag: ElementType, options?: StyledOptions) => {
if (isDevelopment) {
if (tag === undefined) {
throw new Error(
Expand All @@ -66,8 +72,8 @@ let createStyled /*: CreateStyled */ = (
const isReal = tag.__emotion_real === tag
const baseTag = (isReal && tag.__emotion_base) || tag

let identifierName
let targetClassName
let identifierName: string | undefined
let targetClassName: string | undefined
if (options !== undefined) {
identifierName = options.label
targetClassName = options.target
Expand All @@ -78,9 +84,11 @@ let createStyled /*: CreateStyled */ = (
shouldForwardProp || getDefaultShouldForwardProp(baseTag)
const shouldUseAs = !defaultShouldForwardProp('as')

/* return function<Props>(): PrivateStyledComponent<Props> { */
return function () {
let args = arguments
// eslint-disable-next-line prefer-rest-params
let args = arguments as any as Array<
TemplateStringsArray | Interpolation<Theme>
>
let styles =
isReal && tag.__emotion_styles !== undefined
? tag.__emotion_styles.slice(0)
Expand All @@ -89,29 +97,35 @@ let createStyled /*: CreateStyled */ = (
if (identifierName !== undefined) {
styles.push(`label:${identifierName};`)
}
if (args[0] == null || args[0].raw === undefined) {
if (
args[0] == null ||
(args[0] as TemplateStringsArray).raw === undefined
) {
// eslint-disable-next-line prefer-spread
styles.push.apply(styles, args)
} else {
if (isDevelopment && args[0][0] === undefined) {
const templateStringsArr = args[0] as TemplateStringsArray
if (isDevelopment && templateStringsArr[0] === undefined) {
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
}
styles.push(args[0][0])
styles.push(templateStringsArr[0])
let len = args.length
let i = 1
for (; i < len; i++) {
if (isDevelopment && args[0][i] === undefined) {
if (isDevelopment && templateStringsArr[i] === undefined) {
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
}
styles.push(args[i], args[0][i])
styles.push(args[i], templateStringsArr[i])
}
}

const Styled /*: PrivateStyledComponent<Props> */ = withEmotionCache(
(props, cache, ref) => {
const FinalTag = (shouldUseAs && props.as) || baseTag
const Styled: ElementType = withEmotionCache(
(props: Record<string, unknown>, cache, ref) => {
const FinalTag =
(shouldUseAs && (props.as as React.ElementType)) || baseTag

let className = ''
let classInterpolations = []
let classInterpolations: Interpolation<Theme>[] = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
Expand Down Expand Up @@ -146,7 +160,7 @@ let createStyled /*: CreateStyled */ = (
? getDefaultShouldForwardProp(FinalTag)
: defaultShouldForwardProp

let newProps = {}
let newProps: Record<string, unknown> = {}

for (let key in props) {
if (shouldUseAs && key === 'as') continue
Expand Down Expand Up @@ -196,20 +210,20 @@ let createStyled /*: CreateStyled */ = (
return `.${targetClassName}`
}
})

Styled.withComponent = (
nextTag /*: StyledElementType<Props> */,
nextOptions /* ?: StyledOptions */
;(Styled as any).withComponent = (
nextTag: ElementType,
nextOptions: StyledOptions
) => {
return createStyled(nextTag, {
const newStyled = createStyled(nextTag, {
...options,
...nextOptions,
shouldForwardProp: composeShouldForwardProps(Styled, nextOptions, true)
})(...styles)
})
return (newStyled as any)(...styles)
}

return Styled
}
}

export default createStyled
export default createStyled as CreateStyled
File renamed without changes.
File renamed without changes.
2 changes: 0 additions & 2 deletions packages/styled/src/index.d.ts

This file was deleted.

11 changes: 0 additions & 11 deletions packages/styled/src/index.js

This file was deleted.

42 changes: 42 additions & 0 deletions packages/styled/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Theme } from '@emotion/react'
import styled from './base'
import { ReactJSXIntrinsicElements } from './jsx-namespace'
import { tags } from './tags'
import {
CreateStyledComponent,
CreateStyled as BaseCreateStyled
} from './types'
export type {
ArrayInterpolation,
ComponentSelector,
CSSObject,
FunctionInterpolation,
Interpolation
} from '@emotion/serialize'
export type {
CreateStyledComponent,
FilteringStyledOptions,
StyledComponent,
StyledOptions
} from './types'

export type StyledTags = {
[Tag in keyof ReactJSXIntrinsicElements]: CreateStyledComponent<
{
theme?: Theme
as?: React.ElementType
},
ReactJSXIntrinsicElements[Tag]
>
}

export interface CreateStyled extends BaseCreateStyled, StyledTags {}

// bind it to avoid mutating the original function
const newStyled = styled.bind(null) as CreateStyled

tags.forEach(tagName => {
;(newStyled as any)[tagName] = newStyled(tagName as keyof typeof newStyled)
})

export default newStyled
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ type IsPreReact19 = 2 extends Parameters<React.FunctionComponent<any>>['length']
? true
: false

export type ReactJSXIntrinsicElements = true extends IsPreReact19
? /** @ts-ignore */
JSX.IntrinsicElements
: /** @ts-ignore */
React.JSX.IntrinsicElements
// prettier-ignore
/** @ts-ignore */
export type ReactJSXIntrinsicElements = true extends IsPreReact19 ? JSX.IntrinsicElements : React.JSX.IntrinsicElements
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,4 @@ export const tags = [
'svg',
'text',
'tspan'
]
] as const
Loading

0 comments on commit a19d019

Please sign in to comment.