Skip to content

refactor: migrate source from Flow to TypeScript#805

Merged
STRML merged 5 commits into
masterfrom
ts-migration
May 29, 2026
Merged

refactor: migrate source from Flow to TypeScript#805
STRML merged 5 commits into
masterfrom
ts-migration

Conversation

@STRML
Copy link
Copy Markdown
Collaborator

@STRML STRML commented May 28, 2026

Summary

Migrates the library source from Flow to TypeScript with zero public-API changes, so it can ship as a minor release. lib/*.js (Flow) becomes lib/*.ts/*.tsx, the Babel+Flow toolchain is replaced by tsup (CJS + ESM + auto-generated .d.ts), and webpack is retained only for the UMD bundle. TypeScript types are now generated from source instead of hand-maintained in typings/index.d.ts, eliminating the long-standing Flow↔TS drift problem.

No package.json version bump and no CHANGELOG.md entry are included — this PR is the migration only; releasing is a separate step.

Why now

The codebase carried two parallel type systems (Flow internally, hand-written TS definitions shipped to consumers) that had to be kept in sync by hand. Consolidating on TypeScript with generated declarations removes that maintenance burden and the drift risk.

What changed

  • Source: lib/{Draggable,DraggableCore}.{js→tsx}, lib/utils/*.{js→ts}, lib/cjs.{js→ts}, new lib/umd.ts. // @flow removed; Flow syntax translated to TS. Runtime logic untouched.
  • Build: tsup.config.ts emits CJS+ESM+.d.ts into build/cjs; webpack still emits the UMD global build/web/react-draggable.min.js. .babelrc.js and .flowconfig removed; Makefile updated.
  • Types: hand-written typings/index.d.ts deleted; declarations now generated. typings/tsconfig.json repointed at the generated declaration and typings/test.tsx left unchanged as a compile-time contract check.
  • Lint/test: ESLint switched to @typescript-eslint; Vitest now handles TS natively (Babel-Flow transform removed). Added test/typeCompat/ (asserts the generated public surface matches the pre-migration one) plus extra unit tests.

Compatibility (minor-version safe)

Verified against master HEAD — the migration introduces no behavior or public-surface change:

  • CJS: require() returns module.exports === Draggable, with .default === Draggable and .DraggableCore (preserves PR make Draggable explicit default export #254 / Draggable is undefined with normal import syntax in TypeScript #266). Confirmed at runtime against the built artifact.
  • UMD: bundle still exposes global ReactDraggable with react/react-dom mapped to React/ReactDOM externals.
  • Types: generated .d.ts exports the same surface (Draggable default, DraggableCore, DraggableProps, DraggableCoreProps, DraggableBounds, DraggableData, DraggableEvent, DraggableEventHandler, ControlPosition, PositionOffsetControlPosition); the unchanged typings/test.tsx compiles against it. DraggableCore children stays React.ReactNode (the internal Flow type was ReactElement) — guarded by a regression case in test/typeCompat.
  • deps/peerDeps: unchanged.

Status — 2026-05-28

Check Result
tsc --noEmit (typecheck) ✅ clean
yarn lint (eslint + tsc + typings tsc) ✅ clean
yarn test (vitest) 198 passed (baseline 131)
yarn build (tsup + webpack) ✅ emits build/cjs/* + build/web/react-draggable.min.js
CJS export shape vs baseline module.exports===Draggable, .default, .DraggableCore
UMD global + externals ReactDraggable, react/react-dom external
Generated .d.ts vs old surface ✅ matches; typings/test.tsx compiles

All CI jobs green on the latest commit: lint, typecheck, test (Node 20/22/24), test-browser (Puppeteer), coverage, plus CodeRabbit.

CI fixes applied after open (post-migration follow-ups)

The initial push was red because the migration left build-order assumptions the workflow's own verify (run against a pre-populated build/) had masked:

  • b42284d — typecheck job still ran the removed yarn flow; make lint and the type-compat test resolved react-draggable to the generated build/cjs/cjs.d.ts, absent in a fresh checkout. Repointed type validation at the source entry (lib/cjs.ts) — same public surface, no build dependency — and replaced the dead flow step with tsc --noEmit.
  • 5c8762e — the type-compat test shells out to a full tsc compile (~6s on Node 20/22 runners), exceeding vitest's 5s default and timing out (Node 24 masked it). Raised the per-case timeout to 60s.

Open follow-ups (resume here)

  • Release as 4.6.0 (version bump + CHANGELOG) in a separate step. Note: the CHANGELOG should also mention the already-merged unreleased ctrl+click (fix: treat ctrl+click as right-click on macOS #786) and React 19 findDOMNode changes that land in the same release.

Test plan

  • yarn lint
  • yarn test (198 pass)
  • yarn build + runtime CJS/UMD/.d.ts shape verification
  • yarn test:browser (Puppeteer, green in CI)
  • Full CI matrix green (Node 20/22/24)

Summary by CodeRabbit

  • Refactor
    • Migrated codebase to TypeScript with improved typings and React 19 compatibility; preserved CommonJS and UMD consumer contracts.
  • Build / Tooling
    • Modernized build and lint workflows, updated package metadata and bundling outputs, and added a post-build verification step.
  • Tests
    • Added type-compatibility tests and expanded unit coverage to protect public API and runtime behaviors.

Convert lib/*.js (Flow) to TypeScript (.ts/.tsx) and replace the Babel+Flow toolchain with tsup (CJS+ESM+auto-generated .d.ts), keeping webpack for the UMD bundle. Types are now generated from source instead of hand-maintained in typings/index.d.ts, eliminating Flow/TS drift.

Public API is unchanged (minor-version-safe): CJS keeps module.exports===Draggable plus .default and .DraggableCore (PR #254/#266); UMD bundle still exposes global ReactDraggable with react/react-dom externals; generated .d.ts matches the previously-shipped surface and the unchanged typings/test.tsx compiles against it. DraggableCore children stays React.ReactNode in the public types (the internal Flow type was ReactElement) with a regression guard in test/typeCompat.

Adds tsup.config.ts, root tsconfig.json, eslint @typescript-eslint, native-TS vitest, and extra unit tests (198 pass, up from 131). No version bump or CHANGELOG change.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e321e4f7-fdad-47a1-9f04-9be014a0a0d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea337f and c7fec38.

📒 Files selected for processing (2)
  • Makefile
  • scripts/verify-build.cjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/verify-build.cjs
  • Makefile

📝 Walkthrough

Walkthrough

This PR migrates react-draggable from Flow/Babel to TypeScript, adds tsup-based builds and CJS/UMD compatibility shims, refactors components and utilities to TypeScript, updates lint/test/build tooling, and introduces type-compat and expanded unit tests.

Changes

Flow to TypeScript Migration

Layer / File(s) Summary
Build system and TypeScript configuration
Makefile, tsconfig.json, tsup.config.ts, package.json, .github/workflows/ci.yml, webpack.config.js, scripts/verify-build.cjs
Add tsconfig.json and tsup.config.ts; refactor Makefile to build-lib (tsup) + build-web; update package exports/types and devDependencies; update CI typecheck steps; adapt webpack for UMD output; add post-build verification script.
Core component type migration and entry points
lib/Draggable.tsx, lib/DraggableCore.tsx, lib/cjs.ts, lib/umd.ts, lib/cjs.js
Migrate components to TypeScript with Partial<...> public props and declared internal props; adapt findDOMNode for React 19 compatibility; update defaultProps typing via casts; add lib/cjs.ts and lib/umd.ts to preserve CommonJS/UMD runtime contracts.
Utility functions TypeScript migration
lib/utils/domFns.ts, lib/utils/getPrefix.ts, lib/utils/log.ts, lib/utils/positionFns.ts, lib/utils/shims.ts, lib/utils/types.ts
Convert utilities to TypeScript: stricter DOM/event typings, typed event listener APIs, type-guard predicates, nullability and runtime guards, and removal of Flow shim types.
Linting, testing, packaging, CI
eslint.config.mjs, vitest.config.js, webpack.config.js, package.json, typings/tsconfig.json, .github/workflows/ci.yml
ESLint switched to @typescript-eslint parser/plugins; Vitest configured for TS/TSX; Webpack UMD entry now lib/umd.ts and uses esbuild-loader; package.json exports/types and devDependencies updated; CI typecheck runs tsc for source and typings.
Type compatibility test suite
test/typeCompat.test.ts, test/typeCompat/fixture.tsx, test/typeCompat/tsconfig.json
Add Vitest harness that runs tsc against a fixture and the typings project to enforce public type-surface compatibility; fixture exercises exported types and JSX usage.
Extended unit tests for utilities
test/utils/domFns.extra.test.js, test/utils/getPrefix.extra.test.js, test/utils/positionFns.bounds.test.js, test/utils/shims.extra.test.js
Add comprehensive unit tests for domFns, prefix detection, bounds/control-position math, and shims helpers covering many edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 From Flow to Types so crisp and new,
I hopped through code to check each view.
tsup hums builds and tests run bright,
Types guard the drag with gentle might.
A carrot for CI — all green tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'refactor: migrate source from Flow to TypeScript' directly and accurately summarizes the primary change across the entire changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ts-migration

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

scripts/verify-build.cjs

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Makefile`:
- Around line 14-17: The lint target currently runs "tsc -p typings" but that
tsconfig depends on a generated declaration (build/cjs/cjs.d.ts), causing
non-deterministic failures; update the Makefile's lint target (the lint rule
that runs "$(BIN)/eslint lib", "$(BIN)/tsc --noEmit", "$(BIN)/tsc -p typings")
to first produce the required declarations (either by invoking the build step
that creates build/cjs/cjs.d.ts or by running tsc with emitDeclarationOnly
against the appropriate tsconfig) so that the subsequent "$(BIN)/tsc -p typings"
always has the generated artifact available. Ensure the change references the
same lint target and the "$(BIN)/tsc -p typings" invocation so reviewers can
locate and verify the fix.

In `@test/typeCompat.test.ts`:
- Around line 50-56: The beforeAll hook in typeCompat.test.ts currently
hard-fails when the built declaration (generatedDts) is missing; change it so
the suite is build-aware: inside beforeAll (the function referencing existsSync
and generatedDts) attempt to produce the artefact (e.g., run a synchronous build
command like yarn build via child_process.execSync or the repo's build script)
if the file is absent, then re-check existsSync; if the file still isn't
present, skip the suite or fail with a clear message (use the test framework's
skip mechanism or a controlled throw) rather than unconditionally throwing at
first sight.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbd92e1c-a5aa-4477-afbb-62af0baf8930

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6e95a and 6618f76.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (29)
  • .babelrc.js
  • .flowconfig
  • Makefile
  • eslint.config.mjs
  • lib/Draggable.tsx
  • lib/DraggableCore.tsx
  • lib/cjs.js
  • lib/cjs.ts
  • lib/umd.ts
  • lib/utils/domFns.ts
  • lib/utils/getPrefix.ts
  • lib/utils/log.ts
  • lib/utils/positionFns.ts
  • lib/utils/shims.ts
  • lib/utils/types.ts
  • package.json
  • test/typeCompat.test.ts
  • test/typeCompat/fixture.tsx
  • test/typeCompat/tsconfig.json
  • test/utils/domFns.extra.test.js
  • test/utils/getPrefix.extra.test.js
  • test/utils/positionFns.bounds.test.js
  • test/utils/shims.extra.test.js
  • tsconfig.json
  • tsup.config.ts
  • typings/index.d.ts
  • typings/tsconfig.json
  • vitest.config.js
  • webpack.config.js
💤 Files with no reviewable changes (4)
  • lib/cjs.js
  • typings/index.d.ts
  • .flowconfig
  • .babelrc.js

Comment thread Makefile
Comment on lines 14 to 17
lint:
@$(BIN)/flow
@$(BIN)/eslint lib/* lib/utils/*
@$(BIN)/eslint lib
@$(BIN)/tsc --noEmit
@$(BIN)/tsc -p typings
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make lint deterministic against generated declarations.

Line 17 now depends on generated build/cjs/cjs.d.ts (via typings/tsconfig.json), but lint does not ensure that artifact exists. This can fail on clean environments and pass only on machines with stale build output.

🔧 Proposed fix
-lint:
+lint: build-lib
 	@$(BIN)/eslint lib
 	@$(BIN)/tsc --noEmit
 	@$(BIN)/tsc -p typings
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
lint:
@$(BIN)/flow
@$(BIN)/eslint lib/* lib/utils/*
@$(BIN)/eslint lib
@$(BIN)/tsc --noEmit
@$(BIN)/tsc -p typings
lint: build-lib
@$(BIN)/eslint lib
@$(BIN)/tsc --noEmit
@$(BIN)/tsc -p typings
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 14 - 17, The lint target currently runs "tsc -p
typings" but that tsconfig depends on a generated declaration
(build/cjs/cjs.d.ts), causing non-deterministic failures; update the Makefile's
lint target (the lint rule that runs "$(BIN)/eslint lib", "$(BIN)/tsc --noEmit",
"$(BIN)/tsc -p typings") to first produce the required declarations (either by
invoking the build step that creates build/cjs/cjs.d.ts or by running tsc with
emitDeclarationOnly against the appropriate tsconfig) so that the subsequent
"$(BIN)/tsc -p typings" always has the generated artifact available. Ensure the
change references the same lint target and the "$(BIN)/tsc -p typings"
invocation so reviewers can locate and verify the fix.

Comment thread test/typeCompat.test.ts Outdated
CI was red because the migration left three build-order assumptions that only surfaced in a fresh checkout: the typecheck job still ran the removed `yarn flow`; `make lint` and the typeCompat test resolved `react-draggable` to the generated build/cjs/cjs.d.ts, which doesn't exist before `yarn build`. The workflow's own verify ran against a pre-populated build/, hiding it.

Repoint typings/tsconfig.json and test/typeCompat/tsconfig.json at the source entry (lib/cjs.ts) instead of the built declaration — same public surface, no build dependency. Drop the build-exists guard in typeCompat.test.ts. Replace the dead `yarn flow` CI step with `yarn tsc --noEmit`. Verified lint/typecheck/test/coverage/build all green with build/ absent.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/typeCompat.test.ts (1)

56-60: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Increase test timeout to accommodate TypeScript compilation.

This test is timing out in CI after 5000ms. TypeScript compilation of the typings project takes longer than the default Vitest timeout, especially on CI runners.

Add a timeout parameter to allow sufficient time for tsc to complete.

⏱️ Proposed fix
-  it('the existing typings/test.tsx still compiles unchanged against the public surface', () => {
+  it('the existing typings/test.tsx still compiles unchanged against the public surface', () => {
     const {ok, output} = runTsc(resolve(repoRoot, 'typings'));
     expect(output, output).toBe('');
     expect(ok).toBe(true);
-  });
+  }, 30000);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/typeCompat.test.ts` around lines 56 - 60, The test times out because tsc
can take longer than Vitest's default; update the test in typeCompat.test.ts
that calls runTsc(resolve(repoRoot, 'typings')) to pass a longer timeout to the
it() call (e.g., add a third or second argument timeout value to it(...) or use
the optional timeout parameter signature) so the test has enough time to
complete in CI while keeping the existing assertions using runTsc, resolve,
repoRoot, and output unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/typeCompat.test.ts`:
- Around line 50-54: The TypeScript compile test can exceed Vitest's default
timeout; update the two test cases (including the one named "public surface is
API-compatible with the old hand-written surface") to pass an increased timeout
value (e.g., 20000 ms) as the third argument to the it(...) call so tsc has
enough time in CI; locate the tests around runTsc(...) and add the timeout
parameter to both it invocations.

---

Outside diff comments:
In `@test/typeCompat.test.ts`:
- Around line 56-60: The test times out because tsc can take longer than
Vitest's default; update the test in typeCompat.test.ts that calls
runTsc(resolve(repoRoot, 'typings')) to pass a longer timeout to the it() call
(e.g., add a third or second argument timeout value to it(...) or use the
optional timeout parameter signature) so the test has enough time to complete in
CI while keeping the existing assertions using runTsc, resolve, repoRoot, and
output unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d5dcfa2-e775-4e7a-9b90-cea3f5bce7ac

📥 Commits

Reviewing files that changed from the base of the PR and between 6618f76 and b42284d.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • test/typeCompat.test.ts
  • test/typeCompat/tsconfig.json
  • typings/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/typeCompat/tsconfig.json

Comment thread test/typeCompat.test.ts Outdated
The type-compat cases shell out to a full tsc compile of the source type graph (~6s on Node 20/22 CI runners), exceeding vitest's 5s default and timing out; the faster Node 24 runner masked it. Give both cases a 60s timeout, matching the project's other slow (browser) suites.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Quality cleanups from a /simplify pass, no behavior or API change: move the public DraggableEvent union into lib/utils/types.ts alongside the other public types (it was the lone public type defined in the entry file) and re-export it from cjs.ts; replace 'as Pick<DraggableState, keyof DraggableState>' with the equivalent 'as DraggableState'; drop DraggableCore's redundant propTypes index-signature annotation to match Draggable; remove inert declaration/emitDeclarationOnly/outDir keys from the root tsconfig (noEmit wins; tsup owns dts emit); fix a stale fixture comment that described build-artifact validation after it was repointed at source.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

lib/cjs.ts instructs verifying the module.exports===Draggable shape with a runtime require() check, but nothing enforced it — the historical CJS dual-export contract (PR #254/#266) and the UMD global could regress invisibly and only surface when a consumer installs the package.

Add scripts/verify-build.cjs: requires the built CJS and asserts root===Draggable, root===.default, and .DraggableCore present; asserts the UMD bundle exposes global ReactDraggable. Wired into the Makefile build target's recipe so it runs after build-lib + build-web on both `yarn build` and `make build`. Verified it fails on a corrupted export shape. Not build-order-coupled to the unit-test matrix.
@STRML STRML merged commit a969889 into master May 29, 2026
8 checks passed
@STRML STRML deleted the ts-migration branch May 29, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant