@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 +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 +73 -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.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
|
|
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,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 (
|
|
240
|
-
let
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
//
|
|
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) =>
|
|
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 });
|
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.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
|
|
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
|
},
|