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/react's source code to TypeScript #3281

Merged
merged 20 commits into from
Dec 5, 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/rotten-baboons-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@emotion/react': minor
---

Source code has been migrated to TypeScript. From now on type declarations will be emitted based on that, instead of being hand-written.
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,8 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/ci-setup

- name: build
run: yarn build
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would I be correct in assuming this is added because the tsconfigs used for dtslint don't use a module resolution mode that supports the imports/exports fields so the build is necessary so preconstruct outputs stuff that for the default case doesn't require supporting the imports/exports fields?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the answer is yes from reading the commits

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not even that - although when you mention this, I think I could now remove resolved-condition.ts files and the associated configs

the core of the problem fixed by this is that older TS versions might trip on some new syntax. We are OK with using more modern TS in /src (like even satisfies) as long as the emitted declarations are free of the modern syntax. dtslint tests a range of TS versions so the older ones were tripping over new syntax when reading directly from /src. That said, we could just change the min required TS version (or rather, the min TS version that we test against) but it felt nicer not to do that just for this reason.

testing against compiled types is also safer for us, I've seen some declaration emit regressions in TS - especially recently when a lot of the internals were changed in isolatedDeclarations-related work


- name: dtslint
run: yarn test:typescript
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
"@testing-library/react": "13.0.0-alpha.5",
"@types/jest": "^29.5.12",
"@types/node": "^12.20.37",
"@types/react": "18.2.6",
"@types/react": "18.3.12",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"babel-check-duplicated-nodes": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cache/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from '../src'
export * from '..'
2 changes: 0 additions & 2 deletions packages/css/test/no-babel/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import styled from '@emotion/styled'
let consoleError = console.error

afterEach(() => {
// $FlowFixMe
console.error = consoleError
})

Expand Down Expand Up @@ -146,7 +145,6 @@ describe('css', () => {
})

const spy = jest.fn()
// $FlowFixMe
console.error = spy

expect(() =>
Expand Down
2 changes: 0 additions & 2 deletions packages/css/test/sheet.dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { sheet } from '@emotion/css'
const consoleError = console.error

afterEach(() => {
// $FlowFixMe
console.error = consoleError
})

Expand Down Expand Up @@ -38,7 +37,6 @@ describe('sheet', () => {
test('throws', () => {
sheet.speedy(true)
const spy = jest.fn()
// $FlowFixMe
console.error = spy
sheet.insert('.asdfasdf4###112121211{')
expect(spy.mock.calls.length).toBe(1)
Expand Down
2 changes: 1 addition & 1 deletion packages/native/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Definitions by: Pat Sissons <https://github.com/patsissons>
// TypeScript Version: 3.4
// TypeScript Version: 4.1

import * as RN from 'react-native'
import { Theme } from '@emotion/react'
Expand Down
2 changes: 2 additions & 0 deletions packages/native/types/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"jsx": "react",
"lib": ["es6", "dom"],
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
"noEmit": true,
"strict": true,
"target": "es5",
Expand Down
3 changes: 2 additions & 1 deletion packages/primitives-core/src/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export function createStyled(

// do we really want to use the same infra as the web since it only really uses theming?
let Styled = React.forwardRef<unknown, StyledProps>((props, ref) => {
const finalTag = (shouldUseAs && props.as) || component
const finalTag =
(shouldUseAs && (props.as as React.ElementType)) || component

let mergedProps = props
if (props.theme == null) {
Expand Down
4 changes: 0 additions & 4 deletions packages/react/__tests__/element.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// @flow
/** @jsx jsx */
import { render } from '@testing-library/react'
import { jsx, css, CacheProvider, ThemeProvider } from '@emotion/react'
import createCache from '@emotion/cache'

// $FlowFixMe
console.error = jest.fn()

beforeEach(() => {
// $FlowFixMe
document.head.innerHTML = ''
jest.clearAllMocks()
})
Expand All @@ -18,7 +15,6 @@ describe('EmotionElement', () => {
const theme = { color: 'blue' }
const cache = createCache({ key: 'context' })

// $FlowFixMe
const Comp = ({ flag }) => (
<ThemeProvider theme={theme}>
<CacheProvider value={cache}>
Expand Down
1 change: 0 additions & 1 deletion packages/react/__tests__/get-label-from-stack-trace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import { getLabelFromStackTrace } from '../src/get-label-from-stack-trace'

/**
Expand Down
1 change: 0 additions & 1 deletion packages/react/__tests__/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from '@emotion/react'
import createCache from '@emotion/cache'

// $FlowFixMe
console.error = jest.fn()

beforeEach(() => {
Expand Down
1 change: 0 additions & 1 deletion packages/react/__tests__/import-prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ expect.addSnapshotSerializer({
const render = children =>
new Promise(resolve => {
const el = document.createElement('div')
// $FlowFixMe
document.body.appendChild(el)

if (ReactDOM.createRoot) {
Expand Down
1 change: 0 additions & 1 deletion packages/react/__tests__/theme-provider.dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as React from 'react'
import { jsx, ThemeProvider } from '@emotion/react'

beforeEach(() => {
// $FlowFixMe
document.head.innerHTML = ''
})

Expand Down
1 change: 1 addition & 0 deletions packages/react/_isolated-hnrs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"main": "dist/emotion-react-_isolated-hnrs.cjs.js",
"module": "dist/emotion-react-_isolated-hnrs.esm.js",
"umd:main": "dist/emotion-react-_isolated-hnrs.umd.min.js",
"types": "dist/emotion-react-_isolated-hnrs.cjs.d.ts",
"sideEffects": false,
"preconstruct": {
"umdName": "emotionHoistNonReactStatics"
Expand Down
2 changes: 1 addition & 1 deletion packages/react/jsx-dev-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"main": "dist/emotion-react-jsx-dev-runtime.cjs.js",
"module": "dist/emotion-react-jsx-dev-runtime.esm.js",
"umd:main": "dist/emotion-react-jsx-dev-runtime.umd.min.js",
"types": "../types/jsx-dev-runtime",
"types": "dist/emotion-react-jsx-dev-runtime.cjs.d.ts",
"sideEffects": false,
"preconstruct": {
"umdName": "emotionReactJSXDevRuntime"
Expand Down
2 changes: 1 addition & 1 deletion packages/react/jsx-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"main": "dist/emotion-react-jsx-runtime.cjs.js",
"module": "dist/emotion-react-jsx-runtime.esm.js",
"umd:main": "dist/emotion-react-jsx-runtime.umd.min.js",
"types": "../types/jsx-runtime",
"types": "dist/emotion-react-jsx-runtime.cjs.d.ts",
"sideEffects": false,
"preconstruct": {
"umdName": "emotionReactJSXRuntime"
Expand Down
27 changes: 14 additions & 13 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "11.13.5",
"main": "dist/emotion-react.cjs.js",
"module": "dist/emotion-react.esm.js",
"types": "dist/emotion-react.cjs.d.ts",
"exports": {
".": {
"types": {
Expand Down Expand Up @@ -232,25 +233,24 @@
},
"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"
}
},
"types": "types/index.d.ts",
"files": [
"src",
"dist",
"jsx-runtime",
"jsx-dev-runtime",
"_isolated-hnrs",
"types/*.d.ts",
"types/css-prop.d.ts",
"macro.*"
],
"sideEffects": false,
Expand Down Expand Up @@ -283,6 +283,7 @@
"@emotion/css-prettifier": "1.1.4",
"@emotion/server": "11.11.0",
"@emotion/styled": "11.13.5",
"@types/hoist-non-react-statics": "^3.3.5",
"html-tag-names": "^1.1.2",
"react": "16.14.0",
"svg-tag-names": "^1.1.1",
Expand All @@ -295,10 +296,10 @@
"umd:main": "dist/emotion-react.umd.min.js",
"preconstruct": {
"entrypoints": [
"./index.js",
"./jsx-runtime.js",
"./jsx-dev-runtime.js",
"./_isolated-hnrs.js"
"./index.ts",
"./jsx-runtime.ts",
"./jsx-dev-runtime.ts",
"./_isolated-hnrs.ts"
],
"umdName": "emotionReact",
"exports": {
Expand Down
3 changes: 0 additions & 3 deletions packages/react/src/_isolated-hnrs.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ import hoistNonReactStatics from 'hoist-non-react-statics'
// have to wrap it in a proxy function because Rollup is too damn smart
// and if this module doesn't actually contain any logic of its own
// then Rollup just use 'hoist-non-react-statics' directly in other chunks
export default (targetComponent, sourceComponent) =>
hoistNonReactStatics(targetComponent, sourceComponent)
export default <
T extends React.ComponentType<any>,
S extends React.ComponentType<any>
>(
targetComponent: T,
sourceComponent: S
) => hoistNonReactStatics(targetComponent, sourceComponent)
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import * as React from 'react'
import {
EmotionCache,
getRegisteredStyles,
insertStyles,
registerStyles
registerStyles,
SerializedStyles
} from '@emotion/utils'
import { serializeStyles } from '@emotion/serialize'
import { CSSInterpolation, serializeStyles } from '@emotion/serialize'
import isDevelopment from '#is-development'
import { withEmotionCache } from './context'
import { ThemeContext } from './theming'
import { Theme, ThemeContext } from './theming'
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
import isBrowser from '#is-browser'

/*
type ClassNameArg =
export interface ArrayClassNamesArg extends Array<ClassNamesArg> {}

export type ClassNamesArg =
| undefined
| null
| string
| boolean
| { [key: string]: boolean }
| Array<ClassNameArg>
| null
| void
*/
| { [className: string]: boolean | null | undefined }
| ArrayClassNamesArg

let classnames = (args /*: Array<ClassNameArg> */) /*: string */ => {
let classnames = (args: ArrayClassNamesArg): string => {
let len = args.length
let i = 0
let cls = ''
Expand Down Expand Up @@ -69,11 +71,11 @@ let classnames = (args /*: Array<ClassNameArg> */) /*: string */ => {
return cls
}
function merge(
registered /*: Object */,
css /*: (...args: Array<any>) => string */,
className /*: string */
registered: EmotionCache['registered'],
css: ClassNamesContent['css'],
className: string
) {
const registeredStyles = []
const registeredStyles: string[] = []

const rawClassName = getRegisteredStyles(
registered,
Expand All @@ -87,7 +89,13 @@ function merge(
return rawClassName + css(registeredStyles)
}

const Insertion = ({ cache, serializedArr }) => {
const Insertion = ({
cache,
serializedArr
}: {
cache: EmotionCache
serializedArr: SerializedStyles[]
}) => {
let rules = useInsertionEffectAlwaysWithSyncFallback(() => {
let rules = ''
for (let i = 0; i < serializedArr.length; i++) {
Expand All @@ -101,14 +109,14 @@ const Insertion = ({ cache, serializedArr }) => {
}
})

if (!isBrowser && rules.length !== 0) {
if (!isBrowser && rules!.length !== 0) {
return (
<style
{...{
[`data-emotion`]: `${cache.key} ${serializedArr
.map(serialized => serialized.name)
.join(' ')}`,
dangerouslySetInnerHTML: { __html: rules },
dangerouslySetInnerHTML: { __html: rules! },
nonce: cache.sheet.nonce
}}
/>
Expand All @@ -117,21 +125,23 @@ const Insertion = ({ cache, serializedArr }) => {
return null
}

/*
type Props = {
children: ({
css: (...args: any) => string,
cx: (...args: Array<ClassNameArg>) => string,
theme: Object
}) => React.Node
} */
export interface ClassNamesContent {
css(template: TemplateStringsArray, ...args: Array<CSSInterpolation>): string
css(...args: Array<CSSInterpolation>): string
cx(...args: Array<ClassNamesArg>): string
theme: Theme
}

export interface ClassNamesProps {
children(content: ClassNamesContent): React.ReactNode
}

export const ClassNames /*: React.AbstractComponent<Props>*/ =
/* #__PURE__ */ withEmotionCache((props, cache) => {
export const ClassNames = /* #__PURE__ */ withEmotionCache<ClassNamesProps>(
(props, cache) => {
let hasRendered = false
let serializedArr = []
let serializedArr: SerializedStyles[] = []

let css = (...args /*: Array<any> */) => {
let css: ClassNamesContent['css'] = (...args) => {
if (hasRendered && isDevelopment) {
throw new Error('css can only be used during render')
}
Expand All @@ -142,7 +152,7 @@ export const ClassNames /*: React.AbstractComponent<Props>*/ =
registerStyles(cache, serialized, false)
return `${cache.key}-${serialized.name}`
}
let cx = (...args /*: Array<ClassNameArg>*/) => {
let cx = (...args: Array<ClassNamesArg>) => {
if (hasRendered && isDevelopment) {
throw new Error('cx can only be used during render')
}
Expand All @@ -162,7 +172,8 @@ export const ClassNames /*: React.AbstractComponent<Props>*/ =
{ele}
</>
)
})
}
)

if (isDevelopment) {
ClassNames.displayName = 'EmotionClassNames'
Expand Down
Loading
Loading