@divriots/jampack 0.8.1 → 0.9.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/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.rm(path.join(CACHE_FOLDER, f), { recursive: true }));
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,60 @@ 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
+ if (isAboveTheFold) {
253
+ let imageToEmbed = newImage;
254
+ // If new image was not generated then take the old image
255
+ if (!imageToEmbed) {
256
+ imageToEmbed = {
257
+ data: await originalImage.getData(),
258
+ format: await originalImage.getImageFormat(),
259
+ };
252
260
  }
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
- });
261
+ // Only embed small images
262
+ // matching the embed size
263
+ if (imageToEmbed.data.length <= config.image.embed_size) {
264
+ let datauri = undefined;
265
+ const ifmt = imageToEmbed.format;
266
+ switch (ifmt) {
267
+ case 'svg':
268
+ datauri = svgToMiniDataURI(imageToEmbed.data.toString());
269
+ break;
270
+ case 'webp':
271
+ case 'jpg':
272
+ case 'png':
273
+ datauri = `data:image/${ifmt === 'jpg' ? 'jpeg' : ifmt};base64,${imageToEmbed.data.toString('base64')}`;
274
+ break;
275
+ }
276
+ if (datauri) {
277
+ isEmbed = true;
278
+ img.attr('src', datauri);
279
+ img.removeAttr('loading');
280
+ img.removeAttr('decoding');
281
+ $state.reportSummary({
282
+ action: `${imageToEmbed.format}->embed`,
283
+ originalSize: await originalImage.getLen(),
284
+ compressedSize: imageToEmbed.data.length,
285
+ });
286
+ }
263
287
  }
264
288
  }
265
- if (isEmbed) {
266
- // Image is embed, no need for more processing
289
+ if (isEmbed && (await originalImage.getExt()) === 'svg') {
290
+ // SVG has a different behaviour with image size
291
+ // Needs to be tested more
292
+ // For the moment skip image size for SVG
267
293
  return;
268
294
  }
269
295
  /*
270
296
  * Attribute 'width' & 'height'
271
297
  */
272
298
  const [w, h] = await setImageSize(htmlfile, img, originalImage);
299
+ if (isEmbed) {
300
+ // Image is embed, no need for more processing
301
+ return;
302
+ }
273
303
  //
274
304
  // Stop here if svg
275
305
  //
@@ -364,7 +394,12 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
364
394
  async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
365
395
  const ext = path.extname(originalImage.src);
366
396
  const fullbasename = originalImage.src.slice(0, -ext.length);
367
- const imageSrc = (addition) => `${fullbasename}${addition}${options.toFormat === 'unchanged' ? ext : '.' + options.toFormat}`;
397
+ const imageSrc = (addition) => {
398
+ var _a;
399
+ return `${fullbasename}${addition}${options.toFormat === 'unchanged'
400
+ ? ext
401
+ : `.${(_a = options.toFormat) === null || _a === void 0 ? void 0 : _a.split('+')[0]}`}`;
402
+ };
368
403
  // Start from original image
369
404
  let new_srcset = '';
370
405
  const meta = await originalImage.getImageMeta();
@@ -537,8 +572,8 @@ async function setImageSize(htmlfile, img, image) {
537
572
  // Something when wrong
538
573
  throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
539
574
  }
540
- export async function optimize(exclude) {
541
- const glob = ['**/*.{htm,html}'];
575
+ export async function optimize(include, exclude) {
576
+ const glob = include ? [include] : ['**/*.{htm,html}'];
542
577
  if (exclude)
543
578
  glob.push('!' + exclude);
544
579
  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.1",
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
  },