A charting dependency your product relies on publishes only a CommonJS build and accounts for a large share of your main bundle. As the person making the call, how do you weigh working around it, forking it, replacing it, or simply accepting the bytes?
answer
- first ask who actually pays
- cheapest reversible option first
- a fork is a permanent obligation
- replacement is a migration, price it
- accepting the bytes is a valid decision
basics
~20 sDecide by who pays and what the alternatives cost, not by whether the bytes offend you. Establish how many users actually reach the feature, try deep imports first, price a replacement's migration honestly, and treat forking as taking on permanent maintenance.
solid answer
~60 sI start with who pays. If every visitor downloads the library but only a small share ever opens a chart, the cost is misallocated and worth real effort; if charts are the product, the bytes are the product too and I would stop optimising there. Then I work up the ladder of cost. Deep imports into the package's individual files often recover most of it for an afternoon's work and no ownership. Replacing the library is priced as a migration — API differences, visual parity, test rewrites, the risk of discovering a missing feature late — and I want a spike before committing, not an estimate. Forking to publish an ES-module build is the option people underrate: it looks like a week and it is a permanent obligation to track upstream and its security fixes. Contributing that build upstream is strictly better when the maintainers will take it. Accepting the cost is a legitimate outcome, provided it is written down as a decision with a number attached rather than left as an accident.
go deeper
Know that some packages simply cannot be trimmed by the build, and that importing only the specific files you need is the first thing to try before anything more dramatic.
Be able to lay out the options in order — deep imports, upstream fix, fork, replace, accept — and explain why the reversible ones come first.
Show that you would quantify the cost before acting: who downloads the library, what the delay means on real devices, and whether the saving is even noticeable to users.
Own the tradeoff and the policy: price a fork as a permanent maintenance obligation, price a replacement as a migration with a spike, defend accepting the cost when it is right, and put an adoption check in front of the next dependency.
## Frame the decision before comparing options The instinct is to rank the technical options. The useful move first is to establish what the bytes are actually costing, because that number sets how much effort is justified and it varies by an order of magnitude between products. Three questions answer it: 1. **Who downloads it?** If the library is in the entry that every visitor loads, everyone pays. If only a subset of users reach the feature, the population that pays is smaller — and the first thing to check is whether the cost is falling on people who never see the feature. 2. **What does the delay translate to?** Bytes are not the metric anyone cares about; they are a proxy for time on a slow connection and time on the main thread parsing and executing. Connect the size to a user-visible effect on real devices, not to a number in a report. 3. **What is the counterfactual?** If removing this dependency saves 15% of a bundle that is already fine on your users' devices and networks, the whole exercise is discretionary. Measure your field data before you spend a quarter on it. A principal-level answer starts here. An answer that jumps straight to "fork it and publish ESM" has skipped the only step that decides whether any of this is worth doing. ## The ladder of options, by cost of ownership **Work around it in place.** Many CommonJS packages are still split into files, so importing the specific modules you use recovers a large share of the size with no change of dependency, no fork, and no migration. It is reversible, it can be enforced with a lint rule, and it is what you should try before anything else. It fails when the package really is one monolithic file, or when its internals cross-reference so heavily that the deep import pulls in most of the library anyway — which you find out in an hour. **Trim what you actually use.** Some charting and utility libraries ship a plugin or registry architecture where you register only the pieces you need. Where that exists, it is the intended path and costs nothing but discipline. **Push the fix upstream.** Opening an issue or a pull request that adds an ES-module build is the highest-leverage option when the maintainers are responsive: you pay once, everyone benefits, and you do not own the result. It is also the slowest and least certain, so it pairs well with a workaround shipped now. **Fork it.** This is the option teams consistently underprice. The visible cost is the week it takes to produce the build; the real cost is permanent: tracking upstream releases, re-applying your changes, owning security advisories against a package your dependency scanner no longer recognises, and explaining the fork to every future engineer. Take it only when the dependency is strategically important, upstream is unresponsive, and someone will own it by name. **Replace it.** Priced as a migration, not as a swap. Charting libraries in particular hide their cost in the long tail: the tooltip behaviour someone depends on, the export-to-image path, the accessibility work already done, the exact visual identity of the existing charts. Spike the two or three hardest charts against the candidate before committing, and count the test rewrite. **Accept it.** Perfectly defensible. What makes it a decision rather than a drift is writing it down: what the library costs, why the alternatives were rejected, what would cause you to revisit — a change in who reaches the feature, an ES-module build appearing upstream, the number crossing an agreed limit. ## What separates a strong answer Three things. First, sequencing by reversibility: try the cheap, undoable thing before the expensive, permanent one. Second, honest pricing of the fork — most candidates treat it as a one-week task and never mention the maintenance tail. Third, treating "accept it" as an available answer, because a leader who cannot say "this cost is fine" will spend the team's quarters on savings nobody feels. ## The policy behind the incident The last move is to stop this arriving as a surprise again. That means an adoption check on new dependencies — what does it publish, how big is it in your build, what does it depend on transitively — applied at the moment someone proposes it, when switching is free. The expensive version of this decision only exists because the cheap version was skipped a year earlier.
- What would make you reject a fork even when it clearly saves the most bytes?No named owner. A fork without someone accountable for tracking upstream releases and security advisories decays into an unpatched copy within a year, and the dependency scanner stops recognising it. If nobody will own it by name for the foreseeable future, the byte saving is borrowed against a future incident.
- How do you decide the effort is not worth it at all?By checking the field data first. If real users on real devices are already comfortably inside the targets the team agreed on, the saving is discretionary and competes with everything else on the roadmap. I would record the size as known, set a trigger for revisiting it, and spend the quarter on something users can feel.
- What check would you put in place so the next dependency does not arrive the same way?A short adoption review at proposal time: what the package publishes, its measured cost in our build, its transitive dependencies, and whether a lighter alternative covers the use case. It takes minutes when switching is free, and it converts this expensive decision into a cheap one that already happened.
saying these in an interview costs you the question
- Jumps to forking without pricing the maintenance tail
- Treats replacing a library as a straight swap
- Optimises bytes without checking who downloads them
- Refuses to accept a cost even when users feel nothing
- Leaves the decision undocumented so it recurs