From fc4d7bd744c205f55513dcd4e4e5134198c219de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 5 Dec 2024 11:53:48 +0100 Subject: [PATCH] Convert `@emotion/react`'s source code to TypeScript (#3281) * Convert `@emotion/react`'s source code to TypeScript * add changeset * limit `package.json['files']` * fixed type issue * try `esModuleInterop` * add required cast * use `resolveJsonModule` too * fixed extra things * fixed a silly mistake * one extra thing * fixed type error * try building before running dtslint * fixed import * try to bump tested TS versions * add extra ts-ignore * make sure the ts-ignore is above the problematic line * rewrite tests to stop relying on implementation detail * fixed type error * corrext tests * fixed entrypoints types --- .changeset/rotten-baboons-knock.md | 5 + .github/workflows/main.yml | 3 + package.json | 2 +- packages/cache/types/index.d.ts | 2 +- packages/css/test/no-babel/index.test.js | 2 - packages/css/test/sheet.dom.test.js | 2 - packages/native/types/index.d.ts | 2 +- packages/native/types/tsconfig.json | 2 + packages/primitives-core/src/styled.ts | 3 +- packages/react/__tests__/element.js | 4 - .../__tests__/get-label-from-stack-trace.js | 1 - packages/react/__tests__/global.js | 1 - packages/react/__tests__/import-prod.js | 1 - .../react/__tests__/theme-provider.dom.js | 1 - packages/react/_isolated-hnrs/package.json | 1 + packages/react/jsx-dev-runtime/package.json | 2 +- packages/react/jsx-runtime/package.json | 2 +- packages/react/package.json | 27 ++-- packages/react/src/_isolated-hnrs.d.ts | 3 - .../{_isolated-hnrs.js => _isolated-hnrs.ts} | 9 +- .../src/{class-names.js => class-names.tsx} | 75 +++++---- .../src/conditions/{false.js => false.ts} | 0 .../{is-browser.js => is-browser.ts} | 0 .../react/src/conditions/{true.js => true.ts} | 0 .../react/src/{context.js => context.tsx} | 47 +++--- packages/react/src/css.js | 8 - packages/react/src/css.ts | 14 ++ ...emotion-element.js => emotion-element.tsx} | 54 +++++-- ...trace.js => get-label-from-stack-trace.ts} | 11 +- packages/react/src/global.js | 145 ----------------- packages/react/src/global.tsx | 147 ++++++++++++++++++ packages/react/src/index.d.ts | 1 - packages/react/src/{index.js => index.ts} | 27 +++- packages/react/src/jsx-dev-runtime.d.ts | 1 - ...{jsx-dev-runtime.js => jsx-dev-runtime.ts} | 14 +- .../jsx-namespace.ts} | 50 +++--- packages/react/src/jsx-runtime.d.ts | 1 - packages/react/src/jsx-runtime.js | 21 --- packages/react/src/jsx-runtime.ts | 32 ++++ packages/react/src/jsx.js | 25 --- packages/react/src/jsx.ts | 45 ++++++ packages/react/src/keyframes.js | 23 --- packages/react/src/keyframes.ts | 27 ++++ .../react/src/{theming.js => theming.tsx} | 58 ++++--- .../react/{types/helper.d.ts => src/types.ts} | 0 packages/react/src/{utils.js => utils.ts} | 0 packages/react/types/index.d.ts | 117 +------------- packages/react/types/jsx-dev-runtime.d.ts | 1 - packages/react/types/jsx-runtime.d.ts | 1 - packages/react/types/tests.tsx | 63 ++++---- packages/react/types/theming.d.ts | 31 ---- packages/react/types/tsconfig.json | 2 + packages/serialize/types/index.d.ts | 2 +- packages/sheet/__tests__/index.js | 1 - packages/styled/types/index.d.ts | 2 +- packages/styled/types/tsconfig.json | 2 + packages/utils/src/index.ts | 2 +- site/package.json | 2 +- yarn.lock | 40 +++-- 59 files changed, 582 insertions(+), 585 deletions(-) create mode 100644 .changeset/rotten-baboons-knock.md delete mode 100644 packages/react/src/_isolated-hnrs.d.ts rename packages/react/src/{_isolated-hnrs.js => _isolated-hnrs.ts} (69%) rename packages/react/src/{class-names.js => class-names.tsx} (70%) rename packages/react/src/conditions/{false.js => false.ts} (100%) rename packages/react/src/conditions/{is-browser.js => is-browser.ts} (100%) rename packages/react/src/conditions/{true.js => true.ts} (100%) rename packages/react/src/{context.js => context.tsx} (61%) delete mode 100644 packages/react/src/css.js create mode 100644 packages/react/src/css.ts rename packages/react/src/{emotion-element.js => emotion-element.tsx} (77%) rename packages/react/src/{get-label-from-stack-trace.js => get-label-from-stack-trace.ts} (81%) delete mode 100644 packages/react/src/global.js create mode 100644 packages/react/src/global.tsx delete mode 100644 packages/react/src/index.d.ts rename packages/react/src/{index.js => index.ts} (68%) delete mode 100644 packages/react/src/jsx-dev-runtime.d.ts rename packages/react/src/{jsx-dev-runtime.js => jsx-dev-runtime.ts} (59%) rename packages/react/{types/jsx-namespace.d.ts => src/jsx-namespace.ts} (62%) delete mode 100644 packages/react/src/jsx-runtime.d.ts delete mode 100644 packages/react/src/jsx-runtime.js create mode 100644 packages/react/src/jsx-runtime.ts delete mode 100644 packages/react/src/jsx.js create mode 100644 packages/react/src/jsx.ts delete mode 100644 packages/react/src/keyframes.js create mode 100644 packages/react/src/keyframes.ts rename packages/react/src/{theming.js => theming.tsx} (55%) rename packages/react/{types/helper.d.ts => src/types.ts} (100%) rename packages/react/src/{utils.js => utils.ts} (100%) delete mode 100644 packages/react/types/jsx-dev-runtime.d.ts delete mode 100644 packages/react/types/jsx-runtime.d.ts delete mode 100644 packages/react/types/theming.d.ts diff --git a/.changeset/rotten-baboons-knock.md b/.changeset/rotten-baboons-knock.md new file mode 100644 index 0000000000..8ac079329e --- /dev/null +++ b/.changeset/rotten-baboons-knock.md @@ -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. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6be13eb6c..916f09ebab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,5 +88,8 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/actions/ci-setup + - name: build + run: yarn build + - name: dtslint run: yarn test:typescript diff --git a/package.json b/package.json index e00ea7151e..ac464ba243 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/cache/types/index.d.ts b/packages/cache/types/index.d.ts index bf6a4ae6cb..546becea7f 100644 --- a/packages/cache/types/index.d.ts +++ b/packages/cache/types/index.d.ts @@ -1 +1 @@ -export * from '../src' +export * from '..' diff --git a/packages/css/test/no-babel/index.test.js b/packages/css/test/no-babel/index.test.js index 2b0daf8e72..0695aa3db0 100644 --- a/packages/css/test/no-babel/index.test.js +++ b/packages/css/test/no-babel/index.test.js @@ -7,7 +7,6 @@ import styled from '@emotion/styled' let consoleError = console.error afterEach(() => { - // $FlowFixMe console.error = consoleError }) @@ -146,7 +145,6 @@ describe('css', () => { }) const spy = jest.fn() - // $FlowFixMe console.error = spy expect(() => diff --git a/packages/css/test/sheet.dom.test.js b/packages/css/test/sheet.dom.test.js index 90833e56e1..9468b0fd74 100644 --- a/packages/css/test/sheet.dom.test.js +++ b/packages/css/test/sheet.dom.test.js @@ -3,7 +3,6 @@ import { sheet } from '@emotion/css' const consoleError = console.error afterEach(() => { - // $FlowFixMe console.error = consoleError }) @@ -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) diff --git a/packages/native/types/index.d.ts b/packages/native/types/index.d.ts index 975f2a7bf9..e7c6036366 100644 --- a/packages/native/types/index.d.ts +++ b/packages/native/types/index.d.ts @@ -1,5 +1,5 @@ // Definitions by: Pat Sissons -// TypeScript Version: 3.4 +// TypeScript Version: 4.1 import * as RN from 'react-native' import { Theme } from '@emotion/react' diff --git a/packages/native/types/tsconfig.json b/packages/native/types/tsconfig.json index ae37b14af8..f9cd18d21c 100644 --- a/packages/native/types/tsconfig.json +++ b/packages/native/types/tsconfig.json @@ -5,6 +5,8 @@ "jsx": "react", "lib": ["es6", "dom"], "module": "commonjs", + "esModuleInterop": true, + "resolveJsonModule": true, "noEmit": true, "strict": true, "target": "es5", diff --git a/packages/primitives-core/src/styled.ts b/packages/primitives-core/src/styled.ts index 1248833dbd..45ba0fd9a2 100644 --- a/packages/primitives-core/src/styled.ts +++ b/packages/primitives-core/src/styled.ts @@ -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((props, ref) => { - const finalTag = (shouldUseAs && props.as) || component + const finalTag = + (shouldUseAs && (props.as as React.ElementType)) || component let mergedProps = props if (props.theme == null) { diff --git a/packages/react/__tests__/element.js b/packages/react/__tests__/element.js index 9206e89d6b..44d2201e8d 100644 --- a/packages/react/__tests__/element.js +++ b/packages/react/__tests__/element.js @@ -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() }) @@ -18,7 +15,6 @@ describe('EmotionElement', () => { const theme = { color: 'blue' } const cache = createCache({ key: 'context' }) - // $FlowFixMe const Comp = ({ flag }) => ( diff --git a/packages/react/__tests__/get-label-from-stack-trace.js b/packages/react/__tests__/get-label-from-stack-trace.js index e4c3827751..aa2c33f4f3 100644 --- a/packages/react/__tests__/get-label-from-stack-trace.js +++ b/packages/react/__tests__/get-label-from-stack-trace.js @@ -1,4 +1,3 @@ -// @flow import { getLabelFromStackTrace } from '../src/get-label-from-stack-trace' /** diff --git a/packages/react/__tests__/global.js b/packages/react/__tests__/global.js index a9258c6e2d..0834c77700 100644 --- a/packages/react/__tests__/global.js +++ b/packages/react/__tests__/global.js @@ -9,7 +9,6 @@ import { } from '@emotion/react' import createCache from '@emotion/cache' -// $FlowFixMe console.error = jest.fn() beforeEach(() => { diff --git a/packages/react/__tests__/import-prod.js b/packages/react/__tests__/import-prod.js index 73e5f81f78..4ab884bf21 100644 --- a/packages/react/__tests__/import-prod.js +++ b/packages/react/__tests__/import-prod.js @@ -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) { diff --git a/packages/react/__tests__/theme-provider.dom.js b/packages/react/__tests__/theme-provider.dom.js index 4c56ba9846..c3ea4e0f47 100644 --- a/packages/react/__tests__/theme-provider.dom.js +++ b/packages/react/__tests__/theme-provider.dom.js @@ -6,7 +6,6 @@ import * as React from 'react' import { jsx, ThemeProvider } from '@emotion/react' beforeEach(() => { - // $FlowFixMe document.head.innerHTML = '' }) diff --git a/packages/react/_isolated-hnrs/package.json b/packages/react/_isolated-hnrs/package.json index 72a77af979..47b752b2a1 100644 --- a/packages/react/_isolated-hnrs/package.json +++ b/packages/react/_isolated-hnrs/package.json @@ -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" diff --git a/packages/react/jsx-dev-runtime/package.json b/packages/react/jsx-dev-runtime/package.json index 5c5a6d908b..df34655a8b 100644 --- a/packages/react/jsx-dev-runtime/package.json +++ b/packages/react/jsx-dev-runtime/package.json @@ -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" diff --git a/packages/react/jsx-runtime/package.json b/packages/react/jsx-runtime/package.json index 6b6dab3eda..4166077e90 100644 --- a/packages/react/jsx-runtime/package.json +++ b/packages/react/jsx-runtime/package.json @@ -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" diff --git a/packages/react/package.json b/packages/react/package.json index 6bdbc605c3..f2a8405b0e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -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": { @@ -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, @@ -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", @@ -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": { diff --git a/packages/react/src/_isolated-hnrs.d.ts b/packages/react/src/_isolated-hnrs.d.ts deleted file mode 100644 index 1444ee061e..0000000000 --- a/packages/react/src/_isolated-hnrs.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// this entry point is not publicly available so we don't need to expose any types here -// we need to define a definition file for each entrypoint though, otherwise Preconstruct might get confused -export {} diff --git a/packages/react/src/_isolated-hnrs.js b/packages/react/src/_isolated-hnrs.ts similarity index 69% rename from packages/react/src/_isolated-hnrs.js rename to packages/react/src/_isolated-hnrs.ts index e1bd34508f..282af41816 100644 --- a/packages/react/src/_isolated-hnrs.js +++ b/packages/react/src/_isolated-hnrs.ts @@ -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, + S extends React.ComponentType +>( + targetComponent: T, + sourceComponent: S +) => hoistNonReactStatics(targetComponent, sourceComponent) diff --git a/packages/react/src/class-names.js b/packages/react/src/class-names.tsx similarity index 70% rename from packages/react/src/class-names.js rename to packages/react/src/class-names.tsx index c5b35276cb..3563811595 100644 --- a/packages/react/src/class-names.js +++ b/packages/react/src/class-names.tsx @@ -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 {} + +export type ClassNamesArg = + | undefined + | null | string | boolean - | { [key: string]: boolean } - | Array - | null - | void -*/ + | { [className: string]: boolean | null | undefined } + | ArrayClassNamesArg -let classnames = (args /*: Array */) /*: string */ => { +let classnames = (args: ArrayClassNamesArg): string => { let len = args.length let i = 0 let cls = '' @@ -69,11 +71,11 @@ let classnames = (args /*: Array */) /*: string */ => { return cls } function merge( - registered /*: Object */, - css /*: (...args: Array) => string */, - className /*: string */ + registered: EmotionCache['registered'], + css: ClassNamesContent['css'], + className: string ) { - const registeredStyles = [] + const registeredStyles: string[] = [] const rawClassName = getRegisteredStyles( registered, @@ -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++) { @@ -101,14 +109,14 @@ const Insertion = ({ cache, serializedArr }) => { } }) - if (!isBrowser && rules.length !== 0) { + if (!isBrowser && rules!.length !== 0) { return (