@divriots/jampack 0.20.1 → 0.21.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 +15 -1
- package/dist/cache.js +1 -1
- package/dist/config-default.js +1 -1
- package/dist/index.js +3 -0
- package/dist/optimize.js +24 -16
- package/dist/optimizers/inline-critical-css.js +2 -0
- package/dist/state.d.ts +1 -0
- package/dist/utils/resource.d.ts +2 -1
- package/dist/utils/resource.js +19 -3
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -106,7 +106,21 @@ Lazy-load assets below-the-fold ⬇️.
|
|
|
106
106
|
|
|
107
107
|
- Images and Iframes are lazy loaded.
|
|
108
108
|
|
|
109
|
-
See [
|
|
109
|
+
See [Above-the-fold optimisations](https://jampack.divriots.com/features/optimize-above-the-fold/) for details.
|
|
110
|
+
|
|
111
|
+
## Inline critical CSS
|
|
112
|
+
|
|
113
|
+
Avoid [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) while stylesheets are downloaded and parsed.
|
|
114
|
+
`jampack` will inline critical CSS and lazy-load the rest of CSS.
|
|
115
|
+
|
|
116
|
+
See [Inline critical CSS](https://jampack.divriots.com/features/inline-critical-css/) for details.
|
|
117
|
+
|
|
118
|
+
## Prefetch links
|
|
119
|
+
|
|
120
|
+
Speedup the future page navigations by prefetching the links on the page.
|
|
121
|
+
Thanks to [quicklink](https://github.com/GoogleChromeLabs/quicklink), this can be done dynamically as links enter the viewport.
|
|
122
|
+
|
|
123
|
+
See [Prefetch links](https://jampack.divriots.com/features/prefetch-links/) for details.
|
|
110
124
|
|
|
111
125
|
### All assets are compressed
|
|
112
126
|
|
package/dist/cache.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as fs from 'fs/promises';
|
|
|
4
4
|
import $state from './state.js';
|
|
5
5
|
import { CACHE_VERSIONS } from './packagejson.js';
|
|
6
6
|
const listOfCategories = ['img', 'img-ext'];
|
|
7
|
-
const CACHE_FOLDER = '.jampack/cache';
|
|
7
|
+
const CACHE_FOLDER = $state.args.cache_folder || '.jampack/cache';
|
|
8
8
|
async function cleanCache(full) {
|
|
9
9
|
if (full) {
|
|
10
10
|
try {
|
package/dist/config-default.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -38,11 +38,14 @@ program
|
|
|
38
38
|
.option('--fail', 'Exits with a non-zero return code if issues.')
|
|
39
39
|
.option('--onlyoptim', 'Only optimize (PASS 1).')
|
|
40
40
|
.option('--onlycomp', 'Only compress (PASS 2).')
|
|
41
|
+
.option('--cache_folder <cache_folder>', 'Default: .jampack/cache')
|
|
41
42
|
.option('--cleancache', 'Clean cache before running')
|
|
42
43
|
.option('--nocache', 'Run with no use of cache')
|
|
43
44
|
.action(async (dir, options) => {
|
|
45
|
+
// Arguments
|
|
44
46
|
$state.dir = dir;
|
|
45
47
|
$state.args = options;
|
|
48
|
+
// Print options
|
|
46
49
|
if (options) {
|
|
47
50
|
console.log('Options:');
|
|
48
51
|
console.log(options);
|
package/dist/optimize.js
CHANGED
|
@@ -121,8 +121,7 @@ async function analyse(file) {
|
|
|
121
121
|
//
|
|
122
122
|
if (config.css.inline_critical_css) {
|
|
123
123
|
try {
|
|
124
|
-
|
|
125
|
-
htmlResult = await inlineCriticalCss(location, htmlResult);
|
|
124
|
+
htmlResult = await inlineCriticalCss($state.dir, htmlResult);
|
|
126
125
|
}
|
|
127
126
|
catch (e) {
|
|
128
127
|
console.warn('Fail to inline critical CSS', e);
|
|
@@ -156,7 +155,7 @@ function getTheFold($) {
|
|
|
156
155
|
return 0;
|
|
157
156
|
}
|
|
158
157
|
async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
159
|
-
var _a, _b;
|
|
158
|
+
var _a, _b, _c;
|
|
160
159
|
const img = $(imgElement);
|
|
161
160
|
/*
|
|
162
161
|
* Attribute 'src'
|
|
@@ -265,19 +264,17 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
265
264
|
let srcToFormat = 'unchanged';
|
|
266
265
|
if (isAboveTheFold) {
|
|
267
266
|
// Above the fold, nothing beats progressive JPEG for LCP
|
|
268
|
-
//
|
|
269
|
-
if ((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
// But jpeg doesn't support transparency
|
|
268
|
+
if ((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.isOpaque) {
|
|
269
|
+
srcToFormat = 'jpeg+progressive';
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
272
|
srcToFormat =
|
|
273
273
|
(await originalImage.getMime()) === 'image/avif' ||
|
|
274
274
|
(await originalImage.getMime()) === 'image/webp'
|
|
275
275
|
? 'unchanged'
|
|
276
276
|
: 'webp';
|
|
277
277
|
}
|
|
278
|
-
else {
|
|
279
|
-
srcToFormat = 'jpeg+progressive';
|
|
280
|
-
}
|
|
281
278
|
}
|
|
282
279
|
else if (isInPicture) {
|
|
283
280
|
// srcToFormat should not change if in picture
|
|
@@ -429,18 +426,29 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
429
426
|
// Only try to generate better images
|
|
430
427
|
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
431
428
|
//
|
|
432
|
-
|
|
433
|
-
|
|
429
|
+
const mime = await originalImage.getMime();
|
|
430
|
+
switch (mime) {
|
|
434
431
|
case 'image/png':
|
|
432
|
+
case 'image/jpeg':
|
|
435
433
|
sourcesToGenerate.push({
|
|
436
434
|
mime: 'image/webp',
|
|
437
435
|
format: 'webp',
|
|
438
436
|
});
|
|
439
437
|
case 'image/webp':
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
438
|
+
// Only add AVIF source for lossy images
|
|
439
|
+
// AVIF don't perform well on lossless images
|
|
440
|
+
// PNG and WebP will perform better
|
|
441
|
+
// https://www.reddit.com/r/jpegxl/comments/l9ta2u/how_does_lossless_jpegxl_compared_to_png/
|
|
442
|
+
// https://twitter.com/jonsneyers/status/1346389917816008704?s=19
|
|
443
|
+
const isLossless = mime === 'image/png' ||
|
|
444
|
+
(mime === 'image/webp' &&
|
|
445
|
+
((_c = (await originalImage.getImageMeta())) === null || _c === void 0 ? void 0 : _c.isLossless));
|
|
446
|
+
if (!isLossless) {
|
|
447
|
+
sourcesToGenerate.push({
|
|
448
|
+
mime: 'image/avif',
|
|
449
|
+
format: 'avif',
|
|
450
|
+
});
|
|
451
|
+
}
|
|
444
452
|
case 'image/avif':
|
|
445
453
|
// Nothing better
|
|
446
454
|
break;
|
package/dist/state.d.ts
CHANGED
package/dist/utils/resource.d.ts
CHANGED
package/dist/utils/resource.js
CHANGED
|
@@ -21,21 +21,37 @@ export class Resource {
|
|
|
21
21
|
async getImageMeta() {
|
|
22
22
|
if (this.image_meta === undefined) {
|
|
23
23
|
const ext = await this.getExt();
|
|
24
|
+
let isLossless = false;
|
|
24
25
|
switch (ext) {
|
|
25
26
|
case 'svg':
|
|
26
27
|
case 'gif':
|
|
27
|
-
case 'jpg':
|
|
28
28
|
case 'png':
|
|
29
|
-
case 'webp':
|
|
30
29
|
case 'tif':
|
|
30
|
+
isLossless = true;
|
|
31
|
+
case 'webp':
|
|
32
|
+
if (!isLossless) {
|
|
33
|
+
try {
|
|
34
|
+
// TODO check if webp is lossless
|
|
35
|
+
// const info = await WebPInfo.from(await this.getData());
|
|
36
|
+
// isLossless = info.summary.isLossless;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.warn('Failed to get WebP info');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
case 'avif':
|
|
43
|
+
// TODO
|
|
44
|
+
// Check for lossless avif
|
|
45
|
+
case 'jpg':
|
|
32
46
|
let sharpFile = await sharp(await this.getData(), { animated: true });
|
|
33
47
|
const meta = await sharpFile.metadata();
|
|
48
|
+
const stats = await sharpFile.stats();
|
|
34
49
|
this.image_meta = {
|
|
35
50
|
width: meta.width,
|
|
36
51
|
height: meta.height,
|
|
37
52
|
isProgressive: meta.isProgressive || false,
|
|
38
|
-
|
|
53
|
+
isOpaque: stats.isOpaque,
|
|
54
|
+
isLossless,
|
|
39
55
|
};
|
|
40
56
|
break;
|
|
41
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -22,29 +22,29 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@divriots/cheerio": "1.0.0-rc.12",
|
|
24
24
|
"@proload/core": "^0.3.3",
|
|
25
|
-
"@swc/core": "^1.3.
|
|
25
|
+
"@swc/core": "^1.3.86",
|
|
26
26
|
"add": "^2.0.6",
|
|
27
27
|
"commander": "^10.0.1",
|
|
28
28
|
"critters": "^0.0.20",
|
|
29
29
|
"deepmerge": "^4.3.1",
|
|
30
|
-
"esbuild": "^0.19.
|
|
30
|
+
"esbuild": "^0.19.3",
|
|
31
31
|
"file-type": "^18.5.0",
|
|
32
32
|
"globby": "^13.2.2",
|
|
33
33
|
"hasha": "^5.2.2",
|
|
34
34
|
"html-minifier-terser": "^7.2.0",
|
|
35
35
|
"kleur": "^4.1.5",
|
|
36
|
-
"lightningcss": "^1.
|
|
36
|
+
"lightningcss": "^1.22.0",
|
|
37
37
|
"mini-svg-data-uri": "^1.4.4",
|
|
38
38
|
"ora": "^7.0.1",
|
|
39
39
|
"quicklink": "^2.3.0",
|
|
40
|
-
"sharp": "^0.32.
|
|
40
|
+
"sharp": "^0.32.6",
|
|
41
41
|
"svgo": "^3.0.2",
|
|
42
42
|
"table": "^6.8.1",
|
|
43
|
-
"undici": "^5.
|
|
43
|
+
"undici": "^5.24.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/html-minifier-terser": "^7.0.0",
|
|
47
|
-
"@types/node": "^20.
|
|
47
|
+
"@types/node": "^20.6.2",
|
|
48
48
|
"shx": "^0.3.3",
|
|
49
49
|
"ts-node": "^10.2.1",
|
|
50
50
|
"tslib": "^2.6.2",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"watch": "pnpm build --watch",
|
|
55
|
-
"try": "shx rm -fr demo && cp -R
|
|
55
|
+
"try": "shx rm -fr demo && cp -R demo_drc demo && node ./dist/index.js ./demo --nocache",
|
|
56
56
|
"build": "shx rm -rf dist && tsc -b",
|
|
57
57
|
"lint": "eslint . --ext .ts --config .eslintrc",
|
|
58
58
|
"prepublishOnly": "pnpm build",
|