@divriots/jampack 0.24.4 → 0.25.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 +57 -57
- 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,13 @@ 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 config = state.options;
|
|
223
223
|
if (!isIncluded(attrib_src, config.image.src_include, config.image.src_exclude)) {
|
|
224
224
|
// Setup to ignore
|
|
225
225
|
return;
|
|
@@ -229,7 +229,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
229
229
|
*/
|
|
230
230
|
const attrib_alt = img.attr('alt');
|
|
231
231
|
if (attrib_alt === undefined) {
|
|
232
|
-
|
|
232
|
+
state.reportIssue(htmlfile, {
|
|
233
233
|
type: 'a11y',
|
|
234
234
|
msg: `Missing [alt] on img src="${attrib_src}" - Adding alt="" meanwhile.`,
|
|
235
235
|
});
|
|
@@ -261,7 +261,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
261
261
|
// Don't touch it
|
|
262
262
|
break;
|
|
263
263
|
default:
|
|
264
|
-
|
|
264
|
+
state.reportIssue(htmlfile, {
|
|
265
265
|
type: 'invalid',
|
|
266
266
|
msg: `Invalid [loading]="${attr_loading}" on img src="${attrib_src}"`,
|
|
267
267
|
});
|
|
@@ -294,7 +294,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
294
294
|
break;
|
|
295
295
|
let attrib_width = getIntAttr(img, 'width');
|
|
296
296
|
if (!attrib_width) {
|
|
297
|
-
|
|
297
|
+
state.reportIssue(htmlfile, {
|
|
298
298
|
type: 'warn',
|
|
299
299
|
msg: `Missing or malformed'width' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
300
300
|
});
|
|
@@ -302,13 +302,13 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
302
302
|
}
|
|
303
303
|
let attrib_height = getIntAttr(img, 'height');
|
|
304
304
|
if (!attrib_height) {
|
|
305
|
-
|
|
305
|
+
state.reportIssue(htmlfile, {
|
|
306
306
|
type: 'warn',
|
|
307
307
|
msg: `Missing or malformed 'height' attribute for image ${attrib_src}, unable to perform CDN transform`,
|
|
308
308
|
});
|
|
309
309
|
return;
|
|
310
310
|
}
|
|
311
|
-
const new_srcset = await generateSrcSetForCdn(attrib_src, cdnTransformer, attrib_width, attrib_height);
|
|
311
|
+
const new_srcset = await generateSrcSetForCdn(state, attrib_src, cdnTransformer, attrib_width, attrib_height);
|
|
312
312
|
if (new_srcset !== null) {
|
|
313
313
|
img.attr('srcset', new_srcset);
|
|
314
314
|
}
|
|
@@ -326,11 +326,11 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
326
326
|
if (!isIncluded(attrib_src, config.image.external.src_include, config.image.external.src_exclude))
|
|
327
327
|
break;
|
|
328
328
|
try {
|
|
329
|
-
attrib_src = await downloadExternalImage(htmlfile, attrib_src);
|
|
329
|
+
attrib_src = await downloadExternalImage(state, htmlfile, attrib_src);
|
|
330
330
|
img.attr('src', attrib_src);
|
|
331
331
|
}
|
|
332
332
|
catch (e) {
|
|
333
|
-
|
|
333
|
+
state.reportIssue(htmlfile, {
|
|
334
334
|
type: 'warn',
|
|
335
335
|
msg: `Failed to download image: ${attrib_src} - ${e.message}`,
|
|
336
336
|
});
|
|
@@ -342,10 +342,10 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
342
342
|
/*
|
|
343
343
|
* Loading image
|
|
344
344
|
*/
|
|
345
|
-
const originalImage = await Resource.loadResource(
|
|
345
|
+
const originalImage = await Resource.loadResource(state.dir, htmlfile, attrib_src);
|
|
346
346
|
// No file -> give up
|
|
347
347
|
if (!originalImage) {
|
|
348
|
-
|
|
348
|
+
state.reportIssue(htmlfile, {
|
|
349
349
|
type: 'erro',
|
|
350
350
|
msg: `Can't find img on disk src="${attrib_src}"`,
|
|
351
351
|
});
|
|
@@ -369,7 +369,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
369
369
|
: 'webp';
|
|
370
370
|
}
|
|
371
371
|
if (config.image.compress) {
|
|
372
|
-
newImage = await compressImage(await originalImage.getData(), {
|
|
372
|
+
newImage = await compressImage(state, await originalImage.getData(), {
|
|
373
373
|
toFormat: srcToFormat,
|
|
374
374
|
resize: { width: config.image.srcset_max_width },
|
|
375
375
|
});
|
|
@@ -378,13 +378,13 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
378
378
|
const newFilename = path.join(path.dirname(originalImage.filePathAbsolute), path.basename(originalImage.filePathAbsolute, path.extname(originalImage.filePathAbsolute)) + `.${newImage.format}`);
|
|
379
379
|
const newSrc = path.join(path.dirname(attrib_src), path.basename(attrib_src, path.extname(attrib_src)) +
|
|
380
380
|
`.${newImage.format}`);
|
|
381
|
-
if (
|
|
381
|
+
if (!state.compressedFiles.has(newFilename) && !state.args.nowrite) {
|
|
382
382
|
fs.writeFile(newFilename, newImage.data);
|
|
383
383
|
}
|
|
384
384
|
// Mark new file as compressed
|
|
385
|
-
|
|
385
|
+
state.compressedFiles.add(newFilename);
|
|
386
386
|
// Report compression result
|
|
387
|
-
|
|
387
|
+
state.reportSummary({
|
|
388
388
|
action: newFilename !== originalImage.filePathAbsolute
|
|
389
389
|
? `${await originalImage.getExt()}->${newImage.format}`
|
|
390
390
|
: path.extname(originalImage.filePathAbsolute),
|
|
@@ -397,7 +397,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
397
397
|
// Drop new Image
|
|
398
398
|
newImage = undefined;
|
|
399
399
|
// Report non-compression
|
|
400
|
-
|
|
400
|
+
state.reportSummary({
|
|
401
401
|
action: path.extname(originalImage.filePathAbsolute),
|
|
402
402
|
originalSize: await originalImage.getLen(),
|
|
403
403
|
compressedSize: await originalImage.getLen(),
|
|
@@ -437,7 +437,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
437
437
|
img.attr('src', datauri);
|
|
438
438
|
img.removeAttr('loading');
|
|
439
439
|
img.removeAttr('decoding');
|
|
440
|
-
|
|
440
|
+
state.reportSummary({
|
|
441
441
|
action: `${imageToEmbed.format}->embed`,
|
|
442
442
|
originalSize: await originalImage.getLen(),
|
|
443
443
|
compressedSize: imageToEmbed.data.length,
|
|
@@ -454,7 +454,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
454
454
|
/*
|
|
455
455
|
* Attribute 'width' & 'height'
|
|
456
456
|
*/
|
|
457
|
-
const [w, h] = await setImageSize(htmlfile, img, originalImage);
|
|
457
|
+
const [w, h] = await setImageSize(state, htmlfile, img, originalImage);
|
|
458
458
|
if (isEmbed) {
|
|
459
459
|
// Image is embed, no need for more processing
|
|
460
460
|
return;
|
|
@@ -479,7 +479,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
479
479
|
// of the srcset
|
|
480
480
|
}
|
|
481
481
|
else {
|
|
482
|
-
const new_srcset = await generateSrcSet(htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
482
|
+
const new_srcset = await generateSrcSet(state, htmlfile, originalImage, img.attr('src'), imageLengthToBeatInSrcSet, { toFormat: srcToFormat });
|
|
483
483
|
if (new_srcset !== null) {
|
|
484
484
|
img.attr('srcset', new_srcset);
|
|
485
485
|
}
|
|
@@ -533,7 +533,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
533
533
|
// Ignore the creation of sources that already exist
|
|
534
534
|
continue;
|
|
535
535
|
}
|
|
536
|
-
const srcset = await generateSrcSet(htmlfile, originalImage, undefined, undefined, {
|
|
536
|
+
const srcset = await generateSrcSet(state, htmlfile, originalImage, undefined, undefined, {
|
|
537
537
|
toFormat: s.format,
|
|
538
538
|
});
|
|
539
539
|
if (!srcset) {
|
|
@@ -547,7 +547,7 @@ async function processImage(htmlfile, img, isAboveTheFold) {
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
const isIncluded = (src, includeConf, excludeConf) => !!src.match(includeConf) && (!excludeConf || !src.match(excludeConf));
|
|
550
|
-
async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc) {
|
|
550
|
+
async function _generateSrcSet({ options }, startSrc, imageWidth, imageHeight, transformSrc) {
|
|
551
551
|
// Start from original image
|
|
552
552
|
let new_srcset = '';
|
|
553
553
|
if (!imageWidth || !imageHeight) {
|
|
@@ -557,8 +557,8 @@ async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc)
|
|
|
557
557
|
// Start reduction
|
|
558
558
|
const step = 300; //px
|
|
559
559
|
let valueW = !startSrc ? imageWidth : imageWidth - step;
|
|
560
|
-
valueW = Math.min(valueW,
|
|
561
|
-
while (valueW >=
|
|
560
|
+
valueW = Math.min(valueW, options.image.srcset_max_width);
|
|
561
|
+
while (valueW >= options.image.srcset_min_width) {
|
|
562
562
|
let src = await transformSrc(valueW);
|
|
563
563
|
if (src) {
|
|
564
564
|
new_srcset += `, ${src} ${valueW}w`;
|
|
@@ -573,7 +573,7 @@ async function _generateSrcSet(startSrc, imageWidth, imageHeight, transformSrc)
|
|
|
573
573
|
}
|
|
574
574
|
return null;
|
|
575
575
|
}
|
|
576
|
-
async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
576
|
+
async function generateSrcSet(state, htmlfile, originalImage, startSrc, startSrcLength, options) {
|
|
577
577
|
const ext = path.extname(originalImage.src);
|
|
578
578
|
const fullbasename = originalImage.src.slice(0, -ext.length);
|
|
579
579
|
const imageSrc = (addition) => {
|
|
@@ -586,12 +586,12 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
586
586
|
const imageWidth = (meta === null || meta === void 0 ? void 0 : meta.width) || 0;
|
|
587
587
|
const imageHeight = (meta === null || meta === void 0 ? void 0 : meta.height) || 0;
|
|
588
588
|
let previousImageSize = startSrcLength || Number.MAX_VALUE;
|
|
589
|
-
return _generateSrcSet(startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
589
|
+
return _generateSrcSet(state, startSrc, imageWidth, imageHeight, async (valueW) => {
|
|
590
590
|
const src = imageSrc(`@${valueW}w`);
|
|
591
|
-
const absoluteFilename = translateSrc(
|
|
591
|
+
const absoluteFilename = translateSrc(state.dir, path.dirname(htmlfile), src);
|
|
592
592
|
// Don't generate srcset file twice
|
|
593
|
-
if (
|
|
594
|
-
const compressedImage = await compressImage(await originalImage.getData(), { ...options, resize: { width: valueW } });
|
|
593
|
+
if (!state.compressedFiles.has(absoluteFilename)) {
|
|
594
|
+
const compressedImage = await compressImage(state, await originalImage.getData(), { ...options, resize: { width: valueW } });
|
|
595
595
|
if (!(compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) ||
|
|
596
596
|
compressedImage.data.length >= previousImageSize) {
|
|
597
597
|
// New image is not compressed or bigger than previous image in srcset
|
|
@@ -600,20 +600,20 @@ async function generateSrcSet(htmlfile, originalImage, startSrc, startSrcLength,
|
|
|
600
600
|
}
|
|
601
601
|
else {
|
|
602
602
|
// Write file
|
|
603
|
-
if (
|
|
603
|
+
if (!state.args.nowrite) {
|
|
604
604
|
fs.writeFile(absoluteFilename, compressedImage.data);
|
|
605
605
|
}
|
|
606
606
|
// Set new previous size to beat
|
|
607
607
|
previousImageSize = compressedImage.data.length;
|
|
608
608
|
// Add file to list avoid recompression
|
|
609
|
-
|
|
609
|
+
state.compressedFiles.add(absoluteFilename);
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
return src;
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
|
-
async function generateSrcSetForCdn(startSrc, cdnTransformer, imageWidth, imageHeight) {
|
|
616
|
-
return _generateSrcSet('', imageWidth, imageHeight, (valueW) => {
|
|
615
|
+
async function generateSrcSetForCdn(state, startSrc, cdnTransformer, imageWidth, imageHeight) {
|
|
616
|
+
return _generateSrcSet(state, '', imageWidth, imageHeight, (valueW) => {
|
|
617
617
|
var _a, _b;
|
|
618
618
|
return (_b = (_a = cdnTransformer({
|
|
619
619
|
url: startSrc,
|
|
@@ -621,19 +621,19 @@ async function generateSrcSetForCdn(startSrc, cdnTransformer, imageWidth, imageH
|
|
|
621
621
|
})) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.replace(UNPIC_DEFAULT_HOST_REGEX, '/');
|
|
622
622
|
});
|
|
623
623
|
}
|
|
624
|
-
async function setImageSize(htmlfile, img, image) {
|
|
624
|
+
async function setImageSize(state, htmlfile, img, image) {
|
|
625
625
|
let width = img.attr('width');
|
|
626
626
|
let height = img.attr('height');
|
|
627
627
|
let width_new = undefined;
|
|
628
628
|
let height_new = undefined;
|
|
629
629
|
const img_fmt = await image.getExt();
|
|
630
|
-
if (img_fmt === 'svg' && !
|
|
630
|
+
if (img_fmt === 'svg' && !state.options.image.svg.add_width_and_height) {
|
|
631
631
|
return [];
|
|
632
632
|
}
|
|
633
633
|
// Check valid values
|
|
634
634
|
if (width !== undefined) {
|
|
635
635
|
if (!isNumeric(width)) {
|
|
636
|
-
|
|
636
|
+
state.reportIssue(htmlfile, {
|
|
637
637
|
type: 'fix',
|
|
638
638
|
msg: `Invalid width attribute format: "${width}" - overriding for ${image.src}`,
|
|
639
639
|
});
|
|
@@ -642,7 +642,7 @@ async function setImageSize(htmlfile, img, image) {
|
|
|
642
642
|
}
|
|
643
643
|
if (height !== undefined) {
|
|
644
644
|
if (!isNumeric(height)) {
|
|
645
|
-
|
|
645
|
+
state.reportIssue(htmlfile, {
|
|
646
646
|
type: 'fix',
|
|
647
647
|
msg: `Invalid height attribute format: "${height}" - overriding for ${image.src}`,
|
|
648
648
|
});
|
|
@@ -750,13 +750,13 @@ async function processIframe(htmlfile, $, imgElement, isAboveTheFold) {
|
|
|
750
750
|
iframe.attr('loading', 'lazy');
|
|
751
751
|
}
|
|
752
752
|
}
|
|
753
|
-
export async function optimize(include, exclude) {
|
|
753
|
+
export async function optimize(state, include, exclude) {
|
|
754
754
|
const glob = include ? [include] : ['**/*.{htm,html}'];
|
|
755
755
|
if (exclude)
|
|
756
756
|
glob.push('!' + exclude);
|
|
757
|
-
const paths = await globby(glob, { cwd:
|
|
757
|
+
const paths = await globby(glob, { cwd: state.dir });
|
|
758
758
|
// Sequential async
|
|
759
759
|
for (const file of paths) {
|
|
760
|
-
await analyse(file);
|
|
760
|
+
await analyse(state, file);
|
|
761
761
|
}
|
|
762
762
|
}
|
|
@@ -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.25.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
|
}
|