Skip to content

adapter-evm: headless consumers pull the full UI/React dependency tree via package-level dependencies (subpath exports are framework-free) #58

Description

@pasevin

Summary

@openzeppelin/adapter-evm (and, by symmetry, the other adapter-* packages) exposes framework-free per-capability subpath exports — e.g. @openzeppelin/adapter-evm/erc3643, /irs, /execution — which is exactly right for headless/backend consumers. However, the package's top-level dependencies still list the UI/React stack as hard runtime deps (@wagmi/core, @wagmi/connectors, @web3icons/react, lucide-react, and the React-facing UI packages). As a result, a headless consumer that imports only a capability subpath still pulls the entire UI/React dependency tree into node_modules at install time.

Impact

Headless / backend consumers (Relayer plugins, codegen-*, CLIs, indexers) that only need the framework-free capabilities get a heavily bloated install (wagmi + React + icon packages) they never import. It also muddies dependency audits and supply-chain review for backend services that should have no browser/React surface.

The imports themselves are clean — nothing UI is bundled into a headless consumer's build (the capability subpaths tree-shake correctly). This is purely about the install graph driven by the package-level dependencies manifest.

Concrete case

The OpenZeppelin Reference Implementations tokenized-deposits Relayer plugin (a headless backend) consumes only:

import { createERC3643 } from '@openzeppelin/adapter-evm/erc3643';
import { createIRS }     from '@openzeppelin/adapter-evm/irs';

Yet pnpm why react / the install graph resolves the full wagmi + React + @web3icons/react + lucide-react set, because they are declared in adapter-evm's root dependencies.

Suggested direction (non-prescriptive)

Split the capability-only surface from the UI surface at the dependency level, so headless consumers get a lean install. Options, roughly in order of preference:

  1. Move the UI-only libraries (@wagmi/*, @web3icons/react, lucide-react, React-facing UI packages) to optionalDependencies and/or peerDependencies (with peerDependenciesMeta.optional), so only consumers that import the UI subpaths install them.
  2. Or factor the UI subpaths into a separate package/entry, leaving the capability subpaths dependency-light.

The subpath exports already draw the framework-free boundary correctly; this is only about aligning the dependencies manifest with that boundary.

Environment

  • @openzeppelin/[email protected] (published, from npm)
  • Consumer: headless Relayer plugin (Node ESM, tsdown-built)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions