@divriots/jampack 0.31.1 → 0.33.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/LICENSE +0 -0
- package/README.md +0 -0
- package/dist/cache.js +7 -1
- package/dist/compress.js +5 -1
- package/dist/config-default.js +6 -0
- package/dist/config-types.d.ts +6 -0
- package/dist/index.js +0 -0
- package/dist/optimize.js +171 -84
- package/dist/optimizers/img-external.js +3 -3
- package/dist/optimizers/process-iframe.d.ts +1 -1
- package/dist/optimizers/process-iframe.js +3 -16
- package/dist/optimizers/process-video.d.ts +3 -0
- package/dist/optimizers/process-video.js +29 -0
- package/dist/state.d.ts +2 -0
- package/dist/utils/install-dep.d.ts +1 -0
- package/dist/utils/install-dep.js +24 -2
- package/dist/utils/resource.d.ts +4 -3
- package/dist/utils/resource.js +12 -9
- package/package.json +9 -10
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/dist/cache.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { hashSync as hasha } from 'hasha';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import * as
|
|
3
|
+
import * as fsp from 'fs/promises';
|
|
4
4
|
import { CACHE_VERSIONS } from './packagejson.js';
|
|
5
5
|
const listOfCategories = ['img', 'img-ext'];
|
|
6
6
|
function getCacheFolder(state) {
|
|
7
7
|
return state.args.cache_folder || '.jampack/cache';
|
|
8
8
|
}
|
|
9
9
|
async function cleanCache(state, full) {
|
|
10
|
+
var _a;
|
|
10
11
|
const CACHE_FOLDER = getCacheFolder(state);
|
|
12
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
11
13
|
if (full) {
|
|
12
14
|
try {
|
|
13
15
|
await fs.rm(CACHE_FOLDER, { recursive: true });
|
|
@@ -66,9 +68,11 @@ function getLocation(state, hash, category) {
|
|
|
66
68
|
return path.join(CACHE_FOLDER, category, getVersionOfCategory(category), hash);
|
|
67
69
|
}
|
|
68
70
|
async function getFromCache(state, category, hash) {
|
|
71
|
+
var _a;
|
|
69
72
|
if (state.args.nocache) {
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
75
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
72
76
|
const location = getLocation(state, hash, category);
|
|
73
77
|
try {
|
|
74
78
|
const buffer = await fs.readFile(path.join(location, 'data'));
|
|
@@ -81,9 +85,11 @@ async function getFromCache(state, category, hash) {
|
|
|
81
85
|
return undefined;
|
|
82
86
|
}
|
|
83
87
|
async function addToCache(state, category, hash, data) {
|
|
88
|
+
var _a;
|
|
84
89
|
if (state.args.nocache) {
|
|
85
90
|
return;
|
|
86
91
|
}
|
|
92
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
87
93
|
const location = getLocation(state, hash, category);
|
|
88
94
|
await fs.mkdir(location, { recursive: true });
|
|
89
95
|
await fs.writeFile(path.join(location, 'data'), data.buffer);
|
package/dist/compress.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as fsp from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { formatBytes } from './utils.js';
|
|
4
4
|
import { globby } from 'globby';
|
|
@@ -8,7 +8,9 @@ import { compressJS } from './compressors/js.js';
|
|
|
8
8
|
import { compressHTML } from './compressors/html.js';
|
|
9
9
|
import { compressImage } from './compressors/images.js';
|
|
10
10
|
const processFile = async (state, file, stats) => {
|
|
11
|
+
var _a;
|
|
11
12
|
let writeData = undefined;
|
|
13
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
12
14
|
try {
|
|
13
15
|
const ext = path.extname(file);
|
|
14
16
|
switch (ext) {
|
|
@@ -69,6 +71,8 @@ const processFile = async (state, file, stats) => {
|
|
|
69
71
|
state.reportSummary(result);
|
|
70
72
|
};
|
|
71
73
|
export async function compressFolder(state, exclude) {
|
|
74
|
+
var _a;
|
|
75
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
72
76
|
const spinner = ora(getProgressText(state)).start();
|
|
73
77
|
const globs = ['**/**', '!_jampack/**']; // Exclude jampack folder because already compressed
|
|
74
78
|
if (exclude)
|
package/dist/config-default.js
CHANGED
package/dist/config-types.d.ts
CHANGED
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/optimize.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { globby } from 'globby';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import * as
|
|
3
|
+
import * as fsp from 'fs/promises';
|
|
4
4
|
import { getCanonicalCdnForUrl, getTransformer } from 'unpic';
|
|
5
5
|
import * as cheerio from '@divriots/cheerio';
|
|
6
6
|
import { isNumeric } from './utils.js';
|
|
@@ -13,6 +13,7 @@ import { downloadExternalImage } from './optimizers/img-external.js';
|
|
|
13
13
|
import { inlineCriticalCss } from './optimizers/inline-critical-css.js';
|
|
14
14
|
import { prefetch_links_in_viewport } from './optimizers/prefetch-links.js';
|
|
15
15
|
import { processIframe } from './optimizers/process-iframe.js';
|
|
16
|
+
import { processVideo } from './optimizers/process-video.js';
|
|
16
17
|
const UNPIC_DEFAULT_HOST_REGEX = /^https:\/\/n\//g;
|
|
17
18
|
const ABOVE_FOLD_DATA_ATTR = 'data-abovethefold';
|
|
18
19
|
function getIntAttr(img, attr) {
|
|
@@ -25,76 +26,20 @@ function getIntAttr(img, attr) {
|
|
|
25
26
|
return parsed;
|
|
26
27
|
}
|
|
27
28
|
async function analyse(state, file) {
|
|
29
|
+
var _a;
|
|
28
30
|
console.log('▶ ' + file);
|
|
29
31
|
// For processes to add into
|
|
30
32
|
const appendToBody = {};
|
|
33
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
31
34
|
const html = (await fs.readFile(path.join(state.dir, file))).toString();
|
|
32
35
|
if (!html.includes('<html'))
|
|
33
36
|
return;
|
|
34
37
|
const $ = cheerio.load(html, { sourceCodeLocationInfo: true });
|
|
35
38
|
const theFold = getTheFold($);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
// Process images sequentially
|
|
42
|
-
const spinnerImg = ora({ prefixText: ' ' }).start();
|
|
43
|
-
for (let i = 0; i < imgsArray.length; i++) {
|
|
44
|
-
const imgElement = imgsArray[i];
|
|
45
|
-
spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr('src')}`);
|
|
46
|
-
const img = $(imgElement);
|
|
47
|
-
const isAboveTheFold = isElementAboveTheFold(img, imgElement, theFold);
|
|
48
|
-
img.removeAttr(ABOVE_FOLD_DATA_ATTR);
|
|
49
|
-
try {
|
|
50
|
-
await processImage(state, file, img, isAboveTheFold);
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
state.reportIssue(file, {
|
|
54
|
-
type: 'erro',
|
|
55
|
-
msg: e.message ||
|
|
56
|
-
`Unexpected error while processing image: ${JSON.stringify(e)}`,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// Reset spinner
|
|
61
|
-
spinnerImg.text = kleur.dim(`<img> [${imgsArray.length}/${imgsArray.length}]`);
|
|
62
|
-
// Notify issues
|
|
63
|
-
const issues = state.issues.get(file);
|
|
64
|
-
if (issues) {
|
|
65
|
-
spinnerImg.fail();
|
|
66
|
-
console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ? 's' : ''}`));
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
spinnerImg.succeed();
|
|
70
|
-
}
|
|
71
|
-
const iframes = $('iframe');
|
|
72
|
-
const iframesArray = [];
|
|
73
|
-
iframes.each((index, ifElement) => {
|
|
74
|
-
iframesArray.push(ifElement);
|
|
75
|
-
});
|
|
76
|
-
// Process iframes sequentially
|
|
77
|
-
const spinnerIframe = ora({ prefixText: ' ' }).start();
|
|
78
|
-
for (let i = 0; i < iframesArray.length; i++) {
|
|
79
|
-
const ifElement = iframesArray[i];
|
|
80
|
-
spinnerIframe.text = kleur.dim(`<iframe> [${i + 1}/${iframesArray.length}] ${$(ifElement).attr('src')}`);
|
|
81
|
-
const ifr = $(ifElement);
|
|
82
|
-
const isAboveTheFold = isElementAboveTheFold(ifr, ifElement, theFold);
|
|
83
|
-
ifr.removeAttr(ABOVE_FOLD_DATA_ATTR);
|
|
84
|
-
try {
|
|
85
|
-
await processIframe(state, file, $, ifElement, isAboveTheFold, appendToBody);
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
state.reportIssue(file, {
|
|
89
|
-
type: 'erro',
|
|
90
|
-
msg: e.message ||
|
|
91
|
-
`Unexpected error while processing image: ${JSON.stringify(e)}`,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Reset spinner
|
|
96
|
-
spinnerIframe.text = kleur.dim(`<iframe> [${iframesArray.length}/${iframesArray.length}]`);
|
|
97
|
-
spinnerIframe.succeed();
|
|
39
|
+
await processTag(state, file, $, 'img', 'src', processImage, theFold, appendToBody);
|
|
40
|
+
await processTag(state, file, $, 'iframe', 'src', processIframe, theFold, appendToBody);
|
|
41
|
+
await processTag(state, file, $, 'video', 'src', processVideo, theFold, appendToBody);
|
|
42
|
+
await processTag(state, file, $, 'meta[property$=":image"], meta[name$=":image"]', 'content', processMetaImage, theFold, appendToBody);
|
|
98
43
|
// Remove the fold
|
|
99
44
|
//
|
|
100
45
|
if (theFold) {
|
|
@@ -206,7 +151,7 @@ function getTheFold($) {
|
|
|
206
151
|
return 0;
|
|
207
152
|
}
|
|
208
153
|
async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
209
|
-
var _a, _b;
|
|
154
|
+
var _a, _b, _c;
|
|
210
155
|
/*
|
|
211
156
|
* Attribute 'src'
|
|
212
157
|
*/
|
|
@@ -284,28 +229,21 @@ async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
|
284
229
|
/*
|
|
285
230
|
* Check for external images
|
|
286
231
|
*/
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
232
|
+
const { external } = state.options.image;
|
|
233
|
+
if (isExternalImage(attrib_src, external)) {
|
|
234
|
+
if (external.process === 'off')
|
|
235
|
+
// Don't process external images
|
|
291
236
|
return;
|
|
292
237
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
attrib_src =
|
|
296
|
-
}
|
|
297
|
-
else if (typeof config.image.external.process === 'function') {
|
|
298
|
-
// Custom processing for external image
|
|
299
|
-
attrib_src = await config.image.external.process(attrib_src);
|
|
300
|
-
}
|
|
301
|
-
img.attr('src', attrib_src);
|
|
238
|
+
const external_src = await externalImage(attrib_src, state, htmlfile);
|
|
239
|
+
if (external_src)
|
|
240
|
+
img.attr('src', (attrib_src = external_src));
|
|
302
241
|
}
|
|
303
242
|
catch (e) {
|
|
304
|
-
state.reportIssue(htmlfile, {
|
|
243
|
+
return state.reportIssue(htmlfile, {
|
|
305
244
|
type: 'warn',
|
|
306
245
|
msg: `Failed to process external image: ${attrib_src} - ${e.message}`,
|
|
307
|
-
});
|
|
308
|
-
return; // No more processing
|
|
246
|
+
}); // No more processing
|
|
309
247
|
}
|
|
310
248
|
}
|
|
311
249
|
switch (config.image.cdn.process) {
|
|
@@ -357,7 +295,7 @@ async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
|
357
295
|
/*
|
|
358
296
|
* Loading image
|
|
359
297
|
*/
|
|
360
|
-
const originalImage = await Resource.loadResource(state
|
|
298
|
+
const originalImage = await Resource.loadResource(state, htmlfile, attrib_src);
|
|
361
299
|
// No file -> give up
|
|
362
300
|
if (!originalImage) {
|
|
363
301
|
state.reportIssue(htmlfile, {
|
|
@@ -394,7 +332,7 @@ async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
|
394
332
|
const newSrc = path.join(path.dirname(attrib_src), path.basename(attrib_src, path.extname(attrib_src)) +
|
|
395
333
|
`.${newImage.format}`);
|
|
396
334
|
if (!state.compressedFiles.has(newFilename) && !state.args.nowrite) {
|
|
397
|
-
|
|
335
|
+
await ((_b = state.vfs) !== null && _b !== void 0 ? _b : fsp).writeFile(newFilename, newImage.data);
|
|
398
336
|
}
|
|
399
337
|
// Mark new file as compressed
|
|
400
338
|
state.compressedFiles.add(newFilename);
|
|
@@ -535,7 +473,7 @@ async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
|
535
473
|
// https://twitter.com/jonsneyers/status/1346389917816008704?s=19
|
|
536
474
|
const isLossless = mime === 'image/png' ||
|
|
537
475
|
(mime === 'image/webp' &&
|
|
538
|
-
((
|
|
476
|
+
((_c = (await originalImage.getImageMeta())) === null || _c === void 0 ? void 0 : _c.isLossless));
|
|
539
477
|
if (!isLossless) {
|
|
540
478
|
sourcesToGenerate.push({
|
|
541
479
|
mime: 'image/avif',
|
|
@@ -566,6 +504,103 @@ async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
|
566
504
|
}
|
|
567
505
|
}
|
|
568
506
|
}
|
|
507
|
+
async function processMetaImage(state, htmlfile, meta) {
|
|
508
|
+
var _a, _b, _c;
|
|
509
|
+
const attribute = 'content';
|
|
510
|
+
let content = meta.attr(attribute);
|
|
511
|
+
if (!content)
|
|
512
|
+
return state.reportIssue(htmlfile, {
|
|
513
|
+
type: 'warn',
|
|
514
|
+
msg: `Missing [content] on meta - processing skipped.`,
|
|
515
|
+
});
|
|
516
|
+
// Setup to ignore
|
|
517
|
+
const { src_exclude, src_include, cdn, external } = state.options.image;
|
|
518
|
+
if (!isIncluded(content, src_include, src_exclude))
|
|
519
|
+
return;
|
|
520
|
+
/*
|
|
521
|
+
* Check for external images
|
|
522
|
+
*/
|
|
523
|
+
if (isExternalImage(content, external)) {
|
|
524
|
+
if (external.process === 'off')
|
|
525
|
+
// Don't process external images
|
|
526
|
+
return;
|
|
527
|
+
try {
|
|
528
|
+
const external_content = await externalImage(content, state, htmlfile);
|
|
529
|
+
if (external_content)
|
|
530
|
+
meta.attr(attribute, (content = external_content));
|
|
531
|
+
}
|
|
532
|
+
catch (e) {
|
|
533
|
+
return state.reportIssue(htmlfile, {
|
|
534
|
+
type: 'warn',
|
|
535
|
+
msg: `Failed to process external image: ${attribute} - ${e.message}`,
|
|
536
|
+
}); // No more processing
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const propName = meta.attr('property') ? 'property' : 'name';
|
|
540
|
+
const width = meta.closest(`meta[${propName}:width]`).attr(attribute);
|
|
541
|
+
switch (cdn.process) {
|
|
542
|
+
case 'off':
|
|
543
|
+
break;
|
|
544
|
+
case 'optimize':
|
|
545
|
+
let cdnTransformer = cdn.transformer;
|
|
546
|
+
if (cdnTransformer && !cdn.src_include) {
|
|
547
|
+
throw new Error('config.image.cdn.src_include is required when specifying a config.image.cdn.transformer');
|
|
548
|
+
}
|
|
549
|
+
if (!cdnTransformer) {
|
|
550
|
+
const canonical = getCanonicalCdnForUrl(content);
|
|
551
|
+
if (!canonical)
|
|
552
|
+
break;
|
|
553
|
+
cdnTransformer = getTransformer(canonical.cdn);
|
|
554
|
+
}
|
|
555
|
+
if (!cdnTransformer)
|
|
556
|
+
break;
|
|
557
|
+
if (!isIncluded(content, cdn.src_include || /.*/, cdn.src_exclude))
|
|
558
|
+
break;
|
|
559
|
+
if (!width)
|
|
560
|
+
return state.reportIssue(htmlfile, {
|
|
561
|
+
type: 'warn',
|
|
562
|
+
msg: `Missing or malformed'width' attribute for image ${content}, unable to perform CDN transform`,
|
|
563
|
+
});
|
|
564
|
+
meta.attr(attribute, (_b = (_a = cdnTransformer({ url: content, width: +width })) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(UNPIC_DEFAULT_HOST_REGEX, '/'));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
/*
|
|
568
|
+
* Loading image
|
|
569
|
+
*/
|
|
570
|
+
const originalImage = await Resource.loadResource(state, htmlfile, content);
|
|
571
|
+
if (!originalImage)
|
|
572
|
+
return state.reportIssue(htmlfile, {
|
|
573
|
+
type: 'erro',
|
|
574
|
+
msg: `Can't find img on disk ${attribute}="${content}"`,
|
|
575
|
+
});
|
|
576
|
+
/*
|
|
577
|
+
* Compress image
|
|
578
|
+
*/
|
|
579
|
+
const newImage = await compressImage(state, await originalImage.getData(), {
|
|
580
|
+
resize: width ? { width: +width } : undefined,
|
|
581
|
+
toFormat: meta.closest(`meta[${propName}:type]`).attr(attribute) === 'image/png'
|
|
582
|
+
? 'png'
|
|
583
|
+
: 'jpeg',
|
|
584
|
+
});
|
|
585
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < (await originalImage.getLen())) {
|
|
586
|
+
meta.attr(attribute, path.join(path.dirname(content), path.basename(content, path.extname(content)) + `.${newImage.format}`));
|
|
587
|
+
const newFilename = path.join(path.dirname(originalImage.filePathAbsolute), path.basename(originalImage.filePathAbsolute, path.extname(originalImage.filePathAbsolute)) + `.${newImage.format}`);
|
|
588
|
+
if (!state.compressedFiles.has(newFilename)) {
|
|
589
|
+
if (!state.args.nowrite)
|
|
590
|
+
await ((_c = state.vfs) !== null && _c !== void 0 ? _c : fsp).writeFile(newFilename, newImage.data);
|
|
591
|
+
// Mark new file as compressed
|
|
592
|
+
state.compressedFiles.add(newFilename);
|
|
593
|
+
// Report compression result
|
|
594
|
+
state.reportSummary({
|
|
595
|
+
action: newFilename !== originalImage.filePathAbsolute
|
|
596
|
+
? `${await originalImage.getExt()}->${newImage.format}`
|
|
597
|
+
: path.extname(originalImage.filePathAbsolute),
|
|
598
|
+
originalSize: await originalImage.getLen(),
|
|
599
|
+
compressedSize: newImage.data.length,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
569
604
|
const isIncluded = (src, includeConf, excludeConf) => !!src.match(includeConf) && (!excludeConf || !src.match(excludeConf));
|
|
570
605
|
async function _generateSrcSet({ options }, startSrc, imageWidth, imageHeight, transformSrc) {
|
|
571
606
|
// Start from original image
|
|
@@ -607,6 +642,7 @@ async function generateSrcSet(state, htmlfile, originalImage, startSrc, startSrc
|
|
|
607
642
|
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
608
643
|
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
609
644
|
return _generateSrcSet(state, startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
645
|
+
var _a;
|
|
610
646
|
const src = imageSrc(`@${valueW}w`);
|
|
611
647
|
const absoluteFilename = translateSrc(state.dir, path.dirname(htmlfile), src);
|
|
612
648
|
// Don't generate srcset file twice
|
|
@@ -621,7 +657,7 @@ async function generateSrcSet(state, htmlfile, originalImage, startSrc, startSrc
|
|
|
621
657
|
else {
|
|
622
658
|
// Write file
|
|
623
659
|
if (!state.args.nowrite) {
|
|
624
|
-
|
|
660
|
+
await ((_a = state.vfs) !== null && _a !== void 0 ? _a : fsp).writeFile(absoluteFilename, compressedImage.data);
|
|
625
661
|
}
|
|
626
662
|
// Set new previous size to beat
|
|
627
663
|
previousImageSize = compressedImage.data.length;
|
|
@@ -765,6 +801,7 @@ async function setImageSize(state, htmlfile, img, image) {
|
|
|
765
801
|
throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
|
|
766
802
|
}
|
|
767
803
|
export async function optimize(state, include, exclude) {
|
|
804
|
+
var _a;
|
|
768
805
|
const glob = include ? [include] : ['**/*.{htm,html}'];
|
|
769
806
|
if (exclude)
|
|
770
807
|
glob.push('!' + exclude);
|
|
@@ -772,5 +809,55 @@ export async function optimize(state, include, exclude) {
|
|
|
772
809
|
// Sequential async
|
|
773
810
|
for (const file of paths) {
|
|
774
811
|
await analyse(state, file);
|
|
812
|
+
(_a = state.onAnalysedFile) === null || _a === void 0 ? void 0 : _a.call(state, file);
|
|
775
813
|
}
|
|
776
814
|
}
|
|
815
|
+
async function processTag(state, file, $, tag, attribute_to_log, processor, theFold, appendToBody) {
|
|
816
|
+
const previous_issues = state.issues.get(file);
|
|
817
|
+
const tags = $(tag);
|
|
818
|
+
const tagsArray = [];
|
|
819
|
+
tags.each((index, tagElement) => {
|
|
820
|
+
tagsArray.push(tagElement);
|
|
821
|
+
});
|
|
822
|
+
// Process tags sequentially
|
|
823
|
+
const spinnerImg = ora({ prefixText: ' ' }).start();
|
|
824
|
+
for (let i = 0; i < tagsArray.length; i++) {
|
|
825
|
+
const tagElement = tagsArray[i];
|
|
826
|
+
spinnerImg.text = kleur.dim(`<${tag}> [${i + 1}/${tagsArray.length}] ${$(tagElement).attr(attribute_to_log)}`);
|
|
827
|
+
const el = $(tagElement);
|
|
828
|
+
const isAboveTheFold = isElementAboveTheFold(el, tagElement, theFold);
|
|
829
|
+
el.removeAttr(ABOVE_FOLD_DATA_ATTR);
|
|
830
|
+
try {
|
|
831
|
+
await processor(state, file, el, isAboveTheFold, appendToBody);
|
|
832
|
+
}
|
|
833
|
+
catch (e) {
|
|
834
|
+
state.reportIssue(file, {
|
|
835
|
+
type: 'erro',
|
|
836
|
+
msg: e.message ||
|
|
837
|
+
`Unexpected error while processing ${tag}: ${JSON.stringify(e)}`,
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Reset spinner
|
|
842
|
+
spinnerImg.text = kleur.dim(`<${tag}> [${tagsArray.length}/${tagsArray.length}]`);
|
|
843
|
+
// Notify issues
|
|
844
|
+
const issues = state.issues.get(file);
|
|
845
|
+
const newIssues = ((issues === null || issues === void 0 ? void 0 : issues.length) || 0) - ((previous_issues === null || previous_issues === void 0 ? void 0 : previous_issues.length) || 0);
|
|
846
|
+
if (newIssues > 0) {
|
|
847
|
+
spinnerImg.fail();
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
spinnerImg.succeed();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const isExternalImage = (content, external) => !isLocal(content) &&
|
|
854
|
+
isIncluded(content, external.src_include, external.src_exclude);
|
|
855
|
+
async function externalImage(content, state, htmlfile) {
|
|
856
|
+
const { process } = state.options.image.external;
|
|
857
|
+
if (process === 'download')
|
|
858
|
+
// Download external image for local processing
|
|
859
|
+
return await downloadExternalImage(state, htmlfile, content);
|
|
860
|
+
if (typeof process === 'function')
|
|
861
|
+
// Custom processing for external image
|
|
862
|
+
return await process(content);
|
|
863
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import * as
|
|
2
|
+
import * as fsp from 'fs/promises';
|
|
3
3
|
import { hashSync as hasha } from 'hasha';
|
|
4
4
|
import { fileTypeFromBuffer } from 'file-type';
|
|
5
5
|
import { addToCache, getFromCache } from '../cache.js';
|
|
6
6
|
import { parse } from '../utils/cache-control-parser.js';
|
|
7
7
|
import '../utils/polyfill-fetch.js';
|
|
8
8
|
export async function downloadExternalImage(state, htmlfile, href) {
|
|
9
|
-
var _a, _b, _c, _d, _e;
|
|
9
|
+
var _a, _b, _c, _d, _e, _f;
|
|
10
10
|
const hash = hasha(href, {
|
|
11
11
|
algorithm: 'md5',
|
|
12
12
|
});
|
|
@@ -97,6 +97,6 @@ export async function downloadExternalImage(state, htmlfile, href) {
|
|
|
97
97
|
throw new Error('Unknown image format');
|
|
98
98
|
const htmlFolder = path.dirname(htmlfile);
|
|
99
99
|
const filename = path.relative(path.join(state.dir, htmlFolder), path.join(state.dir, `_jampack/${contentHash}.${ext}`));
|
|
100
|
-
await
|
|
100
|
+
await ((_f = state.vfs) !== null && _f !== void 0 ? _f : fsp).writeFile(path.join(state.dir, htmlFolder, filename), buffer);
|
|
101
101
|
return filename;
|
|
102
102
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import * as cheerio from '@divriots/cheerio';
|
|
2
2
|
import type { GlobalState } from '../state.js';
|
|
3
|
-
export declare function processIframe(state: GlobalState, htmlfile: string,
|
|
3
|
+
export declare function processIframe(state: GlobalState, htmlfile: string, iframe: cheerio.Cheerio<cheerio.Element>, isAboveTheFold: boolean, appendToBody: Record<string, string>): Promise<void>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export async function processIframe(state, htmlfile,
|
|
3
|
-
const iframe = $(imgElement);
|
|
1
|
+
import { install_lozad } from '../utils/install-dep.js';
|
|
2
|
+
export async function processIframe(state, htmlfile, iframe, isAboveTheFold, appendToBody) {
|
|
4
3
|
// Reset loading attribute
|
|
5
4
|
iframe.removeAttr('loading');
|
|
6
5
|
const lazyloadOptions = state.options.iframe.lazyload;
|
|
@@ -18,19 +17,7 @@ export async function processIframe(state, htmlfile, $, imgElement, isAboveTheFo
|
|
|
18
17
|
iframe.attr('class', 'jampack-lozad');
|
|
19
18
|
iframe.attr('data-src', src);
|
|
20
19
|
iframe.removeAttr('src');
|
|
21
|
-
await
|
|
22
|
-
source: {
|
|
23
|
-
npm_package_name: 'lozad',
|
|
24
|
-
absolute_path_to_file: '/dist',
|
|
25
|
-
filename: 'lozad.es.js',
|
|
26
|
-
},
|
|
27
|
-
destination: {
|
|
28
|
-
folder_name: 'lozad-1.16',
|
|
29
|
-
code_loader: `import lozad from "./lozad.es.js";
|
|
30
|
-
const observer = lozad('.jampack-lozad', { rootMargin: '100px 0px', threshold: [0.1] });
|
|
31
|
-
observer.observe();`,
|
|
32
|
-
},
|
|
33
|
-
}, appendToBody);
|
|
20
|
+
await install_lozad(state, htmlfile, appendToBody);
|
|
34
21
|
}
|
|
35
22
|
}
|
|
36
23
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import * as cheerio from '@divriots/cheerio';
|
|
2
|
+
import type { GlobalState } from '../state.js';
|
|
3
|
+
export declare function processVideo(state: GlobalState, htmlfile: string, video: cheerio.Cheerio<cheerio.Element>, isAboveTheFold: boolean, appendToBody: Record<string, string>): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { install_lozad } from '../utils/install-dep.js';
|
|
2
|
+
export async function processVideo(state, htmlfile, video, isAboveTheFold, appendToBody) {
|
|
3
|
+
const autoplay = video.attr('autoplay');
|
|
4
|
+
if (!autoplay || autoplay === '0' || autoplay === 'false') {
|
|
5
|
+
// If the video is not autoplay we can postpone loading
|
|
6
|
+
// if there is a poster
|
|
7
|
+
if (video.attr('poster')) {
|
|
8
|
+
video.attr('preload', 'none');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// TODO create poster for videos without poster
|
|
12
|
+
}
|
|
13
|
+
const lazyloadOptions = state.options.video.autoplay_lazyload;
|
|
14
|
+
if (lazyloadOptions.when === 'never') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (lazyloadOptions.when === 'always' ||
|
|
18
|
+
(lazyloadOptions.when === 'below-the-fold' && !isAboveTheFold)) {
|
|
19
|
+
if (lazyloadOptions.how === 'js') {
|
|
20
|
+
video.attr('class', 'jampack-lozad');
|
|
21
|
+
const src = video.attr('src');
|
|
22
|
+
if (src) {
|
|
23
|
+
video.attr('data-src', src);
|
|
24
|
+
video.removeAttr('src');
|
|
25
|
+
}
|
|
26
|
+
await install_lozad(state, htmlfile, appendToBody);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/state.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export declare class GlobalState {
|
|
|
29
29
|
installed_dependencies: Set<string>;
|
|
30
30
|
summary: Summary;
|
|
31
31
|
summaryByExtension: Record<string, Summary>;
|
|
32
|
+
vfs?: typeof import('fs/promises');
|
|
33
|
+
onAnalysedFile?: (file: string) => void;
|
|
32
34
|
reportIssue(sourceFile: string, issue: Issue): void;
|
|
33
35
|
reportSummary(r: ReportItem): void;
|
|
34
36
|
}
|
|
@@ -10,3 +10,4 @@ export declare function install_dependency(state: GlobalState, html_file: string
|
|
|
10
10
|
code_loader: string;
|
|
11
11
|
};
|
|
12
12
|
}, appendToBody: Record<string, string>): Promise<void>;
|
|
13
|
+
export declare function install_lozad(state: GlobalState, html_file: string, appendToBody: Record<string, string>): Promise<void>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as fsp from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
export async function install_dependency(state, html_file, options, appendToBody) {
|
|
6
|
+
var _a;
|
|
6
7
|
const path_html = path.dirname('/' + html_file);
|
|
7
8
|
const folder = `/_jampack/${options.destination.folder_name}`;
|
|
8
9
|
const src_filename = options.source.filename;
|
|
9
10
|
const url_loader = `${folder}/loader.js`;
|
|
11
|
+
const fs = (_a = state.vfs) !== null && _a !== void 0 ? _a : fsp;
|
|
10
12
|
// Install dependency in /_jampack if not done yet
|
|
11
13
|
if (!state.installed_dependencies.has(options.source.npm_package_name)) {
|
|
12
14
|
const quickLinkDestination = path.join(state.dir, folder, src_filename);
|
|
@@ -17,7 +19,12 @@ export async function install_dependency(state, html_file, options, appendToBody
|
|
|
17
19
|
await fs.writeFile(path_loader, code_loader);
|
|
18
20
|
// Write quicklink code
|
|
19
21
|
const source = require.resolve(`${options.source.npm_package_name}${options.source.absolute_path_to_file}/${src_filename}`);
|
|
20
|
-
|
|
22
|
+
if (state.vfs) {
|
|
23
|
+
await state.vfs.writeFile(quickLinkDestination, await fsp.readFile(source));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
await fs.copyFile(source, quickLinkDestination);
|
|
27
|
+
}
|
|
21
28
|
state.installed_dependencies.add(options.source.npm_package_name);
|
|
22
29
|
}
|
|
23
30
|
// Add custom code to the end of body if not done yet
|
|
@@ -25,3 +32,18 @@ export async function install_dependency(state, html_file, options, appendToBody
|
|
|
25
32
|
appendToBody[options.source.npm_package_name] = `<script type="module" src="${path.relative(path_html, url_loader)}"></script>`;
|
|
26
33
|
}
|
|
27
34
|
}
|
|
35
|
+
export async function install_lozad(state, html_file, appendToBody) {
|
|
36
|
+
return install_dependency(state, html_file, {
|
|
37
|
+
source: {
|
|
38
|
+
npm_package_name: 'lozad',
|
|
39
|
+
absolute_path_to_file: '/dist',
|
|
40
|
+
filename: 'lozad.es.js',
|
|
41
|
+
},
|
|
42
|
+
destination: {
|
|
43
|
+
folder_name: 'lozad-1.16',
|
|
44
|
+
code_loader: `import lozad from "./lozad.es.js";
|
|
45
|
+
const observer = lozad('.jampack-lozad', { rootMargin: '100px 0px', threshold: [0.1] });
|
|
46
|
+
observer.observe();`,
|
|
47
|
+
},
|
|
48
|
+
}, appendToBody);
|
|
49
|
+
}
|
package/dist/utils/resource.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FileExtension, MimeType } from 'file-type';
|
|
2
2
|
import { ImageFormat } from '../compressors/images.js';
|
|
3
|
+
import { GlobalState } from '../state.js';
|
|
3
4
|
type ImageMeta = {
|
|
4
5
|
width: number | undefined;
|
|
5
6
|
height: number | undefined;
|
|
@@ -11,13 +12,14 @@ type ImageMeta = {
|
|
|
11
12
|
* File extension, mime and data are loaded only on demand, then cached.
|
|
12
13
|
*/
|
|
13
14
|
export declare class Resource {
|
|
15
|
+
private state;
|
|
14
16
|
readonly src: string;
|
|
15
17
|
readonly filePathAbsolute: string;
|
|
16
18
|
private ext;
|
|
17
19
|
private mime;
|
|
18
20
|
private data;
|
|
19
21
|
private image_meta;
|
|
20
|
-
constructor(src: string, filePathAbsolute: string);
|
|
22
|
+
constructor(state: GlobalState, src: string, filePathAbsolute: string);
|
|
21
23
|
getData(): Promise<Buffer>;
|
|
22
24
|
getImageMeta(): Promise<ImageMeta | null | undefined>;
|
|
23
25
|
getLen(): Promise<number>;
|
|
@@ -25,9 +27,8 @@ export declare class Resource {
|
|
|
25
27
|
getImageFormat(): Promise<ImageFormat | undefined>;
|
|
26
28
|
getMime(): Promise<MimeType | 'image/svg+xml'>;
|
|
27
29
|
private loadFileType;
|
|
28
|
-
static loadResource(
|
|
30
|
+
static loadResource(state: GlobalState, relativeFile: string, src: string): Promise<Resource | undefined>;
|
|
29
31
|
}
|
|
30
32
|
export declare function isLocal(src: string): boolean;
|
|
31
|
-
export declare function fileExists(path: string): Promise<boolean>;
|
|
32
33
|
export declare function translateSrc(projectRoot: string, htmlRelativePath: string, src: string): string;
|
|
33
34
|
export {};
|
package/dist/utils/resource.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as url from 'url';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import * as
|
|
3
|
+
import * as fsp from 'fs/promises';
|
|
4
4
|
import { fileTypeFromBuffer } from 'file-type';
|
|
5
5
|
import sharp from 'sharp';
|
|
6
6
|
import { AllImageFormat } from '../compressors/images.js';
|
|
@@ -8,13 +8,15 @@ import { AllImageFormat } from '../compressors/images.js';
|
|
|
8
8
|
* File extension, mime and data are loaded only on demand, then cached.
|
|
9
9
|
*/
|
|
10
10
|
export class Resource {
|
|
11
|
-
constructor(src, filePathAbsolute) {
|
|
11
|
+
constructor(state, src, filePathAbsolute) {
|
|
12
|
+
this.state = state;
|
|
12
13
|
this.src = src;
|
|
13
14
|
this.filePathAbsolute = filePathAbsolute;
|
|
14
15
|
}
|
|
15
16
|
async getData() {
|
|
17
|
+
var _a;
|
|
16
18
|
if (this.data === undefined) {
|
|
17
|
-
this.data = await
|
|
19
|
+
this.data = await ((_a = this.state.vfs) !== null && _a !== void 0 ? _a : fsp).readFile(this.filePathAbsolute);
|
|
18
20
|
}
|
|
19
21
|
return this.data;
|
|
20
22
|
}
|
|
@@ -98,7 +100,7 @@ export class Resource {
|
|
|
98
100
|
throw new Error(`Unknown file type "${this.src}"`);
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
|
-
static async loadResource(
|
|
103
|
+
static async loadResource(state, relativeFile, src) {
|
|
102
104
|
if (!isLocal(src)) {
|
|
103
105
|
throw new Error('src should be local');
|
|
104
106
|
}
|
|
@@ -106,10 +108,10 @@ export class Resource {
|
|
|
106
108
|
if (!u.pathname) {
|
|
107
109
|
throw new Error(`Invalid src format "${src}"`);
|
|
108
110
|
}
|
|
109
|
-
const relativePath = path.join(
|
|
111
|
+
const relativePath = path.join(state.dir, src.startsWith('/') ? '' : path.dirname(relativeFile), u.pathname);
|
|
110
112
|
let absolutePath = path.resolve(relativePath);
|
|
111
|
-
if (await fileExists(absolutePath)) {
|
|
112
|
-
return new Resource(src, absolutePath);
|
|
113
|
+
if (await fileExists(state, absolutePath)) {
|
|
114
|
+
return new Resource(state, src, absolutePath);
|
|
113
115
|
}
|
|
114
116
|
return undefined;
|
|
115
117
|
}
|
|
@@ -118,9 +120,10 @@ export function isLocal(src) {
|
|
|
118
120
|
const u = url.parse(src);
|
|
119
121
|
return !u.host;
|
|
120
122
|
}
|
|
121
|
-
|
|
123
|
+
async function fileExists(state, path) {
|
|
124
|
+
var _a;
|
|
122
125
|
try {
|
|
123
|
-
await
|
|
126
|
+
await ((_a = state.vfs) !== null && _a !== void 0 ? _a : fsp).stat(path);
|
|
124
127
|
}
|
|
125
128
|
catch (e) {
|
|
126
129
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -60,18 +60,17 @@
|
|
|
60
60
|
"tslib": "^2.6.2",
|
|
61
61
|
"typescript": "^5.4.5"
|
|
62
62
|
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=14.0.0"
|
|
65
|
+
},
|
|
66
|
+
"bugs": "https://github.com/divriots/jampack/issues",
|
|
67
|
+
"keywords": [],
|
|
68
|
+
"types": "dist/index.d.ts",
|
|
63
69
|
"scripts": {
|
|
64
70
|
"watch": "pnpm build --watch",
|
|
65
71
|
"try": "shx rm -fr demo && cp -R demo_drc demo && node ./dist/index.js ./demo --nocache",
|
|
66
72
|
"build": "shx rm -rf dist && tsc -b",
|
|
67
73
|
"lint": "eslint . --ext .ts --config .eslintrc",
|
|
68
|
-
"prepublishOnly": "pnpm build",
|
|
69
74
|
"cleanAllDotJampack": "find -s . | grep '.jampack$' | xargs rm -fr"
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
"node": ">=14.0.0"
|
|
73
|
-
},
|
|
74
|
-
"bugs": "https://github.com/divriots/jampack/issues",
|
|
75
|
-
"keywords": [],
|
|
76
|
-
"types": "dist/index.d.ts"
|
|
77
|
-
}
|
|
75
|
+
}
|
|
76
|
+
}
|