Docs
Vendor Isolation

Vendor Isolation

@apollion-dsi/core consumes external libraries (animation, virtualization, positioning, form management, tables, dates, etc.) behind its own wrappers. DS components import only from those wrappers — never from the library directly.

TL;DR

Every external library lives under src/vendors/<Lib>/ with an interface Apollion controls. Consumers of @apollion-dsi/core never import from a vendor — only from the public subpaths.

The policy is enforced by an ESLint rule in @apollion-dsi/eslint-config — crossing core's internal boundary fails lint.

Why?

  1. Replaceability. Swapping tippy.js@floating-ui/react is a change localized to the wrapper; no DS component needs a rewrite. That's exactly the churn the wrapper exists to absorb.
  2. Deterministic bundle size. The wrapper exports only what the DS uses — the tree-shaker drops the rest.
  3. A stable API for you. An internal upgrade (react-window@tanstack/react-virtual, formik+yupreact-hook-form+zod, react-router-dom → a polymorphic Link) never becomes a breaking change for the public component's consumer.
  4. No manual discipline required. The ESLint rule breaks the build if someone imports a restricted dependency directly from a component.

Where they live

18 wrappers under packages/core/src/vendors/ — each with its own README.md documenting what came in, what was left out, when to re-evaluate:

Culori · DateFns · Deepmerge · EmotionIsPropValid · FastDeepEqual
FloatingUI · LodashDebounce · Nanoid · ObjectHash
ReactContentLoader · ReactDayPicker · ReactHookForm · ReactSelect
StyledComponents · StyledNormalize · TanstackReactTable
TanstackReactVirtual · Zod

Since v6.0.0, the FramerMotion wrapper is gone entirely: ADR-027 replaced the DS's animation runtime with CSS-first motion tokens + the motion preset prop, so there is nothing left to vendor. A residual JS engine (Motion One, per the vendor's old swap trigger) stays demand-gated — never re-added preemptively.

Since v5.1, the Culori wrapper is the policy's terminal case: it stopped wrapping the external library and now contains a bit-exact own color engine, matching the original culori — which survives only as the parity tests' oracle (a devDependency). No consumer changed a single line: it's exactly the upstream swap the policy exists to allow.

Mental demo — one import line

❌ Straight from the library

import { DayPicker } from 'react-day-picker';

ESLint: error · build red.

✅ Through the wrapper

import { DayPicker } from '../vendors/ReactDayPicker';

ESLint: ok · build green.

What's NOT under a wrapper

  • react / react-dom — explicit peer deps (19.2.x). There's no point vendoring the framework's own runtime.

styled-components is under a wrapper (vendors/StyledComponents) despite being a regular dependency — the facade isolates the global DefaultTheme type and lays the groundwork for a future swap (zero-runtime CSS-in-JS).

For you (consumer)

Nothing changes in your code. Vendor isolation is an internal DS decision — what you import from @apollion-dsi/core/* stays stable and remains the public extension point.

For DS contributors

When adding a new dependency to core:

  1. Create src/vendors/<NewLib>/ with index.ts + README.md.
  2. Export only the surface the DS needs.
  3. Import from the wrapper inside components.
  4. Document in the wrapper's README: what came in, what was left out, when to re-evaluate.

The ESLint rule handles the rest.