@joycostudio/iris 0.0.0-stage → 0.0.1
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 +15 -0
- package/README.md +137 -2
- package/dist/browser.cjs +466 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +69 -0
- package/dist/browser.d.ts +69 -0
- package/dist/browser.js +439 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.cjs +681 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +658 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.cjs +355 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +8 -0
- package/dist/core.d.ts +8 -0
- package/dist/core.js +328 -0
- package/dist/core.js.map +1 -0
- package/dist/next.cjs +380 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +16 -0
- package/dist/next.d.ts +16 -0
- package/dist/next.js +352 -0
- package/dist/next.js.map +1 -0
- package/dist/node.cjs +580 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +47 -0
- package/dist/node.d.ts +47 -0
- package/dist/node.js +542 -0
- package/dist/node.js.map +1 -0
- package/dist/policy-types-LoD2Ldcl.d.cts +147 -0
- package/dist/policy-types-LoD2Ldcl.d.ts +147 -0
- package/dist/react.cjs +628 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +42 -0
- package/dist/react.d.ts +42 -0
- package/dist/react.js +611 -0
- package/dist/react.js.map +1 -0
- package/dist/types-Dv3DS4OX.d.cts +17 -0
- package/dist/types-Dv3DS4OX.d.ts +17 -0
- package/package.json +102 -4
- package/skills/iris-audit/SKILL.md +77 -0
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// packages/react/index.tsx
|
|
22
|
+
var react_exports = {};
|
|
23
|
+
__export(react_exports, {
|
|
24
|
+
IrisPlanImage: () => IrisPlanImage,
|
|
25
|
+
IrisProvider: () => IrisProvider,
|
|
26
|
+
useIrisDebugger: () => useIrisDebugger
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(react_exports);
|
|
29
|
+
var import_react2 = require("react");
|
|
30
|
+
|
|
31
|
+
// packages/browser/index.ts
|
|
32
|
+
function inspectImageElement(image, manifest = {}, lcpElements = /* @__PURE__ */ new Set(), timing = null) {
|
|
33
|
+
const rect = image.getBoundingClientRect();
|
|
34
|
+
const renderedWidth = image.clientWidth || rect.width;
|
|
35
|
+
const renderedHeight = image.clientHeight || rect.height;
|
|
36
|
+
const id = image.dataset.iris || image.currentSrc || image.src;
|
|
37
|
+
const source = manifest[image.dataset.irisSrc || id] ?? manifest[id] ?? manifest[image.currentSrc] ?? manifest[image.src] ?? null;
|
|
38
|
+
const candidates = parseSrcSet(image.srcset, renderedWidth);
|
|
39
|
+
const selectedUrl = image.currentSrc || image.src;
|
|
40
|
+
const selectedWidth = selectedCandidateWidth(selectedUrl, candidates);
|
|
41
|
+
const dpr = window.devicePixelRatio || 1;
|
|
42
|
+
const idealWidth = Math.ceil(renderedWidth * dpr);
|
|
43
|
+
const declaredSlotWidth = resolveSizes(image.sizes);
|
|
44
|
+
const resource = resourceTiming(selectedUrl);
|
|
45
|
+
const diagnostics = diagnoseRuntime({
|
|
46
|
+
source,
|
|
47
|
+
renderedWidth,
|
|
48
|
+
renderedHeight,
|
|
49
|
+
visualWidth: rect.width,
|
|
50
|
+
visualHeight: rect.height,
|
|
51
|
+
idealWidth,
|
|
52
|
+
selectedWidth,
|
|
53
|
+
availableWidths: candidates.map((candidate) => candidate.width),
|
|
54
|
+
declaredSlotWidth,
|
|
55
|
+
objectFit: getComputedStyle(image).objectFit
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
source,
|
|
60
|
+
renderedWidth,
|
|
61
|
+
renderedHeight,
|
|
62
|
+
visualWidth: rect.width,
|
|
63
|
+
visualHeight: rect.height,
|
|
64
|
+
naturalWidth: image.naturalWidth,
|
|
65
|
+
dpr,
|
|
66
|
+
idealWidth,
|
|
67
|
+
selectedWidth,
|
|
68
|
+
selectedUrl,
|
|
69
|
+
availableWidths: candidates.map((candidate) => candidate.width),
|
|
70
|
+
declaredSizes: image.sizes,
|
|
71
|
+
declaredSlotWidth,
|
|
72
|
+
loading: image.loading,
|
|
73
|
+
objectFit: getComputedStyle(image).objectFit,
|
|
74
|
+
transferBytes: resource?.transferSize ?? null,
|
|
75
|
+
duration: resource?.duration ?? null,
|
|
76
|
+
timing,
|
|
77
|
+
isLcp: lcpElements.has(image),
|
|
78
|
+
diagnostics
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function createIrisDebugger(options = {}) {
|
|
82
|
+
const reports = /* @__PURE__ */ new Map();
|
|
83
|
+
const lcpElements = /* @__PURE__ */ new Set();
|
|
84
|
+
let mutationObserver = null;
|
|
85
|
+
let resizeObserver = null;
|
|
86
|
+
let performanceObserver = null;
|
|
87
|
+
let frame = 0;
|
|
88
|
+
let tableTimer = null;
|
|
89
|
+
let lastTableSignature = "";
|
|
90
|
+
const pending = /* @__PURE__ */ new Set();
|
|
91
|
+
const timelines = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
const selector = options.scope === "document" ? "img" : "img[data-iris]";
|
|
93
|
+
const measure = (image) => {
|
|
94
|
+
const report2 = inspectImageElement(
|
|
95
|
+
image,
|
|
96
|
+
options.manifest,
|
|
97
|
+
lcpElements,
|
|
98
|
+
timelines.get(image) ?? null
|
|
99
|
+
);
|
|
100
|
+
reports.set(image, report2);
|
|
101
|
+
scheduleTable();
|
|
102
|
+
};
|
|
103
|
+
const table = () => logReportTable(report(), options.console ?? "warnings");
|
|
104
|
+
const scheduleTable = () => {
|
|
105
|
+
if (options.console === "silent") return;
|
|
106
|
+
if (tableTimer) clearTimeout(tableTimer);
|
|
107
|
+
tableTimer = setTimeout(() => {
|
|
108
|
+
tableTimer = null;
|
|
109
|
+
const signature = reportSignature(report());
|
|
110
|
+
if (signature === lastTableSignature) return;
|
|
111
|
+
lastTableSignature = signature;
|
|
112
|
+
table();
|
|
113
|
+
}, 250);
|
|
114
|
+
};
|
|
115
|
+
const schedule = (image) => {
|
|
116
|
+
pending.add(image);
|
|
117
|
+
if (frame) return;
|
|
118
|
+
frame = requestAnimationFrame(() => {
|
|
119
|
+
frame = 0;
|
|
120
|
+
for (const pendingImage of pending) measure(pendingImage);
|
|
121
|
+
pending.clear();
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const observe = (root = document) => {
|
|
125
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (entry.target instanceof HTMLImageElement) schedule(entry.target);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
const register = (image) => {
|
|
131
|
+
resizeObserver?.observe(image);
|
|
132
|
+
image.addEventListener("load", () => schedule(image), { once: true });
|
|
133
|
+
if (image.complete) schedule(image);
|
|
134
|
+
};
|
|
135
|
+
root.querySelectorAll(selector).forEach(register);
|
|
136
|
+
mutationObserver = new MutationObserver((records) => {
|
|
137
|
+
for (const record of records) {
|
|
138
|
+
for (const node of record.addedNodes) {
|
|
139
|
+
if (!(node instanceof Element)) continue;
|
|
140
|
+
if (node.matches(selector)) register(node);
|
|
141
|
+
node.querySelectorAll(selector).forEach(register);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
mutationObserver.observe(root, { childList: true, subtree: true });
|
|
146
|
+
if (typeof PerformanceObserver !== "undefined") {
|
|
147
|
+
performanceObserver = new PerformanceObserver((list) => {
|
|
148
|
+
for (const entry of list.getEntries()) {
|
|
149
|
+
const element = entry.element;
|
|
150
|
+
if (element) {
|
|
151
|
+
lcpElements.add(element);
|
|
152
|
+
if (element instanceof HTMLImageElement) schedule(element);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
performanceObserver.observe({
|
|
158
|
+
type: "largest-contentful-paint",
|
|
159
|
+
buffered: true
|
|
160
|
+
});
|
|
161
|
+
} catch {
|
|
162
|
+
performanceObserver = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return api;
|
|
166
|
+
};
|
|
167
|
+
const disconnect = () => {
|
|
168
|
+
mutationObserver?.disconnect();
|
|
169
|
+
resizeObserver?.disconnect();
|
|
170
|
+
performanceObserver?.disconnect();
|
|
171
|
+
cancelAnimationFrame(frame);
|
|
172
|
+
if (tableTimer) clearTimeout(tableTimer);
|
|
173
|
+
tableTimer = null;
|
|
174
|
+
frame = 0;
|
|
175
|
+
pending.clear();
|
|
176
|
+
};
|
|
177
|
+
const report = () => [...reports.values()];
|
|
178
|
+
const startTimeline = (image) => {
|
|
179
|
+
timelines.set(image, {
|
|
180
|
+
blurMs: null,
|
|
181
|
+
previewMs: null,
|
|
182
|
+
finalMs: null,
|
|
183
|
+
startedAt: null,
|
|
184
|
+
previewAt: null,
|
|
185
|
+
finalAt: null
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const markPreview = (image, preview, at = performance.now()) => {
|
|
189
|
+
const timing = timelines.get(image);
|
|
190
|
+
if (!timing) return;
|
|
191
|
+
timing.startedAt = resourceTiming(preview.currentSrc || preview.src)?.startTime ?? at;
|
|
192
|
+
timing.previewAt = at;
|
|
193
|
+
timing.blurMs = roundDuration(at - timing.startedAt);
|
|
194
|
+
schedule(image);
|
|
195
|
+
};
|
|
196
|
+
const markFinal = (image, at = performance.now()) => {
|
|
197
|
+
const timing = timelines.get(image);
|
|
198
|
+
if (!timing) return;
|
|
199
|
+
const finalStartedAt = resourceTiming(image.currentSrc || image.src)?.startTime ?? at;
|
|
200
|
+
timing.finalAt = at;
|
|
201
|
+
timing.finalMs = roundDuration(at - finalStartedAt);
|
|
202
|
+
if (timing.previewAt === null) {
|
|
203
|
+
timing.startedAt = finalStartedAt;
|
|
204
|
+
timing.blurMs = timing.finalMs;
|
|
205
|
+
} else timing.previewMs = roundDuration(at - timing.previewAt);
|
|
206
|
+
schedule(image);
|
|
207
|
+
};
|
|
208
|
+
const summary = () => ({
|
|
209
|
+
inspected: reports.size,
|
|
210
|
+
healthy: report().filter((item) => item.diagnostics.length === 0).length,
|
|
211
|
+
warnings: report().flatMap((item) => item.diagnostics).filter((item) => item.severity === "warning").length,
|
|
212
|
+
errors: report().flatMap((item) => item.diagnostics).filter((item) => item.severity === "error").length
|
|
213
|
+
});
|
|
214
|
+
const api = {
|
|
215
|
+
observe,
|
|
216
|
+
disconnect,
|
|
217
|
+
report,
|
|
218
|
+
table,
|
|
219
|
+
summary,
|
|
220
|
+
startTimeline,
|
|
221
|
+
markPreview,
|
|
222
|
+
markFinal
|
|
223
|
+
};
|
|
224
|
+
return api;
|
|
225
|
+
}
|
|
226
|
+
function roundDuration(value) {
|
|
227
|
+
return Math.round(value * 10) / 10;
|
|
228
|
+
}
|
|
229
|
+
function resourceTiming(url) {
|
|
230
|
+
const normalized = normalizeUrl(url);
|
|
231
|
+
const entries = performance.getEntriesByType(
|
|
232
|
+
"resource"
|
|
233
|
+
);
|
|
234
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
235
|
+
if (normalizeUrl(entries[index].name) === normalized) return entries[index];
|
|
236
|
+
}
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
function parseSrcSet(srcset, renderedWidth) {
|
|
240
|
+
return srcset.split(",").map((value) => value.trim().split(/\s+/)).map(([url, descriptor]) => ({
|
|
241
|
+
url,
|
|
242
|
+
width: descriptor?.endsWith("w") ? Number.parseInt(descriptor, 10) : descriptor?.endsWith("x") ? Math.round(Number.parseFloat(descriptor) * renderedWidth) : 0
|
|
243
|
+
})).filter((candidate) => candidate.url && candidate.width > 0);
|
|
244
|
+
}
|
|
245
|
+
function selectedCandidateWidth(selectedUrl, candidates) {
|
|
246
|
+
const selected = normalizeUrl(selectedUrl);
|
|
247
|
+
const candidate = candidates.find(
|
|
248
|
+
(item) => normalizeUrl(item.url) === selected
|
|
249
|
+
);
|
|
250
|
+
if (candidate) return candidate.width;
|
|
251
|
+
const queryWidth = new URL(
|
|
252
|
+
selectedUrl,
|
|
253
|
+
window.location.href
|
|
254
|
+
).searchParams.get("w");
|
|
255
|
+
return queryWidth ? Number(queryWidth) : null;
|
|
256
|
+
}
|
|
257
|
+
function normalizeUrl(url) {
|
|
258
|
+
try {
|
|
259
|
+
return new URL(url, window.location.href).href;
|
|
260
|
+
} catch {
|
|
261
|
+
return url;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function diagnoseRuntime(input) {
|
|
265
|
+
const diagnostics = [];
|
|
266
|
+
if (input.visualWidth !== void 0 && input.renderedWidth > 0 && Math.abs(input.visualWidth - input.renderedWidth) / input.renderedWidth > 0.02) {
|
|
267
|
+
diagnostics.push(
|
|
268
|
+
runtimeDiagnostic(
|
|
269
|
+
"css-transform-affects-visual-size",
|
|
270
|
+
"info",
|
|
271
|
+
"high",
|
|
272
|
+
"A CSS transform changes the visual width; responsive selection still uses the untransformed layout slot.",
|
|
273
|
+
{
|
|
274
|
+
renderedWidth: input.renderedWidth,
|
|
275
|
+
visualWidth: input.visualWidth
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (input.declaredSlotWidth && input.renderedWidth > 0) {
|
|
281
|
+
const pixelDifference = input.declaredSlotWidth - input.renderedWidth;
|
|
282
|
+
const difference = pixelDifference / input.renderedWidth;
|
|
283
|
+
const isMaterialMismatch = Math.abs(pixelDifference) >= 32;
|
|
284
|
+
if (difference < -0.1 && isMaterialMismatch) {
|
|
285
|
+
diagnostics.push(
|
|
286
|
+
runtimeDiagnostic(
|
|
287
|
+
"sizes-understates-layout",
|
|
288
|
+
"warning",
|
|
289
|
+
"high",
|
|
290
|
+
"The active sizes slot is smaller than the rendered CSS width.",
|
|
291
|
+
{
|
|
292
|
+
declaredSlotWidth: input.declaredSlotWidth,
|
|
293
|
+
renderedWidth: input.renderedWidth,
|
|
294
|
+
pixelDifference,
|
|
295
|
+
difference
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
} else if (difference > 0.25 && isMaterialMismatch) {
|
|
300
|
+
diagnostics.push(
|
|
301
|
+
runtimeDiagnostic(
|
|
302
|
+
"sizes-overstates-layout",
|
|
303
|
+
"warning",
|
|
304
|
+
"high",
|
|
305
|
+
"The active sizes slot is substantially larger than the rendered CSS width.",
|
|
306
|
+
{
|
|
307
|
+
declaredSlotWidth: input.declaredSlotWidth,
|
|
308
|
+
renderedWidth: input.renderedWidth,
|
|
309
|
+
pixelDifference,
|
|
310
|
+
difference
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (input.source && input.source.width < input.idealWidth) {
|
|
317
|
+
diagnostics.push(
|
|
318
|
+
runtimeDiagnostic(
|
|
319
|
+
"source-too-small",
|
|
320
|
+
"error",
|
|
321
|
+
"high",
|
|
322
|
+
"The source is smaller than the rendered width \xD7 DPR target.",
|
|
323
|
+
{
|
|
324
|
+
sourceWidth: input.source.width,
|
|
325
|
+
idealWidth: input.idealWidth
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (input.selectedWidth && input.selectedWidth < input.idealWidth) {
|
|
331
|
+
const hasLarger = input.availableWidths.some(
|
|
332
|
+
(width) => width >= input.idealWidth
|
|
333
|
+
);
|
|
334
|
+
diagnostics.push(
|
|
335
|
+
runtimeDiagnostic(
|
|
336
|
+
hasLarger ? "browser-downselected" : "missing-useful-candidate",
|
|
337
|
+
hasLarger ? "info" : "warning",
|
|
338
|
+
"high",
|
|
339
|
+
hasLarger ? "The browser selected below the theoretical target although a larger candidate exists." : "No available candidate reaches the theoretical target.",
|
|
340
|
+
{ selectedWidth: input.selectedWidth, idealWidth: input.idealWidth }
|
|
341
|
+
)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (input.source && input.objectFit === "cover") {
|
|
345
|
+
const sourceRatio = input.source.width / input.source.height;
|
|
346
|
+
const renderedRatio = input.renderedWidth / input.renderedHeight;
|
|
347
|
+
if (Math.abs(sourceRatio - renderedRatio) / sourceRatio > 0.02) {
|
|
348
|
+
diagnostics.push(
|
|
349
|
+
runtimeDiagnostic(
|
|
350
|
+
"cropped-image",
|
|
351
|
+
"info",
|
|
352
|
+
"high",
|
|
353
|
+
"object-fit: cover crops part of the downloaded source.",
|
|
354
|
+
{
|
|
355
|
+
sourceAspectRatio: sourceRatio,
|
|
356
|
+
renderedAspectRatio: renderedRatio
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return diagnostics;
|
|
363
|
+
}
|
|
364
|
+
function resolveSizes(sizes) {
|
|
365
|
+
if (!sizes || sizes.trim() === "auto") return null;
|
|
366
|
+
for (const rule of splitTopLevel(sizes)) {
|
|
367
|
+
const value = rule.trim();
|
|
368
|
+
let length = value;
|
|
369
|
+
if (value.startsWith("(")) {
|
|
370
|
+
const end = matchingParenthesis(value);
|
|
371
|
+
if (end < 0) continue;
|
|
372
|
+
if (!matchMedia(value.slice(0, end + 1)).matches) continue;
|
|
373
|
+
length = value.slice(end + 1).trim();
|
|
374
|
+
}
|
|
375
|
+
if (!length || length === "auto") continue;
|
|
376
|
+
const probe = document.createElement("div");
|
|
377
|
+
probe.style.cssText = `position:fixed;visibility:hidden;pointer-events:none;width:${length};height:0`;
|
|
378
|
+
document.body.append(probe);
|
|
379
|
+
const width = probe.getBoundingClientRect().width;
|
|
380
|
+
probe.remove();
|
|
381
|
+
return Number.isFinite(width) && width > 0 ? width : null;
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
function splitTopLevel(value) {
|
|
386
|
+
const output = [];
|
|
387
|
+
let depth = 0;
|
|
388
|
+
let start = 0;
|
|
389
|
+
for (let index = 0; index < value.length; index++) {
|
|
390
|
+
if (value[index] === "(") depth++;
|
|
391
|
+
if (value[index] === ")") depth--;
|
|
392
|
+
if (value[index] === "," && depth === 0) {
|
|
393
|
+
output.push(value.slice(start, index));
|
|
394
|
+
start = index + 1;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
output.push(value.slice(start));
|
|
398
|
+
return output;
|
|
399
|
+
}
|
|
400
|
+
function matchingParenthesis(value) {
|
|
401
|
+
let depth = 0;
|
|
402
|
+
for (let index = 0; index < value.length; index++) {
|
|
403
|
+
if (value[index] === "(") depth++;
|
|
404
|
+
if (value[index] === ")" && --depth === 0) return index;
|
|
405
|
+
}
|
|
406
|
+
return -1;
|
|
407
|
+
}
|
|
408
|
+
function runtimeDiagnostic(code, severity, confidence, message, evidence) {
|
|
409
|
+
return { code, severity, confidence, message, evidence };
|
|
410
|
+
}
|
|
411
|
+
function logReportTable(reports, mode) {
|
|
412
|
+
if (mode === "silent") return;
|
|
413
|
+
const visible = reports.filter(
|
|
414
|
+
(report) => mode === "all" || report.diagnostics.some((diagnostic) => diagnostic.severity !== "info")
|
|
415
|
+
);
|
|
416
|
+
if (visible.length === 0) return;
|
|
417
|
+
console.table(
|
|
418
|
+
visible.map((report) => ({
|
|
419
|
+
asset: report.id,
|
|
420
|
+
stage: imageStage(report),
|
|
421
|
+
rendered: `${Math.round(report.renderedWidth)}\xD7${Math.round(report.renderedHeight)}`,
|
|
422
|
+
selected: report.selectedWidth,
|
|
423
|
+
ideal: report.idealWidth,
|
|
424
|
+
bytes: report.transferBytes,
|
|
425
|
+
blurMs: report.timing?.blurMs ?? null,
|
|
426
|
+
previewMs: report.timing?.previewMs ?? null,
|
|
427
|
+
finalMs: report.timing?.finalMs ?? null,
|
|
428
|
+
lazy: report.loading === "lazy",
|
|
429
|
+
lcp: report.isLcp,
|
|
430
|
+
status: diagnosticStatus(report),
|
|
431
|
+
diagnostics: report.diagnostics.map((diagnostic) => diagnostic.code).join(", ")
|
|
432
|
+
}))
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
function imageStage(report) {
|
|
436
|
+
if (report.timing?.finalAt !== null && report.timing?.finalAt !== void 0)
|
|
437
|
+
return "final";
|
|
438
|
+
if (report.timing?.previewAt) return "preview";
|
|
439
|
+
if (report.timing) return "blur";
|
|
440
|
+
return report.naturalWidth > 0 ? "final" : "loading";
|
|
441
|
+
}
|
|
442
|
+
function diagnosticStatus(report) {
|
|
443
|
+
if (report.diagnostics.some((item) => item.severity === "error"))
|
|
444
|
+
return "error";
|
|
445
|
+
if (report.diagnostics.some((item) => item.severity === "warning"))
|
|
446
|
+
return "warning";
|
|
447
|
+
if (report.diagnostics.some((item) => item.severity === "info")) return "info";
|
|
448
|
+
return "healthy";
|
|
449
|
+
}
|
|
450
|
+
function reportSignature(reports) {
|
|
451
|
+
return reports.map(
|
|
452
|
+
(report) => [
|
|
453
|
+
report.id,
|
|
454
|
+
imageStage(report),
|
|
455
|
+
report.selectedWidth,
|
|
456
|
+
report.transferBytes,
|
|
457
|
+
report.timing?.blurMs,
|
|
458
|
+
report.timing?.previewMs,
|
|
459
|
+
report.timing?.finalMs,
|
|
460
|
+
diagnosticStatus(report)
|
|
461
|
+
].join(":")
|
|
462
|
+
).join("|");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// packages/react/plan-image.tsx
|
|
466
|
+
var import_react = require("react");
|
|
467
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
468
|
+
function IrisPlanImage(props) {
|
|
469
|
+
const { plan } = props;
|
|
470
|
+
const key = JSON.stringify([
|
|
471
|
+
plan.src,
|
|
472
|
+
plan.final.url,
|
|
473
|
+
plan.preview?.url,
|
|
474
|
+
plan.placeholder?.url,
|
|
475
|
+
plan.placeholder?.blur
|
|
476
|
+
]);
|
|
477
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlanImage, { ...props }, key);
|
|
478
|
+
}
|
|
479
|
+
function PlanImage({
|
|
480
|
+
plan,
|
|
481
|
+
alt,
|
|
482
|
+
className,
|
|
483
|
+
style,
|
|
484
|
+
onLoad,
|
|
485
|
+
onError,
|
|
486
|
+
loading = "lazy",
|
|
487
|
+
...props
|
|
488
|
+
}) {
|
|
489
|
+
const [finalReady, setFinalReady] = (0, import_react.useState)(false);
|
|
490
|
+
const [previewReady, setPreviewReady] = (0, import_react.useState)(false);
|
|
491
|
+
const previewDecoding = (0, import_react.useRef)(false);
|
|
492
|
+
const previewShown = (0, import_react.useRef)(false);
|
|
493
|
+
const finalRef = (0, import_react.useCallback)((image) => {
|
|
494
|
+
if (image?.complete && image.naturalWidth) setFinalReady(true);
|
|
495
|
+
}, []);
|
|
496
|
+
const revealPreview = (0, import_react.useCallback)(async (image) => {
|
|
497
|
+
if (previewDecoding.current || previewShown.current) return;
|
|
498
|
+
previewDecoding.current = true;
|
|
499
|
+
try {
|
|
500
|
+
await image.decode?.();
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
previewDecoding.current = false;
|
|
504
|
+
if (image.isConnected && image.naturalWidth && !previewShown.current) {
|
|
505
|
+
previewShown.current = true;
|
|
506
|
+
setPreviewReady(true);
|
|
507
|
+
}
|
|
508
|
+
}, []);
|
|
509
|
+
const previewRef = (0, import_react.useCallback)(
|
|
510
|
+
(image) => {
|
|
511
|
+
if (image?.complete && image.naturalWidth) void revealPreview(image);
|
|
512
|
+
},
|
|
513
|
+
[revealPreview]
|
|
514
|
+
);
|
|
515
|
+
const progressive = Boolean(plan.final.progressive);
|
|
516
|
+
const surface = {
|
|
517
|
+
position: "relative",
|
|
518
|
+
display: "block",
|
|
519
|
+
...style
|
|
520
|
+
};
|
|
521
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className, "data-slot": "iris-image", style: surface, children: [
|
|
522
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
523
|
+
"img",
|
|
524
|
+
{
|
|
525
|
+
...props,
|
|
526
|
+
ref: finalRef,
|
|
527
|
+
src: plan.final.url,
|
|
528
|
+
alt,
|
|
529
|
+
width: plan.final.width,
|
|
530
|
+
height: plan.final.height,
|
|
531
|
+
"data-slot": "iris-final",
|
|
532
|
+
"data-iris-src": plan.src,
|
|
533
|
+
loading,
|
|
534
|
+
style: {
|
|
535
|
+
position: "relative",
|
|
536
|
+
display: "block",
|
|
537
|
+
width: "100%",
|
|
538
|
+
height: "auto"
|
|
539
|
+
},
|
|
540
|
+
onLoad: (event) => {
|
|
541
|
+
setFinalReady(true);
|
|
542
|
+
onLoad?.(event);
|
|
543
|
+
},
|
|
544
|
+
onError
|
|
545
|
+
}
|
|
546
|
+
),
|
|
547
|
+
!progressive && !finalReady && !previewReady && plan.placeholder && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
548
|
+
"span",
|
|
549
|
+
{
|
|
550
|
+
"aria-hidden": "true",
|
|
551
|
+
"data-slot": "iris-placeholder",
|
|
552
|
+
"data-stage": "blur",
|
|
553
|
+
style: {
|
|
554
|
+
position: "absolute",
|
|
555
|
+
inset: 0,
|
|
556
|
+
backgroundImage: `url(${JSON.stringify(plan.placeholder.url)})`,
|
|
557
|
+
backgroundSize: "100% 100%",
|
|
558
|
+
filter: `blur(${plan.placeholder.blur}px)`,
|
|
559
|
+
clipPath: "inset(0)",
|
|
560
|
+
pointerEvents: "none",
|
|
561
|
+
zIndex: 2
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
),
|
|
565
|
+
!progressive && plan.preview && !finalReady && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
566
|
+
"img",
|
|
567
|
+
{
|
|
568
|
+
ref: previewRef,
|
|
569
|
+
src: plan.preview.url,
|
|
570
|
+
alt: "",
|
|
571
|
+
"aria-hidden": "true",
|
|
572
|
+
"data-slot": "iris-preview",
|
|
573
|
+
width: plan.final.width,
|
|
574
|
+
height: plan.final.height,
|
|
575
|
+
loading,
|
|
576
|
+
decoding: props.decoding,
|
|
577
|
+
crossOrigin: props.crossOrigin,
|
|
578
|
+
referrerPolicy: props.referrerPolicy,
|
|
579
|
+
style: {
|
|
580
|
+
position: "absolute",
|
|
581
|
+
inset: 0,
|
|
582
|
+
width: "100%",
|
|
583
|
+
height: "100%",
|
|
584
|
+
objectFit: "contain",
|
|
585
|
+
pointerEvents: "none",
|
|
586
|
+
zIndex: 1,
|
|
587
|
+
opacity: previewReady ? 1 : 0
|
|
588
|
+
},
|
|
589
|
+
onLoad: (event) => void revealPreview(event.currentTarget)
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
] });
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// packages/react/index.tsx
|
|
596
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
597
|
+
var IrisDebuggerContext = (0, import_react2.createContext)(null);
|
|
598
|
+
function IrisProvider({
|
|
599
|
+
children,
|
|
600
|
+
enabled = true,
|
|
601
|
+
manifest,
|
|
602
|
+
scope = "marked",
|
|
603
|
+
console: consoleMode = "warnings"
|
|
604
|
+
}) {
|
|
605
|
+
const [iris] = (0, import_react2.useState)(
|
|
606
|
+
() => typeof window === "undefined" ? null : createIrisDebugger({ manifest, scope, console: consoleMode })
|
|
607
|
+
);
|
|
608
|
+
(0, import_react2.useEffect)(() => {
|
|
609
|
+
if (!enabled || !iris) return;
|
|
610
|
+
iris.observe(document);
|
|
611
|
+
window.__IRIS__ = iris;
|
|
612
|
+
return () => {
|
|
613
|
+
iris.disconnect();
|
|
614
|
+
if (window.__IRIS__ === iris) delete window.__IRIS__;
|
|
615
|
+
};
|
|
616
|
+
}, [enabled, iris]);
|
|
617
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IrisDebuggerContext.Provider, { value: enabled ? iris : null, children });
|
|
618
|
+
}
|
|
619
|
+
function useIrisDebugger() {
|
|
620
|
+
return (0, import_react2.useContext)(IrisDebuggerContext);
|
|
621
|
+
}
|
|
622
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
623
|
+
0 && (module.exports = {
|
|
624
|
+
IrisPlanImage,
|
|
625
|
+
IrisProvider,
|
|
626
|
+
useIrisDebugger
|
|
627
|
+
});
|
|
628
|
+
//# sourceMappingURL=react.cjs.map
|