@divriots/jampack 0.8.0 → 0.8.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/README.md +2 -2
- package/dist/compressors/images.d.ts +1 -2
- package/dist/compressors/images.js +6 -4
- package/dist/optimize.js +86 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,8 +43,8 @@ Optimizes static websites for best user experience and best Core Web Vitals scor
|
|
|
43
43
|
- [Optimize assets above-the-fold](https://jampack.divriots.com/features/optimize-above-the-fold)
|
|
44
44
|
- [Embed small images](https://jampack.divriots.com/features/embed-small-images)
|
|
45
45
|
- [Compress all assets](https://jampack.divriots.com/features/compress-all)
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
46
|
+
- [Fix things automatically](https://jampack.divriots.com/features/autofixes)
|
|
47
|
+
- [Raise warnings](https://jampack.divriots.com/features/warnings)
|
|
48
48
|
|
|
49
49
|
## Quick use
|
|
50
50
|
|
|
@@ -9,7 +9,6 @@ export type Image = {
|
|
|
9
9
|
};
|
|
10
10
|
export type ImageOutputOptions = {
|
|
11
11
|
resize?: sharp.ResizeOptions;
|
|
12
|
-
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'unchanged';
|
|
13
|
-
progressive?: boolean;
|
|
12
|
+
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'jpeg+progressive' | 'unchanged';
|
|
14
13
|
};
|
|
15
14
|
export declare function compressImage(data: Buffer, options: ImageOutputOptions): Promise<Image | undefined>;
|
|
@@ -79,13 +79,15 @@ export async function compressImage(data, options) {
|
|
|
79
79
|
// To format
|
|
80
80
|
switch (toFormat) {
|
|
81
81
|
case 'jpeg':
|
|
82
|
-
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options
|
|
83
|
-
|
|
82
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options } || {});
|
|
83
|
+
outputFormat = 'jpg';
|
|
84
|
+
break;
|
|
85
|
+
case 'jpeg+progressive':
|
|
86
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options, progressive: true } || {});
|
|
84
87
|
outputFormat = 'jpg';
|
|
85
88
|
break;
|
|
86
89
|
case 'png':
|
|
87
|
-
sharpFile = sharpFile.png({ ...config.image.png.options
|
|
88
|
-
{});
|
|
90
|
+
sharpFile = sharpFile.png({ ...config.image.png.options } || {});
|
|
89
91
|
outputFormat = 'png';
|
|
90
92
|
break;
|
|
91
93
|
case 'webp':
|
package/dist/optimize.js
CHANGED
|
@@ -79,7 +79,7 @@ function getTheFold($) {
|
|
|
79
79
|
return theFolds[theFolds.length - 1].startIndex;
|
|
80
80
|
}
|
|
81
81
|
async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
82
|
-
var _a;
|
|
82
|
+
var _a, _b;
|
|
83
83
|
const img = $(imgElement);
|
|
84
84
|
/*
|
|
85
85
|
* Attribute 'src'
|
|
@@ -161,9 +161,24 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
161
161
|
let newImage;
|
|
162
162
|
const isInPicture = (_a = img.parent()) === null || _a === void 0 ? void 0 : _a.is('picture');
|
|
163
163
|
let srcToFormat = 'unchanged';
|
|
164
|
-
if (
|
|
164
|
+
if (isAboveTheFold) {
|
|
165
|
+
// Above the fold, nothing beats progressive JPEG for LCP
|
|
166
|
+
// but it's not transparent
|
|
167
|
+
if ((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.hasAlpha) {
|
|
168
|
+
// TODO some images have Alpha metadata but have 0 alpha pixels
|
|
169
|
+
// We should be able to detect these to take advantage of Progressive JPEG
|
|
170
|
+
srcToFormat =
|
|
171
|
+
(await originalImage.getMime()) === 'image/avif' ||
|
|
172
|
+
(await originalImage.getMime()) === 'image/webp'
|
|
173
|
+
? 'unchanged'
|
|
174
|
+
: 'webp';
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
srcToFormat = 'jpeg+progressive';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (isInPicture) {
|
|
165
181
|
// srcToFormat should not change if in picture
|
|
166
|
-
// If above the fold, we will create progressive images
|
|
167
182
|
}
|
|
168
183
|
else {
|
|
169
184
|
// Let's go to avif -> avif , webp -> webp, else * -> webp
|
|
@@ -178,10 +193,10 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
178
193
|
$state.optimizedFiles.add(originalImage.filePathAbsolute);
|
|
179
194
|
newImage = await compressImage(await originalImage.getData(), {
|
|
180
195
|
toFormat: srcToFormat,
|
|
181
|
-
progressive: isAboveTheFold,
|
|
182
196
|
});
|
|
183
197
|
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
|
|
184
|
-
(newImage.data.length < (await originalImage.getLen()) ||
|
|
198
|
+
(newImage.data.length < (await originalImage.getLen()) ||
|
|
199
|
+
srcToFormat === 'jpeg+progressive') // Progressive override all even if worse
|
|
185
200
|
) {
|
|
186
201
|
// Do we need to add an new extension?
|
|
187
202
|
const newExtension = `.${newImage.format}`;
|
|
@@ -275,7 +290,7 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
275
290
|
// of the srcset
|
|
276
291
|
}
|
|
277
292
|
else {
|
|
278
|
-
const new_srcset = await generateSrcSet(htmlfile, originalImage,
|
|
293
|
+
const new_srcset = await generateSrcSet(htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
279
294
|
if (new_srcset !== null) {
|
|
280
295
|
img.attr('srcset', new_srcset);
|
|
281
296
|
}
|
|
@@ -289,68 +304,82 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
289
304
|
*/
|
|
290
305
|
if (isInPicture) {
|
|
291
306
|
const picture = img.parent();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
297
|
-
//
|
|
298
|
-
switch (await originalImage.getMime()) {
|
|
299
|
-
case 'image/jpeg':
|
|
300
|
-
case 'image/png':
|
|
301
|
-
sourcesToGenerate.push({
|
|
302
|
-
mime: 'image/webp',
|
|
303
|
-
format: 'webp',
|
|
304
|
-
});
|
|
305
|
-
case 'image/webp':
|
|
306
|
-
sourcesToGenerate.push({
|
|
307
|
-
mime: 'image/avif',
|
|
308
|
-
format: 'avif',
|
|
309
|
-
});
|
|
310
|
-
case 'image/avif':
|
|
311
|
-
// Nothing better
|
|
312
|
-
break;
|
|
307
|
+
if (isAboveTheFold && srcToFormat === 'jpeg+progressive') {
|
|
308
|
+
// We don't need anything else than jpeg progressive above the fold
|
|
309
|
+
const nodes = picture.children(`source`);
|
|
310
|
+
nodes.remove();
|
|
313
311
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
312
|
+
else {
|
|
313
|
+
// List of possible sources to generate
|
|
314
|
+
//
|
|
315
|
+
const sourcesToGenerate = [];
|
|
316
|
+
// Only try to generate better images
|
|
317
|
+
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
318
|
+
//
|
|
319
|
+
switch (await originalImage.getMime()) {
|
|
320
|
+
case 'image/jpeg':
|
|
321
|
+
case 'image/png':
|
|
322
|
+
sourcesToGenerate.push({
|
|
323
|
+
mime: 'image/webp',
|
|
324
|
+
format: 'webp',
|
|
325
|
+
});
|
|
326
|
+
case 'image/webp':
|
|
327
|
+
sourcesToGenerate.push({
|
|
328
|
+
mime: 'image/avif',
|
|
329
|
+
format: 'avif',
|
|
330
|
+
});
|
|
331
|
+
case 'image/avif':
|
|
332
|
+
// Nothing better
|
|
333
|
+
break;
|
|
319
334
|
}
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
335
|
+
const sizes = img.attr('sizes');
|
|
336
|
+
for (const s of sourcesToGenerate) {
|
|
337
|
+
const sourceWithThisMimeType = picture.children(`source[type="${s.mime}"]`);
|
|
338
|
+
if (sourceWithThisMimeType.length > 0) {
|
|
339
|
+
// Ignore the creation of sources that already exist
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
343
|
+
toFormat: s.format,
|
|
344
|
+
});
|
|
345
|
+
if (!srcset) {
|
|
346
|
+
// No sourceset generated
|
|
347
|
+
// Image is too small or can't be compressed better
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const source = `<source ${sizes ? `sizes="${sizes}"` : ''} srcset="${srcset}" type="${s.mime}">`;
|
|
351
|
+
picture.prepend(source);
|
|
328
352
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
353
|
+
// Reorder sources to priority order 1)avif 2)webp
|
|
354
|
+
//
|
|
355
|
+
function popupSource(type) {
|
|
356
|
+
const nodes = picture.children(`source[type="${type}"]`);
|
|
357
|
+
picture.prepend(nodes);
|
|
358
|
+
}
|
|
359
|
+
popupSource('image/webp');
|
|
360
|
+
popupSource('image/avif');
|
|
337
361
|
}
|
|
338
|
-
popupSource('image/webp');
|
|
339
|
-
popupSource('image/avif');
|
|
340
362
|
}
|
|
341
363
|
}
|
|
342
|
-
async function generateSrcSet(htmlfile, originalImage,
|
|
343
|
-
const ext = path.extname(
|
|
344
|
-
const fullbasename =
|
|
364
|
+
async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
365
|
+
const ext = path.extname(originalImage.src);
|
|
366
|
+
const fullbasename = originalImage.src.slice(0, -ext.length);
|
|
345
367
|
const imageSrc = (addition) => `${fullbasename}${addition}${options.toFormat === 'unchanged' ? ext : '.' + options.toFormat}`;
|
|
346
368
|
// Start from original image
|
|
347
369
|
let new_srcset = '';
|
|
370
|
+
const meta = await originalImage.getImageMeta();
|
|
371
|
+
const imageWidth = (meta === null || meta === void 0 ? void 0 : meta.width) || 0;
|
|
372
|
+
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
373
|
+
if (!imageWidth || !imageHeight) {
|
|
374
|
+
// Forget about srcset
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
348
377
|
// Start reduction
|
|
349
378
|
const step = 300; //px
|
|
350
379
|
const ratio = imageWidth / imageHeight;
|
|
351
|
-
let valueW = !
|
|
380
|
+
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
352
381
|
let valueH = Math.trunc(valueW / ratio);
|
|
353
|
-
let previousImageSize =
|
|
382
|
+
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
354
383
|
while (valueW >= config.image.srcset_min_width) {
|
|
355
384
|
const src = imageSrc(`@${valueW}w`);
|
|
356
385
|
const absoluteFilename = translateSrc($state.dir, path.dirname(htmlfile), src);
|
|
@@ -383,8 +412,8 @@ async function generateSrcSet(htmlfile, originalImage, attrib_src, imageWidth, i
|
|
|
383
412
|
valueH = Math.trunc(valueW / ratio);
|
|
384
413
|
}
|
|
385
414
|
if (new_srcset) {
|
|
386
|
-
return
|
|
387
|
-
? `${
|
|
415
|
+
return startSrc
|
|
416
|
+
? `${startSrc} ${imageWidth}w` + new_srcset
|
|
388
417
|
: new_srcset.slice(2);
|
|
389
418
|
}
|
|
390
419
|
return null;
|