react-fastload 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/index.cjs +1348 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +562 -0
- package/dist/index.d.ts +562 -0
- package/dist/index.js +1330 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 react-fastload contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# react-fastload
|
|
2
|
+
|
|
3
|
+
**An adaptive resource-loading scheduler for React — with real,
|
|
4
|
+
browser-measured performance metrics instead of marketing claims.**
|
|
5
|
+
|
|
6
|
+
<p>
|
|
7
|
+
<img alt="npm version" src="https://img.shields.io/npm/v/react-fastload?color=cb3837&label=npm" />
|
|
8
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-lightgrey" />
|
|
9
|
+
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178c6" />
|
|
10
|
+
<img alt="bundle" src="https://img.shields.io/badge/tree--shakeable-yes-brightgreen" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
One shared scheduler coordinates *when* images, video, audio, and
|
|
14
|
+
dynamically imported components actually fetch — based on viewport
|
|
15
|
+
proximity, priority, network conditions, and a concurrency budget —
|
|
16
|
+
instead of each resource type solving "don't load this yet" independently
|
|
17
|
+
with no coordination between them.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { FastLoadProvider, SmartImage, SmartVideo, SmartAudio, lazyComponent } from "react-fastload";
|
|
21
|
+
|
|
22
|
+
const Analytics = lazyComponent(() => import("./Analytics"), { priority: "LOW" });
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
return (
|
|
26
|
+
<FastLoadProvider strategy="adaptive" preloadDistance={1000}>
|
|
27
|
+
<SmartImage src="/hero.webp" alt="Hero" priority="CRITICAL" />
|
|
28
|
+
<SmartImage src="/card.webp" alt="Card" priority="auto" />
|
|
29
|
+
<SmartVideo src="/demo.mp4" poster="/poster.webp" priority="LOW" />
|
|
30
|
+
<SmartAudio src="/theme.mp3" label="Theme song" priority="IDLE" />
|
|
31
|
+
<Analytics />
|
|
32
|
+
</FastLoadProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Table of contents
|
|
38
|
+
|
|
39
|
+
- [Installation](#installation)
|
|
40
|
+
- [Why this exists](#why-this-exists)
|
|
41
|
+
- [How it works](#how-it-works)
|
|
42
|
+
- [How it differs from native lazy loading / React.lazy / code splitting](#how-it-differs)
|
|
43
|
+
- [Quick start](#quick-start)
|
|
44
|
+
- [Reading live metrics](#reading-live-metrics)
|
|
45
|
+
- [Debug mode](#debug-mode)
|
|
46
|
+
- [API reference](#api-reference)
|
|
47
|
+
- [Browser compatibility](#browser-compatibility)
|
|
48
|
+
- [When to use it — and when not to](#when-to-use-it--and-when-not-to)
|
|
49
|
+
- [Benchmarks — and their current limitations](#benchmarks--and-their-current-limitations)
|
|
50
|
+
- [Limitations](#limitations)
|
|
51
|
+
- [Roadmap](#roadmap)
|
|
52
|
+
- [Development](#development)
|
|
53
|
+
- [Contributing](#contributing)
|
|
54
|
+
- [License](#license)
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install react-fastload
|
|
60
|
+
# or
|
|
61
|
+
pnpm add react-fastload
|
|
62
|
+
# or
|
|
63
|
+
yarn add react-fastload
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Peer dependencies: `react >= 17`, `react-dom >= 17`. Ships ESM + CommonJS
|
|
67
|
+
builds and full TypeScript declarations; React is not bundled.
|
|
68
|
+
|
|
69
|
+
## Why this exists
|
|
70
|
+
|
|
71
|
+
Native `loading="lazy"` and `React.lazy` each solve one narrow slice of
|
|
72
|
+
"don't fetch this yet," independently, with **no shared concept of
|
|
73
|
+
priority and no shared concurrency budget across resource types.** A page
|
|
74
|
+
with many below-the-fold images, a video, and a few lazy components can
|
|
75
|
+
still burst-request everything with no coordination — the network has no
|
|
76
|
+
way to know your `HIGH`-priority hero image matters more than a `LOW`-
|
|
77
|
+
priority footer icon that happened to scroll into view a moment earlier.
|
|
78
|
+
|
|
79
|
+
ReactFastLoad puts images, video, and components through **one registry
|
|
80
|
+
and one priority-aware scheduler**, so priority is meaningful across the
|
|
81
|
+
whole page, not just within one resource type.
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Resource → Registry → Priority Engine → Viewport/Network/User signals
|
|
87
|
+
→ Scheduler → Loading decision → Browser resource → Metrics
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- **Registry** — bookkeeping for every resource ReactFastLoad knows about:
|
|
91
|
+
id, type, priority, state, viewport distance, timestamps.
|
|
92
|
+
- **Priority Engine** — turns declared priority (`CRITICAL` → `IDLE`) plus
|
|
93
|
+
live signals (viewport distance, connection quality) into an effective
|
|
94
|
+
score. `CRITICAL` always loads first, unconditionally.
|
|
95
|
+
- **Scheduler** — dispatches eligible resources within a concurrency cap,
|
|
96
|
+
in priority order, and logs every decision it makes.
|
|
97
|
+
- **Metrics** — real `PerformanceObserver` / Navigation / Resource Timing
|
|
98
|
+
reads, kept strictly separate from ReactFastLoad's own internal
|
|
99
|
+
bookkeeping (see [Benchmarks](#benchmarks--and-their-current-limitations)).
|
|
100
|
+
|
|
101
|
+
Two things that happen automatically, not as opt-in features: **request
|
|
102
|
+
deduplication** (two components rendering the same `src` share one load,
|
|
103
|
+
not two — see `LoadManager`'s reference counting in
|
|
104
|
+
[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)) and **abort-on-unmount**
|
|
105
|
+
(an in-flight, not-yet-loaded resource is cancelled via `AbortController`
|
|
106
|
+
once its last consumer unmounts, rather than finishing a fetch nobody
|
|
107
|
+
needs anymore).
|
|
108
|
+
|
|
109
|
+
Full detail in [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md).
|
|
110
|
+
|
|
111
|
+
## How it differs
|
|
112
|
+
|
|
113
|
+
| | Native `loading="lazy"` | `React.lazy` | ReactFastLoad |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| Cross-resource priority | ❌ | ❌ | ✅ shared priority levels |
|
|
116
|
+
| Shared concurrency budget | ❌ (browser-managed, opaque) | ❌ | ✅ configurable |
|
|
117
|
+
| Works across images + video + audio + components | per-`<img>` only | components only | ✅ all four, one scheduler |
|
|
118
|
+
| Configurable preload distance | limited/inconsistent | n/a | ✅ `preloadDistance` |
|
|
119
|
+
| Debug visibility into *why* something loaded when | ❌ | ❌ | ✅ debug panel + decision log |
|
|
120
|
+
|
|
121
|
+
It doesn't replace these mechanisms — `SmartImage` still sets
|
|
122
|
+
`decoding="async"` and `fetchpriority`; `lazyComponent` still relies on
|
|
123
|
+
your bundler's code splitting. It adds a coordination layer on top.
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { FastLoadProvider, SmartImage, SmartVideo, lazyComponent, useFastLoadMetrics } from "react-fastload";
|
|
129
|
+
|
|
130
|
+
const Analytics = lazyComponent(() => import("./Analytics"), { priority: "LOW" });
|
|
131
|
+
|
|
132
|
+
function Gallery({ items }: { items: { id: string; src: string; alt: string }[] }) {
|
|
133
|
+
return (
|
|
134
|
+
<FastLoadProvider strategy="adaptive" preloadDistance={1000}>
|
|
135
|
+
<SmartImage src="/hero.webp" alt="Hero" priority="CRITICAL" />
|
|
136
|
+
|
|
137
|
+
{items.map((item) => (
|
|
138
|
+
<SmartImage key={item.id} src={item.src} alt={item.alt} priority="auto" />
|
|
139
|
+
))}
|
|
140
|
+
|
|
141
|
+
<SmartVideo src="/demo.mp4" poster="/demo-poster.webp" priority="LOW" />
|
|
142
|
+
<Analytics />
|
|
143
|
+
</FastLoadProvider>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Reading live metrics
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
function PerfBadge() {
|
|
152
|
+
const { lcp, deferredRequests, bytesDeferred } = useFastLoadMetrics();
|
|
153
|
+
return (
|
|
154
|
+
<div>
|
|
155
|
+
LCP: {lcp ? `${Math.round(lcp)}ms` : "measuring…"} · deferred: {deferredRequests} (
|
|
156
|
+
{(bytesDeferred / 1024).toFixed(0)} KB est.)
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Every field is documented in [docs/API.md](./docs/API.md) as one of three
|
|
163
|
+
kinds — **browser-observed** (from `PerformanceObserver`/Navigation/
|
|
164
|
+
Resource Timing), **internal** (ReactFastLoad's own bookkeeping), or
|
|
165
|
+
**estimated** (only as accurate as the `estimatedSize` you provide) — so
|
|
166
|
+
you always know what you're looking at.
|
|
167
|
+
|
|
168
|
+
## Debug mode
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<FastLoadProvider debug>
|
|
172
|
+
<App />
|
|
173
|
+
</FastLoadProvider>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Renders a floating panel (dev builds only) listing every registered
|
|
177
|
+
resource, its state, and the scheduler's recent load/defer/prefetch
|
|
178
|
+
decisions with reasons — useful for verifying *why* something loaded when
|
|
179
|
+
it did, rather than guessing.
|
|
180
|
+
|
|
181
|
+
## API reference
|
|
182
|
+
|
|
183
|
+
See [docs/API.md](./docs/API.md) for the full `<FastLoadProvider>`,
|
|
184
|
+
`<SmartImage>`, `<SmartVideo>`, `<SmartAudio>`, `lazyComponent()`, hooks,
|
|
185
|
+
and type reference. `LoadManager`, `ResourceRegistry`, `PriorityEngine`, and
|
|
186
|
+
`Scheduler` are also exported directly for building custom resource
|
|
187
|
+
wrappers on the same scheduler.
|
|
188
|
+
|
|
189
|
+
## Browser compatibility
|
|
190
|
+
|
|
191
|
+
Every feature degrades gracefully instead of throwing:
|
|
192
|
+
|
|
193
|
+
| Feature | Fallback when unsupported |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `IntersectionObserver` | Resource reports as immediately eligible |
|
|
196
|
+
| Network Information API | `ConnectionInfo` fields are `null`/`false`; no connection-based adjustment |
|
|
197
|
+
| `PerformanceObserver` (LCP/CLS/paint) | Corresponding metric fields stay `null`, never estimated |
|
|
198
|
+
| `fetchPriority` attribute | Omitted; `loading`/`decoding` still applied |
|
|
199
|
+
| `requestIdleCallback` | Falls back to a short `setTimeout` |
|
|
200
|
+
|
|
201
|
+
## When to use it — and when not to
|
|
202
|
+
|
|
203
|
+
**Use it when** your page has enough below-the-fold images/video/
|
|
204
|
+
components that load order and concurrency actually matter, and you want
|
|
205
|
+
one consistent priority model plus real metrics to verify the effect.
|
|
206
|
+
|
|
207
|
+
**Skip it when** your page only has a handful of resources (native
|
|
208
|
+
`loading="lazy"` is simpler and sufficient), or you need guaranteed load
|
|
209
|
+
order regardless of viewport (use `priority="CRITICAL"` + `strategy="eager"`
|
|
210
|
+
on those specific resources instead of reaching for a different tool).
|
|
211
|
+
|
|
212
|
+
## Benchmarks — and their current limitations
|
|
213
|
+
|
|
214
|
+
The `/benchmark` app compares a Baseline page (plain `<img>`/`<video>`/
|
|
215
|
+
`React.lazy`) against a ReactFastLoad page, reading real
|
|
216
|
+
`PerformanceObserver`/Navigation/Resource Timing values — nothing is
|
|
217
|
+
hardcoded. **That said, treat any single run's numbers as illustrative,
|
|
218
|
+
not conclusive**, until run under the protocol below. An early internal
|
|
219
|
+
run surfaced exactly the kind of confound this warning exists for: a TTFB
|
|
220
|
+
drop that a client-side scheduler cannot legitimately produce, which
|
|
221
|
+
almost always means the two pages weren't served under identical
|
|
222
|
+
navigation/cache/dev-server conditions. If you see something similar,
|
|
223
|
+
it's a benchmark-setup bug, not a real effect — see
|
|
224
|
+
[docs/METHODOLOGY.md](./docs/METHODOLOGY.md) for the full breakdown of
|
|
225
|
+
which metrics can and can't be affected by a client-side library, and for
|
|
226
|
+
known caveats (e.g. `transferSize` reporting `0` for opaque cross-origin
|
|
227
|
+
responses).
|
|
228
|
+
|
|
229
|
+
**For a defensible comparison:**
|
|
230
|
+
1. Serve both pages from a **production build**, not the dev server.
|
|
231
|
+
2. Alternate Baseline/ReactFastLoad runs in **fresh browser contexts**
|
|
232
|
+
(no shared cache/service worker) rather than switching pages in the
|
|
233
|
+
same tab.
|
|
234
|
+
3. Run each mode **multiple times** (e.g. 10 runs) and report median/p75,
|
|
235
|
+
not a single sample.
|
|
236
|
+
4. Report **requests avoided** and **bytes transferred** as separate
|
|
237
|
+
numbers — a library can cut transfer size substantially while barely
|
|
238
|
+
changing request count, and conflating the two overstates what
|
|
239
|
+
changed.
|
|
240
|
+
5. Only compare numbers gathered under identical connection/device
|
|
241
|
+
conditions.
|
|
242
|
+
|
|
243
|
+
The benchmark app now supports exactly this: three content-weight
|
|
244
|
+
scenarios (Light/Heavy/Extreme) and a repeated-run harness that reports
|
|
245
|
+
median/p75/p95 rather than a single sample — see
|
|
246
|
+
[benchmark/README.md](./benchmark/README.md).
|
|
247
|
+
|
|
248
|
+
## Roadmap
|
|
249
|
+
|
|
250
|
+
Several larger ideas (request deduplication, a real dependency graph,
|
|
251
|
+
service-worker integration, predictive prefetch, chunked/range loading,
|
|
252
|
+
a multi-level cache) have been proposed for a future version. See
|
|
253
|
+
[docs/ROADMAP.md](./docs/ROADMAP.md) for an honest triage of which of
|
|
254
|
+
those are worth building next, which need more design first, and which
|
|
255
|
+
I'd push back on or scope down — rather than a promise to build all of
|
|
256
|
+
it.
|
|
257
|
+
|
|
258
|
+
## Limitations
|
|
259
|
+
|
|
260
|
+
- The scheduler only coordinates resources it's explicitly told about —
|
|
261
|
+
it never intercepts `fetch`, monkey-patches globals, or touches
|
|
262
|
+
third-party scripts.
|
|
263
|
+
- `CRITICAL`-priority resources are never delayed, by design — don't mark
|
|
264
|
+
something `CRITICAL` unless it genuinely must load first regardless of
|
|
265
|
+
network conditions.
|
|
266
|
+
- `bytesDeferred` is only as accurate as the `estimatedSize` you pass per
|
|
267
|
+
resource — omit it and that resource contributes `0`, which can make
|
|
268
|
+
the deferred-bytes total look artificially low even when real bytes
|
|
269
|
+
were deferred. Pass `estimatedSize` on `SmartImage`/`SmartVideo`/
|
|
270
|
+
`SmartAudio` for a meaningful number.
|
|
271
|
+
- LCP/CLS can still change after being read; see
|
|
272
|
+
[docs/METHODOLOGY.md](./docs/METHODOLOGY.md).
|
|
273
|
+
|
|
274
|
+
## Development
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm install
|
|
278
|
+
npm run typecheck
|
|
279
|
+
npm test
|
|
280
|
+
npm run build
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cd benchmark
|
|
285
|
+
npm install
|
|
286
|
+
npm run dev
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Contributing
|
|
290
|
+
|
|
291
|
+
Issues and PRs welcome. Please include or update tests for any behavioral
|
|
292
|
+
change to `core/`, `observers/`, or `metrics/` — these are the modules the
|
|
293
|
+
rest of the library's correctness depends on.
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT — see [LICENSE](./LICENSE).
|