@divriots/jampack 0.23.4 → 0.24.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 +0 -0
- package/README.md +0 -1
- 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/index.js +0 -0
- package/dist/optimize.js +52 -83
- package/dist/optimizers/img-external.js +1 -1
- package/dist/utils/resource.js +5 -1
- package/package.json +26 -27
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
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/index.js
CHANGED
|
File without changes
|
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
|
}
|
|
@@ -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 = {
|
|
@@ -113,6 +115,8 @@ export class Resource {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
export function isLocal(src) {
|
|
118
|
+
if (src.startsWith('/'))
|
|
119
|
+
return false;
|
|
116
120
|
const u = url.parse(src);
|
|
117
121
|
return !u.host;
|
|
118
122
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -28,48 +28,47 @@
|
|
|
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
|
+
"engines": {
|
|
62
|
+
"node": ">=14.0.0"
|
|
63
|
+
},
|
|
64
|
+
"bugs": "https://github.com/divriots/jampack/issues",
|
|
65
|
+
"keywords": [],
|
|
66
|
+
"types": "dist/index.d.ts",
|
|
61
67
|
"scripts": {
|
|
62
68
|
"watch": "pnpm build --watch",
|
|
63
69
|
"try": "shx rm -fr demo && cp -R demo_drc demo && node ./dist/index.js ./demo --nocache",
|
|
64
70
|
"build": "shx rm -rf dist && tsc -b",
|
|
65
71
|
"lint": "eslint . --ext .ts --config .eslintrc",
|
|
66
|
-
"prepublishOnly": "pnpm build",
|
|
67
72
|
"cleanAllDotJampack": "find -s . | grep '.jampack$' | xargs rm -fr"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
"node": ">=14.0.0"
|
|
71
|
-
},
|
|
72
|
-
"bugs": "https://github.com/divriots/jampack/issues",
|
|
73
|
-
"keywords": [],
|
|
74
|
-
"types": "dist/index.d.ts"
|
|
75
|
-
}
|
|
73
|
+
}
|
|
74
|
+
}
|