Context
In #12 (v1.0.0) lenis was made a required peer dependency. A cross-model (Codex) review caught that the main hamo entry statically re-exports useScrollTrigger (src/index.ts), which statically does import { useLenis } from 'lenis/react'. So the main entry's module graph always pulls in lenis/react — declaring lenis optional was not a robust guarantee (it could fail to resolve in Vite dev / SSR / non-tree-shaking paths for a consumer who only uses, say, useWindowSize and never installed lenis).
The v1 fix was the minimal one — make lenis required — which is correct but heavier than ideal: every hamo consumer must now install lenis, even if they only use the non-scroll hooks (useRect, useWindowSize, useMediaQuery, useObjectFit, …). This issue tracks decoupling lenis so it's only a dependency for consumers who actually use scroll triggers.
Goal
A consumer using only non-scroll hooks should not need lenis installed, while useScrollTrigger keeps its Lenis integration (with the native-scroll fallback intact). Pick one of the variants below.
Variants
A. Subpath export — hamo/scroll-trigger (recommended)
Move useScrollTrigger / useTransform (and the existing hamo/scroll-trigger/debugger) out of the root barrel into a dedicated subpath. The main hamo entry then never imports lenis/react, so lenis goes back to a genuinely optional peer — only pulled when you import hamo/scroll-trigger.
- ✅ Cleanest separation; mirrors how the debugger subpath already works; matches
sideEffects: false intent without relying on tree-shaking.
- ⚠️ Breaking import path for the scroll hooks (
hamo → hamo/scroll-trigger). Cheap to do now-ish since the hooks are new in v1; would be a major bump later.
B. Dependency injection — pass Lenis in, don't import it
useScrollTrigger stops importing lenis/react and instead receives the Lenis instance (or a useLenis-like accessor) via options or a small context the consumer wires up. The static dependency disappears entirely.
- ✅ Removes the hard
lenis/react import; lenis becomes a peer only the consumer's app references.
- ⚠️ API change to
useScrollTrigger; more wiring for the common case; need a clean default that still "just works" with ReactLenis.
C. Dynamic import of lenis
Lazy-load lenis/react inside useScrollTrigger.
- ⚠️ Likely not viable:
useLenis is a hook and can't be conditionally/dynamically imported without violating rules-of-hooks. Documented here only to rule it out.
D. Status quo — keep lenis required
No decoupling. Simplest, but every consumer carries lenis.
Acceptance criteria
Notes
Context
In #12 (v1.0.0)
leniswas made a required peer dependency. A cross-model (Codex) review caught that the mainhamoentry statically re-exportsuseScrollTrigger(src/index.ts), which statically doesimport { useLenis } from 'lenis/react'. So the main entry's module graph always pulls inlenis/react— declaringlenisoptional was not a robust guarantee (it could fail to resolve in Vite dev / SSR / non-tree-shaking paths for a consumer who only uses, say,useWindowSizeand never installed lenis).The v1 fix was the minimal one — make
lenisrequired — which is correct but heavier than ideal: every hamo consumer must now installlenis, even if they only use the non-scroll hooks (useRect,useWindowSize,useMediaQuery,useObjectFit, …). This issue tracks decouplinglenisso it's only a dependency for consumers who actually use scroll triggers.Goal
A consumer using only non-scroll hooks should not need
lenisinstalled, whileuseScrollTriggerkeeps its Lenis integration (with the native-scroll fallback intact). Pick one of the variants below.Variants
A. Subpath export —
hamo/scroll-trigger(recommended)Move
useScrollTrigger/useTransform(and the existinghamo/scroll-trigger/debugger) out of the root barrel into a dedicated subpath. The mainhamoentry then never importslenis/react, solenisgoes back to a genuinely optional peer — only pulled when you importhamo/scroll-trigger.sideEffects: falseintent without relying on tree-shaking.hamo→hamo/scroll-trigger). Cheap to do now-ish since the hooks are new in v1; would be a major bump later.B. Dependency injection — pass Lenis in, don't import it
useScrollTriggerstops importinglenis/reactand instead receives the Lenis instance (or auseLenis-like accessor) via options or a small context the consumer wires up. The static dependency disappears entirely.lenis/reactimport;lenisbecomes a peer only the consumer's app references.useScrollTrigger; more wiring for the common case; need a clean default that still "just works" withReactLenis.C. Dynamic import of lenis
Lazy-load
lenis/reactinsideuseScrollTrigger.useLenisis a hook and can't be conditionally/dynamically imported without violating rules-of-hooks. Documented here only to rule it out.D. Status quo — keep
lenisrequiredNo decoupling. Simplest, but every consumer carries
lenis.Acceptance criteria
hamoresolves with nolenisinstalled (verify in a Vite dev app and an SSR/RSC build, not just a tree-shaken prod bundle — that's the path publint/attw/local builds miss because lenis is in devDeps).useScrollTriggerstill integrates with Lenis and still falls back to native scroll when no Lenis instance is mounted.lenisis declared optional again (peerDependenciesMeta) if variant A/B is chosen.publintclean;attwsane; CHANGELOG + READMEs updated.Notes
lenisrequired. This is a v1.1/v2 ergonomics improvement; variant A is a breaking import move, so decide before it accretes external callers.