@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.
@@ -18,6 +18,11 @@ const default_options = {
18
18
  src_include: /^.*$/,
19
19
  src_exclude: null,
20
20
  },
21
+ cdn: {
22
+ process: 'off',
23
+ src_include: /^.*$/,
24
+ src_exclude: null,
25
+ },
21
26
  compress: true,
22
27
  jpeg: {
23
28
  options: {
@@ -23,6 +23,11 @@ export type Options = {
23
23
  src_include: RegExp;
24
24
  src_exclude: RegExp | null;
25
25
  };
26
+ cdn: {
27
+ process: 'off' | 'optimize';
28
+ src_include: RegExp;
29
+ src_exclude: RegExp | null;
30
+ };
26
31
  compress: boolean;
27
32
  jpeg: {
28
33
  options: {
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 isAboveTheFold = imgElement.startIndex < theFold;
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, $, imgElement, isAboveTheFold);
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, $, imgElement, isAboveTheFold) {
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
- while (valueW >= config.image.srcset_min_width) {
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
- doAddToSrcSet = false;
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
- if (doAddToSrcSet) {
533
- new_srcset += `, ${src} ${valueW}w`;
534
- }
535
- // reduce size
536
- valueW -= step;
537
- }
538
- if (new_srcset) {
539
- return startSrc
540
- ? `${startSrc} ${imageWidth}w` + new_srcset
541
- : new_srcset.slice(2);
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.21.1",
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": {