@divriots/jampack 0.4.1 → 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 +7 -26
- package/dist/compress.d.ts +0 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +14 -14
- package/dist/index.js +3 -2
- package/dist/optimize.js +31 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +6 -3
- package/package.json +1 -1
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
|
-
|
|
54
|
+
<div>
|
|
55
|
+
|
|
56
|
+
## What can `jampack` do?
|
|
55
57
|
|
|
56
|
-
|
|
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,32 +72,9 @@ Check the [Documentation side](https://jampack.divriots.com).
|
|
|
68
72
|
|
|
69
73
|
## Why is it called `jampack`?
|
|
70
74
|
|
|
71
|
-
- `
|
|
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
|
-
| 🔽 `<img>` | | | |
|
|
88
|
-
| Optimize `png` to `WebP` | Given `foo.png`, creates a **near loss less** `foo.png.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
89
|
-
| Optimize `jpeg` to `WebP` | Given `foo.jpg`, creates a **lossly** `foo.jpg.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
90
|
-
| Optimize `svg` | | | ✅ |
|
|
91
|
-
| 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/) | ✅ |
|
|
92
|
-
| Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
|
|
93
|
-
| 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) | ✅ |
|
|
94
|
-
| 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) | ✅ |
|
|
95
|
-
| Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
|
|
96
|
-
| Set async decoding by default | Sets `decoding="async"`. | | ✅ |
|
|
97
|
-
| Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
|
|
98
|
-
| 🔽 `<picture>` | | | |
|
|
99
|
-
| - TODO | | | ❌ |
|
package/dist/compress.d.ts
CHANGED
package/dist/config.d.ts
CHANGED
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:
|
|
21
|
+
mode: "lossless",
|
|
22
22
|
},
|
|
23
23
|
options_lossly: {
|
|
24
24
|
effort: 4,
|
|
25
25
|
quality: 80,
|
|
26
|
-
mode:
|
|
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/index.js
CHANGED
|
@@ -10,20 +10,21 @@ import { printTitle } from './logger.js';
|
|
|
10
10
|
import { exit } from 'process';
|
|
11
11
|
import kleur from 'kleur';
|
|
12
12
|
import { cleanCache } from './cache.js';
|
|
13
|
+
import { VERSION } from './version.js';
|
|
13
14
|
const logo = ` __ __
|
|
14
15
|
|__|____ _____ ___________ ____ | | __
|
|
15
16
|
| \\__ \\ / \\\\____ \\__ \\ _/ ___\\| |/ /
|
|
16
17
|
| |/ __ \\| Y Y \\ |_> > __ \\\\ \\___| <
|
|
17
18
|
/\\__| (____ /__|_| / __(____ /\\___ >__|_ \\
|
|
18
19
|
\\______| \\/ \\/| | \\/ \\/ \\/
|
|
19
|
-
|
|
20
|
+
v${VERSION.padEnd(14)} |__| by ‹div›RIOTS
|
|
20
21
|
`;
|
|
21
22
|
console.log(logo);
|
|
22
23
|
const program = new Command();
|
|
23
24
|
program
|
|
24
25
|
.name('jampack')
|
|
25
26
|
.description('Static website Optimizer')
|
|
26
|
-
.version(
|
|
27
|
+
.version(VERSION);
|
|
27
28
|
program.command('pack', { isDefault: true })
|
|
28
29
|
.description('todo')
|
|
29
30
|
.argument('<dir>', 'Directory to pack')
|
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, {
|
|
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
|
|
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:
|
|
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
|
-
|
|
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}
|
|
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(), {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION:
|
|
1
|
+
export declare const VERSION: any;
|
package/dist/version.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const pkgjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')).toString());
|
|
6
|
+
export const VERSION = pkgjson.version;
|