@divriots/jampack 0.23.3 → 0.24.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/README.md +3 -8
- package/dist/cache.js +1 -1
- package/dist/compress.js +7 -4
- package/dist/compressors/images.d.ts +1 -1
- package/dist/compressors/images.js +2 -5
- package/dist/config-default.js +1 -0
- package/dist/config-types.d.ts +1 -0
- package/dist/optimize.js +57 -84
- package/dist/optimizers/img-external.js +1 -1
- package/dist/utils/resource.js +3 -1
- package/package.json +18 -18
package/README.md
CHANGED
|
@@ -219,19 +219,14 @@ For more options see [CLI options](https://jampack.divriots.com/cli-options/).
|
|
|
219
219
|
|
|
220
220
|
## `jampack` used in the wild
|
|
221
221
|
|
|
222
|
-
- [
|
|
223
|
-
- [Backlight.dev](https://backlight.dev)
|
|
224
|
-
- [story.to.design](https://story.to.design)
|
|
225
|
-
- [html.to.design](https://html.to.design/docs)
|
|
226
|
-
- [WebComponents.dev](https://WebComponents.dev)
|
|
227
|
-
- [LWC.studio](https://lwc.studio)
|
|
228
|
-
- [Components.studio](https://components.studio)
|
|
222
|
+
- Powering all [‹div›RIOTS](https://divRIOTS.com)' websites
|
|
229
223
|
- [keycloak.ch](https://keycloak.ch)
|
|
230
224
|
- [bayjs.org](https://bayjs.org/)
|
|
231
225
|
- [qwind.pages.dev](https://qwind.pages.dev/)
|
|
232
226
|
- [Bloycey's Blog](https://bloycey.blog/)
|
|
233
227
|
- [gxanshu.in](https://gxanshu.in/)
|
|
234
|
-
|
|
228
|
+
- [cloudcannon.com](https://cloudcannon.com)
|
|
229
|
+
- [groupsync.app](https://groupsync.app)
|
|
235
230
|
> Add yours here
|
|
236
231
|
|
|
237
232
|
## Why is it called `jampack`?
|
package/dist/cache.js
CHANGED
package/dist/compress.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { formatBytes } from './utils.js';
|
|
4
|
+
import config from './config.js';
|
|
4
5
|
import $state from './state.js';
|
|
5
6
|
import { globby } from 'globby';
|
|
6
7
|
import ora from 'ora';
|
|
@@ -19,10 +20,12 @@ const processFile = async (file, stats) => {
|
|
|
19
20
|
case '.svg':
|
|
20
21
|
case '.webp':
|
|
21
22
|
case '.avif':
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (config.image.compress) {
|
|
24
|
+
const imgData = await fs.readFile(file);
|
|
25
|
+
const newImage = await compressImage(imgData, {});
|
|
26
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < stats.size) {
|
|
27
|
+
writeData = newImage.data;
|
|
28
|
+
}
|
|
26
29
|
}
|
|
27
30
|
break;
|
|
28
31
|
case '.html':
|
|
@@ -10,6 +10,6 @@ export type Image = {
|
|
|
10
10
|
};
|
|
11
11
|
export type ImageOutputOptions = {
|
|
12
12
|
resize?: sharp.ResizeOptions;
|
|
13
|
-
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | '
|
|
13
|
+
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'unchanged';
|
|
14
14
|
};
|
|
15
15
|
export declare function compressImage(data: Buffer, options: ImageOutputOptions): Promise<Image | undefined>;
|
|
@@ -19,7 +19,8 @@ export async function compressImage(data, options) {
|
|
|
19
19
|
}
|
|
20
20
|
// Load modifiable toFormat
|
|
21
21
|
let toFormat = options.toFormat || 'unchanged';
|
|
22
|
-
let sharpFile =
|
|
22
|
+
let sharpFile = sharp(data, { animated: true });
|
|
23
|
+
sharpFile = sharpFile.rotate(); // Rotate image based on EXIF data (because EXIF data is removed)
|
|
23
24
|
const meta = await sharpFile.metadata();
|
|
24
25
|
if (meta.pages && meta.pages > 1) {
|
|
25
26
|
// Skip animated images for the moment.
|
|
@@ -85,10 +86,6 @@ export async function compressImage(data, options) {
|
|
|
85
86
|
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options } || {});
|
|
86
87
|
outputFormat = 'jpg';
|
|
87
88
|
break;
|
|
88
|
-
case 'jpeg+progressive':
|
|
89
|
-
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options, progressive: true } || {});
|
|
90
|
-
outputFormat = 'jpg';
|
|
91
|
-
break;
|
|
92
89
|
case 'png':
|
|
93
90
|
sharpFile = sharpFile.png({ ...config.image.png.options } || {});
|
|
94
91
|
outputFormat = 'png';
|
package/dist/config-default.js
CHANGED
package/dist/config-types.d.ts
CHANGED
package/dist/optimize.js
CHANGED
|
@@ -201,7 +201,7 @@ function getTheFold($) {
|
|
|
201
201
|
return 0;
|
|
202
202
|
}
|
|
203
203
|
async function processImage(htmlfile, img, isAboveTheFold) {
|
|
204
|
-
var _a, _b
|
|
204
|
+
var _a, _b;
|
|
205
205
|
/*
|
|
206
206
|
* Attribute 'src'
|
|
207
207
|
*/
|
|
@@ -350,21 +350,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
350
350
|
let newImage;
|
|
351
351
|
const isInPicture = (_a = img.parent()) === null || _a === void 0 ? void 0 : _a.is('picture');
|
|
352
352
|
let srcToFormat = 'unchanged';
|
|
353
|
-
if (
|
|
354
|
-
// Above the fold, nothing beats progressive JPEG for LCP
|
|
355
|
-
// But jpeg doesn't support transparency
|
|
356
|
-
if ((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.isOpaque) {
|
|
357
|
-
srcToFormat = 'jpeg+progressive';
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
srcToFormat =
|
|
361
|
-
(await originalImage.getMime()) === 'image/avif' ||
|
|
362
|
-
(await originalImage.getMime()) === 'image/webp'
|
|
363
|
-
? 'unchanged'
|
|
364
|
-
: 'webp';
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
else if (isInPicture) {
|
|
353
|
+
if (isInPicture) {
|
|
368
354
|
// srcToFormat should not change if in picture
|
|
369
355
|
}
|
|
370
356
|
else {
|
|
@@ -381,12 +367,10 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
381
367
|
resize: { width: config.image.srcset_max_width },
|
|
382
368
|
});
|
|
383
369
|
}
|
|
384
|
-
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
|
|
385
|
-
(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const additionalExtension = `.${newImage.format}`;
|
|
389
|
-
const newFilename = originalImage.filePathAbsolute + additionalExtension;
|
|
370
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < (await originalImage.getLen())) {
|
|
371
|
+
const newFilename = path.join(path.dirname(originalImage.filePathAbsolute), path.basename(originalImage.filePathAbsolute, path.extname(originalImage.filePathAbsolute)) + `.${newImage.format}`);
|
|
372
|
+
const newSrc = path.join(path.dirname(attrib_src), path.basename(attrib_src, path.extname(attrib_src)) +
|
|
373
|
+
`.${newImage.format}`);
|
|
390
374
|
if (!$state.compressedFiles.has(newFilename) && !$state.args.nowrite) {
|
|
391
375
|
fs.writeFile(newFilename, newImage.data);
|
|
392
376
|
}
|
|
@@ -400,7 +384,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
400
384
|
originalSize: await originalImage.getLen(),
|
|
401
385
|
compressedSize: newImage.data.length,
|
|
402
386
|
});
|
|
403
|
-
img.attr('src',
|
|
387
|
+
img.attr('src', newSrc);
|
|
404
388
|
}
|
|
405
389
|
else {
|
|
406
390
|
// Drop new Image
|
|
@@ -502,71 +486,56 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
502
486
|
*/
|
|
503
487
|
if (isInPicture) {
|
|
504
488
|
const picture = img.parent();
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
489
|
+
// List of possible sources to generate
|
|
490
|
+
//
|
|
491
|
+
const sourcesToGenerate = [];
|
|
492
|
+
// Only try to generate better images
|
|
493
|
+
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
494
|
+
//
|
|
495
|
+
const mime = await originalImage.getMime();
|
|
496
|
+
switch (mime) {
|
|
497
|
+
case 'image/png':
|
|
498
|
+
case 'image/jpeg':
|
|
499
|
+
sourcesToGenerate.push({
|
|
500
|
+
mime: 'image/webp',
|
|
501
|
+
format: 'webp',
|
|
502
|
+
});
|
|
503
|
+
case 'image/webp':
|
|
504
|
+
// Only add AVIF source for lossy images
|
|
505
|
+
// AVIF don't perform well on lossless images
|
|
506
|
+
// PNG and WebP will perform better
|
|
507
|
+
// https://www.reddit.com/r/jpegxl/comments/l9ta2u/how_does_lossless_jpegxl_compared_to_png/
|
|
508
|
+
// https://twitter.com/jonsneyers/status/1346389917816008704?s=19
|
|
509
|
+
const isLossless = mime === 'image/png' ||
|
|
510
|
+
(mime === 'image/webp' &&
|
|
511
|
+
((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.isLossless));
|
|
512
|
+
if (!isLossless) {
|
|
521
513
|
sourcesToGenerate.push({
|
|
522
|
-
mime: 'image/
|
|
523
|
-
format: '
|
|
514
|
+
mime: 'image/avif',
|
|
515
|
+
format: 'avif',
|
|
524
516
|
});
|
|
525
|
-
case 'image/webp':
|
|
526
|
-
// Only add AVIF source for lossy images
|
|
527
|
-
// AVIF don't perform well on lossless images
|
|
528
|
-
// PNG and WebP will perform better
|
|
529
|
-
// https://www.reddit.com/r/jpegxl/comments/l9ta2u/how_does_lossless_jpegxl_compared_to_png/
|
|
530
|
-
// https://twitter.com/jonsneyers/status/1346389917816008704?s=19
|
|
531
|
-
const isLossless = mime === 'image/png' ||
|
|
532
|
-
(mime === 'image/webp' &&
|
|
533
|
-
((_c = (await originalImage.getImageMeta())) === null || _c === void 0 ? void 0 : _c.isLossless));
|
|
534
|
-
if (!isLossless) {
|
|
535
|
-
sourcesToGenerate.push({
|
|
536
|
-
mime: 'image/avif',
|
|
537
|
-
format: 'avif',
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
case 'image/avif':
|
|
541
|
-
// Nothing better
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
const sizes = img.attr('sizes');
|
|
545
|
-
for (const s of sourcesToGenerate) {
|
|
546
|
-
const sourceWithThisMimeType = picture.children(`source[type="${s.mime}"]`);
|
|
547
|
-
if (sourceWithThisMimeType.length > 0) {
|
|
548
|
-
// Ignore the creation of sources that already exist
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
552
|
-
toFormat: s.format,
|
|
553
|
-
});
|
|
554
|
-
if (!srcset) {
|
|
555
|
-
// No sourceset generated
|
|
556
|
-
// Image is too small or can't be compressed better
|
|
557
|
-
continue;
|
|
558
517
|
}
|
|
559
|
-
|
|
560
|
-
|
|
518
|
+
case 'image/avif':
|
|
519
|
+
// Nothing better
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
const sizes = img.attr('sizes');
|
|
523
|
+
for (const s of sourcesToGenerate.reverse()) {
|
|
524
|
+
const sourceWithThisMimeType = picture.children(`source[type="${s.mime}"]`);
|
|
525
|
+
if (sourceWithThisMimeType.length > 0) {
|
|
526
|
+
// Ignore the creation of sources that already exist
|
|
527
|
+
continue;
|
|
561
528
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
529
|
+
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
530
|
+
toFormat: s.format,
|
|
531
|
+
});
|
|
532
|
+
if (!srcset) {
|
|
533
|
+
// No sourceset generated
|
|
534
|
+
// Image is too small or can't be compressed better
|
|
535
|
+
continue;
|
|
567
536
|
}
|
|
568
|
-
|
|
569
|
-
|
|
537
|
+
const source = `<source ${sizes ? `sizes="${sizes}"` : ''} srcset="${srcset}" type="${s.mime}">`;
|
|
538
|
+
img.before(source); // Append before this way existing sources are always top priority
|
|
570
539
|
}
|
|
571
540
|
}
|
|
572
541
|
}
|
|
@@ -650,6 +619,10 @@ async function setImageSize(htmlfile, img, image) {
|
|
|
650
619
|
let height = img.attr('height');
|
|
651
620
|
let width_new = undefined;
|
|
652
621
|
let height_new = undefined;
|
|
622
|
+
const img_fmt = await image.getExt();
|
|
623
|
+
if (img_fmt === 'svg' && !config.image.svg.add_width_and_height) {
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
653
626
|
// Check valid values
|
|
654
627
|
if (width !== undefined) {
|
|
655
628
|
if (!isNumeric(width)) {
|
|
@@ -696,7 +669,7 @@ async function setImageSize(htmlfile, img, image) {
|
|
|
696
669
|
}
|
|
697
670
|
else {
|
|
698
671
|
// No width or height provided - set both to image size
|
|
699
|
-
if (
|
|
672
|
+
if (img_fmt === 'svg') {
|
|
700
673
|
// svg with no height and width has special sizing by browsers
|
|
701
674
|
// They size it inside 300x150 unless they have width and height
|
|
702
675
|
// attributes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import $state from '../state.js';
|
|
4
|
-
import hasha from 'hasha';
|
|
4
|
+
import { hashSync as hasha } from 'hasha';
|
|
5
5
|
import { fileTypeFromBuffer } from 'file-type';
|
|
6
6
|
import { addToCache, getFromCache } from '../cache.js';
|
|
7
7
|
import { parse } from '../utils/cache-control-parser.js';
|
package/dist/utils/resource.js
CHANGED
|
@@ -43,7 +43,9 @@ export class Resource {
|
|
|
43
43
|
// TODO
|
|
44
44
|
// Check for lossless avif
|
|
45
45
|
case 'jpg':
|
|
46
|
-
let sharpFile =
|
|
46
|
+
let sharpFile = sharp(await this.getData(), {
|
|
47
|
+
animated: true,
|
|
48
|
+
});
|
|
47
49
|
const meta = await sharpFile.metadata();
|
|
48
50
|
const stats = await sharpFile.stats();
|
|
49
51
|
this.image_meta = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -28,35 +28,35 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@divriots/cheerio": "1.0.0-rc.12",
|
|
30
30
|
"@proload/core": "^0.3.3",
|
|
31
|
-
"@swc/core": "^1.
|
|
31
|
+
"@swc/core": "^1.4.16",
|
|
32
32
|
"add": "^2.0.6",
|
|
33
|
-
"browserslist": "^4.
|
|
34
|
-
"commander": "^
|
|
35
|
-
"critters": "^0.0.
|
|
33
|
+
"browserslist": "^4.23.0",
|
|
34
|
+
"commander": "^12.0.0",
|
|
35
|
+
"critters": "^0.0.22",
|
|
36
36
|
"deepmerge": "^4.3.1",
|
|
37
|
-
"esbuild": "^0.
|
|
38
|
-
"file-type": "^
|
|
39
|
-
"globby": "^
|
|
40
|
-
"hasha": "^
|
|
37
|
+
"esbuild": "^0.20.2",
|
|
38
|
+
"file-type": "^19.0.0",
|
|
39
|
+
"globby": "^14.0.1",
|
|
40
|
+
"hasha": "^6.0.0",
|
|
41
41
|
"html-minifier-terser": "^7.2.0",
|
|
42
42
|
"kleur": "^4.1.5",
|
|
43
|
-
"lightningcss": "^1.
|
|
43
|
+
"lightningcss": "^1.24.1",
|
|
44
44
|
"mini-svg-data-uri": "^1.4.4",
|
|
45
|
-
"ora": "^
|
|
45
|
+
"ora": "^8.0.1",
|
|
46
46
|
"quicklink": "^2.3.0",
|
|
47
|
-
"sharp": "^0.33.
|
|
48
|
-
"svgo": "^3.
|
|
49
|
-
"table": "^6.8.
|
|
50
|
-
"undici": "^
|
|
51
|
-
"unpic": "^3.
|
|
47
|
+
"sharp": "^0.33.3",
|
|
48
|
+
"svgo": "^3.2.0",
|
|
49
|
+
"table": "^6.8.2",
|
|
50
|
+
"undici": "^6.13.0",
|
|
51
|
+
"unpic": "^3.18.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/html-minifier-terser": "^7.0.2",
|
|
55
|
-
"@types/node": "^20.
|
|
55
|
+
"@types/node": "^20.12.7",
|
|
56
56
|
"shx": "^0.3.3",
|
|
57
57
|
"ts-node": "^10.9.2",
|
|
58
58
|
"tslib": "^2.6.2",
|
|
59
|
-
"typescript": "^5.
|
|
59
|
+
"typescript": "^5.4.5"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=14.0.0"
|