Skip to content

fix: ship implementation libraries as dependencies, not peers (Issue epam/ai-dial-chat#8719) - #871

Merged
PolinaGurinovich97 merged 3 commits into
developmentfrom
fix/dependency-packaging
Sep 14, 2026
Merged

PolinaGurinovich97 merged 3 commits into
developmentfrom
fix/dependency-packaging

Conversation

@PolinaGurinovich97

Copy link
Copy Markdown
Collaborator

Description and UI changes:

No UI changes — packaging only. No component, style or runtime behaviour is
touched.

react and react-dom are now the only required peers of this package.

An embedding host audited what it has to declare in its own package.json to
render a DIAL chat column and found 25 packages it never imports, several of
them ours. This is the ui kit's half of that report.

Implementation libraries the kit renders with → dependencies

Package Where it is reachable from
@floating-ui/react eagerly from every entry point (21 source files)
classnames eagerly from every entry point (37 source files)
@tabler/icons-react eagerly from every entry point (119 source files)

These are implementation detail of this package — a host does not name them, it
just renders a Button. Requiring them as peers meant every consuming
application carried three entries it never imported, and a missing one only
showed up as an install warning.

Editor packages → optional peers

@monaco-editor/react, monaco-editor and @uiw/react-md-editor are
reachable only behind the Lazy* wrappers the ./editors subpath exports, so a
consumer that never mounts an editor has no use for them. As optional peers,
npm install stays quiet without them and the kit reaches for them only when a
lazy editor actually mounts.

They deliberately stay peers rather than becoming dependencies: Monaco is
large, and npm cannot scope a dependency to one entry point, so making them
dependencies would push the whole editor stack into every consumer that only
wants a button.

@uiw/react-markdown-preview → dropped

Nothing in this package imports it, and @uiw/react-md-editor already depends
on it, so requiring it of the host was asking for something that could not be
missing in the first place.

What a consumer does

Nothing, unless they want to. An existing package.json keeps working
unchanged — the point is that four to six entries can now be deleted from it.

Issues:

Verification:

npm run verify:distribution — all 10 fixtures pass end-to-end, the export
surface matches its baseline (342 exports), root/./grid/./file-manager/
./editors keep export parity in ESM and CJS, and all 14 exports targets are
present in the pack.

Two of those fixtures install the editor packages (consumer-markdown-editor,
consumer-json-editor) and two install none of them (consumer-esm,
consumer-mixed-grid-editors-subpath) — the latter importing from ./editors
and still building. That is what makes the optional declaration accurate rather
than a required peer with its warning suppressed.

Also green: typecheck, lint:check, test:run (183 files, 2493 tests),
build, format.

Checklist:

  • the pull request name complies with Conventional Commits
  • the pull request name ends with (Issue #<TICKET_ID>) (comma-separated list of issues) — the issue lives in epam/ai-dial-chat, so the title carries the cross-repository reference

🤖 Generated with Claude Code

…he host

`@floating-ui/react`, `classnames` and `@tabler/icons-react` were required
peers, so every consuming application had to add three packages it never
imports to its own package.json just to satisfy the peer graph. All three are
eagerly reachable from every entry point — the root, `./grid`, `./file-manager`
and `./editors` alike — so they are implementation detail of this package and
now ship as `dependencies`.

The editor packages go the other way. `@monaco-editor/react`, `monaco-editor`
and `@uiw/react-md-editor` are reachable only behind the `Lazy*` wrappers the
`./editors` subpath exports, so a consumer that never mounts an editor has no
use for them. They become optional peers: `npm install` stays quiet without
them, and the kit reaches for them only when a lazy editor actually mounts.

`@uiw/react-markdown-preview` is dropped from the peer list outright. Nothing
in this package imports it, and `@uiw/react-md-editor` already depends on it,
so requiring it of the host was asking for something it could not be missing.

`react` and `react-dom` are now the only required peers.

Nothing changes for a consumer that keeps its current package.json — the point
is that it can now delete four to six entries from it. Reported by an embedding
host in epam/ai-dial-chat#8719, whose audit found 25 packages it had to declare
without importing any of them.

Verified with `verify:distribution`: all 10 fixtures pass end-to-end, including
`consumer-markdown-editor` and `consumer-json-editor`, which install the editor
packages, and `consumer-esm` and `consumer-mixed-grid-editors-subpath`, which
install none of them — the latter importing from `./editors` and still building,
which is what makes the optional declaration accurate rather than a required
peer with its warning suppressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Making `@monaco-editor/react`, `monaco-editor` and `@uiw/react-md-editor`
optional peers stopped npm installing them here: npm auto-installs required
peers but not optional ones, so a clean `npm ci` left this package without
modules its own source imports. `typecheck` failed with TS2307 and the build
with `[MISSING_EXPORT] "Editor" is not exported by
"__vite-optional-peer-dep:@monaco-editor/react"`.

They are runtime-optional for a consumer and build-required for us, which is
what `devDependencies` is for — the same shape `@modelcontextprotocol/sdk`
already has here. `@uiw/react-markdown-preview` joins them: two stories import
its stylesheet directly, so relying on it arriving through
`@uiw/react-md-editor` would be depending on a transitive.

The consumer contract is unchanged — the peer declarations are exactly as the
previous commit left them.

`package-lock.json` is updated with them, and was missing from the previous
commit: CI installs with `npm ci`, which needs the lock to match.

Verified against the CI condition rather than a warm tree: `rm -rf
node_modules && npm ci` installs all four, then `typecheck`, `lint:check`,
`test:run` (183 files, 2493 tests), `build` and `verify:distribution` (10
fixtures, export parity, pack contents) all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@stropalov

stropalov commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Moving @floating-ui/react, @tabler/icons-react, and classnames to dependencies also changes the build output: vite.config.ts derives external exclusively from peerDependencies, so these packages are now bundled into ui-kit.
An A/B build of the current chat application confirmed duplicate Tabler modules between ui-kit and the application’s direct dependency. Initial JavaScript increased by 10,425 bytes gzipped, and the number of initial JS chunks grew from 93 to 109. Heavy-module isolation remained intact in the standalone consumer test.
I suggest keeping these packages in dependencies while explicitly preserving their externalization:

const externalDependencyNames = [
  ...Object.keys(peerDependencies),
  '@floating-ui/react',
  '@tabler/icons-react',
  'classnames',
];

const isExternalDependency = (id: string) =>
  externalDependencyNames.some(
    (dependency) => id === dependency || id.startsWith(`${dependency}/`),
  );

This preserves automatic dependency installation while allowing the application’s bundler to deduplicate them against direct imports.

valerydluski
valerydluski previously approved these changes Sep 14, 2026
`external` was derived exclusively from `peerDependencies`, so moving
`@floating-ui/react`, `@tabler/icons-react` and `classnames` to
`dependencies` silently started bundling them into the kit. A consuming
application that imports the same packages directly then ships a second
copy its bundler cannot deduplicate - measured on the chat application as
+10,425 bytes gzipped of initial JS and 93 -> 109 initial chunks, with
duplicate Tabler modules in the output.

Externalize them explicitly instead: the packages stay `dependencies`, so
`npm install` still brings them in without the host declaring anything,
while the build leaves the import bare for the host bundler to resolve and
deduplicate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@PolinaGurinovich97
PolinaGurinovich97 merged commit cf240c1 into development Sep 14, 2026
10 checks passed
@PolinaGurinovich97
PolinaGurinovich97 deleted the fix/dependency-packaging branch September 14, 2026 07:43
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.

3 participants