@divriots/jampack 0.4.2 → 0.5.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
@@ -51,9 +51,13 @@ npx @divriots/jampack ./dist
51
51
 
52
52
  For more options see [CLI options](https://jampack.divriots.com/cli-options).
53
53
 
54
- ## Documentation
54
+ <div>
55
+
56
+ ## What can `jampack` do?
55
57
 
56
- Check the [Documentation side](https://jampack.divriots.com).
58
+ See [the list of features](https://jampack.divriots.com).
59
+
60
+ </div>
57
61
 
58
62
  ## `jampack` use in the wild
59
63
 
@@ -68,28 +72,9 @@ Check the [Documentation side](https://jampack.divriots.com).
68
72
 
69
73
  ## Why is it called `jampack`?
70
74
 
71
- - ` jam`: From [Jamstack](https://en.wikipedia.org/wiki/Jamstack).
75
+ - `jam`: From [Jamstack](https://en.wikipedia.org/wiki/Jamstack).
72
76
  - `pack`: Reminescent of the [Executable Packers](https://en.wikipedia.org/wiki/Executable_compression#List_of_executable_packers) from the 90s.
73
77
 
74
78
  ## License
75
79
 
76
80
  This software is released under the terms of the [MIT license](https://github.com/divriots/jampack/blob/main/LICENSE).
77
-
78
-
79
- ## Optimizations
80
-
81
- ### PASS 1 - Optimizations
82
-
83
- This pass takes all `*.{html|.htm}` files and parse them for optimisation.
84
-
85
- | Optimization | Technical details | Related links | Available |
86
- | -------------- | --------------------- |:------------:|:---------:|
87
- | Generate responsive images and set `srcset` | Multiple smaller images are generated exclusively in `WebP` and `srcset` attribute is set accordingly. | [web.dev](https://web.dev/patterns/web-vitals-patterns/images/responsive-images/) | ✅ |
88
- | Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
89
- | Set `width` and `height` if missing | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
90
- | Fix `width` or `height` if not numerical | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
91
- | Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
92
- | Set async decoding by default | Sets `decoding="async"`. | | ✅ |
93
- | Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
94
- | 🔽 `<picture>` | | | |
95
- | - TODO | | | ❌ |
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import sharp from 'sharp';
3
2
  import type { Image } from './types.js';
4
3
  export declare const compressImage: (data: Buffer, options: {
package/dist/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type WebpOptions = {
2
2
  effort: number;
3
- mode: 'lossless' | 'lossly';
3
+ mode: "lossless" | "lossly";
4
4
  quality: number;
5
5
  };
6
6
  export type Options = {
package/dist/config.js CHANGED
@@ -6,27 +6,27 @@ const default_options = {
6
6
  jpeg: {
7
7
  options: {
8
8
  quality: 80,
9
- mozjpeg: true
10
- }
9
+ mozjpeg: true,
10
+ },
11
11
  },
12
12
  png: {
13
13
  options: {
14
14
  compressionLevel: 9,
15
- }
15
+ },
16
16
  },
17
17
  webp: {
18
18
  options_lossless: {
19
19
  effort: 4,
20
20
  quality: 80,
21
- mode: 'lossless'
21
+ mode: "lossless",
22
22
  },
23
23
  options_lossly: {
24
24
  effort: 4,
25
25
  quality: 80,
26
- mode: 'lossly'
27
- }
28
- }
29
- }
26
+ mode: "lossly",
27
+ },
28
+ },
29
+ },
30
30
  };
31
31
  const fast_options_override = {
32
32
  image: {
@@ -35,13 +35,13 @@ const fast_options_override = {
35
35
  compress: false,
36
36
  jpeg: {
37
37
  options: {
38
- mozjpeg: false
39
- }
38
+ mozjpeg: false,
39
+ },
40
40
  },
41
41
  png: {
42
42
  options: {
43
43
  compressionLevel: 0,
44
- }
44
+ },
45
45
  },
46
46
  webp: {
47
47
  options_lossless: {
@@ -49,9 +49,9 @@ const fast_options_override = {
49
49
  },
50
50
  options_lossly: {
51
51
  effort: 0,
52
- }
53
- }
54
- }
52
+ },
53
+ },
54
+ },
55
55
  };
56
56
  export function fast() {
57
57
  Object.assign(default_options, fast_options_override);
package/dist/optimize.js CHANGED
@@ -13,7 +13,10 @@ import { isLocal, Resource, translateSrc } from "./utils/resource.js";
13
13
  async function analyse(file) {
14
14
  console.log("▶ " + file);
15
15
  const html = (await fs.readFile(path.join($state.dir, file))).toString();
16
- const $ = cheerio.load(html, { withStartIndices: true, decodeEntities: false });
16
+ const $ = cheerio.load(html, {
17
+ withStartIndices: true,
18
+ decodeEntities: false,
19
+ });
17
20
  const theFold = getTheFold($);
18
21
  const imgs = $("img");
19
22
  const imgsArray = [];
@@ -133,18 +136,20 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
133
136
  return;
134
137
  }
135
138
  /*
136
- * Compress image to WebP
139
+ * Compress image
137
140
  */
138
141
  let newImage;
142
+ const originalImageMeta = await originalImage.getImageMeta();
143
+ const canBeProgressiveJpeg = isAboveTheFold && originalImageMeta && !originalImageMeta.hasAlpha;
139
144
  if (!$state.optimizedFiles.has(originalImage.filePathAbsolute)) {
140
145
  // Let's avoid to optimize same images twice
141
146
  $state.optimizedFiles.add(originalImage.filePathAbsolute);
142
147
  newImage = await compressImage(await originalImage.getData(), {
143
- toFormat: isAboveTheFold ? "pjpg" : "webp",
148
+ toFormat: canBeProgressiveJpeg ? "pjpg" : "webp",
144
149
  });
145
150
  if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
146
151
  (newImage.data.length < (await originalImage.getLen()) ||
147
- isAboveTheFold) // Progressive jpg above the fold should get replaced even if bigger
152
+ canBeProgressiveJpeg) // Progressive jpg above the fold should get replaced even if bigger
148
153
  ) {
149
154
  // Do we need to add an new extension?
150
155
  const newExtension = `.${newImage.format}`;
@@ -201,14 +206,14 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
201
206
  });
202
207
  }
203
208
  }
204
- /*
205
- * Attribute 'width' & 'height'
206
- */
207
- const [w, h] = await setImageSize(img, originalImage);
208
209
  if (isEmbed) {
209
210
  // Image is embed, no need for more processing
210
211
  return;
211
212
  }
213
+ /*
214
+ * Attribute 'width' & 'height'
215
+ */
216
+ const [w, h] = await setImageSize(htmlfile, img, originalImage);
212
217
  //
213
218
  // Stop here if svg
214
219
  //
@@ -228,7 +233,7 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
228
233
  // Generate image set
229
234
  const ext = path.extname(attrib_src);
230
235
  const fullbasename = attrib_src.slice(0, -ext.length);
231
- const imageSrc = (addition) => `${fullbasename}${addition}.webp`;
236
+ const imageSrc = (addition) => `${fullbasename}${addition}.${canBeProgressiveJpeg ? "jpg" : "webp"}`;
232
237
  // Start from original image
233
238
  let new_srcset = "";
234
239
  // Start reduction
@@ -243,7 +248,10 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
243
248
  if (!$state.compressedFiles.has(absoluteFilename)) {
244
249
  // Add file to list avoid recompression
245
250
  $state.compressedFiles.add(absoluteFilename);
246
- const compressedImage = await compressImage(await originalImage.getData(), { resize: { width: valueW, height: valueH }, toFormat: "webp" });
251
+ const compressedImage = await compressImage(await originalImage.getData(), {
252
+ resize: { width: valueW, height: valueH },
253
+ toFormat: canBeProgressiveJpeg ? "pjpg" : "webp",
254
+ });
247
255
  if ((compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) && !$state.args.nowrite) {
248
256
  fs.writeFile(absoluteFilename, compressedImage.data);
249
257
  }
@@ -266,7 +274,7 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
266
274
  });
267
275
  }
268
276
  }
269
- async function setImageSize(img, image) {
277
+ async function setImageSize(htmlfile, img, image) {
270
278
  let width = img.attr("width");
271
279
  let height = img.attr("height");
272
280
  let width_new = undefined;
@@ -274,13 +282,19 @@ async function setImageSize(img, image) {
274
282
  // Check valid values
275
283
  if (width !== undefined) {
276
284
  if (!isNumeric(width)) {
277
- //console.warn(`Invalid width attribute "${width}" - overriding`);
285
+ $state.reportIssue(htmlfile, {
286
+ type: "warn",
287
+ msg: `Invalid width attribute "${width}" format - overriding`,
288
+ });
278
289
  width = undefined;
279
290
  }
280
291
  }
281
292
  if (height !== undefined) {
282
293
  if (!isNumeric(height)) {
283
- //console.warn(`Invalid height attribute "${height}" - overriding`);
294
+ $state.reportIssue(htmlfile, {
295
+ type: "warn",
296
+ msg: `Invalid height attribute "${height}" format - overriding`,
297
+ });
284
298
  height = undefined;
285
299
  }
286
300
  }
@@ -301,7 +315,10 @@ async function setImageSize(img, image) {
301
315
  const providedRatio = Math.round((w / h) * 10) / 10;
302
316
  const imageRatio = Math.round(originalRatio * 10) / 10;
303
317
  if (providedRatio !== imageRatio) {
304
- //console.warn(`Image aspect ratio in HTML (${providedRatio}) differs from image aspect ratio (${imageRatio}) - fix width and height or let jampack fill them.`);
318
+ $state.reportIssue(htmlfile, {
319
+ type: "warn",
320
+ msg: `Image aspect ratio in HTML (${providedRatio}) differs from image aspect ratio (${imageRatio}) - fix width and height or let jampack fill them.`,
321
+ });
305
322
  }
306
323
  return [w, h];
307
324
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divriots/jampack",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Packer for static websites",
5
5
  "author": "Georges Gomes @georges-gomes",
6
6
  "bin": {