stitch2 0.25.0 → 0.25.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/dist/shots.js +46 -28
- package/package.json +1 -1
package/dist/shots.js
CHANGED
|
@@ -13,6 +13,15 @@ import { messagesFile, sourceLocale } from './i18n.js';
|
|
|
13
13
|
import { ROOT, meta, tag } from './screens.js';
|
|
14
14
|
/** The tallest picture, in CSS px: the same limit as the canvas frames and check's renders. */
|
|
15
15
|
const MAX_HEIGHT = 20000;
|
|
16
|
+
/**
|
|
17
|
+
* Pictures are drawn at two pixels for each CSS pixel and kept at a high JPEG quality. At one pixel, a laptop screen
|
|
18
|
+
* (two device pixels for each CSS pixel) shows a picture stretched to twice its size: thin light text on a dark page
|
|
19
|
+
* comes out soft, grainy and low in contrast next to the live page. Chromium cannot draw a picture much taller than
|
|
20
|
+
* MAX_DEVICE_PX device pixels, so a page too tall for that is drawn at one pixel for each CSS pixel.
|
|
21
|
+
*/
|
|
22
|
+
const SCALE = 2;
|
|
23
|
+
const MAX_DEVICE_PX = 16000;
|
|
24
|
+
const QUALITY = 92;
|
|
16
25
|
const DIR = join(tmpdir(), 'stitch2-shots');
|
|
17
26
|
/** Folders under the design directory that hold outputs, not inputs: they never change a picture. */
|
|
18
27
|
const OUTPUTS = new Set(['renders', 'consistency', 'compare', 'states', 'node_modules', '.git']);
|
|
@@ -53,7 +62,7 @@ function keyOf(r) {
|
|
|
53
62
|
return null;
|
|
54
63
|
const messages = r.lang && r.lang !== sourceLocale() ? messagesFile(r.lang) : undefined;
|
|
55
64
|
const parts = [
|
|
56
|
-
'viewport-
|
|
65
|
+
'viewport-v3',
|
|
57
66
|
r.path,
|
|
58
67
|
statSync(file).mtimeMs,
|
|
59
68
|
sharedStamp(),
|
|
@@ -94,39 +103,48 @@ export function shot(base, r) {
|
|
|
94
103
|
return inFlight;
|
|
95
104
|
const job = slot(async () => {
|
|
96
105
|
browser ??= launch();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await page.goto(`${base}/${r.path}?${query}`, { waitUntil: 'networkidle', timeout: 30_000 }).catch(() => { });
|
|
105
|
-
if (r.theme)
|
|
106
|
-
await page.evaluate((t) => { document.documentElement.dataset.theme = t; }, r.theme);
|
|
107
|
-
const attributes = Object.entries(r.modes ?? {});
|
|
108
|
-
if (attributes.length)
|
|
109
|
-
await page.evaluate((list) => { for (const [name, value] of list)
|
|
110
|
-
document.documentElement.setAttribute(name, value); }, attributes);
|
|
111
|
-
await page.evaluate(() => document.fonts.ready);
|
|
112
|
-
// Tailwind's browser build and the components settle after load.
|
|
113
|
-
await page.waitForTimeout(400);
|
|
114
|
-
const height = await page.evaluate(() => Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0));
|
|
115
|
-
await page.setViewportSize({ width: r.width, height: Math.min(MAX_HEIGHT, Math.max(844, height)) });
|
|
116
|
-
const jpg = await page.screenshot({ type: 'jpeg', quality: 80 });
|
|
117
|
-
mkdirSync(DIR, { recursive: true });
|
|
118
|
-
writeFileSync(file, jpg);
|
|
119
|
-
return jpg;
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
await page.close();
|
|
123
|
-
}
|
|
106
|
+
// A page too tall for the sharp scale is drawn again at one pixel for each CSS pixel.
|
|
107
|
+
const jpg = (await draw(base, r, SCALE)) ?? (await draw(base, r, 1));
|
|
108
|
+
if (!jpg)
|
|
109
|
+
throw new Error('no picture of ' + r.path);
|
|
110
|
+
mkdirSync(DIR, { recursive: true });
|
|
111
|
+
writeFileSync(file, jpg);
|
|
112
|
+
return jpg;
|
|
124
113
|
});
|
|
125
114
|
pending.set(key, job);
|
|
126
115
|
const done = () => pending.delete(key);
|
|
127
116
|
job.then(done, done);
|
|
128
117
|
return job;
|
|
129
118
|
}
|
|
119
|
+
/** One picture of a screen at the given scale; null when the page is too tall to be drawn at that scale. */
|
|
120
|
+
async function draw(base, r, scale) {
|
|
121
|
+
const page = await (await browser).newPage({ viewport: { width: r.width, height: 844 }, deviceScaleFactor: scale });
|
|
122
|
+
try {
|
|
123
|
+
const viewport = Math.min(4000, Math.max(200, Number(meta(readFileSync(join(ROOT, r.path), 'utf8'), tag('frame-height'))) || 844));
|
|
124
|
+
await page.setViewportSize({ width: r.width, height: viewport });
|
|
125
|
+
const query = new URLSearchParams({ canvas: String(viewport) });
|
|
126
|
+
if (r.lang && r.lang !== sourceLocale())
|
|
127
|
+
query.set('lang', r.lang);
|
|
128
|
+
await page.goto(`${base}/${r.path}?${query}`, { waitUntil: 'networkidle', timeout: 30_000 }).catch(() => { });
|
|
129
|
+
if (r.theme)
|
|
130
|
+
await page.evaluate((t) => { document.documentElement.dataset.theme = t; }, r.theme);
|
|
131
|
+
const attributes = Object.entries(r.modes ?? {});
|
|
132
|
+
if (attributes.length)
|
|
133
|
+
await page.evaluate((list) => { for (const [name, value] of list)
|
|
134
|
+
document.documentElement.setAttribute(name, value); }, attributes);
|
|
135
|
+
await page.evaluate(() => document.fonts.ready);
|
|
136
|
+
// Tailwind's browser build and the components settle after load.
|
|
137
|
+
await page.waitForTimeout(400);
|
|
138
|
+
const height = Math.min(MAX_HEIGHT, Math.max(844, await page.evaluate(() => Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0))));
|
|
139
|
+
if (scale > 1 && height * scale > MAX_DEVICE_PX)
|
|
140
|
+
return null;
|
|
141
|
+
await page.setViewportSize({ width: r.width, height });
|
|
142
|
+
return await page.screenshot({ type: 'jpeg', quality: QUALITY });
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await page.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
130
148
|
export async function closeShots() {
|
|
131
149
|
const b = await browser?.catch(() => undefined);
|
|
132
150
|
browser = undefined;
|
package/package.json
CHANGED