Consolidated from primary-source research. These notes justify the choices hard-coded in src/.
- API:
https://api.npmjs.org/downloads/range/{start:end}/{package}— public, no auth, daily granularity. - Earliest data: 2015-01-10. Single-package ranges silently truncate beyond ~540 days → we chunk into 500-day windows and stitch (
src/npm_fetch.py). - Scoped packages (
@angular/core) must be percent-encoded and cannot appear in bulk calls. - Package identity matters more than anything:
@angular/core= modern Angular; unscopedangular= AngularJS 1.x, a different EOL product (kept as a separate legacy line, never merged). Track one heartbeat package per framework (@angular/core,react,vue,svelte) — never sum@angular/*, which double-counts dependency fan-out. - A download is a CI/mirror/cache event far more often than a human; npm filters no bots by design. → trust only normalized signals.
- Spec: https://github.com/npm/registry/blob/main/docs/download-counts.md. Methodology: https://blog.isquaredsoftware.com/2022/07/npm-package-market-share-estimates/.
No public "total registry downloads" endpoint exists. We proxy generic npm/CI growth with a basket of ubiquitous, framework-agnostic packages (lodash, chalk, commander, debug, express, typescript, eslint, axios) for the per-capita normalization.
- REST gives current
stargazers_countcheaply; star history requires paginating/stargazerswithAccept: application/vnd.github.star+json. Pagination is capped at 400 pages (40k stars) → for big repos we sample sparse pages and interpolate, like star-history.com. - Unauthenticated limit 60 req/hr → low-res sampling; set
GITHUB_TOKENfor 5000/hr. - Repo splits: modern
angular/angularvs archivedangular/angular.js; Vue 3vuejs/corevs Vue 2vuejs/vue(where most historical stars live). We track the modern repo and flag the split.
- State of JS (retention/sentiment) — GraphQL at
api.devographics.com/graphql; charts not text-extractable so values transcribed. - Stack Overflow Developer Survey — per-respondent CSVs at
survey.stackoverflow.co(2023–25 include AI-tool × framework cross-tabs). Angular and AngularJS are separate options. - Web-Bench (ByteDance, arXiv:2505.07473) — per-framework LLM pass-rate, the hard evidence that AI codegen favors React.
- Stack Overflow question volume collapsed ~70% post-ChatGPT → use % share, not raw counts.
- Vercel AI SDK adapter downloads (
@ai-sdk/reactvs@ai-sdk/angular, etc.) — sharpest single skew marker, re-fetched live from npm.@ai-sdk/angularonly shipped 2025-07-14.
Twice-a-year cadence; no Angular 3 (skipped). Dev-preview ≠ stable — we use the true stabilization dates:
| Date | Ver | Milestone | Status |
|---|---|---|---|
| 2022-06 | v14 | Standalone components | dev preview |
| 2022-11 | v15 | Standalone APIs | stable |
| 2023-05 | v16 | Signals, hydration, esbuild | dev preview |
| 2023-11 | v17 | Control flow, standalone+esbuild default, signals core stable, rebrand/angular.dev | mixed |
| 2024-05 | v18 | Control flow/defer stable, experimental zoneless | mixed |
| 2024-11 | v19 | Signal inputs/outputs/model stable | stable |
| 2025-05 | v20 | effect/linkedSignal/toSignal stable |
stable |
| 2025-11 | v21 | Zoneless default, Vitest, MCP server | stable |
Biggest correction vs. the popular narrative: signals were not wholesale "stable in v17." signal/computed stabilized v17 (Nov 2023); effect()/toSignal only v19–v20; zoneless only v20.2 (Aug 2025), default v21 (Nov 2025). The densest architectural-inflection window is v16–v17 (May–Nov 2023) — which collides with the AI inflection, the central analytical challenge.
Competitor markers used for alignment: React Hooks (2019-02), Vue 3 (2020-09), React 18 (2022-03), SvelteKit 1.0 (2022-12), Next 13.4 RSC GA (2023-05), Svelte 5 runes (2024-10), React 19 (2024-12).
- Preprocess: weekly (kills weekday CI cycle) →
log1p(growth = slope, variance-stabilized) → STL deseasonalize (December dip) → robust MAD spike handling (release-week mirror stampedes). Fit on deseasonalized unsmoothed series; rolling means are visualization-only. - Normalize three ways: logit share-of-cohort (the "framework war"), per-capita vs ubiquity basket (ecosystem detrend), indexed-to-baseline (trajectory comparison).
- Causal stack:
- Confirmatory ITS / segmented regression at known dates, in a local window (the deep pre-2016 history would otherwise distort the trend), with Newey–West (HAC) errors because downloads are heavily autocorrelated. Meaningful term =
trend_change, not the publish-daylevel. - Backbone BSTS / CausalImpact: Angular ~ local-level + regression on a single composite control = the summed downloads of the other three frameworks ("rest of cohort"). The three are highly collinear, so on raw multi-control levels the betas blow up and the counterfactual spikes at year-end; one composite gives a stable coefficient that tracks the cohort's seasonality directly. Run on log levels with a 6-week washout. Implemented on
statsmodelsUnobservedComponentsbecause thecausalimpactPyPI package is broken on pandas 3.0. Validity gate: a pre-fit correlation (fitted vs actual on the pre-period) — high ⇒ trust the counterfactual; low ⇒ discard it, and a low pre-fit is itself a finding (Angular decoupled from the cohort). Plus an in-time placebo. - Exploratory
rupturesPELT over a penalty sweep — "where did breaks really land?"
- Confirmatory ITS / segmented regression at known dates, in a local window (the deep pre-2016 history would otherwise distort the trend), with Newey–West (HAC) errors because downloads are heavily autocorrelated. Meaningful term =
- AI era modeled as a gradual ramp (0→1 over ~2.5y from ChatGPT), as a common shock across all four. Its absolute effect is unidentifiable (no AI-free control); only differential cross-framework responses are.
- Honesty: downloads ≠ adoption ≠ developers; releases are observational; bot/mirror/CI traffic is unfiltered; multiple-comparison risk across releases × normalizations × methods.