Skip to content
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
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}
],
"@babel/preset-react",
"@babel/preset-flow"
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-proposal-class-properties", {"loose": true}],
Expand Down
62 changes: 62 additions & 0 deletions .claude/commands/goal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
description: Convert react-resizable from Flow to TypeScript
---

# Goal: Convert react-resizable from Flow to TypeScript

## Why

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/`).

## Constraints

- No behavior changes. Snapshots and unit tests must continue to pass.
- The public API surface is unchanged. `require('react-resizable').Resizable` and `.ResizableBox` must keep working.
- Keep `prop-types` runtime validation in place (downstream JS consumers rely on it).
- Continue to emit a CommonJS bundle from `build/` plus type declarations next to each module.
- Drop Flow (`flow-bin`, `.flowconfig`, `flow-typed/`, `@babel/preset-flow`, `// @flow` pragmas).

## Acceptance criteria

- [ ] `yarn install` succeeds.
- [ ] `yarn typecheck` (tsc --noEmit) passes with no errors.
- [ ] `yarn lint` passes (ESLint with @typescript-eslint).
- [ ] `yarn test` passes (all existing tests, including snapshots).
- [ ] `yarn build` produces `build/Resizable.js`, `build/ResizableBox.js`, `build/utils.js`, `build/propTypes.js` plus matching `.d.ts` declaration files.
- [ ] `index.js` still resolves to the built output.
- [ ] `package.json` advertises `"types": "./build/index.d.ts"` (or equivalent) so TS consumers get IntelliSense.
- [ ] No `.flow` files remain in `lib/`. No `// @flow` pragmas.

## Plan

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`).
2. **Babel**: replace `@babel/preset-flow` with `@babel/preset-typescript` in `.babelrc`.
3. **Source conversion (`lib/`)**:
- `utils.js` → `utils.ts`
- `propTypes.js` → `propTypes.ts` (keep runtime PropTypes; export TS types alongside)
- `Resizable.js` → `Resizable.tsx`
- `ResizableBox.js` → `ResizableBox.tsx`
- 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>`.
- Strip `// @flow` pragmas.
4. **Tests (`__tests__/`)**:
- Rename `.test.js` → `.test.tsx` where JSX is used, otherwise `.test.ts`.
- Fix any test typings that need help (e.g., enzyme/testing-library queries).
- Re-record snapshots only if structural output is unchanged.
5. **Build pipeline**:
- `build.sh` runs babel (transpile .ts/.tsx → JS) and `tsc --emitDeclarationOnly` for .d.ts files.
- Drop the `cp *.js *.js.flow` step.
6. **package.json scripts**:
- `lint`: ESLint over `.ts,.tsx`.
- `typecheck`: `tsc --noEmit`.
- Remove the `flow` script.
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.
8. **Examples**: keep `examples/example.js` as JS — out of scope for this conversion. Update webpack to still build it.
9. **Run the acceptance gate**: `yarn install && yarn typecheck && yarn lint && yarn test && yarn build`. Fix everything until green.
10. **Commit**: one commit, message `chore: convert library from Flow to TypeScript`.

## Out of scope

- Examples directory (`examples/*.js`).
- API/behavior changes.
- Bumping major dependency versions beyond what's needed for TS to work.
- Migrating ESLint plugins beyond what's needed for TS parsing.
25 changes: 0 additions & 25 deletions .flowconfig

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const props = {
axis: 'x',
draggableOpts: {},
handle: (jest.fn((resizeHandle, ref) => <span className={`test-class-${resizeHandle}`} ref={ref} />): Function),
handle: jest.fn((resizeHandle, ref) => <span className={`test-class-${resizeHandle}`} ref={ref} />) as any,
handleSize: [20, 20],
height: 50,
lockAspectRatio: false,
Expand Down Expand Up @@ -237,7 +237,7 @@
});

describe('event.persist handling', () => {
test('works when event.persist is not available', () => {

Check warning on line 240 in __tests__/ResizableBox.test.tsx

View workflow job for this annotation

GitHub Actions / Run linters

Test has no assertions
const boxRef = React.createRef();
render(<ResizableBox {...props} ref={boxRef}>{children}</ResizableBox>);

Expand Down
File renamed without changes.
12 changes: 4 additions & 8 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
#!/bin/bash -ex
rm -rf ./build

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

# Gen flow configs
# When https://github.com/facebook/flow/issues/2830 et al are fixed we can use this, but not until then
# find ./lib -type f -name '*.js' -exec sh -c "$BIN/flow gen-flow-files \$0 --out-dir build" {} \;

# Copy original source as js.flow; flow will pick them up
find ./lib -type f -name '*.js' -exec sh -c 'cp $0 build/$(basename $0).flow' {} \;
# Emit TypeScript declaration files
./node_modules/.bin/tsc --project tsconfig.build.json
57 changes: 39 additions & 18 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
const babelParser = require('@babel/eslint-parser');
const tsParser = require('@typescript-eslint/parser');
const tsPlugin = require('@typescript-eslint/eslint-plugin');
const reactPlugin = require('eslint-plugin-react');
const jestPlugin = require('eslint-plugin-jest');
const js = require('@eslint/js');

module.exports = [
js.configs.recommended,
{
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.cjs'],
languageOptions: {
parser: babelParser,
globals: {
window: 'readonly',
document: 'readonly',
console: 'readonly',
require: 'readonly',
module: 'readonly',
exports: 'readonly',
__dirname: 'readonly',
process: 'readonly',
global: 'readonly',
},
},
rules: {
'no-unused-vars': 'off',
},
},
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: tsParser,
parserOptions: {
requireConfigFile: false,
babelOptions: {
presets: ['@babel/preset-react', '@babel/preset-flow']
}
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {jsx: true},
},
globals: {
// Browser
window: 'readonly',
document: 'readonly',
console: 'readonly',
HTMLElement: 'readonly',
HTMLDivElement: 'readonly',
HTMLSpanElement: 'readonly',
Element: 'readonly',
DOMRect: 'readonly',
// Node
require: 'readonly',
module: 'readonly',
Expand All @@ -36,16 +59,12 @@ module.exports = [
beforeEach: 'readonly',
afterEach: 'readonly',
it: 'readonly',
// Flow
ReactElement: 'readonly',
ReactClass: 'readonly',
SyntheticEvent: 'readonly',
ClientRect: 'readonly'
}
},
},
plugins: {
'@typescript-eslint': tsPlugin,
react: reactPlugin,
jest: jestPlugin
jest: jestPlugin,
},
rules: {
...jestPlugin.configs.recommended.rules,
Expand All @@ -56,12 +75,14 @@ module.exports = [
'comma-dangle': 'off',
'dot-notation': 'off',
'no-console': 'off',
'no-use-before-define': ['warn', 'nofunc'],
'no-use-before-define': 'off',
'no-underscore-dangle': 'off',
'no-unused-vars': 'off',
'new-cap': 'off',
'react/jsx-uses-vars': 'warn',
'semi': ['warn', 'always']
}
}
'semi': ['warn', 'always'],
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
Loading
Loading