@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 +1 -1
- package/dist/cache.js +19 -3
- package/dist/compressors/images.d.ts +2 -1
- package/dist/compressors/images.js +1 -0
- package/dist/config.js +1 -1
- package/dist/index.js +3 -4
- package/dist/optimize.d.ts +1 -1
- package/dist/optimize.js +74 -38
- package/dist/utils/resource.d.ts +2 -0
- package/dist/utils/resource.js +7 -0
- package/package.json +2 -2
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.
|
|
20
|
+
files = await fs.readdir(CACHE_FOLDER);
|
|
10
21
|
}
|
|
11
22
|
catch (e) {
|
|
12
|
-
// Nothing to do
|
|
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
|
|
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
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
|
-
|
|
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) {
|
package/dist/optimize.d.ts
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
//
|
|
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) =>
|
|
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 });
|
package/dist/utils/resource.d.ts
CHANGED
|
@@ -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>;
|
package/dist/utils/resource.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
},
|