Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert @emotion/styled's source code to TypeScript #3284

Merged
merged 8 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading