Skip to content

Commit 9c19c68

Browse files
committed
chore: convert library from Flow to TypeScript
Migrate lib/ and __tests__/ from Flow to TypeScript: - Replace @babel/preset-flow with @babel/preset-typescript - Convert lib/*.js (Flow) to *.ts/*.tsx with explicit types - Add lib/index.ts as a typed entry-point for declaration emit - Drop flow-bin, flow-typed/, .flowconfig - Add tsconfig.json (typecheck), tsconfig.build.json (.d.ts emit), tsconfig.test.json - build.sh now runs babel + tsc --emitDeclarationOnly - ESLint switched to @typescript-eslint/parser for ts/tsx - Tests renamed to .test.tsx / .test.ts, kept JS-style; jest picks them up via babel - package.json gains 'typecheck' script and 'types' field Public runtime API is unchanged. PropTypes runtime validation preserved. Verified: yarn typecheck, yarn lint, yarn test (50 passing), yarn build (emits .js + .d.ts).
1 parent db2e37e commit 9c19c68

24 files changed

Lines changed: 412 additions & 1451 deletions

.babelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"@babel/preset-react",
10-
"@babel/preset-flow"
10+
"@babel/preset-typescript"
1111
],
1212
"plugins": [
1313
["@babel/plugin-proposal-class-properties", {"loose": true}],

.claude/commands/goal.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
description: Convert react-resizable from Flow to TypeScript
3+
---
4+
5+
# Goal: Convert react-resizable from Flow to TypeScript
6+
7+
## Why
8+
9+
The repo is currently typed with Flow (v0.153.0, pinned to a 2020 version). Flow is no longer well-supported in the React ecosystem and most consumers expect TypeScript declarations. Convert the library source, tests, and build pipeline to TypeScript while preserving all public runtime behavior (including PropTypes) and the existing build output shape (CommonJS lib + ambient declarations consumed via `build/`).
10+
11+
## Constraints
12+
13+
- No behavior changes. Snapshots and unit tests must continue to pass.
14+
- The public API surface is unchanged. `require('react-resizable').Resizable` and `.ResizableBox` must keep working.
15+
- Keep `prop-types` runtime validation in place (downstream JS consumers rely on it).
16+
- Continue to emit a CommonJS bundle from `build/` plus type declarations next to each module.
17+
- Drop Flow (`flow-bin`, `.flowconfig`, `flow-typed/`, `@babel/preset-flow`, `// @flow` pragmas).
18+
19+
## Acceptance criteria
20+
21+
- [ ] `yarn install` succeeds.
22+
- [ ] `yarn typecheck` (tsc --noEmit) passes with no errors.
23+
- [ ] `yarn lint` passes (ESLint with @typescript-eslint).
24+
- [ ] `yarn test` passes (all existing tests, including snapshots).
25+
- [ ] `yarn build` produces `build/Resizable.js`, `build/ResizableBox.js`, `build/utils.js`, `build/propTypes.js` plus matching `.d.ts` declaration files.
26+
- [ ] `index.js` still resolves to the built output.
27+
- [ ] `package.json` advertises `"types": "./build/index.d.ts"` (or equivalent) so TS consumers get IntelliSense.
28+
- [ ] No `.flow` files remain in `lib/`. No `// @flow` pragmas.
29+
30+
## Plan
31+
32+
1. **Tooling**: add `typescript`, `@types/react`, `@types/react-dom`, `@types/prop-types`, `@types/jest`, `@babel/preset-typescript`, `@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`. Remove `flow-bin`, `@babel/preset-flow`. Write `tsconfig.json` (target ES2020, jsx react, declaration true, declarationDir ./build, noEmit configurable via separate `tsconfig.build.json`).
33+
2. **Babel**: replace `@babel/preset-flow` with `@babel/preset-typescript` in `.babelrc`.
34+
3. **Source conversion (`lib/`)**:
35+
- `utils.js``utils.ts`
36+
- `propTypes.js``propTypes.ts` (keep runtime PropTypes; export TS types alongside)
37+
- `Resizable.js``Resizable.tsx`
38+
- `ResizableBox.js``ResizableBox.tsx`
39+
- Map Flow types: `?T``T | null | undefined`, `SyntheticEvent<>``React.SyntheticEvent`, `Node as ReactNode``React.ReactNode`, `Element as ReactElement``React.ReactElement`, `ElementConfig<typeof X>``React.ComponentProps<typeof X>`, `ReactRef<T>``React.RefObject<T>`.
40+
- Strip `// @flow` pragmas.
41+
4. **Tests (`__tests__/`)**:
42+
- Rename `.test.js``.test.tsx` where JSX is used, otherwise `.test.ts`.
43+
- Fix any test typings that need help (e.g., enzyme/testing-library queries).
44+
- Re-record snapshots only if structural output is unchanged.
45+
5. **Build pipeline**:
46+
- `build.sh` runs babel (transpile .ts/.tsx → JS) and `tsc --emitDeclarationOnly` for .d.ts files.
47+
- Drop the `cp *.js *.js.flow` step.
48+
6. **package.json scripts**:
49+
- `lint`: ESLint over `.ts,.tsx`.
50+
- `typecheck`: `tsc --noEmit`.
51+
- Remove the `flow` script.
52+
7. **ESLint**: update `eslint.config.js` to parse TS with `@typescript-eslint/parser` and include `@typescript-eslint` rules at "recommended" level. Keep existing react/jest plugins.
53+
8. **Examples**: keep `examples/example.js` as JS — out of scope for this conversion. Update webpack to still build it.
54+
9. **Run the acceptance gate**: `yarn install && yarn typecheck && yarn lint && yarn test && yarn build`. Fix everything until green.
55+
10. **Commit**: one commit, message `chore: convert library from Flow to TypeScript`.
56+
57+
## Out of scope
58+
59+
- Examples directory (`examples/*.js`).
60+
- API/behavior changes.
61+
- Bumping major dependency versions beyond what's needed for TS to work.
62+
- Migrating ESLint plugins beyond what's needed for TS parsing.

.flowconfig

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('render ResizableBox', () => {
88
const props = {
99
axis: 'x',
1010
draggableOpts: {},
11-
handle: (jest.fn((resizeHandle, ref) => <span className={`test-class-${resizeHandle}`} ref={ref} />): Function),
11+
handle: jest.fn((resizeHandle, ref) => <span className={`test-class-${resizeHandle}`} ref={ref} />) as any,
1212
handleSize: [20, 20],
1313
height: 50,
1414
lockAspectRatio: false,
File renamed without changes.
File renamed without changes.

build.sh

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
#!/bin/bash -ex
22
rm -rf ./build
33

4-
# Simple babel run
5-
./node_modules/.bin/babel --out-dir ./build ./lib
4+
# Transpile TS/TSX with babel
5+
./node_modules/.bin/babel --extensions ".ts,.tsx" --out-dir ./build ./lib
66

7-
# Gen flow configs
8-
# When https://github.com/facebook/flow/issues/2830 et al are fixed we can use this, but not until then
9-
# find ./lib -type f -name '*.js' -exec sh -c "$BIN/flow gen-flow-files \$0 --out-dir build" {} \;
10-
11-
# Copy original source as js.flow; flow will pick them up
12-
find ./lib -type f -name '*.js' -exec sh -c 'cp $0 build/$(basename $0).flow' {} \;
7+
# Emit TypeScript declaration files
8+
./node_modules/.bin/tsc --project tsconfig.build.json

eslint.config.js

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
1-
const babelParser = require('@babel/eslint-parser');
1+
const tsParser = require('@typescript-eslint/parser');
2+
const tsPlugin = require('@typescript-eslint/eslint-plugin');
23
const reactPlugin = require('eslint-plugin-react');
34
const jestPlugin = require('eslint-plugin-jest');
45
const js = require('@eslint/js');
56

67
module.exports = [
78
js.configs.recommended,
89
{
9-
files: ['**/*.js', '**/*.jsx'],
10+
files: ['**/*.js', '**/*.cjs'],
1011
languageOptions: {
11-
parser: babelParser,
12+
globals: {
13+
window: 'readonly',
14+
document: 'readonly',
15+
console: 'readonly',
16+
require: 'readonly',
17+
module: 'readonly',
18+
exports: 'readonly',
19+
__dirname: 'readonly',
20+
process: 'readonly',
21+
global: 'readonly',
22+
},
23+
},
24+
rules: {
25+
'no-unused-vars': 'off',
26+
},
27+
},
28+
{
29+
files: ['**/*.ts', '**/*.tsx'],
30+
languageOptions: {
31+
parser: tsParser,
1232
parserOptions: {
13-
requireConfigFile: false,
14-
babelOptions: {
15-
presets: ['@babel/preset-react', '@babel/preset-flow']
16-
}
33+
ecmaVersion: 'latest',
34+
sourceType: 'module',
35+
ecmaFeatures: {jsx: true},
1736
},
1837
globals: {
1938
// Browser
2039
window: 'readonly',
2140
document: 'readonly',
2241
console: 'readonly',
2342
HTMLElement: 'readonly',
43+
HTMLDivElement: 'readonly',
44+
HTMLSpanElement: 'readonly',
45+
Element: 'readonly',
46+
DOMRect: 'readonly',
2447
// Node
2548
require: 'readonly',
2649
module: 'readonly',
@@ -36,16 +59,12 @@ module.exports = [
3659
beforeEach: 'readonly',
3760
afterEach: 'readonly',
3861
it: 'readonly',
39-
// Flow
40-
ReactElement: 'readonly',
41-
ReactClass: 'readonly',
42-
SyntheticEvent: 'readonly',
43-
ClientRect: 'readonly'
44-
}
62+
},
4563
},
4664
plugins: {
65+
'@typescript-eslint': tsPlugin,
4766
react: reactPlugin,
48-
jest: jestPlugin
67+
jest: jestPlugin,
4968
},
5069
rules: {
5170
...jestPlugin.configs.recommended.rules,
@@ -56,12 +75,14 @@ module.exports = [
5675
'comma-dangle': 'off',
5776
'dot-notation': 'off',
5877
'no-console': 'off',
59-
'no-use-before-define': ['warn', 'nofunc'],
78+
'no-use-before-define': 'off',
6079
'no-underscore-dangle': 'off',
6180
'no-unused-vars': 'off',
6281
'new-cap': 'off',
6382
'react/jsx-uses-vars': 'warn',
64-
'semi': ['warn', 'always']
65-
}
66-
}
83+
'semi': ['warn', 'always'],
84+
'@typescript-eslint/no-unused-vars': 'off',
85+
'@typescript-eslint/no-explicit-any': 'off',
86+
},
87+
},
6788
];

0 commit comments

Comments
 (0)