@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 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
- - [divRIOTS.com](https://divRIOTS.com)
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
@@ -1,4 +1,4 @@
1
- import hasha from 'hasha';
1
+ import { hashSync as hasha } from 'hasha';
2
2
  import path from 'path';
3
3
  import * as fs from 'fs/promises';
4
4
  import $state from './state.js';
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
- const imgData = await fs.readFile(file);
23
- const newImage = await compressImage(imgData, {});
24
- if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < stats.size) {
25
- writeData = newImage.data;
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' | 'jpeg+progressive' | 'unchanged';
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 = await sharp(data, { animated: true });
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';
@@ -53,6 +53,7 @@ const default_options = {
53
53
  },
54
54
  svg: {
55
55
  optimization: true,
56
+ add_width_and_height: false,
56
57
  },
57
58
  },
58
59
  misc: {
@@ -53,6 +53,7 @@ export type Options = {
53
53
  };
54
54
  svg: {
55
55
  optimization: boolean;
56
+ add_width_and_height: boolean;
56
57
  };
57
58
  };
58
59
  misc: {
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, _c;
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 (isAboveTheFold) {
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
- (newImage.data.length < (await originalImage.getLen()) ||
386
- srcToFormat === 'jpeg+progressive') // Progressive override all even if worse
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', attrib_src + additionalExtension);
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
- if (isAboveTheFold && srcToFormat === 'jpeg+progressive') {
506
- // We don't need anything else than jpeg progressive above the fold
507
- const nodes = picture.children(`source`);
508
- nodes.remove();
509
- }
510
- else {
511
- // List of possible sources to generate
512
- //
513
- const sourcesToGenerate = [];
514
- // Only try to generate better images
515
- // TODO generate more compatible formats (avif => webp/jpeg/png)
516
- //
517
- const mime = await originalImage.getMime();
518
- switch (mime) {
519
- case 'image/png':
520
- case 'image/jpeg':
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/webp',
523
- format: 'webp',
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
- const source = `<source ${sizes ? `sizes="${sizes}"` : ''} srcset="${srcset}" type="${s.mime}">`;
560
- picture.prepend(source);
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
- // Reorder sources to priority order 1)avif 2)webp
563
- //
564
- function popupSource(type) {
565
- const nodes = picture.children(`source[type="${type}"]`);
566
- picture.prepend(nodes);
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
- popupSource('image/webp');
569
- popupSource('image/avif');
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 ((await image.getExt()) === 'svg') {
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';
@@ -43,7 +43,9 @@ export class Resource {
43
43
  // TODO
44
44
  // Check for lossless avif
45
45
  case 'jpg':
46
- let sharpFile = await sharp(await this.getData(), { animated: true });
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.23.3",
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.3.100",
31
+ "@swc/core": "^1.4.16",
32
32
  "add": "^2.0.6",
33
- "browserslist": "^4.22.2",
34
- "commander": "^10.0.1",
35
- "critters": "^0.0.20",
33
+ "browserslist": "^4.23.0",
34
+ "commander": "^12.0.0",
35
+ "critters": "^0.0.22",
36
36
  "deepmerge": "^4.3.1",
37
- "esbuild": "^0.19.9",
38
- "file-type": "^18.7.0",
39
- "globby": "^13.2.2",
40
- "hasha": "^5.2.2",
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.22.1",
43
+ "lightningcss": "^1.24.1",
44
44
  "mini-svg-data-uri": "^1.4.4",
45
- "ora": "^7.0.1",
45
+ "ora": "^8.0.1",
46
46
  "quicklink": "^2.3.0",
47
- "sharp": "^0.33.0",
48
- "svgo": "^3.1.0",
49
- "table": "^6.8.1",
50
- "undici": "^5.24.0",
51
- "unpic": "^3.16.0"
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.10.4",
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.3.3"
59
+ "typescript": "^5.4.5"
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=14.0.0"