@divriots/jampack 0.8.1 → 0.9.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/dist/cache.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type { Image } from './compressors/images.js';
3
- declare function cleanCache(): Promise<void>;
3
+ declare function cleanCache(full: boolean): Promise<void>;
4
4
  declare function computeCacheHash(buffer: Buffer, options?: any): string;
5
5
  declare function getFromCacheCompressImage(hash: string): Promise<Image | undefined>;
6
6
  declare function addToCacheCompressImage(hash: string, image: Image): Promise<void>;
package/dist/cache.js CHANGED
@@ -4,13 +4,29 @@ import * as fs from 'fs/promises';
4
4
  import { VERSION } from './version.js';
5
5
  import $state from './state.js';
6
6
  const CACHE_FOLDER = '.jampack/cache';
7
- async function cleanCache() {
7
+ async function cleanCache(full) {
8
+ if (full) {
9
+ try {
10
+ await fs.rm(CACHE_FOLDER, { recursive: true });
11
+ }
12
+ catch (e) {
13
+ // Nothing to do, probably not present
14
+ }
15
+ return;
16
+ }
17
+ // List versions in cache
18
+ let files;
8
19
  try {
9
- await fs.rm(CACHE_FOLDER, { recursive: true });
20
+ files = await fs.readdir(CACHE_FOLDER);
10
21
  }
11
22
  catch (e) {
12
- // Nothing to do, probably not present
23
+ // Nothing to do probably no cache available
24
+ return;
13
25
  }
26
+ // Delete old cache versions
27
+ await files
28
+ .filter((f) => f !== VERSION)
29
+ .forEach(async (f) => fs.rmdir(path.join(CACHE_FOLDER, f), {}));
14
30
  }
15
31
  function computeCacheHash(buffer, options) {
16
32
  if ($state.args.nocache) {
@@ -2,7 +2,8 @@
2
2
  import sharp from 'sharp';
3
3
  import { MimeType } from 'file-type';
4
4
  export type ImageMimeType = MimeType | 'image/svg+xml';
5
- export type ImageFormat = 'webp' | 'svg' | 'jpg' | 'png' | 'avif' | undefined;
5
+ export declare const AllImageFormat: string[];
6
+ export type ImageFormat = typeof AllImageFormat[number] | undefined;
6
7
  export type Image = {
7
8
  format: ImageFormat;
8
9
  data: Buffer;
@@ -2,6 +2,7 @@ import sharp from 'sharp';
2
2
  import { optimize as svgo } from 'svgo';
3
3
  import { getFromCacheCompressImage, addToCacheCompressImage, computeCacheHash, } from '../cache.js';
4
4
  import config from '../config.js';
5
+ export const AllImageFormat = ['webp', 'svg', 'jpg', 'png', 'avif'];
5
6
  function createWebpOptions(opt) {
6
7
  return {
7
8
  nearLossless: opt.mode === 'lossless',
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const default_options = {
2
2
  image: {
3
- embed_size: 400,
3
+ embed_size: 1500,
4
4
  srcset_min_width: 640,
5
5
  compress: true,
6
6
  jpeg: {
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ program
29
29
  .command('pack', { isDefault: true })
30
30
  .description('todo')
31
31
  .argument('<dir>', 'Directory to pack')
32
+ .option('--include <include>', 'Glob to include')
32
33
  .option('--exclude <exclude>', 'Glob to exclude')
33
34
  .option('--nowrite', 'No write')
34
35
  .option('--fast', 'Go fast. Mostly no compression just checks for issues.')
@@ -45,13 +46,11 @@ program
45
46
  fast();
46
47
  }
47
48
  // Clean cache
48
- if (options.cleancache) {
49
- cleanCache();
50
- }
49
+ cleanCache(options.cleancache);
51
50
  if (!options.onlycomp) {
52
51
  printTitle('PASS 1 - Optimizing');
53
52
  console.time('Done');
54
- await optimize(options.exclude);
53
+ await optimize(options.include, options.exclude);
55
54
  console.timeEnd('Done');
56
55
  }
57
56
  if (!options.onlyoptim && !options.fast) {
@@ -1 +1 @@
1
- export declare function optimize(exclude?: string): Promise<void>;
1
+ export declare function optimize(include?: string, exclude?: string): Promise<void>;
package/dist/optimize.js CHANGED
@@ -71,12 +71,26 @@ async function analyse(file) {
71
71
  }
72
72
  function getTheFold($) {
73
73
  const theFolds = $('the-fold');
74
- if (!theFolds[0]) {
75
- return 0;
76
- }
77
- // Pickup the position of the last `<the-fold>`
78
- // @ts-ignore
79
- return theFolds[theFolds.length - 1].startIndex;
74
+ // If you have the-fold
75
+ if (theFolds[0]) {
76
+ // Pickup the position of the last `<the-fold>`
77
+ // @ts-ignore
78
+ return theFolds[theFolds.length - 1].startIndex;
79
+ }
80
+ const theMains = $('main');
81
+ // If you have main
82
+ if (theMains[0]) {
83
+ // @ts-ignore
84
+ return theMains[0].startIndex + 5000;
85
+ }
86
+ const theBodys = $('body');
87
+ // If you have main
88
+ if (theBodys[0]) {
89
+ // Pickup the position of the last `<the-fold>`
90
+ // @ts-ignore
91
+ return theBodys[0].startIndex + 10000;
92
+ }
93
+ return 0;
80
94
  }
81
95
  async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
82
96
  var _a, _b;
@@ -220,6 +234,8 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
220
234
  img.attr('src', attrib_src + additionalExtension);
221
235
  }
222
236
  else {
237
+ // Drop new Image
238
+ newImage = undefined;
223
239
  // Report non-compression
224
240
  $state.reportSummary({
225
241
  action: path.extname(originalImage.filePathAbsolute),
@@ -230,46 +246,61 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
230
246
  }
231
247
  /*
232
248
  * Embed small images
233
- *
234
- * TODO this is only embedding images that have
235
- * successfully be compressed. Should embed original
236
- * image if it fits the size.
249
+ * Only above the fold
237
250
  */
238
251
  let isEmbed = false;
239
- if (newImage && newImage.data.length <= config.image.embed_size) {
240
- let datauri = undefined;
241
- switch (newImage.format) {
242
- case 'svg':
243
- datauri = svgToMiniDataURI(newImage.data.toString());
244
- break;
245
- case 'webp':
246
- datauri = `data:image/webp;base64,${newImage.data.toString('base64')}`;
247
- break;
248
- case 'jpg':
249
- case 'png':
250
- // TODO but not possible in current code
251
- break;
252
+ let imageToEmbed;
253
+ if (isAboveTheFold) {
254
+ imageToEmbed = newImage;
255
+ // If new image was not generated then take the old image
256
+ if (!imageToEmbed) {
257
+ imageToEmbed = {
258
+ data: await originalImage.getData(),
259
+ format: await originalImage.getImageFormat(),
260
+ };
252
261
  }
253
- if (datauri) {
254
- isEmbed = true;
255
- img.attr('src', datauri);
256
- img.removeAttr('loading');
257
- img.removeAttr('decoding');
258
- $state.reportSummary({
259
- action: `${newImage.format}->embed`,
260
- originalSize: await originalImage.getLen(),
261
- compressedSize: newImage.data.length,
262
- });
262
+ // Only embed small images
263
+ // matching the embed size
264
+ if (imageToEmbed.data.length <= config.image.embed_size) {
265
+ let datauri = undefined;
266
+ const ifmt = imageToEmbed.format;
267
+ switch (ifmt) {
268
+ case 'svg':
269
+ datauri = svgToMiniDataURI(imageToEmbed.data.toString());
270
+ break;
271
+ case 'webp':
272
+ case 'jpg':
273
+ case 'png':
274
+ datauri = `data:image/${ifmt === 'jpg' ? 'jpeg' : ifmt};base64,${imageToEmbed.data.toString('base64')}`;
275
+ break;
276
+ }
277
+ if (datauri) {
278
+ isEmbed = true;
279
+ img.attr('src', datauri);
280
+ img.removeAttr('loading');
281
+ img.removeAttr('decoding');
282
+ $state.reportSummary({
283
+ action: `${imageToEmbed.format}->embed`,
284
+ originalSize: await originalImage.getLen(),
285
+ compressedSize: imageToEmbed.data.length,
286
+ });
287
+ }
263
288
  }
264
289
  }
265
- if (isEmbed) {
266
- // Image is embed, no need for more processing
290
+ if (isEmbed && (imageToEmbed === null || imageToEmbed === void 0 ? void 0 : imageToEmbed.format) === 'svg') {
291
+ // SVG has a different behaviour with image size
292
+ // Needs to be tested more
293
+ // For the moment skip image size for SVG
267
294
  return;
268
295
  }
269
296
  /*
270
297
  * Attribute 'width' & 'height'
271
298
  */
272
299
  const [w, h] = await setImageSize(htmlfile, img, originalImage);
300
+ if (isEmbed) {
301
+ // Image is embed, no need for more processing
302
+ return;
303
+ }
273
304
  //
274
305
  // Stop here if svg
275
306
  //
@@ -364,7 +395,12 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
364
395
  async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
365
396
  const ext = path.extname(originalImage.src);
366
397
  const fullbasename = originalImage.src.slice(0, -ext.length);
367
- const imageSrc = (addition) => `${fullbasename}${addition}${options.toFormat === 'unchanged' ? ext : '.' + options.toFormat}`;
398
+ const imageSrc = (addition) => {
399
+ var _a;
400
+ return `${fullbasename}${addition}${options.toFormat === 'unchanged'
401
+ ? ext
402
+ : `.${(_a = options.toFormat) === null || _a === void 0 ? void 0 : _a.split('+')[0]}`}`;
403
+ };
368
404
  // Start from original image
369
405
  let new_srcset = '';
370
406
  const meta = await originalImage.getImageMeta();
@@ -537,8 +573,8 @@ async function setImageSize(htmlfile, img, image) {
537
573
  // Something when wrong
538
574
  throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
539
575
  }
540
- export async function optimize(exclude) {
541
- const glob = ['**/*.{htm,html}'];
576
+ export async function optimize(include, exclude) {
577
+ const glob = include ? [include] : ['**/*.{htm,html}'];
542
578
  if (exclude)
543
579
  glob.push('!' + exclude);
544
580
  const paths = await globby(glob, { cwd: $state.dir });
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import { FileExtension, MimeType } from 'file-type';
3
+ import { ImageFormat } from '../compressors/images.js';
3
4
  type ImageMeta = {
4
5
  width: number | undefined;
5
6
  height: number | undefined;
@@ -21,6 +22,7 @@ export declare class Resource {
21
22
  getImageMeta(): Promise<ImageMeta | null | undefined>;
22
23
  getLen(): Promise<number>;
23
24
  getExt(): Promise<FileExtension | 'svg'>;
25
+ getImageFormat(): Promise<ImageFormat | undefined>;
24
26
  getMime(): Promise<MimeType | 'image/svg+xml'>;
25
27
  private loadFileType;
26
28
  static loadResource(projectRoot: string, relativeFile: string, src: string): Promise<Resource | undefined>;
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import * as fs from 'fs/promises';
4
4
  import { fileTypeFromBuffer } from 'file-type';
5
5
  import sharp from 'sharp';
6
+ import { AllImageFormat } from '../compressors/images.js';
6
7
  /**
7
8
  * File extension, mime and data are loaded only on demand, then cached.
8
9
  */
@@ -53,6 +54,12 @@ export class Resource {
53
54
  }
54
55
  return this.ext;
55
56
  }
57
+ async getImageFormat() {
58
+ const ext = (await this.getExt());
59
+ if (AllImageFormat.includes(ext))
60
+ return ext;
61
+ return undefined;
62
+ }
56
63
  async getMime() {
57
64
  if (this.mime === undefined) {
58
65
  await this.loadFileType();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divriots/jampack",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Packer for static websites",
5
5
  "author": "Georges Gomes @georges-gomes",
6
6
  "bin": {
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "scripts": {
51
51
  "watch": "pnpm build --watch",
52
- "try": "shx rm -fr demo && cp -R demo_avif demo && node ./dist/index.js ./demo --nocache",
52
+ "try": "shx rm -fr demo && cp -R demo_wcd demo && node ./dist/index.js ./demo --nocache --onlyoptim",
53
53
  "build": "shx rm -rf dist && tsc -b",
54
54
  "lint": "eslint . --ext .ts --config .eslintrc"
55
55
  },