Skip to content

Declare @types/react and @types/lodash as optional peers of slate-react - #6110

Open
unrevised6419 wants to merge 1 commit into
ianstormtaylor:mainfrom
unrevised6419:fix/slate-react-types-peer-deps
Open

unrevised6419 wants to merge 1 commit into
ianstormtaylor:mainfrom
unrevised6419:fix/slate-react-types-peer-deps

Conversation

@unrevised6419

@unrevised6419 unrevised6419 commented Sep 20, 2026

Copy link
Copy Markdown

Problem

slate-react's shipped type declarations reference typings packages that package.json never declares:

  • react — 33 references across dist/**/*.d.ts, plus nine /// <reference types="react" /> directives (use-slate-static.d.ts, use-focused.d.ts, use-element.d.ts, …)
  • lodashdist/hooks/android-input-manager/android-input-manager.d.ts imports DebouncedFunc in a type position

@types/react and @types/lodash are present only in devDependencies, so they are not part of the published contract.

Under a hoisted node_modules layout this resolves by accident, which is why it survives normal testing (and skipLibCheck: true hides the resulting errors inside the .d.ts). It does not resolve under pnpm's isolated linker with a global virtual store: each package is linked from a store path outside the consuming project, and TypeScript resolves a package's imports from where that package physically lives. react then resolves to the untyped react/index.js, the editor component props degrade to any, and consumers building with noImplicitAny get errors like:

error TS7016: Could not find a declaration file for module 'react'.
'.../react/index.js' implicitly has an 'any' type.

Change

@types/react and @types/lodash are added as optional peer dependencies of slate-react. Optional matters: JavaScript-only consumers must not get an unmet-peer warning (verified with strictPeerDependencies: true and no @types/* installed).

The ranges track majors and are deliberately open-ended at the top: @types/react majors follow React majors, and the meaningful constraint is already carried by the existing react peer, so pinning an upper bound would only produce false unmet-peer failures the day @types/react 20 ships. @types/lodash tracks lodash 4.x, matching the lodash dependency range. The shipped declarations were typechecked against @types/react 17, 18 and 19 and are clean in all three.

"peerDependencies": {
  "@types/lodash": "^4.14.0",
  "@types/react": ">=18.0.0",
  "react": ">=18.2.0",
  "react-dom": ">=18.2.0",
  "slate": ">=0.121.0",
  "slate-dom": ">=0.119.1"
},
"peerDependenciesMeta": {
  "@types/lodash": { "optional": true },
  "@types/react": { "optional": true }
}

The existing react, react-dom, slate and slate-dom ranges are unchanged. A patch changeset is included.

No @types/react-dom entry: react-dom is imported only for values (unstable_batchedUpdates, flushSync) and does not appear in any type position in the shipped declarations.

@types/lodash is only reachable through a deep import today (slate-react/dist/hooks/android-input-manager/android-input-manager), since that module is not re-exported from index.d.ts. It is still a specifier appearing in a type position in a shipped .d.ts, and the package has no exports map, so deep imports are possible. An alternative worth considering separately is dropping the DebouncedFunc reference from that declaration in favour of a structural type, which would remove the requirement entirely.

Sibling packages

The other packages in the monorepo were swept with the same rule — every specifier appearing in a type position in a shipped .d.ts must be declared — and all are already correct:

Package External specifiers in dist/**/*.d.ts Status
slate none OK
slate-dom slate already a peer
slate-history slate already a peer
slate-hyperscript slate already a peer (no React or JSX types)
slate-react react, lodash, slate, slate-dom slate/slate-dom already peers; the two @types/* entries are this PR

Verification

All five packages were built and packed, and both a patched and an unpatched baseline slate-react tarball were installed into scratch consumers.

  1. pnpm 11.26 isolated linker with enableGlobalVirtualStore: true and a virtualStoreDir outside the consumer, typechecking with skipLibCheck: false and noImplicitAny:
    • unpatched: TS7016 for react in editable.d.ts, element.d.ts, leaf.d.ts, text.d.ts and others
    • patched: clean
  2. The assertion is written as a negative, because a positive one keeps passing when types widen to any:
    // @ts-expect-error — collapses to `any` if @types/react is unreachable
    <Editable nonexistentProp={1} />
    It still errors as expected with the patch applied, so the props are real types rather than any.
  3. tsc --traceResolution from slate-react/dist/components/editable.d.ts: Module name 'react' was successfully resolved to '.../@types/react/index.d.ts' — a .d.ts, not react/index.js.
  4. npm with --install-strategy=nested typechecks cleanly both before and after, confirming that a hoisted layout forgives the missing declaration.
  5. attw --pack reports "No problems found" for all five tarballs. publint reports no errors; the warnings it does emit are pre-existing and identical across all five packages (repository uses the git:// shorthand, dist/index.es.js is ESM interpreted as CJS, no exports map) and are untouched here.

Note on side findings

Two unrelated packaging observations surfaced during the sweep. Neither is addressed in this PR, since both are dependency removals with their own compatibility risk for anyone relying on the transitive install, and they are worth deciding on separately:

  • slate-dom declares five dependencies that its bundle never requires: @juggle/resize-observer, direction, lodash, scroll-into-view-if-needed and is-plain-object. The built bundle only requires is-hotkey and slate.
  • slate-react declares is-hotkey and tiny-invariant, which its bundle likewise never requires.

The publint warnings listed above are a third such finding: they affect every package equally and predate this change.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b1f0ae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
slate-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@unrevised6419
unrevised6419 force-pushed the fix/slate-react-types-peer-deps branch from 487beab to 6a2fcf5 Compare September 20, 2026 00:29
slate-react's shipped .d.ts files import React types (33 references plus
nine `/// <reference types="react" />` directives) and, in
hooks/android-input-manager, lodash's `DebouncedFunc`. Neither typings
package was declared in package.json — both were only devDependencies.

A hoisted node_modules layout resolves them by accident. pnpm's isolated
linker with a global virtual store does not: the package is linked from a
store path outside the consuming project, TypeScript resolves a package's
imports from where that package physically lives, so `react` resolves to
the untyped `react/index.js`. Consumers then get TS7016 implicit-any
errors, or silently degraded `any` props, for the editor components.

Both entries are optional peers so JavaScript-only consumers do not get
unmet-peer warnings. The existing react, react-dom, slate and slate-dom
peer ranges are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@unrevised6419
unrevised6419 force-pushed the fix/slate-react-types-peer-deps branch from 6a2fcf5 to 9b1f0ae Compare September 20, 2026 00:35

This branch has not been deployed

No deployments
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