@luigisbox/luma 0.0.0-stage → 0.2.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.md +90 -0
- package/README.md +615 -2
- package/a11y/audit.mjs +735 -0
- package/a11y/axe.mjs +73 -0
- package/a11y/report.mjs +347 -0
- package/a11y/run.mjs +256 -0
- package/a11y/wcag.mjs +233 -0
- package/bin/config.mjs +60 -0
- package/bin/luma.mjs +99 -0
- package/bin/paths.mjs +11 -0
- package/bin/tools.mjs +43 -0
- package/build/baseline.d.mts +8 -0
- package/build/baseline.mjs +167 -0
- package/build/tooling.mjs +102 -0
- package/build/vite.mjs +507 -0
- package/chrome/block.mjs +8 -0
- package/chrome/run.mjs +150 -0
- package/docs/ACCESSIBILITY.md +501 -0
- package/docs/INTEGRATION-PERFORMANCE.md +213 -0
- package/docs/PARITY.md +946 -0
- package/footprint/model.mjs +184 -0
- package/footprint/run.mjs +331 -0
- package/package.json +58 -4
- package/parity/alignment.mjs +109 -0
- package/parity/archive.mjs +223 -0
- package/parity/args.mjs +137 -0
- package/parity/behaviour.mjs +379 -0
- package/parity/compare.mjs +985 -0
- package/parity/engine.mjs +848 -0
- package/parity/hints.mjs +132 -0
- package/parity/hold.mjs +54 -0
- package/parity/init.mjs +703 -0
- package/parity/map.mjs +640 -0
- package/parity/owner.mjs +170 -0
- package/parity/pairing.mjs +110 -0
- package/parity/pixels.mjs +33 -0
- package/parity/probe.mjs +1239 -0
- package/parity/report.mjs +236 -0
- package/parity/run.mjs +267 -0
- package/snippet/early-hold.js +64 -0
- package/snippet/early-hold.min.js +1 -0
- package/src/analytics/chain.ts +137 -0
- package/src/analytics/collect.ts +64 -0
- package/src/analytics/datalayer.ts +67 -0
- package/src/analytics/from-ga4.ts +68 -0
- package/src/analytics/from-luma.ts +21 -0
- package/src/analytics/index.ts +24 -0
- package/src/analytics/outbox.ts +118 -0
- package/src/analytics/page.ts +46 -0
- package/src/analytics/pageview.ts +82 -0
- package/src/analytics/ref.ts +19 -0
- package/src/analytics/setup.ts +41 -0
- package/src/analytics/start.ts +53 -0
- package/src/analytics/triggers.ts +239 -0
- package/src/analytics/user.ts +12 -0
- package/src/analytics/wire.ts +67 -0
- package/src/api/ai.ts +98 -0
- package/src/api/index.ts +87 -0
- package/src/api/token-key.ts +42 -0
- package/src/api/token.ts +312 -0
- package/src/core/a11y-keys.ts +60 -0
- package/src/core/a11y.ts +160 -0
- package/src/core/boot-state.ts +6 -0
- package/src/core/color.ts +42 -0
- package/src/core/context.ts +108 -0
- package/src/core/early-rules.ts +96 -0
- package/src/core/entries.ts +25 -0
- package/src/core/fold.ts +7 -0
- package/src/core/health.ts +26 -0
- package/src/core/hold.ts +74 -0
- package/src/core/i18n.ts +66 -0
- package/src/core/image-load.ts +102 -0
- package/src/core/image-tags.ts +26 -0
- package/src/core/images.ts +170 -0
- package/src/core/integration.ts +126 -0
- package/src/core/kept.ts +25 -0
- package/src/core/listings.ts +100 -0
- package/src/core/locales.ts +14 -0
- package/src/core/own.ts +49 -0
- package/src/core/page.ts +14 -0
- package/src/core/prefetch.ts +88 -0
- package/src/core/recommenders.ts +166 -0
- package/src/core/reconciler.ts +397 -0
- package/src/core/result.ts +73 -0
- package/src/core/routes.ts +115 -0
- package/src/core/scheduler.ts +83 -0
- package/src/core/sense.ts +107 -0
- package/src/core/speculation.ts +59 -0
- package/src/core/stash.ts +38 -0
- package/src/core/storage.ts +45 -0
- package/src/core/styles.ts +106 -0
- package/src/core/support.ts +12 -0
- package/src/core/takeover.ts +220 -0
- package/src/core/templates.ts +74 -0
- package/src/core/tiles.ts +137 -0
- package/src/core/turbo.ts +64 -0
- package/src/core/url.ts +164 -0
- package/src/core/widget-inits.ts +40 -0
- package/src/env.d.ts +3 -0
- package/src/lit.ts +11 -0
- package/src/locales/cs.json +94 -0
- package/src/locales/de.json +88 -0
- package/src/locales/en.json +88 -0
- package/src/main.ts +493 -0
- package/src/types.ts +778 -0
- package/src/widgets/autocomplete/index.ts +1387 -0
- package/src/widgets/autocomplete/style-autocomplete.css +266 -0
- package/src/widgets/autocomplete/template-autocomplete-box.ts +61 -0
- package/src/widgets/autocomplete/template-autocomplete-empty.ts +53 -0
- package/src/widgets/autocomplete/template-autocomplete-result-default.ts +82 -0
- package/src/widgets/autocomplete/template-autocomplete-sheet.ts +74 -0
- package/src/widgets/listing/element.ts +1772 -0
- package/src/widgets/listing/flow.ts +30 -0
- package/src/widgets/listing/instant.ts +133 -0
- package/src/widgets/listing/intent.ts +193 -0
- package/src/widgets/listing/query.ts +298 -0
- package/src/widgets/listing/stash.ts +97 -0
- package/src/widgets/listing/state.ts +131 -0
- package/src/widgets/listing/view.ts +257 -0
- package/src/widgets/plp/index.ts +11 -0
- package/src/widgets/plp/route.ts +263 -0
- package/src/widgets/recommender/index.ts +261 -0
- package/src/widgets/recommender/style-recommender.css +26 -0
- package/src/widgets/recommender/template-recommender-box-default.ts +11 -0
- package/src/widgets/recommender/template-recommender-popup.ts +12 -0
- package/src/widgets/recommender/template-recommender-result-default.ts +10 -0
- package/src/widgets/search/assets.ts +7 -0
- package/src/widgets/search/first.ts +15 -0
- package/src/widgets/search/form-url.ts +59 -0
- package/src/widgets/search/in-place.ts +226 -0
- package/src/widgets/search/index.ts +16 -0
- package/src/widgets/search/range.ts +260 -0
- package/src/widgets/search/route.ts +32 -0
- package/src/widgets/search/speculate.ts +202 -0
- package/src/widgets/search/style-search.css +65 -0
- package/src/widgets/search/template-search-box.ts +17 -0
- package/src/widgets/search/template-search-facets-default.ts +73 -0
- package/src/widgets/search/template-search-header.ts +14 -0
- package/src/widgets/search/template-search-pagination.ts +12 -0
- package/src/widgets/search/template-search-range-default.ts +48 -0
- package/src/widgets/search/template-search-result-default.ts +13 -0
- package/src/widgets/search/template-search-results-default.ts +8 -0
- package/tasks/model.mjs +116 -0
- package/tasks/run.mjs +305 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Integration performance: what to measure, what to optimize
|
|
2
|
+
|
|
3
|
+
A guideline for integration work (an integration repository with Luma as the
|
|
4
|
+
`@luigisbox/luma` package). Luma's own budgets are
|
|
5
|
+
held by its own test suites. An integration can still undo them with heavy tiles, a
|
|
6
|
+
layout that moves, or a slow install. This page lists the numbers to watch,
|
|
7
|
+
in priority order, and the levers an integration has.
|
|
8
|
+
|
|
9
|
+
## The metrics, in priority order
|
|
10
|
+
|
|
11
|
+
| # | Metric | Target | Where to read it |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| 1 | **Time to listing shown** (first tiles painted, from navigation) | Repeat visit ≤ 1.2 s; first visit as low as the API chain allows (below) | `__lbx.health.timeline`, `yarn luma footprint` (`ready`), DevTools Performance |
|
|
14
|
+
| 2 | **Layout shift caused by us** | 0 after ours is shown | `yarn luma footprint`, a `layout-shift` PerformanceObserver, DevTools Performance (Layout shifts track) |
|
|
15
|
+
| 3 | **Longest main-thread task of ours** | ≤ 16 ms of our own code (a frame) at 4x CPU | `yarn luma tasks` (per task, on the storefront, by source line); the `rendered in N frames (a/b/c ms)` mark; DevTools at 4x |
|
|
16
|
+
| 4 | **Script chain before the listing renders** | Entry ≤ 14 kB brotli (1 round trip); chain depth ≤ 3 | `yarn luma footprint` (entry size, round trips, chain) |
|
|
17
|
+
| 5 | **API requests per page** | Category: lookup (first visit) + products + facets. Search: products + facets + one per quick type. Token: once per session | `yarn luma footprint` (API requests), DevTools Network |
|
|
18
|
+
| 6 | **Recommenders** | Inline: never empty in view. Popup: open within ~100 ms of the notice | DevTools; `reserve` set |
|
|
19
|
+
| 7 | **Bytes per scenario** | No growth without a reason | `yarn luma footprint` (scripts and bytes per page); `--dictionary` for a returning visitor after a deploy |
|
|
20
|
+
| 8 | **Autocomplete as you type** | Keystroke to next paint ≤ 50 ms (INP), first opening included; the typed phrase shown ≤ debounce + one answer after the last key | Event Timing (`event` entries), `ac ask <q>` / `ac shown <q>` in `__lbx.health.timeline`, `yarn luma footprint` (typing) |
|
|
21
|
+
|
|
22
|
+
What does not matter much: total JS bytes of chunks that load after the
|
|
23
|
+
listing shows (autocomplete, recommender), CORS preflights (cached 20 min),
|
|
24
|
+
and Lighthouse scores on pages without a listing.
|
|
25
|
+
|
|
26
|
+
### 1. Time to listing shown
|
|
27
|
+
|
|
28
|
+
This is the number shoppers feel. It is a chain:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
HTML → loader → entry (1 RTT) → [challenge → token: first page of a session]
|
|
32
|
+
→ [category lookup: first visit of that category] → products → (facets)
|
|
33
|
+
→ render (first screen in ~100–120 ms at 4x) → shown
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **On a real network, not in a sandbox.** Behind a proxy, the connect and TLS
|
|
37
|
+
setup alone can take several hundred ms per host, which hides what
|
|
38
|
+
preconnects save. Measure on your machine with `yarn luma footprint --rtt 100` (an
|
|
39
|
+
emulated 100 ms round trip), and with DevTools on the live site.
|
|
40
|
+
- Read it in the page:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
__lbx.health.timeline // [ms since navigation, what]; for example (illustrative):
|
|
44
|
+
// [ 812, 'plp render 24 hits' ]
|
|
45
|
+
// [ 941, 'plp rendered in 4 frames (9/14/15/12 ms)' ]
|
|
46
|
+
// [ 760, 'plp products before their facets' ]
|
|
47
|
+
// [1210, 'search quick types after the products' ]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Together with the Network panel: the gap between the products response
|
|
51
|
+
and `render` is ours. The gap between navigation and the products request is
|
|
52
|
+
the chain above.
|
|
53
|
+
- Reference (a production integration on its stub page, 4x CPU): ours is shown 108–121 ms after its
|
|
54
|
+
render starts. The rest of the listing finishes 90–140 ms later, busy
|
|
55
|
+
meanwhile. A repeat visit shows in 0.9–1.2 s. A first visit is dominated by
|
|
56
|
+
the serial API chain (challenge, token, lookup, products).
|
|
57
|
+
- **What an integration controls:**
|
|
58
|
+
- the snippet in `<head>` (below);
|
|
59
|
+
- where the facets and quick types sit. Beside the grid, in a drawer, or
|
|
60
|
+
below it, the products are shown without waiting for them. Above the
|
|
61
|
+
grid, they wait so that they don't push it down. Luma learns this per
|
|
62
|
+
route and width; you choose it with the layout;
|
|
63
|
+
- tile cost: a tile template that renders in ≤ 1 ms at 4x;
|
|
64
|
+
- `size`: the first page is what is rendered; a screen plus a row is enough,
|
|
65
|
+
and "show more" is cheap.
|
|
66
|
+
|
|
67
|
+
### 2. Layout shift
|
|
68
|
+
|
|
69
|
+
Zero after ours is shown. The first frame's swap (their listing collapsing as
|
|
70
|
+
ours takes its place) is the takeover's and expected. Anything after it is a
|
|
71
|
+
bug in the integration or in Luma. Sources, most common first:
|
|
72
|
+
|
|
73
|
+
- **Images without a box.** Use `image()` / `img()` from templates: they emit
|
|
74
|
+
`aspect-ratio` from `images.width/height`. Set those to the catalog's real
|
|
75
|
+
ratio.
|
|
76
|
+
- **Parts arriving late above the grid.** Luma waits for the facets and quick
|
|
77
|
+
types where they sit above, but a template that renders something else
|
|
78
|
+
above the list from data that comes later (a banner, a count) shifts it.
|
|
79
|
+
Use `facetsPending` / `quickPending` in the scope to keep their place.
|
|
80
|
+
- **Fonts.** Ours shows in the site's final font; a tile that uses a font the
|
|
81
|
+
page does not load swaps later. Use the site's fonts.
|
|
82
|
+
- **Recommenders** without `reserve`: the block grows when it fills. Set
|
|
83
|
+
`reserve` to the rendered height.
|
|
84
|
+
- **The reserved height of a hard load** (`reserveSelector`, the remembered
|
|
85
|
+
height): check with the footer in view on a tall screen.
|
|
86
|
+
|
|
87
|
+
Measure it with:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
new PerformanceObserver((l) => l.getEntries().forEach((e) => !e.hadRecentInput && console.log(e.startTime|0, e.value, e.sources?.map((s) => s.node)))).observe({ type: 'layout-shift', buffered: true });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Main thread: no task of ours over a frame (16 ms) at 4x CPU
|
|
94
|
+
|
|
95
|
+
`yarn luma tasks` measures it on the storefront (or the stub page): every
|
|
96
|
+
scenario (the `luma.config.mjs` pages loaded, a change of the listing's state, show
|
|
97
|
+
more, Back, typing) three times at 4x CPU, and for each the worst task by
|
|
98
|
+
**our own** time, with its functions named by the integration's source. Our
|
|
99
|
+
own time leaves out the site's code that runs inside our calls (its
|
|
100
|
+
`pushState` hooks, its handlers an integration hook calls): that is in
|
|
101
|
+
`ours`, reported, but not ours to make faster. Exit 1 when a scenario's
|
|
102
|
+
median is over the budget (`--budget`, default 16).
|
|
103
|
+
|
|
104
|
+
What it found on a production integration, integration-side, the kind of
|
|
105
|
+
thing to look for:
|
|
106
|
+
|
|
107
|
+
- work at a module's top level in `_init.ts`: an `Intl.NumberFormat` made
|
|
108
|
+
there loads its locale's data on every page (18 ms of a 30 ms boot task);
|
|
109
|
+
make such things lazily, or not at all (a fixed price format written out);
|
|
110
|
+
- an `onOpen` that calls into the site (its search overlay opening, jQuery
|
|
111
|
+
`:visible` checks forcing a layout): 9-18 ms in the autocomplete's first
|
|
112
|
+
opening; Luma draws the rows in a task before it, but the hook's own cost
|
|
113
|
+
is the integration's;
|
|
114
|
+
- the browser's `history.pushState` (8-15 ms on a heavy page) in the URL
|
|
115
|
+
write after a filter: reported under `core/sense.ts`, not reducible by us.
|
|
116
|
+
|
|
117
|
+
The first render runs in steps: the first 4 tiles with the skeleton, then
|
|
118
|
+
the facet groups and quick types, then the rest, each step within ~16 ms. The
|
|
119
|
+
`rendered in N frames (…ms)` mark lists every step's time. A step over 30 ms
|
|
120
|
+
means the template is too heavy. Typical causes:
|
|
121
|
+
|
|
122
|
+
- per-tile work in the template (formatting, regexes, `toLocaleString` per
|
|
123
|
+
call): precompute it in `_init.ts` or in a `result` mapper;
|
|
124
|
+
- deep markup per tile (SVG icons inline per tile: use a `<use href>` sprite);
|
|
125
|
+
- many facet values rendered open (`facetSize`, collapsed groups).
|
|
126
|
+
|
|
127
|
+
`contentVisibility: true` (opt-in) makes re-renders after "show more" and
|
|
128
|
+
sorting cheaper on long listings (style, layout and paint 243 → 144 ms on
|
|
129
|
+
a production integration's listing), but it contains every tile: check tile hover effects, absolutely
|
|
130
|
+
positioned badges and overflowing shadows before turning it on.
|
|
131
|
+
|
|
132
|
+
### 4. Scripts: size and chain
|
|
133
|
+
|
|
134
|
+
`yarn luma footprint` reports the entry (brotli) and the round trips it needs. Keep
|
|
135
|
+
the entry within the TCP first flight (14 kB): one round trip. Integration
|
|
136
|
+
code in `_init.ts` and `_config.ts` lands in the entry. Keep it small: no
|
|
137
|
+
libraries, no catalog data, no templates (templates go to their widget's
|
|
138
|
+
chunk). `--preload` shows what a loader's preload hint for the scenario's
|
|
139
|
+
chunks would save (−100 to −300 ms at 100 ms RTT on a production integration).
|
|
140
|
+
`--dictionary <previous dist>` shows what a returning visitor downloads after a
|
|
141
|
+
deploy with Use-As-Dictionary (category page 56.4 kB → 3.7 kB after a
|
|
142
|
+
one-commit change).
|
|
143
|
+
|
|
144
|
+
### 5. API requests
|
|
145
|
+
|
|
146
|
+
Every request is a round trip on a first visit and load on the API.
|
|
147
|
+
Luma's own suites pin its counts. An integration adds
|
|
148
|
+
requests by:
|
|
149
|
+
|
|
150
|
+
- quick types (one request each; 2 is typical);
|
|
151
|
+
- `emptyValues` (the unfiltered facets, once per scope or query, on a filtered page);
|
|
152
|
+
- several recommenders on a page (each its own request);
|
|
153
|
+
- range facets over fields that are not numeric (a lookup per scope).
|
|
154
|
+
|
|
155
|
+
The token is minted once per session, off the main thread. The challenge is
|
|
156
|
+
asked by the entry while the minting code loads.
|
|
157
|
+
|
|
158
|
+
### 6. Recommenders
|
|
159
|
+
|
|
160
|
+
Inline recommenders are asked two screens ahead, after the page's load event
|
|
161
|
+
(not competing with it). Popups are asked at the click that opens them.
|
|
162
|
+
Watch:
|
|
163
|
+
|
|
164
|
+
- **empty in view:** scroll at normal speed; the block must be filled
|
|
165
|
+
before it enters the viewport;
|
|
166
|
+
- `reserve`: the block's rendered height, so filling it does not shift.
|
|
167
|
+
|
|
168
|
+
### 7. Autocomplete as you type
|
|
169
|
+
|
|
170
|
+
Two numbers. **Input latency**: no keystroke waits on us. The first opening is
|
|
171
|
+
the risky one: its one-time costs (templates, the first style and layout of
|
|
172
|
+
each text style) are rehearsed after focus. **Freshness**: how far the
|
|
173
|
+
dropdown trails the phrase. A batch goes out on the first 3 characters, then
|
|
174
|
+
after 300 ms of quiet or every 800 ms while typing (`instantChars`,
|
|
175
|
+
`debounceMs`, `maxWaitMs`). It renders once every type has answered, so the
|
|
176
|
+
slowest type sets the pace (usually products).
|
|
177
|
+
|
|
178
|
+
- The timeline pairs each phrase's `ac ask <q>` with its `ac shown <q>`. The
|
|
179
|
+
gap is the API's answer plus the pictures (≤ 100 ms) plus our render.
|
|
180
|
+
- Keep product rows light: few `fields`, `size` ≤ 7. Every field is server time
|
|
181
|
+
and bytes on every keystroke batch.
|
|
182
|
+
- Templates: the same rules as tiles (no per-row formatting work). A batch's
|
|
183
|
+
render should stay within one frame at 4x on a phone.
|
|
184
|
+
- Measure with real typing (150–250 ms between keys), cold (first opening on
|
|
185
|
+
a page) and warm. On a phone the sheet opens on the tap: that tap's task is
|
|
186
|
+
the one to watch.
|
|
187
|
+
|
|
188
|
+
## How to measure, step by step
|
|
189
|
+
|
|
190
|
+
0. `yarn luma tooling`: the integration has the one `luma` script and its
|
|
191
|
+
storefront's host in `luma.config.mjs` (`--write` adds them); `yarn luma`
|
|
192
|
+
lists the tools.
|
|
193
|
+
1. `yarn luma footprint --rtt 100 --runs 5` in the integration repository, before
|
|
194
|
+
and after the change. Compare the cold and warm `ready`, main thread, scripts,
|
|
195
|
+
bytes, chain and API requests per scenario. Commit the JSON (`--json`) so
|
|
196
|
+
the next change has a baseline.
|
|
197
|
+
2. `yarn luma tasks`: no task of our own over 16 ms at 4x CPU, per scenario. A
|
|
198
|
+
bot challenge stops the live run and is never worked around: run it on the
|
|
199
|
+
stub page, or ask the client to allowlist the runner.
|
|
200
|
+
3. On the live site with `?_lbx_env=development`, in DevTools with 4x CPU
|
|
201
|
+
and Fast 4G: a Performance recording of a category page and a search page,
|
|
202
|
+
cold (new profile) and warm. Check the Layout shifts track, the longest task
|
|
203
|
+
in ours, and `__lbx.health.timeline`.
|
|
204
|
+
4. `yarn luma a11y` and `yarn luma parity`: speed must not cost accessibility or parity
|
|
205
|
+
with the site's listing.
|
|
206
|
+
5. Phone width, desktop width, and a tall screen (1600 px): the learnt
|
|
207
|
+
layout (facets and quick types above or beside) differs by width, and the
|
|
208
|
+
first screen holds more tiles on a tall one.
|
|
209
|
+
|
|
210
|
+
## What the site includes
|
|
211
|
+
|
|
212
|
+
The snippet with the connection hints is in README.md ("What the customer
|
|
213
|
+
includes").
|