@divriots/jampack 0.8.0 → 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/README.md +2 -2
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +19 -3
- package/dist/compressors/images.d.ts +3 -3
- package/dist/compressors/images.js +7 -4
- package/dist/config.js +1 -1
- package/dist/index.js +3 -4
- package/dist/optimize.d.ts +1 -1
- package/dist/optimize.js +160 -95
- package/dist/utils/resource.d.ts +2 -0
- package/dist/utils/resource.js +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,8 +43,8 @@ Optimizes static websites for best user experience and best Core Web Vitals scor
|
|
|
43
43
|
- [Optimize assets above-the-fold](https://jampack.divriots.com/features/optimize-above-the-fold)
|
|
44
44
|
- [Embed small images](https://jampack.divriots.com/features/embed-small-images)
|
|
45
45
|
- [Compress all assets](https://jampack.divriots.com/features/compress-all)
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
46
|
+
- [Fix things automatically](https://jampack.divriots.com/features/autofixes)
|
|
47
|
+
- [Raise warnings](https://jampack.divriots.com/features/warnings)
|
|
48
48
|
|
|
49
49
|
## Quick use
|
|
50
50
|
|
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,14 +2,14 @@
|
|
|
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;
|
|
9
10
|
};
|
|
10
11
|
export type ImageOutputOptions = {
|
|
11
12
|
resize?: sharp.ResizeOptions;
|
|
12
|
-
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'unchanged';
|
|
13
|
-
progressive?: boolean;
|
|
13
|
+
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'jpeg+progressive' | 'unchanged';
|
|
14
14
|
};
|
|
15
15
|
export declare function compressImage(data: Buffer, options: ImageOutputOptions): Promise<Image | undefined>;
|
|
@@ -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',
|
|
@@ -79,13 +80,15 @@ export async function compressImage(data, options) {
|
|
|
79
80
|
// To format
|
|
80
81
|
switch (toFormat) {
|
|
81
82
|
case 'jpeg':
|
|
82
|
-
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options
|
|
83
|
-
|
|
83
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options } || {});
|
|
84
|
+
outputFormat = 'jpg';
|
|
85
|
+
break;
|
|
86
|
+
case 'jpeg+progressive':
|
|
87
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options, progressive: true } || {});
|
|
84
88
|
outputFormat = 'jpg';
|
|
85
89
|
break;
|
|
86
90
|
case 'png':
|
|
87
|
-
sharpFile = sharpFile.png({ ...config.image.png.options
|
|
88
|
-
{});
|
|
91
|
+
sharpFile = sharpFile.png({ ...config.image.png.options } || {});
|
|
89
92
|
outputFormat = 'png';
|
|
90
93
|
break;
|
|
91
94
|
case 'webp':
|
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,15 +71,29 @@ 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
|
-
var _a;
|
|
96
|
+
var _a, _b;
|
|
83
97
|
const img = $(imgElement);
|
|
84
98
|
/*
|
|
85
99
|
* Attribute 'src'
|
|
@@ -161,9 +175,24 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
161
175
|
let newImage;
|
|
162
176
|
const isInPicture = (_a = img.parent()) === null || _a === void 0 ? void 0 : _a.is('picture');
|
|
163
177
|
let srcToFormat = 'unchanged';
|
|
164
|
-
if (
|
|
178
|
+
if (isAboveTheFold) {
|
|
179
|
+
// Above the fold, nothing beats progressive JPEG for LCP
|
|
180
|
+
// but it's not transparent
|
|
181
|
+
if ((_b = (await originalImage.getImageMeta())) === null || _b === void 0 ? void 0 : _b.hasAlpha) {
|
|
182
|
+
// TODO some images have Alpha metadata but have 0 alpha pixels
|
|
183
|
+
// We should be able to detect these to take advantage of Progressive JPEG
|
|
184
|
+
srcToFormat =
|
|
185
|
+
(await originalImage.getMime()) === 'image/avif' ||
|
|
186
|
+
(await originalImage.getMime()) === 'image/webp'
|
|
187
|
+
? 'unchanged'
|
|
188
|
+
: 'webp';
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
srcToFormat = 'jpeg+progressive';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (isInPicture) {
|
|
165
195
|
// srcToFormat should not change if in picture
|
|
166
|
-
// If above the fold, we will create progressive images
|
|
167
196
|
}
|
|
168
197
|
else {
|
|
169
198
|
// Let's go to avif -> avif , webp -> webp, else * -> webp
|
|
@@ -178,10 +207,10 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
178
207
|
$state.optimizedFiles.add(originalImage.filePathAbsolute);
|
|
179
208
|
newImage = await compressImage(await originalImage.getData(), {
|
|
180
209
|
toFormat: srcToFormat,
|
|
181
|
-
progressive: isAboveTheFold,
|
|
182
210
|
});
|
|
183
211
|
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
|
|
184
|
-
(newImage.data.length < (await originalImage.getLen()) ||
|
|
212
|
+
(newImage.data.length < (await originalImage.getLen()) ||
|
|
213
|
+
srcToFormat === 'jpeg+progressive') // Progressive override all even if worse
|
|
185
214
|
) {
|
|
186
215
|
// Do we need to add an new extension?
|
|
187
216
|
const newExtension = `.${newImage.format}`;
|
|
@@ -205,6 +234,8 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
205
234
|
img.attr('src', attrib_src + additionalExtension);
|
|
206
235
|
}
|
|
207
236
|
else {
|
|
237
|
+
// Drop new Image
|
|
238
|
+
newImage = undefined;
|
|
208
239
|
// Report non-compression
|
|
209
240
|
$state.reportSummary({
|
|
210
241
|
action: path.extname(originalImage.filePathAbsolute),
|
|
@@ -215,46 +246,61 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
215
246
|
}
|
|
216
247
|
/*
|
|
217
248
|
* Embed small images
|
|
218
|
-
*
|
|
219
|
-
* TODO this is only embedding images that have
|
|
220
|
-
* successfully be compressed. Should embed original
|
|
221
|
-
* image if it fits the size.
|
|
249
|
+
* Only above the fold
|
|
222
250
|
*/
|
|
223
251
|
let isEmbed = false;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
case 'jpg':
|
|
234
|
-
case 'png':
|
|
235
|
-
// TODO but not possible in current code
|
|
236
|
-
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
|
+
};
|
|
237
261
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
+
}
|
|
248
288
|
}
|
|
249
289
|
}
|
|
250
|
-
if (isEmbed) {
|
|
251
|
-
//
|
|
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
|
|
252
294
|
return;
|
|
253
295
|
}
|
|
254
296
|
/*
|
|
255
297
|
* Attribute 'width' & 'height'
|
|
256
298
|
*/
|
|
257
299
|
const [w, h] = await setImageSize(htmlfile, img, originalImage);
|
|
300
|
+
if (isEmbed) {
|
|
301
|
+
// Image is embed, no need for more processing
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
258
304
|
//
|
|
259
305
|
// Stop here if svg
|
|
260
306
|
//
|
|
@@ -275,7 +321,7 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
275
321
|
// of the srcset
|
|
276
322
|
}
|
|
277
323
|
else {
|
|
278
|
-
const new_srcset = await generateSrcSet(htmlfile, originalImage,
|
|
324
|
+
const new_srcset = await generateSrcSet(htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
279
325
|
if (new_srcset !== null) {
|
|
280
326
|
img.attr('srcset', new_srcset);
|
|
281
327
|
}
|
|
@@ -289,68 +335,87 @@ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
289
335
|
*/
|
|
290
336
|
if (isInPicture) {
|
|
291
337
|
const picture = img.parent();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
297
|
-
//
|
|
298
|
-
switch (await originalImage.getMime()) {
|
|
299
|
-
case 'image/jpeg':
|
|
300
|
-
case 'image/png':
|
|
301
|
-
sourcesToGenerate.push({
|
|
302
|
-
mime: 'image/webp',
|
|
303
|
-
format: 'webp',
|
|
304
|
-
});
|
|
305
|
-
case 'image/webp':
|
|
306
|
-
sourcesToGenerate.push({
|
|
307
|
-
mime: 'image/avif',
|
|
308
|
-
format: 'avif',
|
|
309
|
-
});
|
|
310
|
-
case 'image/avif':
|
|
311
|
-
// Nothing better
|
|
312
|
-
break;
|
|
338
|
+
if (isAboveTheFold && srcToFormat === 'jpeg+progressive') {
|
|
339
|
+
// We don't need anything else than jpeg progressive above the fold
|
|
340
|
+
const nodes = picture.children(`source`);
|
|
341
|
+
nodes.remove();
|
|
313
342
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
343
|
+
else {
|
|
344
|
+
// List of possible sources to generate
|
|
345
|
+
//
|
|
346
|
+
const sourcesToGenerate = [];
|
|
347
|
+
// Only try to generate better images
|
|
348
|
+
// TODO generate more compatible formats (avif => webp/jpeg/png)
|
|
349
|
+
//
|
|
350
|
+
switch (await originalImage.getMime()) {
|
|
351
|
+
case 'image/jpeg':
|
|
352
|
+
case 'image/png':
|
|
353
|
+
sourcesToGenerate.push({
|
|
354
|
+
mime: 'image/webp',
|
|
355
|
+
format: 'webp',
|
|
356
|
+
});
|
|
357
|
+
case 'image/webp':
|
|
358
|
+
sourcesToGenerate.push({
|
|
359
|
+
mime: 'image/avif',
|
|
360
|
+
format: 'avif',
|
|
361
|
+
});
|
|
362
|
+
case 'image/avif':
|
|
363
|
+
// Nothing better
|
|
364
|
+
break;
|
|
319
365
|
}
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
366
|
+
const sizes = img.attr('sizes');
|
|
367
|
+
for (const s of sourcesToGenerate) {
|
|
368
|
+
const sourceWithThisMimeType = picture.children(`source[type="${s.mime}"]`);
|
|
369
|
+
if (sourceWithThisMimeType.length > 0) {
|
|
370
|
+
// Ignore the creation of sources that already exist
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
374
|
+
toFormat: s.format,
|
|
375
|
+
});
|
|
376
|
+
if (!srcset) {
|
|
377
|
+
// No sourceset generated
|
|
378
|
+
// Image is too small or can't be compressed better
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const source = `<source ${sizes ? `sizes="${sizes}"` : ''} srcset="${srcset}" type="${s.mime}">`;
|
|
382
|
+
picture.prepend(source);
|
|
328
383
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
384
|
+
// Reorder sources to priority order 1)avif 2)webp
|
|
385
|
+
//
|
|
386
|
+
function popupSource(type) {
|
|
387
|
+
const nodes = picture.children(`source[type="${type}"]`);
|
|
388
|
+
picture.prepend(nodes);
|
|
389
|
+
}
|
|
390
|
+
popupSource('image/webp');
|
|
391
|
+
popupSource('image/avif');
|
|
337
392
|
}
|
|
338
|
-
popupSource('image/webp');
|
|
339
|
-
popupSource('image/avif');
|
|
340
393
|
}
|
|
341
394
|
}
|
|
342
|
-
async function generateSrcSet(htmlfile, originalImage,
|
|
343
|
-
const ext = path.extname(
|
|
344
|
-
const fullbasename =
|
|
345
|
-
const imageSrc = (addition) =>
|
|
395
|
+
async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
396
|
+
const ext = path.extname(originalImage.src);
|
|
397
|
+
const fullbasename = originalImage.src.slice(0, -ext.length);
|
|
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
|
+
};
|
|
346
404
|
// Start from original image
|
|
347
405
|
let new_srcset = '';
|
|
406
|
+
const meta = await originalImage.getImageMeta();
|
|
407
|
+
const imageWidth = (meta === null || meta === void 0 ? void 0 : meta.width) || 0;
|
|
408
|
+
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
409
|
+
if (!imageWidth || !imageHeight) {
|
|
410
|
+
// Forget about srcset
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
348
413
|
// Start reduction
|
|
349
414
|
const step = 300; //px
|
|
350
415
|
const ratio = imageWidth / imageHeight;
|
|
351
|
-
let valueW = !
|
|
416
|
+
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
352
417
|
let valueH = Math.trunc(valueW / ratio);
|
|
353
|
-
let previousImageSize =
|
|
418
|
+
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
354
419
|
while (valueW >= config.image.srcset_min_width) {
|
|
355
420
|
const src = imageSrc(`@${valueW}w`);
|
|
356
421
|
const absoluteFilename = translateSrc($state.dir, path.dirname(htmlfile), src);
|
|
@@ -383,8 +448,8 @@ async function generateSrcSet(htmlfile, originalImage, attrib_src, imageWidth, i
|
|
|
383
448
|
valueH = Math.trunc(valueW / ratio);
|
|
384
449
|
}
|
|
385
450
|
if (new_srcset) {
|
|
386
|
-
return
|
|
387
|
-
? `${
|
|
451
|
+
return startSrc
|
|
452
|
+
? `${startSrc} ${imageWidth}w` + new_srcset
|
|
388
453
|
: new_srcset.slice(2);
|
|
389
454
|
}
|
|
390
455
|
return null;
|
|
@@ -508,8 +573,8 @@ async function setImageSize(htmlfile, img, image) {
|
|
|
508
573
|
// Something when wrong
|
|
509
574
|
throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
|
|
510
575
|
}
|
|
511
|
-
export async function optimize(exclude) {
|
|
512
|
-
const glob = ['**/*.{htm,html}'];
|
|
576
|
+
export async function optimize(include, exclude) {
|
|
577
|
+
const glob = include ? [include] : ['**/*.{htm,html}'];
|
|
513
578
|
if (exclude)
|
|
514
579
|
glob.push('!' + exclude);
|
|
515
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
|
},
|