@divriots/jampack 0.24.4 → 0.26.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.d.ts +5 -4
- package/dist/cache.js +14 -15
- package/dist/compress.d.ts +2 -1
- package/dist/compress.js +19 -21
- package/dist/compressors/css.d.ts +4 -3
- package/dist/compressors/css.js +5 -5
- package/dist/compressors/html.d.ts +2 -1
- package/dist/compressors/html.js +9 -10
- package/dist/compressors/images.d.ts +2 -1
- package/dist/compressors/images.js +13 -13
- package/dist/compressors/js.d.ts +2 -1
- package/dist/compressors/js.js +2 -3
- package/dist/config.d.ts +3 -4
- package/dist/config.js +8 -6
- package/dist/index.js +19 -21
- package/dist/optimize.d.ts +2 -1
- package/dist/optimize.js +64 -58
- package/dist/optimizers/img-external.d.ts +2 -1
- package/dist/optimizers/img-external.js +5 -6
- package/dist/optimizers/prefetch-links.d.ts +2 -1
- package/dist/optimizers/prefetch-links.js +7 -6
- package/dist/state.d.ts +3 -2
- package/dist/state.js +7 -5
- package/package.json +10 -10
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { GlobalState } from './state.js';
|
|
2
3
|
declare const listOfCategories: readonly ["img", "img-ext"];
|
|
3
4
|
export type Category = (typeof listOfCategories)[number];
|
|
4
5
|
export type CacheData = {
|
|
5
6
|
buffer: Buffer;
|
|
6
7
|
meta: any;
|
|
7
8
|
};
|
|
8
|
-
declare function cleanCache(
|
|
9
|
-
declare function computeCacheHash(buffer: Buffer, options?: any): string;
|
|
10
|
-
declare function getFromCache(category: Category, hash: string): Promise<CacheData | undefined>;
|
|
11
|
-
declare function addToCache(category: Category, hash: string, data: CacheData): Promise<void>;
|
|
9
|
+
declare function cleanCache(state: GlobalState, full?: boolean): Promise<void>;
|
|
10
|
+
declare function computeCacheHash(state: GlobalState, buffer: Buffer, options?: any): string;
|
|
11
|
+
declare function getFromCache(state: GlobalState, category: Category, hash: string): Promise<CacheData | undefined>;
|
|
12
|
+
declare function addToCache(state: GlobalState, category: Category, hash: string, data: CacheData): Promise<void>;
|
|
12
13
|
export { cleanCache, computeCacheHash, getFromCache, addToCache };
|
package/dist/cache.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { hashSync as hasha } from 'hasha';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import * as fs from 'fs/promises';
|
|
4
|
-
import $state from './state.js';
|
|
5
4
|
import { CACHE_VERSIONS } from './packagejson.js';
|
|
6
5
|
const listOfCategories = ['img', 'img-ext'];
|
|
7
|
-
function getCacheFolder() {
|
|
8
|
-
return
|
|
6
|
+
function getCacheFolder(state) {
|
|
7
|
+
return state.args.cache_folder || '.jampack/cache';
|
|
9
8
|
}
|
|
10
|
-
async function cleanCache(full) {
|
|
11
|
-
const CACHE_FOLDER = getCacheFolder();
|
|
9
|
+
async function cleanCache(state, full) {
|
|
10
|
+
const CACHE_FOLDER = getCacheFolder(state);
|
|
12
11
|
if (full) {
|
|
13
12
|
try {
|
|
14
13
|
await fs.rm(CACHE_FOLDER, { recursive: true });
|
|
@@ -49,8 +48,8 @@ async function cleanCache(full) {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
|
-
function computeCacheHash(buffer, options) {
|
|
53
|
-
if (
|
|
51
|
+
function computeCacheHash(state, buffer, options) {
|
|
52
|
+
if (state.args.nocache) {
|
|
54
53
|
return '';
|
|
55
54
|
}
|
|
56
55
|
let hash = `${hasha(buffer, { algorithm: 'sha256' })}`;
|
|
@@ -62,15 +61,15 @@ function computeCacheHash(buffer, options) {
|
|
|
62
61
|
function getVersionOfCategory(category) {
|
|
63
62
|
return CACHE_VERSIONS[category];
|
|
64
63
|
}
|
|
65
|
-
function getLocation(hash, category) {
|
|
66
|
-
const CACHE_FOLDER = getCacheFolder();
|
|
64
|
+
function getLocation(state, hash, category) {
|
|
65
|
+
const CACHE_FOLDER = getCacheFolder(state);
|
|
67
66
|
return path.join(CACHE_FOLDER, category, getVersionOfCategory(category), hash);
|
|
68
67
|
}
|
|
69
|
-
async function getFromCache(category, hash) {
|
|
70
|
-
if (
|
|
68
|
+
async function getFromCache(state, category, hash) {
|
|
69
|
+
if (state.args.nocache) {
|
|
71
70
|
return undefined;
|
|
72
71
|
}
|
|
73
|
-
const location = getLocation(hash, category);
|
|
72
|
+
const location = getLocation(state, hash, category);
|
|
74
73
|
try {
|
|
75
74
|
const buffer = await fs.readFile(path.join(location, 'data'));
|
|
76
75
|
const meta = JSON.parse((await fs.readFile(path.join(location, 'meta'))).toString());
|
|
@@ -81,11 +80,11 @@ async function getFromCache(category, hash) {
|
|
|
81
80
|
}
|
|
82
81
|
return undefined;
|
|
83
82
|
}
|
|
84
|
-
async function addToCache(category, hash, data) {
|
|
85
|
-
if (
|
|
83
|
+
async function addToCache(state, category, hash, data) {
|
|
84
|
+
if (state.args.nocache) {
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
|
-
const location = getLocation(hash, category);
|
|
87
|
+
const location = getLocation(state, hash, category);
|
|
89
88
|
await fs.mkdir(location, { recursive: true });
|
|
90
89
|
await fs.writeFile(path.join(location, 'data'), data.buffer);
|
|
91
90
|
await fs.writeFile(path.join(location, 'meta'), JSON.stringify(data.meta));
|
package/dist/compress.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { GlobalState } from './state.js';
|
|
2
|
+
export declare function compressFolder(state: GlobalState, exclude?: string): Promise<void>;
|
package/dist/compress.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { formatBytes } from './utils.js';
|
|
4
|
-
import config from './config.js';
|
|
5
|
-
import $state from './state.js';
|
|
6
4
|
import { globby } from 'globby';
|
|
7
5
|
import ora from 'ora';
|
|
8
6
|
import { compressCSS } from './compressors/css.js';
|
|
9
7
|
import { compressJS } from './compressors/js.js';
|
|
10
8
|
import { compressHTML } from './compressors/html.js';
|
|
11
9
|
import { compressImage } from './compressors/images.js';
|
|
12
|
-
const processFile = async (file, stats) => {
|
|
10
|
+
const processFile = async (state, file, stats) => {
|
|
13
11
|
let writeData = undefined;
|
|
14
12
|
try {
|
|
15
13
|
const ext = path.extname(file);
|
|
@@ -20,9 +18,9 @@ const processFile = async (file, stats) => {
|
|
|
20
18
|
case '.svg':
|
|
21
19
|
case '.webp':
|
|
22
20
|
case '.avif':
|
|
23
|
-
if (
|
|
21
|
+
if (state.options.image.compress) {
|
|
24
22
|
const imgData = await fs.readFile(file);
|
|
25
|
-
const newImage = await compressImage(imgData, {});
|
|
23
|
+
const newImage = await compressImage(state, imgData, {});
|
|
26
24
|
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < stats.size) {
|
|
27
25
|
writeData = newImage.data;
|
|
28
26
|
}
|
|
@@ -31,19 +29,19 @@ const processFile = async (file, stats) => {
|
|
|
31
29
|
case '.html':
|
|
32
30
|
case '.htm':
|
|
33
31
|
const htmldata = await fs.readFile(file);
|
|
34
|
-
const newhtmlData = await compressHTML(htmldata);
|
|
32
|
+
const newhtmlData = await compressHTML(state, htmldata);
|
|
35
33
|
writeData = newhtmlData;
|
|
36
34
|
break;
|
|
37
35
|
case '.css':
|
|
38
36
|
const cssdata = await fs.readFile(file);
|
|
39
|
-
const newCSS = await compressCSS(cssdata);
|
|
37
|
+
const newCSS = await compressCSS(state, cssdata);
|
|
40
38
|
if (newCSS && newCSS.length < cssdata.length) {
|
|
41
39
|
writeData = newCSS;
|
|
42
40
|
}
|
|
43
41
|
break;
|
|
44
42
|
case '.js':
|
|
45
43
|
const jsdata = await fs.readFile(file);
|
|
46
|
-
const newJS = await compressJS(jsdata.toString());
|
|
44
|
+
const newJS = await compressJS(state, jsdata.toString());
|
|
47
45
|
if (newJS && newJS.length < jsdata.length) {
|
|
48
46
|
writeData = newJS;
|
|
49
47
|
}
|
|
@@ -63,30 +61,30 @@ const processFile = async (file, stats) => {
|
|
|
63
61
|
// Writedata
|
|
64
62
|
if (writeData && writeData.length < result.originalSize) {
|
|
65
63
|
result.compressedSize = writeData.length;
|
|
66
|
-
if (
|
|
64
|
+
if (!state.args.nowrite) {
|
|
67
65
|
await fs.writeFile(file, writeData);
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
state.compressedFiles.add(file);
|
|
69
|
+
state.reportSummary(result);
|
|
72
70
|
};
|
|
73
|
-
export async function compressFolder(exclude) {
|
|
74
|
-
const spinner = ora(getProgressText()).start();
|
|
71
|
+
export async function compressFolder(state, exclude) {
|
|
72
|
+
const spinner = ora(getProgressText(state)).start();
|
|
75
73
|
const globs = ['**/**', '!_jampack/**']; // Exclude jampack folder because already compressed
|
|
76
74
|
if (exclude)
|
|
77
75
|
globs.push('!' + exclude);
|
|
78
|
-
const paths = await globby(globs, { cwd:
|
|
76
|
+
const paths = await globby(globs, { cwd: state.dir, absolute: true });
|
|
79
77
|
// "Parallel" processing
|
|
80
78
|
await Promise.all(paths.map(async (file) => {
|
|
81
|
-
if (
|
|
82
|
-
await processFile(file, await fs.stat(file));
|
|
83
|
-
spinner.text = getProgressText();
|
|
79
|
+
if (!state.compressedFiles.has(file)) {
|
|
80
|
+
await processFile(state, file, await fs.stat(file));
|
|
81
|
+
spinner.text = getProgressText(state);
|
|
84
82
|
}
|
|
85
83
|
}));
|
|
86
|
-
spinner.text = getProgressText();
|
|
84
|
+
spinner.text = getProgressText(state);
|
|
87
85
|
spinner.succeed();
|
|
88
86
|
}
|
|
89
|
-
const getProgressText = () => {
|
|
90
|
-
const gain =
|
|
91
|
-
return `${
|
|
87
|
+
const getProgressText = (state) => {
|
|
88
|
+
const gain = state.summary.dataLenUncompressed - state.summary.dataLenCompressed;
|
|
89
|
+
return `${state.summary.nbFiles} files | ${formatBytes(state.summary.dataLenUncompressed)} → ${formatBytes(state.summary.dataLenCompressed)} | -${formatBytes(gain)} `;
|
|
92
90
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
import {
|
|
3
|
-
export declare
|
|
4
|
-
export declare function
|
|
2
|
+
import { GlobalState } from '../state.js';
|
|
3
|
+
export declare const defaultTargets: () => import("lightningcss").Targets;
|
|
4
|
+
export declare function compressCSS({ targets }: GlobalState, originalCode: Buffer, type?: 'inline' | undefined): Promise<Buffer>;
|
|
5
|
+
export declare function loadConfigCSS(state: GlobalState): void;
|
package/dist/compressors/css.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import browserslist from 'browserslist';
|
|
2
2
|
import { browserslistToTargets, transform as lightcss, transformStyleAttribute as lightcssStyleAttribute, } from 'lightningcss';
|
|
3
|
-
|
|
4
|
-
export async function compressCSS(originalCode, type) {
|
|
3
|
+
export const defaultTargets = () => browserslistToTargets(browserslist('defaults'));
|
|
4
|
+
export async function compressCSS({ targets }, originalCode, type) {
|
|
5
5
|
// Compress with lightningcss
|
|
6
6
|
let lightCSSData = undefined;
|
|
7
7
|
try {
|
|
@@ -28,10 +28,10 @@ export async function compressCSS(originalCode, type) {
|
|
|
28
28
|
}
|
|
29
29
|
let resultBuffer = undefined;
|
|
30
30
|
if (lightCSSData && lightCSSData.length < originalCode.length) {
|
|
31
|
-
resultBuffer = lightCSSData;
|
|
31
|
+
resultBuffer = Buffer.from(lightCSSData);
|
|
32
32
|
}
|
|
33
33
|
return resultBuffer || originalCode;
|
|
34
34
|
}
|
|
35
|
-
export function
|
|
36
|
-
|
|
35
|
+
export function loadConfigCSS(state) {
|
|
36
|
+
const { options } = state;
|
|
37
37
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
|
|
2
|
+
import { GlobalState } from '../state.js';
|
|
3
|
+
export declare function compressHTML(state: GlobalState, originalCode: Buffer): Promise<Buffer>;
|
package/dist/compressors/html.js
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
import { minify } from 'html-minifier-terser';
|
|
2
2
|
import { compressCSS } from './css.js';
|
|
3
3
|
import { compressJS } from './js.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const newCode = await compressJS(originalCode);
|
|
4
|
+
async function minifyJSinHTML(state, originalCode) {
|
|
5
|
+
const newCode = await compressJS(state, originalCode);
|
|
7
6
|
if (newCode && newCode.length < originalCode.length)
|
|
8
7
|
return newCode;
|
|
9
8
|
return originalCode;
|
|
10
9
|
}
|
|
11
|
-
async function minifyCSSinHTML(originalCode, type) {
|
|
10
|
+
async function minifyCSSinHTML(state, originalCode, type) {
|
|
12
11
|
// Don't compress media
|
|
13
|
-
if (type
|
|
12
|
+
if (type !== undefined && type !== 'inline')
|
|
14
13
|
return originalCode;
|
|
15
14
|
const originalBuffer = Buffer.from(originalCode);
|
|
16
|
-
const newCSS = await compressCSS(originalBuffer, type);
|
|
15
|
+
const newCSS = await compressCSS(state, originalBuffer, type);
|
|
17
16
|
if (newCSS && newCSS.length > 0 && newCSS.length < originalBuffer.length)
|
|
18
17
|
return newCSS.toString();
|
|
19
18
|
return originalCode;
|
|
20
19
|
}
|
|
21
|
-
export async function compressHTML(originalCode) {
|
|
20
|
+
export async function compressHTML(state, originalCode) {
|
|
22
21
|
const newhtmlData = await minify(originalCode.toString(), {
|
|
23
|
-
minifyCSS: minifyCSSinHTML,
|
|
24
|
-
minifyJS: minifyJSinHTML,
|
|
22
|
+
minifyCSS: (text, type) => minifyCSSinHTML(state, text, type),
|
|
23
|
+
minifyJS: (text) => minifyJSinHTML(state, text),
|
|
25
24
|
sortClassName: true,
|
|
26
|
-
sortAttributes:
|
|
25
|
+
sortAttributes: state.options.html.sort_attributes,
|
|
27
26
|
});
|
|
28
27
|
if (newhtmlData)
|
|
29
28
|
return Buffer.from(newhtmlData, 'utf-8');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import sharp from 'sharp';
|
|
3
3
|
import { MimeType } from 'file-type';
|
|
4
|
+
import { GlobalState } from '../state.js';
|
|
4
5
|
export type ImageMimeType = MimeType | 'image/svg+xml';
|
|
5
6
|
export declare const AllImageFormat: string[];
|
|
6
7
|
export type ImageFormat = (typeof AllImageFormat)[number] | undefined;
|
|
@@ -12,4 +13,4 @@ export type ImageOutputOptions = {
|
|
|
12
13
|
resize?: sharp.ResizeOptions;
|
|
13
14
|
toFormat?: 'webp' | 'avif' | 'png' | 'jpeg' | 'unchanged';
|
|
14
15
|
};
|
|
15
|
-
export declare function compressImage(data: Buffer, options: ImageOutputOptions): Promise<Image | undefined>;
|
|
16
|
+
export declare function compressImage(state: GlobalState, data: Buffer, options: ImageOutputOptions): Promise<Image | undefined>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import sharp from 'sharp';
|
|
2
2
|
import { optimize as svgo } from 'svgo';
|
|
3
3
|
import { getFromCache, addToCache, computeCacheHash } from '../cache.js';
|
|
4
|
-
import config from '../config.js';
|
|
5
4
|
export const AllImageFormat = ['webp', 'svg', 'jpg', 'png', 'avif'];
|
|
6
5
|
function createWebpOptions(opt) {
|
|
7
6
|
return {
|
|
@@ -10,10 +9,10 @@ function createWebpOptions(opt) {
|
|
|
10
9
|
effort: opt.effort,
|
|
11
10
|
};
|
|
12
11
|
}
|
|
13
|
-
export async function compressImage(data, options) {
|
|
12
|
+
export async function compressImage(state, data, options) {
|
|
14
13
|
var _a, _b;
|
|
15
|
-
const cacheHash = computeCacheHash(data, options);
|
|
16
|
-
const imageFromCache = await getFromCache('img', cacheHash);
|
|
14
|
+
const cacheHash = computeCacheHash(state, data, options);
|
|
15
|
+
const imageFromCache = await getFromCache(state, 'img', cacheHash);
|
|
17
16
|
if (imageFromCache) {
|
|
18
17
|
return { data: imageFromCache.buffer, format: imageFromCache.meta };
|
|
19
18
|
}
|
|
@@ -27,9 +26,10 @@ export async function compressImage(data, options) {
|
|
|
27
26
|
return undefined;
|
|
28
27
|
}
|
|
29
28
|
let outputFormat;
|
|
29
|
+
const imageOptions = state.options.image;
|
|
30
30
|
// Special case for svg
|
|
31
31
|
if (meta.format === 'svg') {
|
|
32
|
-
if (!
|
|
32
|
+
if (!imageOptions.svg.optimization)
|
|
33
33
|
return undefined;
|
|
34
34
|
try {
|
|
35
35
|
const output = svgo(data.toString(), {
|
|
@@ -60,16 +60,16 @@ export async function compressImage(data, options) {
|
|
|
60
60
|
if (toFormat === 'unchanged') {
|
|
61
61
|
switch (meta.format) {
|
|
62
62
|
case 'png':
|
|
63
|
-
sharpFile = sharpFile.png(
|
|
63
|
+
sharpFile = sharpFile.png(imageOptions.png.options || {});
|
|
64
64
|
outputFormat = 'png';
|
|
65
65
|
break;
|
|
66
66
|
case 'jpeg':
|
|
67
67
|
case 'jpg':
|
|
68
|
-
sharpFile = sharpFile.jpeg(
|
|
68
|
+
sharpFile = sharpFile.jpeg(imageOptions.jpeg.options || {});
|
|
69
69
|
outputFormat = 'jpg';
|
|
70
70
|
break;
|
|
71
71
|
case 'webp':
|
|
72
|
-
sharpFile = sharpFile.webp(createWebpOptions(
|
|
72
|
+
sharpFile = sharpFile.webp(createWebpOptions(imageOptions.webp.options_lossly) || {});
|
|
73
73
|
outputFormat = 'webp';
|
|
74
74
|
break;
|
|
75
75
|
case 'heif':
|
|
@@ -83,17 +83,17 @@ export async function compressImage(data, options) {
|
|
|
83
83
|
// To format
|
|
84
84
|
switch (toFormat) {
|
|
85
85
|
case 'jpeg':
|
|
86
|
-
sharpFile = sharpFile.jpeg({ ...
|
|
86
|
+
sharpFile = sharpFile.jpeg({ ...imageOptions.jpeg.options } || {});
|
|
87
87
|
outputFormat = 'jpg';
|
|
88
88
|
break;
|
|
89
89
|
case 'png':
|
|
90
|
-
sharpFile = sharpFile.png({ ...
|
|
90
|
+
sharpFile = sharpFile.png({ ...imageOptions.png.options } || {});
|
|
91
91
|
outputFormat = 'png';
|
|
92
92
|
break;
|
|
93
93
|
case 'webp':
|
|
94
94
|
sharpFile = sharpFile.webp(createWebpOptions(meta.format === 'png'
|
|
95
|
-
?
|
|
96
|
-
:
|
|
95
|
+
? imageOptions.webp.options_lossless
|
|
96
|
+
: imageOptions.webp.options_lossly));
|
|
97
97
|
outputFormat = 'webp';
|
|
98
98
|
break;
|
|
99
99
|
case 'avif':
|
|
@@ -125,7 +125,7 @@ export async function compressImage(data, options) {
|
|
|
125
125
|
format: outputFormat,
|
|
126
126
|
data: await sharpFile.toBuffer(),
|
|
127
127
|
};
|
|
128
|
-
await addToCache('img', cacheHash, {
|
|
128
|
+
await addToCache(state, 'img', cacheHash, {
|
|
129
129
|
buffer: outputImage.data,
|
|
130
130
|
meta: outputImage.format,
|
|
131
131
|
});
|
package/dist/compressors/js.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { GlobalState } from '../state.js';
|
|
2
|
+
export declare function compressJS({ options }: GlobalState, originalCode: string): Promise<string>;
|
package/dist/compressors/js.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import swc from '@swc/core';
|
|
2
2
|
import * as esbuild from 'esbuild';
|
|
3
|
-
|
|
4
|
-
export async function compressJS(originalCode) {
|
|
3
|
+
export async function compressJS({ options }, originalCode) {
|
|
5
4
|
let resultCode = originalCode;
|
|
6
|
-
switch (
|
|
5
|
+
switch (options.js.compressor) {
|
|
7
6
|
case 'esbuild':
|
|
8
7
|
resultCode = (await esbuild.transform(originalCode, {
|
|
9
8
|
minify: true,
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function fast(): void;
|
|
3
|
-
export declare function loadConfig(): Promise<void>;
|
|
4
|
-
export default options;
|
|
1
|
+
import { GlobalState } from './state.js';
|
|
2
|
+
export declare function fast(state: GlobalState): void;
|
|
3
|
+
export declare function loadConfig(state: GlobalState): Promise<void>;
|
package/dist/config.js
CHANGED
|
@@ -3,16 +3,18 @@ import load from '@proload/core';
|
|
|
3
3
|
import deepmerge from 'deepmerge';
|
|
4
4
|
import default_options from './config-default.js';
|
|
5
5
|
import fast_options_override from './config-fast.js';
|
|
6
|
-
|
|
7
|
-
export function fast() {
|
|
8
|
-
|
|
6
|
+
import { loadConfigCSS } from './compressors/css.js';
|
|
7
|
+
export function fast(state) {
|
|
8
|
+
const options = default_options;
|
|
9
|
+
Object.assign(state.options, deepmerge(options, fast_options_override));
|
|
9
10
|
}
|
|
10
|
-
export async function loadConfig() {
|
|
11
|
+
export async function loadConfig(state) {
|
|
12
|
+
const options = default_options;
|
|
11
13
|
const proload = await load('jampack', { mustExist: false });
|
|
12
14
|
if (proload) {
|
|
13
15
|
console.log('Merging default config with:');
|
|
14
16
|
console.log(JSON.stringify(proload.value, null, 2));
|
|
15
|
-
Object.assign(options, deepmerge(options, proload.value));
|
|
17
|
+
Object.assign(state.options, deepmerge(options, proload.value));
|
|
16
18
|
}
|
|
19
|
+
loadConfigCSS(state);
|
|
17
20
|
}
|
|
18
|
-
export default options;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
2
|
+
import { Command } from '@commander-js/extra-typings';
|
|
3
3
|
import { compressFolder } from './compress.js';
|
|
4
4
|
import { optimize } from './optimize.js';
|
|
5
|
-
import
|
|
5
|
+
import { GlobalState } from './state.js';
|
|
6
6
|
import { table } from 'table';
|
|
7
7
|
import { formatBytes } from './utils.js';
|
|
8
|
-
import
|
|
8
|
+
import { fast, loadConfig } from './config.js';
|
|
9
9
|
import { printTitle } from './logger.js';
|
|
10
10
|
import { exit } from 'process';
|
|
11
11
|
import kleur from 'kleur';
|
|
@@ -13,7 +13,6 @@ import { cleanCache } from './cache.js';
|
|
|
13
13
|
import { VERSION } from './packagejson.js';
|
|
14
14
|
import { mkdirSync } from 'fs';
|
|
15
15
|
import { join } from 'path';
|
|
16
|
-
import { loadConfig as loadConfigCSS } from './compressors/css.js';
|
|
17
16
|
const logo = ` __ __
|
|
18
17
|
|__|____ _____ ___________ ____ | | __
|
|
19
18
|
| \\__ \\ / \\\\____ \\__ \\ _/ ___\\| |/ /
|
|
@@ -43,9 +42,10 @@ program
|
|
|
43
42
|
.option('--cleancache', 'Clean cache before running')
|
|
44
43
|
.option('--nocache', 'Run with no use of cache')
|
|
45
44
|
.action(async (dir, options) => {
|
|
45
|
+
const state = new GlobalState();
|
|
46
46
|
// Arguments
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
state.dir = dir;
|
|
48
|
+
state.args = options;
|
|
49
49
|
// Print options
|
|
50
50
|
if (options) {
|
|
51
51
|
console.log('Options:');
|
|
@@ -53,13 +53,13 @@ program
|
|
|
53
53
|
console.log('');
|
|
54
54
|
}
|
|
55
55
|
// Override default config with config file
|
|
56
|
-
await loadConfig();
|
|
56
|
+
await loadConfig(state);
|
|
57
57
|
// Override config with fast options if set
|
|
58
58
|
if (options.fast) {
|
|
59
|
-
fast();
|
|
59
|
+
fast(state);
|
|
60
60
|
}
|
|
61
61
|
// Clean cache
|
|
62
|
-
await cleanCache(options.cleancache);
|
|
62
|
+
await cleanCache(state, options.cleancache);
|
|
63
63
|
// Make _jampack folder
|
|
64
64
|
try {
|
|
65
65
|
mkdirSync(join(dir, '_jampack'));
|
|
@@ -68,28 +68,26 @@ program
|
|
|
68
68
|
console.error('Folder `_jampack` is present in target folder. This means that jampack has already processed this folder. You should always run jampack on clean build of the static website.');
|
|
69
69
|
exit(1);
|
|
70
70
|
}
|
|
71
|
-
// Inject config into modules
|
|
72
|
-
loadConfigCSS(config);
|
|
73
71
|
if (!options.onlycomp) {
|
|
74
72
|
printTitle('PASS 1 - Optimizing');
|
|
75
73
|
console.time('Done');
|
|
76
|
-
await optimize(options.include, options.exclude);
|
|
74
|
+
await optimize(state, options.include, options.exclude);
|
|
77
75
|
console.timeEnd('Done');
|
|
78
76
|
}
|
|
79
77
|
if (!options.onlyoptim && !options.fast) {
|
|
80
78
|
printTitle('PASS 2 - Compressing the rest');
|
|
81
79
|
console.time('Done');
|
|
82
|
-
await compressFolder(options.exclude);
|
|
80
|
+
await compressFolder(state, options.exclude);
|
|
83
81
|
console.timeEnd('Done');
|
|
84
82
|
}
|
|
85
|
-
printSummary();
|
|
86
|
-
printIssues();
|
|
87
|
-
if (options.fail &&
|
|
83
|
+
printSummary(state);
|
|
84
|
+
printIssues(state);
|
|
85
|
+
if (options.fail && state.issues.size > 0) {
|
|
88
86
|
exit(1);
|
|
89
87
|
}
|
|
90
88
|
});
|
|
91
89
|
program.parse();
|
|
92
|
-
function printSummary() {
|
|
90
|
+
function printSummary($state) {
|
|
93
91
|
if ($state.summary.nbFiles > 0) {
|
|
94
92
|
printTitle('Summary');
|
|
95
93
|
const dataTable = [
|
|
@@ -141,22 +139,22 @@ function printSummary() {
|
|
|
141
139
|
console.log(table(dataTable, config));
|
|
142
140
|
}
|
|
143
141
|
}
|
|
144
|
-
function printIssues() {
|
|
142
|
+
function printIssues(state) {
|
|
145
143
|
let issueCount = 0;
|
|
146
|
-
if (
|
|
144
|
+
if (state.issues.size === 0) {
|
|
147
145
|
printTitle('✔ No issues');
|
|
148
146
|
}
|
|
149
147
|
else {
|
|
150
148
|
printTitle('Issues', kleur.bgRed);
|
|
151
149
|
console.log('');
|
|
152
|
-
for (let [file, list] of
|
|
150
|
+
for (let [file, list] of state.issues) {
|
|
153
151
|
console.log('▶ ' + file + '\n');
|
|
154
152
|
list.forEach((issue) => {
|
|
155
153
|
issueCount++;
|
|
156
154
|
console.log(`${kleur.bgYellow(` ${issue.type} `)} ${issue.msg}\n`);
|
|
157
155
|
});
|
|
158
156
|
}
|
|
159
|
-
printTitle(`${issueCount} issue(s) over ${
|
|
157
|
+
printTitle(`${issueCount} issue(s) over ${state.issues.size} files`, kleur.bgYellow);
|
|
160
158
|
console.log('');
|
|
161
159
|
}
|
|
162
160
|
}
|
package/dist/optimize.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { GlobalState } from './state.js';
|
|
2
|
+
export declare function optimize(state: GlobalState, include?: string, exclude?: string): Promise<void>;
|
package/dist/optimize.js
CHANGED
|
@@ -4,10 +4,8 @@ import * as fs 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';
|
|
7
|
-
import config from './config.js';
|
|
8
7
|
import { compressImage, } from './compressors/images.js';
|
|
9
8
|
import svgToMiniDataURI from 'mini-svg-data-uri';
|
|
10
|
-
import $state from './state.js';
|
|
11
9
|
import kleur from 'kleur';
|
|
12
10
|
import ora from 'ora';
|
|
13
11
|
import { isLocal, Resource, translateSrc } from './utils/resource.js';
|
|
@@ -25,9 +23,9 @@ function getIntAttr(img, attr) {
|
|
|
25
23
|
return;
|
|
26
24
|
return parsed;
|
|
27
25
|
}
|
|
28
|
-
async function analyse(file) {
|
|
26
|
+
async function analyse(state, file) {
|
|
29
27
|
console.log('▶ ' + file);
|
|
30
|
-
const html = (await fs.readFile(path.join(
|
|
28
|
+
const html = (await fs.readFile(path.join(state.dir, file))).toString();
|
|
31
29
|
if (!html.includes('<html'))
|
|
32
30
|
return;
|
|
33
31
|
const $ = cheerio.load(html, { sourceCodeLocationInfo: true });
|
|
@@ -46,10 +44,10 @@ async function analyse(file) {
|
|
|
46
44
|
const isAboveTheFold = isImgAboveTheFold(img, imgElement, theFold);
|
|
47
45
|
img.removeAttr(ABOVE_FOLD_DATA_ATTR);
|
|
48
46
|
try {
|
|
49
|
-
await processImage(file, img, isAboveTheFold);
|
|
47
|
+
await processImage(state, file, img, isAboveTheFold);
|
|
50
48
|
}
|
|
51
49
|
catch (e) {
|
|
52
|
-
|
|
50
|
+
state.reportIssue(file, {
|
|
53
51
|
type: 'erro',
|
|
54
52
|
msg: e.message ||
|
|
55
53
|
`Unexpected error while processing image: ${JSON.stringify(e)}`,
|
|
@@ -59,7 +57,7 @@ async function analyse(file) {
|
|
|
59
57
|
// Reset spinner
|
|
60
58
|
spinnerImg.text = kleur.dim(`<img> [${imgsArray.length}/${imgsArray.length}]`);
|
|
61
59
|
// Notify issues
|
|
62
|
-
const issues =
|
|
60
|
+
const issues = state.issues.get(file);
|
|
63
61
|
if (issues) {
|
|
64
62
|
spinnerImg.fail();
|
|
65
63
|
console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ? 's' : ''}`));
|
|
@@ -82,7 +80,7 @@ async function analyse(file) {
|
|
|
82
80
|
await processIframe(file, $, ifElement, isAboveTheFold);
|
|
83
81
|
}
|
|
84
82
|
catch (e) {
|
|
85
|
-
|
|
83
|
+
state.reportIssue(file, {
|
|
86
84
|
type: 'erro',
|
|
87
85
|
msg: e.message ||
|
|
88
86
|
`Unexpected error while processing image: ${JSON.stringify(e)}`,
|
|
@@ -104,7 +102,8 @@ async function analyse(file) {
|
|
|
104
102
|
const heads = $('head'); // always exists because doc loaded with isDocument=true (default)
|
|
105
103
|
const charsetElements$ = heads.find('meta[charset]');
|
|
106
104
|
const wasCharsetFirst = !charsetElements$.prev().length;
|
|
107
|
-
|
|
105
|
+
const { options } = state;
|
|
106
|
+
if (options.html.add_css_reset_as === 'inline') {
|
|
108
107
|
prependToHead += `<style>:where(img){height:auto;}</style>`;
|
|
109
108
|
}
|
|
110
109
|
if (prependToHead || appendToHead) {
|
|
@@ -115,7 +114,7 @@ async function analyse(file) {
|
|
|
115
114
|
}
|
|
116
115
|
switch (charsetElements$.length) {
|
|
117
116
|
case 0:
|
|
118
|
-
|
|
117
|
+
state.reportIssue(file, {
|
|
119
118
|
type: 'fix',
|
|
120
119
|
msg: 'Adding missing <meta charset="utf-8"> to the top of the <head>',
|
|
121
120
|
});
|
|
@@ -123,7 +122,7 @@ async function analyse(file) {
|
|
|
123
122
|
break;
|
|
124
123
|
case 1:
|
|
125
124
|
if (!wasCharsetFirst) {
|
|
126
|
-
|
|
125
|
+
state.reportIssue(file, {
|
|
127
126
|
type: 'perf',
|
|
128
127
|
msg: 'Moving <meta charset> to the top of the <head>',
|
|
129
128
|
});
|
|
@@ -132,7 +131,7 @@ async function analyse(file) {
|
|
|
132
131
|
heads.prepend(charsetElements$);
|
|
133
132
|
break;
|
|
134
133
|
default:
|
|
135
|
-
|
|
134
|
+
state.reportIssue(file, {
|
|
136
135
|
type: 'fix',
|
|
137
136
|
msg: 'Multiple <meta charset> found in the <head>: taking only the first one and deleting the others',
|
|
138
137
|
});
|
|
@@ -142,8 +141,8 @@ async function analyse(file) {
|
|
|
142
141
|
// Add to <body>
|
|
143
142
|
//
|
|
144
143
|
let appendToBody = '';
|
|
145
|
-
if (
|
|
146
|
-
appendToBody += await prefetch_links_in_viewport(file);
|
|
144
|
+
if (options.misc.prefetch_links === 'in-viewport') {
|
|
145
|
+
appendToBody += await prefetch_links_in_viewport(state, file);
|
|
147
146
|
}
|
|
148
147
|
if (appendToBody) {
|
|
149
148
|
const body = $('body');
|
|
@@ -156,16 +155,16 @@ async function analyse(file) {
|
|
|
156
155
|
let htmlResult = $.html();
|
|
157
156
|
// Inline critical css
|
|
158
157
|
//
|
|
159
|
-
if (
|
|
158
|
+
if (options.css.inline_critical_css) {
|
|
160
159
|
try {
|
|
161
|
-
htmlResult = await inlineCriticalCss(
|
|
160
|
+
htmlResult = await inlineCriticalCss(state.dir, htmlResult);
|
|
162
161
|
}
|
|
163
162
|
catch (e) {
|
|
164
163
|
console.warn('Fail to inline critical CSS', e);
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
|
-
if (
|
|
168
|
-
await fs.writeFile(path.join(
|
|
166
|
+
if (!state.args.nowrite) {
|
|
167
|
+
await fs.writeFile(path.join(state.dir, file), htmlResult);
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
170
|
function isImgAboveTheFold(img, imgElement, theFold) {
|
|
@@ -200,7 +199,7 @@ function getTheFold($) {
|
|
|
200
199
|
}
|
|
201
200
|
return 0;
|
|
202
201
|
}
|
|
203
|
-
async function processImage(htmlfile, img, isAboveTheFold) {
|
|
202
|
+
async function processImage(state, htmlfile, img, isAboveTheFold) {
|
|
204
203
|
var _a, _b;
|
|
205
204
|
/*
|
|
206
205
|
* Attribute 'src'
|
|
@@ -214,12 +213,17 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
214
213
|
// Let's not mess with it and hope it's well done
|
|
215
214
|
return; // No warnings
|
|
216
215
|
}
|
|
217
|
-
|
|
216
|
+
state.reportIssue(htmlfile, {
|
|
218
217
|
type: 'warn',
|
|
219
218
|
msg: `Missing [src] on img - processing skipped.`,
|
|
220
219
|
});
|
|
221
220
|
return;
|
|
222
221
|
}
|
|
222
|
+
const fullSizeRaw = img.attr('data-fullsize');
|
|
223
|
+
if (typeof fullSizeRaw !== undefined)
|
|
224
|
+
img.removeAttr('data-fullsize');
|
|
225
|
+
const fullSize = !!fullSizeRaw && parseInt(fullSizeRaw) > 0;
|
|
226
|
+
const config = state.options;
|
|
223
227
|
if (!isIncluded(attrib_src, config.image.src_include, config.image.src_exclude)) {
|
|
224
228
|
// Setup to ignore
|
|
225
229
|
return;
|
|
@@ -229,7 +233,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
229
233
|
*/
|
|
230
234
|
const attrib_alt = img.attr('alt');
|
|
231
235
|
if (attrib_alt === undefined) {
|
|
232
|
-
|
|
236
|
+
state.reportIssue(htmlfile, {
|
|
233
237
|
type: 'a11y',
|
|
234
238
|
msg: `Missing [alt] on img src="${attrib_src}" - Adding alt="" meanwhile.`,
|
|
235
239
|
});
|
|
@@ -261,7 +265,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
261
265
|
// Don't touch it
|
|
262
266
|
break;
|
|
263
267
|
default:
|
|
264
|
-
|
|
268
|
+
state.reportIssue(htmlfile, {
|
|
265
269
|
type: 'invalid',
|
|
266
270
|
msg: `Invalid [loading]="${attr_loading}" on img src="${attrib_src}"`,
|
|
267
271
|
});
|
|
@@ -292,9 +296,11 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
292
296
|
break;
|
|
293
297
|
if (!isIncluded(attrib_src, config.image.cdn.src_include || /.*/, config.image.cdn.src_exclude))
|
|
294
298
|
break;
|
|
299
|
+
if (fullSize)
|
|
300
|
+
return;
|
|
295
301
|
let attrib_width = getIntAttr(img, 'width');
|
|
296
302
|
if (!attrib_width) {
|
|
297
|
-
|
|
303
|
+
state.reportIssue(htmlfile, {
|
|
298
304
|
type: 'warn',
|
|
299
305
|
msg: `Missing or malformed'width' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
300
306
|
});
|
|
@@ -302,13 +308,13 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
302
308
|
}
|
|
303
309
|
let attrib_height = getIntAttr(img, 'height');
|
|
304
310
|
if (!attrib_height) {
|
|
305
|
-
|
|
311
|
+
state.reportIssue(htmlfile, {
|
|
306
312
|
type: 'warn',
|
|
307
313
|
msg: `Missing or malformed 'height' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
308
314
|
});
|
|
309
315
|
return;
|
|
310
316
|
}
|
|
311
|
-
const new_srcset = await generateSrcSetForCdn(attrib_src, cdnTransformer, attrib_width, attrib_height);
|
|
317
|
+
const new_srcset = await generateSrcSetForCdn(state, attrib_src, cdnTransformer, attrib_width, attrib_height);
|
|
312
318
|
if (new_srcset !== null) {
|
|
313
319
|
img.attr('srcset', new_srcset);
|
|
314
320
|
}
|
|
@@ -326,11 +332,11 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
326
332
|
if (!isIncluded(attrib_src, config.image.external.src_include, config.image.external.src_exclude))
|
|
327
333
|
break;
|
|
328
334
|
try {
|
|
329
|
-
attrib_src = await downloadExternalImage(htmlfile, attrib_src);
|
|
335
|
+
attrib_src = await downloadExternalImage(state, htmlfile, attrib_src);
|
|
330
336
|
img.attr('src', attrib_src);
|
|
331
337
|
}
|
|
332
338
|
catch (e) {
|
|
333
|
-
|
|
339
|
+
state.reportIssue(htmlfile, {
|
|
334
340
|
type: 'warn',
|
|
335
341
|
msg: `Failed to download image: ${attrib_src} - ${e.message}`,
|
|
336
342
|
});
|
|
@@ -342,10 +348,10 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
342
348
|
/*
|
|
343
349
|
* Loading image
|
|
344
350
|
*/
|
|
345
|
-
const originalImage = await Resource.loadResource(
|
|
351
|
+
const originalImage = await Resource.loadResource(state.dir, htmlfile, attrib_src);
|
|
346
352
|
// No file -> give up
|
|
347
353
|
if (!originalImage) {
|
|
348
|
-
|
|
354
|
+
state.reportIssue(htmlfile, {
|
|
349
355
|
type: 'erro',
|
|
350
356
|
msg: `Can't find img on disk src="${attrib_src}"`,
|
|
351
357
|
});
|
|
@@ -369,7 +375,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
369
375
|
: 'webp';
|
|
370
376
|
}
|
|
371
377
|
if (config.image.compress) {
|
|
372
|
-
newImage = await compressImage(await originalImage.getData(), {
|
|
378
|
+
newImage = await compressImage(state, await originalImage.getData(), {
|
|
373
379
|
toFormat: srcToFormat,
|
|
374
380
|
resize: { width: config.image.srcset_max_width },
|
|
375
381
|
});
|
|
@@ -378,13 +384,13 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
378
384
|
const newFilename = path.join(path.dirname(originalImage.filePathAbsolute), path.basename(originalImage.filePathAbsolute, path.extname(originalImage.filePathAbsolute)) + `.${newImage.format}`);
|
|
379
385
|
const newSrc = path.join(path.dirname(attrib_src), path.basename(attrib_src, path.extname(attrib_src)) +
|
|
380
386
|
`.${newImage.format}`);
|
|
381
|
-
if (
|
|
387
|
+
if (!state.compressedFiles.has(newFilename) && !state.args.nowrite) {
|
|
382
388
|
fs.writeFile(newFilename, newImage.data);
|
|
383
389
|
}
|
|
384
390
|
// Mark new file as compressed
|
|
385
|
-
|
|
391
|
+
state.compressedFiles.add(newFilename);
|
|
386
392
|
// Report compression result
|
|
387
|
-
|
|
393
|
+
state.reportSummary({
|
|
388
394
|
action: newFilename !== originalImage.filePathAbsolute
|
|
389
395
|
? `${await originalImage.getExt()}->${newImage.format}`
|
|
390
396
|
: path.extname(originalImage.filePathAbsolute),
|
|
@@ -397,7 +403,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
397
403
|
// Drop new Image
|
|
398
404
|
newImage = undefined;
|
|
399
405
|
// Report non-compression
|
|
400
|
-
|
|
406
|
+
state.reportSummary({
|
|
401
407
|
action: path.extname(originalImage.filePathAbsolute),
|
|
402
408
|
originalSize: await originalImage.getLen(),
|
|
403
409
|
compressedSize: await originalImage.getLen(),
|
|
@@ -437,7 +443,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
437
443
|
img.attr('src', datauri);
|
|
438
444
|
img.removeAttr('loading');
|
|
439
445
|
img.removeAttr('decoding');
|
|
440
|
-
|
|
446
|
+
state.reportSummary({
|
|
441
447
|
action: `${imageToEmbed.format}->embed`,
|
|
442
448
|
originalSize: await originalImage.getLen(),
|
|
443
449
|
compressedSize: imageToEmbed.data.length,
|
|
@@ -454,7 +460,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
454
460
|
/*
|
|
455
461
|
* Attribute 'width' & 'height'
|
|
456
462
|
*/
|
|
457
|
-
const [w, h] = await setImageSize(htmlfile, img, originalImage);
|
|
463
|
+
const [w, h] = await setImageSize(state, htmlfile, img, originalImage);
|
|
458
464
|
if (isEmbed) {
|
|
459
465
|
// Image is embed, no need for more processing
|
|
460
466
|
return;
|
|
@@ -478,8 +484,8 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
478
484
|
// The compress pass will compress the images
|
|
479
485
|
// of the srcset
|
|
480
486
|
}
|
|
481
|
-
else {
|
|
482
|
-
const new_srcset = await generateSrcSet(htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
487
|
+
else if (!fullSize) {
|
|
488
|
+
const new_srcset = await generateSrcSet(state, htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
483
489
|
if (new_srcset !== null) {
|
|
484
490
|
img.attr('srcset', new_srcset);
|
|
485
491
|
}
|
|
@@ -533,7 +539,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
533
539
|
// Ignore the creation of sources that already exist
|
|
534
540
|
continue;
|
|
535
541
|
}
|
|
536
|
-
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
542
|
+
const srcset = await generateSrcSet(state, htmlfile, originalImage, undefined, undefined, {
|
|
537
543
|
toFormat: s.format,
|
|
538
544
|
});
|
|
539
545
|
if (!srcset) {
|
|
@@ -547,7 +553,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
547
553
|
}
|
|
548
554
|
}
|
|
549
555
|
const isIncluded = (src, includeConf, excludeConf) => !!src.match(includeConf) && (!excludeConf || !src.match(excludeConf));
|
|
550
|
-
async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc) {
|
|
556
|
+
async function _generateSrcSet({ options }, startSrc, imageWidth, imageHeight, transformSrc) {
|
|
551
557
|
// Start from original image
|
|
552
558
|
let new_srcset = '';
|
|
553
559
|
if (!imageWidth || !imageHeight) {
|
|
@@ -557,8 +563,8 @@ async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc)
|
|
|
557
563
|
// Start reduction
|
|
558
564
|
const step = 300; //px
|
|
559
565
|
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
560
|
-
valueW = Math.min(valueW,
|
|
561
|
-
while (valueW >=
|
|
566
|
+
valueW = Math.min(valueW, options.image.srcset_max_width);
|
|
567
|
+
while (valueW >= options.image.srcset_min_width) {
|
|
562
568
|
let src = await transformSrc(valueW);
|
|
563
569
|
if (src) {
|
|
564
570
|
new_srcset += `, ${src} ${valueW}w`;
|
|
@@ -573,7 +579,7 @@ async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc)
|
|
|
573
579
|
}
|
|
574
580
|
return null;
|
|
575
581
|
}
|
|
576
|
-
async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
582
|
+
async function generateSrcSet(state, htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
577
583
|
const ext = path.extname(originalImage.src);
|
|
578
584
|
const fullbasename = originalImage.src.slice(0, -ext.length);
|
|
579
585
|
const imageSrc = (addition) => {
|
|
@@ -586,12 +592,12 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
586
592
|
const imageWidth = (meta === null || meta === void 0 ? void 0 : meta.width) || 0;
|
|
587
593
|
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
588
594
|
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
589
|
-
return _generateSrcSet(startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
595
|
+
return _generateSrcSet(state, startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
590
596
|
const src = imageSrc(`@${valueW}w`);
|
|
591
|
-
const absoluteFilename = translateSrc(
|
|
597
|
+
const absoluteFilename = translateSrc(state.dir, path.dirname(htmlfile), src);
|
|
592
598
|
// Don't generate srcset file twice
|
|
593
|
-
if (
|
|
594
|
-
const compressedImage = await compressImage(await originalImage.getData(), { ...options, resize: { width: valueW } });
|
|
599
|
+
if (!state.compressedFiles.has(absoluteFilename)) {
|
|
600
|
+
const compressedImage = await compressImage(state, await originalImage.getData(), { ...options, resize: { width: valueW } });
|
|
595
601
|
if (!(compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) ||
|
|
596
602
|
compressedImage.data.length >= previousImageSize) {
|
|
597
603
|
// New image is not compressed or bigger than previous image in srcset
|
|
@@ -600,20 +606,20 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
600
606
|
}
|
|
601
607
|
else {
|
|
602
608
|
// Write file
|
|
603
|
-
if (
|
|
609
|
+
if (!state.args.nowrite) {
|
|
604
610
|
fs.writeFile(absoluteFilename, compressedImage.data);
|
|
605
611
|
}
|
|
606
612
|
// Set new previous size to beat
|
|
607
613
|
previousImageSize = compressedImage.data.length;
|
|
608
614
|
// Add file to list avoid recompression
|
|
609
|
-
|
|
615
|
+
state.compressedFiles.add(absoluteFilename);
|
|
610
616
|
}
|
|
611
617
|
}
|
|
612
618
|
return src;
|
|
613
619
|
});
|
|
614
620
|
}
|
|
615
|
-
async function generateSrcSetForCdn(startSrc, cdnTransformer, imageWidth, imageHeight) {
|
|
616
|
-
return _generateSrcSet('', imageWidth, imageHeight, (valueW) => {
|
|
621
|
+
async function generateSrcSetForCdn(state, startSrc, cdnTransformer, imageWidth, imageHeight) {
|
|
622
|
+
return _generateSrcSet(state, '', imageWidth, imageHeight, (valueW) => {
|
|
617
623
|
var _a, _b;
|
|
618
624
|
return (_b = (_a = cdnTransformer({
|
|
619
625
|
url: startSrc,
|
|
@@ -621,19 +627,19 @@ async function generateSrcSetForCdn(startSrc, cdnTransformer, imageWidth, imageH
|
|
|
621
627
|
})) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(UNPIC_DEFAULT_HOST_REGEX, '/');
|
|
622
628
|
});
|
|
623
629
|
}
|
|
624
|
-
async function setImageSize(htmlfile, img, image) {
|
|
630
|
+
async function setImageSize(state, htmlfile, img, image) {
|
|
625
631
|
let width = img.attr('width');
|
|
626
632
|
let height = img.attr('height');
|
|
627
633
|
let width_new = undefined;
|
|
628
634
|
let height_new = undefined;
|
|
629
635
|
const img_fmt = await image.getExt();
|
|
630
|
-
if (img_fmt === 'svg' && !
|
|
636
|
+
if (img_fmt === 'svg' && !state.options.image.svg.add_width_and_height) {
|
|
631
637
|
return [];
|
|
632
638
|
}
|
|
633
639
|
// Check valid values
|
|
634
640
|
if (width !== undefined) {
|
|
635
641
|
if (!isNumeric(width)) {
|
|
636
|
-
|
|
642
|
+
state.reportIssue(htmlfile, {
|
|
637
643
|
type: 'fix',
|
|
638
644
|
msg: `Invalid width attribute format: "${width}" - overriding for ${image.src}`,
|
|
639
645
|
});
|
|
@@ -642,7 +648,7 @@ async function setImageSize(htmlfile, img, image) {
|
|
|
642
648
|
}
|
|
643
649
|
if (height !== undefined) {
|
|
644
650
|
if (!isNumeric(height)) {
|
|
645
|
-
|
|
651
|
+
state.reportIssue(htmlfile, {
|
|
646
652
|
type: 'fix',
|
|
647
653
|
msg: `Invalid height attribute format: "${height}" - overriding for ${image.src}`,
|
|
648
654
|
});
|
|
@@ -750,13 +756,13 @@ async function processIframe(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
750
756
|
iframe.attr('loading', 'lazy');
|
|
751
757
|
}
|
|
752
758
|
}
|
|
753
|
-
export async function optimize(include, exclude) {
|
|
759
|
+
export async function optimize(state, include, exclude) {
|
|
754
760
|
const glob = include ? [include] : ['**/*.{htm,html}'];
|
|
755
761
|
if (exclude)
|
|
756
762
|
glob.push('!' + exclude);
|
|
757
|
-
const paths = await globby(glob, { cwd:
|
|
763
|
+
const paths = await globby(glob, { cwd: state.dir });
|
|
758
764
|
// Sequential async
|
|
759
765
|
for (const file of paths) {
|
|
760
|
-
await analyse(file);
|
|
766
|
+
await analyse(state, file);
|
|
761
767
|
}
|
|
762
768
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import { GlobalState } from '../state.js';
|
|
1
2
|
import '../utils/polyfill-fetch.js';
|
|
2
|
-
export declare function downloadExternalImage(htmlfile: string, href: string): Promise<string>;
|
|
3
|
+
export declare function downloadExternalImage(state: GlobalState, htmlfile: string, href: string): Promise<string>;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
|
-
import $state from '../state.js';
|
|
4
3
|
import { hashSync as hasha } from 'hasha';
|
|
5
4
|
import { fileTypeFromBuffer } from 'file-type';
|
|
6
5
|
import { addToCache, getFromCache } from '../cache.js';
|
|
7
6
|
import { parse } from '../utils/cache-control-parser.js';
|
|
8
7
|
import '../utils/polyfill-fetch.js';
|
|
9
|
-
export async function downloadExternalImage(htmlfile, href) {
|
|
8
|
+
export async function downloadExternalImage(state, htmlfile, href) {
|
|
10
9
|
var _a, _b, _c, _d, _e;
|
|
11
10
|
const hash = hasha(href, {
|
|
12
11
|
algorithm: 'md5',
|
|
@@ -15,7 +14,7 @@ export async function downloadExternalImage(htmlfile, href) {
|
|
|
15
14
|
let buffer;
|
|
16
15
|
let ext;
|
|
17
16
|
// Image is in cache?
|
|
18
|
-
const dataFromCache = await getFromCache('img-ext', hash);
|
|
17
|
+
const dataFromCache = await getFromCache(state, 'img-ext', hash);
|
|
19
18
|
const cacheControl = (_a = dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.meta) === null || _a === void 0 ? void 0 : _a.CacheControl;
|
|
20
19
|
if (dataFromCache && cacheControl) {
|
|
21
20
|
const date = (_b = dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.meta) === null || _b === void 0 ? void 0 : _b.Date;
|
|
@@ -64,7 +63,7 @@ export async function downloadExternalImage(htmlfile, href) {
|
|
|
64
63
|
}
|
|
65
64
|
else {
|
|
66
65
|
// Add downloaded image to cache
|
|
67
|
-
await addToCache('img-ext', hash, {
|
|
66
|
+
await addToCache(state, 'img-ext', hash, {
|
|
68
67
|
buffer,
|
|
69
68
|
meta: {
|
|
70
69
|
Date: resp.headers.get('Date'),
|
|
@@ -97,7 +96,7 @@ export async function downloadExternalImage(htmlfile, href) {
|
|
|
97
96
|
if (!ext)
|
|
98
97
|
throw new Error('Unknown image format');
|
|
99
98
|
const htmlFolder = path.dirname(htmlfile);
|
|
100
|
-
const filename = path.relative(path.join(
|
|
101
|
-
await fs.writeFile(path.join(
|
|
99
|
+
const filename = path.relative(path.join(state.dir, htmlFolder), path.join(state.dir, `_jampack/${contentHash}.${ext}`));
|
|
100
|
+
await fs.writeFile(path.join(state.dir, htmlFolder, filename), buffer);
|
|
102
101
|
return filename;
|
|
103
102
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { GlobalState } from '../state.js';
|
|
2
|
+
export declare function prefetch_links_in_viewport(state: GlobalState, html_file: string): Promise<string>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import $state from '../state.js';
|
|
2
1
|
import * as fs from 'fs/promises';
|
|
3
2
|
import * as path from 'path';
|
|
4
3
|
import { createRequire } from 'node:module';
|
|
@@ -6,13 +5,15 @@ const require = createRequire(import.meta.url);
|
|
|
6
5
|
const src_filename = 'quicklink.mjs';
|
|
7
6
|
const folder = `/_jampack/quicklink-2.3.0`;
|
|
8
7
|
const url_loader = `${folder}/loader.js`;
|
|
9
|
-
export async function prefetch_links_in_viewport(html_file) {
|
|
8
|
+
export async function prefetch_links_in_viewport(state, html_file) {
|
|
10
9
|
const path_html = path.dirname('/' + html_file);
|
|
11
|
-
const quickLinkDestination = path.join(
|
|
12
|
-
const payloadStored = await fs
|
|
10
|
+
const quickLinkDestination = path.join(state.dir, folder, src_filename);
|
|
11
|
+
const payloadStored = await fs
|
|
12
|
+
.access(quickLinkDestination)
|
|
13
|
+
.catch(() => false);
|
|
13
14
|
if (!payloadStored) {
|
|
14
|
-
const path_loader = path.join(
|
|
15
|
-
await fs.mkdir(path.join(
|
|
15
|
+
const path_loader = path.join(state.dir, url_loader);
|
|
16
|
+
await fs.mkdir(path.join(state.dir, folder), { recursive: true });
|
|
16
17
|
// Write loader
|
|
17
18
|
const code_loader = `import { listen } from "./${src_filename}";
|
|
18
19
|
listen();`;
|
package/dist/state.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ type Issue = {
|
|
|
21
21
|
export declare class GlobalState {
|
|
22
22
|
dir: string;
|
|
23
23
|
args: Args;
|
|
24
|
+
options: import("./config-types.js").Options;
|
|
25
|
+
targets: import("lightningcss").Targets;
|
|
24
26
|
compressedFiles: Set<string>;
|
|
25
27
|
issues: Map<string, Issue[]>;
|
|
26
28
|
summary: Summary;
|
|
@@ -28,5 +30,4 @@ export declare class GlobalState {
|
|
|
28
30
|
reportIssue(sourceFile: string, issue: Issue): void;
|
|
29
31
|
reportSummary(r: ReportItem): void;
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
export default state;
|
|
33
|
+
export {};
|
package/dist/state.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { defaultTargets } from './compressors/css.js';
|
|
2
|
+
import default_options from './config-default.js';
|
|
1
3
|
export class GlobalState {
|
|
2
4
|
constructor() {
|
|
3
5
|
this.dir = 'dist';
|
|
4
6
|
this.args = {};
|
|
7
|
+
this.options = default_options;
|
|
8
|
+
this.targets = defaultTargets();
|
|
5
9
|
this.compressedFiles = new Set();
|
|
6
10
|
this.issues = new Map();
|
|
7
11
|
this.summary = {
|
|
@@ -23,9 +27,9 @@ export class GlobalState {
|
|
|
23
27
|
reportSummary(r) {
|
|
24
28
|
const isCompressed = r.compressedSize < r.originalSize ? 1 : 0;
|
|
25
29
|
this.summary.nbFiles++;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
this.summary.nbFilesCompressed += isCompressed;
|
|
31
|
+
this.summary.dataLenUncompressed += r.originalSize;
|
|
32
|
+
this.summary.dataLenCompressed += r.compressedSize;
|
|
29
33
|
if (r.action) {
|
|
30
34
|
let summary = this.summaryByExtension[r.action];
|
|
31
35
|
if (!summary) {
|
|
@@ -44,5 +48,3 @@ export class GlobalState {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
const state = new GlobalState();
|
|
48
|
-
export default state;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"cache-version": {
|
|
5
5
|
"img": "v1",
|
|
6
6
|
"img-ext": "v1"
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"./state": "./dist/state.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@commander-js/extra-typings": "^12.0.0",
|
|
29
30
|
"@divriots/cheerio": "1.0.0-rc.12",
|
|
30
31
|
"@proload/core": "^0.3.3",
|
|
31
32
|
"@swc/core": "^1.4.16",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"hasha": "^6.0.0",
|
|
41
42
|
"html-minifier-terser": "^7.2.0",
|
|
42
43
|
"kleur": "^4.1.5",
|
|
43
|
-
"lightningcss": "^1.
|
|
44
|
+
"lightningcss": "^1.25.1",
|
|
44
45
|
"mini-svg-data-uri": "^1.4.4",
|
|
45
46
|
"ora": "^8.0.1",
|
|
46
47
|
"quicklink": "^2.3.0",
|
|
@@ -58,18 +59,17 @@
|
|
|
58
59
|
"tslib": "^2.6.2",
|
|
59
60
|
"typescript": "^5.4.5"
|
|
60
61
|
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=14.0.0"
|
|
64
|
+
},
|
|
65
|
+
"bugs": "https://github.com/divriots/jampack/issues",
|
|
66
|
+
"keywords": [],
|
|
67
|
+
"types": "dist/index.d.ts",
|
|
61
68
|
"scripts": {
|
|
62
69
|
"watch": "pnpm build --watch",
|
|
63
70
|
"try": "shx rm -fr demo && cp -R demo_drc demo && node ./dist/index.js ./demo --nocache",
|
|
64
71
|
"build": "shx rm -rf dist && tsc -b",
|
|
65
72
|
"lint": "eslint . --ext .ts --config .eslintrc",
|
|
66
|
-
"prepublishOnly": "pnpm build",
|
|
67
73
|
"cleanAllDotJampack": "find -s . | grep '.jampack$' | xargs rm -fr"
|
|
68
|
-
}
|
|
69
|
-
"engines": {
|
|
70
|
-
"node": ">=14.0.0"
|
|
71
|
-
},
|
|
72
|
-
"bugs": "https://github.com/divriots/jampack/issues",
|
|
73
|
-
"keywords": [],
|
|
74
|
-
"types": "dist/index.d.ts"
|
|
74
|
+
}
|
|
75
75
|
}
|