@divriots/jampack 0.21.1 → 0.22.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/dist/config-default.js +5 -0
- package/dist/config-types.d.ts +5 -0
- package/dist/optimize.js +106 -29
- package/package.json +8 -1
package/dist/config-default.js
CHANGED
package/dist/config-types.d.ts
CHANGED
package/dist/optimize.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { globby } from 'globby';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs/promises';
|
|
4
|
+
import { getCanonicalCdnForUrl, getTransformer } from 'unpic';
|
|
4
5
|
import * as cheerio from '@divriots/cheerio';
|
|
5
6
|
import { isNumeric } from './utils.js';
|
|
6
7
|
import config from './config.js';
|
|
@@ -13,6 +14,17 @@ import { isLocal, Resource, translateSrc } from './utils/resource.js';
|
|
|
13
14
|
import { downloadExternalImage } from './optimizers/img-external.js';
|
|
14
15
|
import { inlineCriticalCss } from './optimizers/inline-critical-css.js';
|
|
15
16
|
import { prefetch_links_in_viewport } from './optimizers/prefetch-links.js';
|
|
17
|
+
const UNPIC_DEFAULT_HOST_REGEX = /^https:\/\/n\//g;
|
|
18
|
+
const ABOVE_FOLD_DATA_ATTR = 'data-abovethefold';
|
|
19
|
+
function getIntAttr(img, attr) {
|
|
20
|
+
const stringValue = img.attr(attr);
|
|
21
|
+
if (!stringValue)
|
|
22
|
+
return;
|
|
23
|
+
const parsed = parseInt(stringValue);
|
|
24
|
+
if (isNaN(parsed))
|
|
25
|
+
return;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
16
28
|
async function analyse(file) {
|
|
17
29
|
var _a;
|
|
18
30
|
console.log('▶ ' + file);
|
|
@@ -29,9 +41,11 @@ async function analyse(file) {
|
|
|
29
41
|
for (let i = 0; i < imgsArray.length; i++) {
|
|
30
42
|
const imgElement = imgsArray[i];
|
|
31
43
|
spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr('src')}`);
|
|
32
|
-
const
|
|
44
|
+
const img = $(imgElement);
|
|
45
|
+
const isAboveTheFold = isImgAboveTheFold(img, imgElement, theFold);
|
|
46
|
+
img.removeAttr(ABOVE_FOLD_DATA_ATTR);
|
|
33
47
|
try {
|
|
34
|
-
await processImage(file,
|
|
48
|
+
await processImage(file, img, isAboveTheFold);
|
|
35
49
|
}
|
|
36
50
|
catch (e) {
|
|
37
51
|
$state.reportIssue(file, {
|
|
@@ -131,6 +145,15 @@ async function analyse(file) {
|
|
|
131
145
|
await fs.writeFile(path.join($state.dir, file), htmlResult);
|
|
132
146
|
}
|
|
133
147
|
}
|
|
148
|
+
function isImgAboveTheFold(img, imgElement, theFold) {
|
|
149
|
+
const aboveTheFoldAttr = img.attr(ABOVE_FOLD_DATA_ATTR);
|
|
150
|
+
if (aboveTheFoldAttr) {
|
|
151
|
+
const parsed = parseInt(aboveTheFoldAttr);
|
|
152
|
+
if (!Number.isNaN(parsed))
|
|
153
|
+
return !!parsed;
|
|
154
|
+
}
|
|
155
|
+
return imgElement.startIndex < theFold;
|
|
156
|
+
}
|
|
134
157
|
function getTheFold($) {
|
|
135
158
|
const theFolds = $('the-fold');
|
|
136
159
|
// If you have the-fold
|
|
@@ -154,9 +177,8 @@ function getTheFold($) {
|
|
|
154
177
|
}
|
|
155
178
|
return 0;
|
|
156
179
|
}
|
|
157
|
-
async function processImage(htmlfile,
|
|
180
|
+
async function processImage(htmlfile, img, isAboveTheFold) {
|
|
158
181
|
var _a, _b, _c;
|
|
159
|
-
const img = $(imgElement);
|
|
160
182
|
/*
|
|
161
183
|
* Attribute 'src'
|
|
162
184
|
*/
|
|
@@ -226,10 +248,50 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
226
248
|
* Check for external images
|
|
227
249
|
*/
|
|
228
250
|
if (!isLocal(attrib_src)) {
|
|
251
|
+
switch (config.image.cdn.process) {
|
|
252
|
+
case 'off':
|
|
253
|
+
break;
|
|
254
|
+
case 'optimize':
|
|
255
|
+
const canonical = getCanonicalCdnForUrl(attrib_src);
|
|
256
|
+
if (!canonical)
|
|
257
|
+
break;
|
|
258
|
+
const cdnTransformer = getTransformer(canonical.cdn);
|
|
259
|
+
if (!cdnTransformer)
|
|
260
|
+
break;
|
|
261
|
+
if (!isIncluded(attrib_src, config.image.cdn.src_include, config.image.cdn.src_exclude))
|
|
262
|
+
break;
|
|
263
|
+
let attrib_width = getIntAttr(img, 'width');
|
|
264
|
+
if (!attrib_width) {
|
|
265
|
+
$state.reportIssue(htmlfile, {
|
|
266
|
+
type: 'warn',
|
|
267
|
+
msg: `Missing or malformed'width' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
let attrib_height = getIntAttr(img, 'height');
|
|
272
|
+
if (!attrib_height) {
|
|
273
|
+
$state.reportIssue(htmlfile, {
|
|
274
|
+
type: 'warn',
|
|
275
|
+
msg: `Missing or malformed 'height' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const new_srcset = await generateSrcSetForCdn(attrib_src, cdnTransformer, attrib_width, attrib_height);
|
|
280
|
+
if (new_srcset !== null) {
|
|
281
|
+
img.attr('srcset', new_srcset);
|
|
282
|
+
}
|
|
283
|
+
// Add sizes attribute if not specified
|
|
284
|
+
if (img.attr('srcset') && !img.attr('sizes')) {
|
|
285
|
+
img.attr('sizes', '100vw');
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
229
289
|
switch (config.image.external.process) {
|
|
230
290
|
case 'off': // Don't process external images
|
|
231
291
|
return;
|
|
232
292
|
case 'download': // Download external image for local processing
|
|
293
|
+
if (!isIncluded(attrib_src, config.image.external.src_include, config.image.external.src_exclude))
|
|
294
|
+
break;
|
|
233
295
|
try {
|
|
234
296
|
attrib_src = await downloadExternalImage(htmlfile, attrib_src);
|
|
235
297
|
img.attr('src', attrib_src);
|
|
@@ -482,6 +544,33 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
482
544
|
}
|
|
483
545
|
}
|
|
484
546
|
}
|
|
547
|
+
const isIncluded = (src, includeConf, excludeConf) => !!src.match(includeConf) && (!excludeConf || !src.match(excludeConf));
|
|
548
|
+
async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc) {
|
|
549
|
+
// Start from original image
|
|
550
|
+
let new_srcset = '';
|
|
551
|
+
if (!imageWidth || !imageHeight) {
|
|
552
|
+
// Forget about srcset
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
// Start reduction
|
|
556
|
+
const step = 300; //px
|
|
557
|
+
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
558
|
+
valueW = Math.min(valueW, config.image.srcset_max_width);
|
|
559
|
+
while (valueW >= config.image.srcset_min_width) {
|
|
560
|
+
let src = await transformSrc(valueW);
|
|
561
|
+
if (src) {
|
|
562
|
+
new_srcset += `, ${src} ${valueW}w`;
|
|
563
|
+
}
|
|
564
|
+
// reduce size
|
|
565
|
+
valueW -= step;
|
|
566
|
+
}
|
|
567
|
+
if (new_srcset) {
|
|
568
|
+
return startSrc
|
|
569
|
+
? `${startSrc} ${imageWidth}w` + new_srcset
|
|
570
|
+
: new_srcset.slice(2);
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
485
574
|
async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
486
575
|
const ext = path.extname(originalImage.src);
|
|
487
576
|
const fullbasename = originalImage.src.slice(0, -ext.length);
|
|
@@ -491,24 +580,13 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
491
580
|
? ext
|
|
492
581
|
: `.${(_a = options.toFormat) === null || _a === void 0 ? void 0 : _a.split('+')[0]}`}`;
|
|
493
582
|
};
|
|
494
|
-
// Start from original image
|
|
495
|
-
let new_srcset = '';
|
|
496
583
|
const meta = await originalImage.getImageMeta();
|
|
497
584
|
const imageWidth = (meta === null || meta === void 0 ? void 0 : meta.width) || 0;
|
|
498
585
|
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
499
|
-
if (!imageWidth || !imageHeight) {
|
|
500
|
-
// Forget about srcset
|
|
501
|
-
return null;
|
|
502
|
-
}
|
|
503
|
-
// Start reduction
|
|
504
|
-
const step = 300; //px
|
|
505
|
-
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
506
|
-
valueW = Math.min(valueW, config.image.srcset_max_width);
|
|
507
586
|
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
508
|
-
|
|
587
|
+
return _generateSrcSet(startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
509
588
|
const src = imageSrc(`@${valueW}w`);
|
|
510
589
|
const absoluteFilename = translateSrc($state.dir, path.dirname(htmlfile), src);
|
|
511
|
-
let doAddToSrcSet = true;
|
|
512
590
|
// Don't generate srcset file twice
|
|
513
591
|
if (!$state.compressedFiles.has(absoluteFilename)) {
|
|
514
592
|
const compressedImage = await compressImage(await originalImage.getData(), { ...options, resize: { width: valueW } });
|
|
@@ -516,7 +594,7 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
516
594
|
compressedImage.data.length >= previousImageSize) {
|
|
517
595
|
// New image is not compressed or bigger than previous image in srcset
|
|
518
596
|
// Don't add to srcset
|
|
519
|
-
|
|
597
|
+
return;
|
|
520
598
|
}
|
|
521
599
|
else {
|
|
522
600
|
// Write file
|
|
@@ -529,18 +607,17 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
529
607
|
$state.compressedFiles.add(absoluteFilename);
|
|
530
608
|
}
|
|
531
609
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
return null;
|
|
610
|
+
return src;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
async function generateSrcSetForCdn(startSrc, cdnTransformer, imageWidth, imageHeight) {
|
|
614
|
+
return _generateSrcSet('', imageWidth, imageHeight, (valueW) => {
|
|
615
|
+
var _a, _b;
|
|
616
|
+
return (_b = (_a = cdnTransformer({
|
|
617
|
+
url: startSrc,
|
|
618
|
+
width: valueW,
|
|
619
|
+
})) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(UNPIC_DEFAULT_HOST_REGEX, '/');
|
|
620
|
+
});
|
|
544
621
|
}
|
|
545
622
|
async function setImageSize(htmlfile, img, image) {
|
|
546
623
|
let width = img.attr('width');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
"/bin",
|
|
20
20
|
"/dist"
|
|
21
21
|
],
|
|
22
|
+
"exports": {
|
|
23
|
+
"./optimize": "./dist/optimize.js",
|
|
24
|
+
"./compress": "./dist/compress.js",
|
|
25
|
+
"./config": "./dist/config.js",
|
|
26
|
+
"./state": "./dist/state.js"
|
|
27
|
+
},
|
|
22
28
|
"dependencies": {
|
|
23
29
|
"@divriots/cheerio": "1.0.0-rc.12",
|
|
24
30
|
"@proload/core": "^0.3.3",
|
|
@@ -40,6 +46,7 @@
|
|
|
40
46
|
"sharp": "^0.32.6",
|
|
41
47
|
"svgo": "^3.0.2",
|
|
42
48
|
"table": "^6.8.1",
|
|
49
|
+
"unpic": "^3.11.0",
|
|
43
50
|
"undici": "^5.24.0"
|
|
44
51
|
},
|
|
45
52
|
"devDependencies": {
|