@divriots/jampack 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/compress.d.ts +2 -1
- package/dist/compress.js +58 -23
- package/dist/index.js +6 -7
- package/dist/optimize.js +52 -38
- package/dist/options.d.ts +20 -0
- package/dist/options.js +24 -1
- package/dist/state copy.d.ts +25 -0
- package/dist/state copy.js +45 -0
- package/dist/state.d.ts +4 -4
- package/dist/state.js +5 -9
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
```
|
|
2
|
+
__ __
|
|
3
|
+
|__|____ _____ ___________ ____ | | __
|
|
4
|
+
| \__ \ / \\____ \__ \ _/ ___\| |/ /
|
|
5
|
+
| |/ __ \| Y Y \ |_> > __ \\ \___| <
|
|
6
|
+
/\__| (____ /__|_| / __(____ /\___ >__|_ \
|
|
7
|
+
\______| \/ \/| | \/ \/ \/
|
|
8
|
+
|__|
|
|
9
|
+
```
|
|
10
|
+
[](https://npmjs.org/package/@divriots/jampack)
|
|
11
|
+
[](https://discord.gg/XkQxSU9)
|
|
2
12
|
|
|
3
|
-
|
|
13
|
+
# `jampack`
|
|
14
|
+
|
|
15
|
+
Optimizes static websites for best user experience and best Core Web Vitals scores.
|
|
16
|
+
|
|
17
|
+
<div>
|
|
18
|
+
<div>Brought to you by</div>
|
|
19
|
+
<a href="https://divRIOTS.com#gh-light-mode-only">
|
|
20
|
+
<img width="150" height="40" src="https://divRIOTS.com/divriots.svg" alt="‹div›RIOTS" />
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://divRIOTS.com#gh-dark-mode-only">
|
|
23
|
+
<img width="150" height="40" src="https://divRIOTS.com/divriots-dark.svg" alt="‹div›RIOTS" />
|
|
24
|
+
</a>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
## What is `jampack`?
|
|
28
|
+
|
|
29
|
+
> Not a bundler. Not a framework.
|
|
30
|
+
|
|
31
|
+
`jampack` is a post-processing tool that takes the output of your [Static Site Generator (aka SSG)](https://jamstack.org/generators/) and optimizes it for best user experience and best [Core Web Vitals](https://web.dev/learn-core-web-vitals/) scores.
|
|
32
|
+
|
|
33
|
+
## Why `jampack`?
|
|
34
|
+
|
|
35
|
+
- **Better user experience**. Specially on low-end devices.
|
|
36
|
+
- **Better SEO**. Page experience now [affects ranking](https://developers.google.com/search/docs/appearance/page-experience).
|
|
37
|
+
- **Easier maintenance**. Generating top performance webpages requires extra engineering in the composition of the pages that is counterproductive with the maintenance of the website.
|
|
38
|
+
- **Time saving**. Spend less time optimizing and let `jampack` perform the heaving-lifting for you.
|
|
39
|
+
|
|
40
|
+
## Optimizations
|
|
41
|
+
|
|
42
|
+
Growing list of optimizations performed:
|
|
43
|
+
|
|
44
|
+
| Optimization | Description |
|
|
45
|
+
| ------------- | ------------- |
|
|
46
|
+
| todo | todo |
|
|
47
|
+
|
|
48
|
+
## Quick use
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
# Optimize static website in `dist` folder
|
|
52
|
+
npx @divriots/jampack ./dist
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
No configuration yet. All defaults.
|
|
58
|
+
|
|
59
|
+
## `jampack` use in the wild
|
|
60
|
+
|
|
61
|
+
- [divRIOTS.com](https://divRIOTS.com)
|
|
62
|
+
- [Backlight.dev](https://backlight.dev)
|
|
63
|
+
- [story.to.design](https://story.to.design)
|
|
64
|
+
- [Components.studio](https://components.studio)
|
|
65
|
+
> Add yours here
|
|
66
|
+
|
|
67
|
+
## Why is it called `jampack`?
|
|
68
|
+
|
|
69
|
+
- ` jam`: From [Jamstack](https://en.wikipedia.org/wiki/Jamstack).
|
|
70
|
+
- `pack`: Reminescent of the [Executable Packers](https://en.wikipedia.org/wiki/Executable_compression#List_of_executable_packers) from the 90s.
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
This software is released under the terms of the [MIT license](https://github.com/divriots/jampack/blob/main/LICENSE).
|
package/dist/compress.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import sharp from 'sharp';
|
|
2
|
-
|
|
2
|
+
import type { Image } from './types.js';
|
|
3
|
+
export declare const compressImage: (data: Buffer, resize: sharp.ResizeOptions, toWebp?: boolean) => Promise<Image | undefined>;
|
|
3
4
|
export declare function compress(glob: string): Promise<void>;
|
package/dist/compress.js
CHANGED
|
@@ -8,6 +8,7 @@ import sharp from 'sharp';
|
|
|
8
8
|
import swc from '@swc/core';
|
|
9
9
|
import globalState from './state.js';
|
|
10
10
|
import { globby } from 'globby';
|
|
11
|
+
import options from './options.js';
|
|
11
12
|
const beginProgress = () => {
|
|
12
13
|
};
|
|
13
14
|
const printProgress = () => {
|
|
@@ -27,24 +28,18 @@ const endProgress = () => {
|
|
|
27
28
|
process.stdout.write('\n');
|
|
28
29
|
};
|
|
29
30
|
const processFile = async (file, stats) => {
|
|
30
|
-
const result = {
|
|
31
|
-
file,
|
|
32
|
-
originalSize: stats.size,
|
|
33
|
-
compressedSize: stats.size
|
|
34
|
-
};
|
|
35
31
|
let writeData = undefined;
|
|
36
32
|
try {
|
|
37
33
|
const ext = path.extname(file);
|
|
38
34
|
switch (ext) {
|
|
39
|
-
case '.svg':
|
|
40
35
|
case '.png':
|
|
41
36
|
case '.jpg':
|
|
42
37
|
case '.jpeg':
|
|
38
|
+
case '.svg':
|
|
43
39
|
case '.webp':
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
writeData = newImageData;
|
|
40
|
+
const newImage = await compressImageFile(file);
|
|
41
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < stats.size) {
|
|
42
|
+
writeData = newImage.data;
|
|
48
43
|
}
|
|
49
44
|
break;
|
|
50
45
|
case '.html':
|
|
@@ -74,6 +69,11 @@ const processFile = async (file, stats) => {
|
|
|
74
69
|
console.error(`\n${file}`);
|
|
75
70
|
console.error(e);
|
|
76
71
|
}
|
|
72
|
+
const result = {
|
|
73
|
+
action: path.extname(file),
|
|
74
|
+
originalSize: stats.size,
|
|
75
|
+
compressedSize: stats.size
|
|
76
|
+
};
|
|
77
77
|
// Writedata
|
|
78
78
|
if (writeData && writeData.length < result.originalSize) {
|
|
79
79
|
result.compressedSize = writeData.length;
|
|
@@ -81,12 +81,26 @@ const processFile = async (file, stats) => {
|
|
|
81
81
|
await fs.writeFile(file, writeData);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
globalState.
|
|
84
|
+
globalState.compressedFiles.add(file);
|
|
85
|
+
globalState.reportItem(result);
|
|
85
86
|
printProgress();
|
|
86
87
|
};
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
function createWebpOptions(opt) {
|
|
89
|
+
return {
|
|
90
|
+
nearLossless: (opt === null || opt === void 0 ? void 0 : opt.mode) === 'lossless',
|
|
91
|
+
quality: (opt === null || opt === void 0 ? void 0 : opt.quality) || 80,
|
|
92
|
+
effort: (opt === null || opt === void 0 ? void 0 : opt.effort) || 4
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export const compressImage = async (data, resize, toWebp = false) => {
|
|
96
|
+
let sharpFile = await sharp(data, { animated: true });
|
|
89
97
|
const meta = await sharpFile.metadata();
|
|
98
|
+
if (meta.pages && meta.pages > 1) {
|
|
99
|
+
// Skip animated images for the moment.
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
let doSharp = false;
|
|
103
|
+
let outputFormat;
|
|
90
104
|
switch (meta.format) {
|
|
91
105
|
case 'svg':
|
|
92
106
|
const newData = svgo(data, {
|
|
@@ -105,23 +119,44 @@ export const compressImage = async (data, resize) => {
|
|
|
105
119
|
console.log(`Error processing svg ${data}`);
|
|
106
120
|
return undefined;
|
|
107
121
|
}
|
|
108
|
-
return Buffer.from(newData.data, 'utf8');
|
|
122
|
+
return { format: 'svg', data: Buffer.from(newData.data, 'utf8') };
|
|
109
123
|
case 'png':
|
|
110
|
-
|
|
124
|
+
outputFormat = 'png';
|
|
125
|
+
if (!toWebp) {
|
|
126
|
+
sharpFile = sharpFile.png(options.image.png.options || {});
|
|
127
|
+
}
|
|
128
|
+
doSharp = true;
|
|
129
|
+
break;
|
|
111
130
|
case 'jpeg':
|
|
112
|
-
case '
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
sf = sf.resize({ ...resize, fit: 'fill', withoutEnlargement: true });
|
|
131
|
+
case 'jpg':
|
|
132
|
+
outputFormat = 'jpg';
|
|
133
|
+
if (!toWebp) {
|
|
134
|
+
sharpFile = sharpFile.jpeg(options.image.jpeg.options || {});
|
|
117
135
|
}
|
|
118
|
-
|
|
136
|
+
doSharp = true;
|
|
137
|
+
break;
|
|
138
|
+
case 'webp':
|
|
139
|
+
outputFormat = 'webp';
|
|
140
|
+
sharpFile = sharpFile.webp(createWebpOptions(options.image.webp.options_lossly) || {});
|
|
141
|
+
doSharp = true;
|
|
142
|
+
toWebp = false; // Don't need to convert to webP again
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
if (doSharp) {
|
|
146
|
+
if (toWebp) {
|
|
147
|
+
sharpFile = sharpFile.webp(createWebpOptions(meta.format === 'png' ? options.image.webp.options_lossless : options.image.webp.options_lossly));
|
|
148
|
+
outputFormat = 'webp';
|
|
149
|
+
}
|
|
150
|
+
if (resize.width && resize.height) {
|
|
151
|
+
sharpFile = sharpFile.resize({ ...resize, fit: 'fill', withoutEnlargement: true });
|
|
152
|
+
}
|
|
153
|
+
return { format: outputFormat, data: await sharpFile.toBuffer() };
|
|
119
154
|
}
|
|
120
155
|
return undefined;
|
|
121
156
|
};
|
|
122
|
-
const compressImageFile = async (file) => {
|
|
157
|
+
const compressImageFile = async (file, toWebP = false) => {
|
|
123
158
|
const buffer = await fs.readFile(file);
|
|
124
|
-
return compressImage(buffer, {});
|
|
159
|
+
return compressImage(buffer, {}, toWebP);
|
|
125
160
|
};
|
|
126
161
|
export async function compress(glob) {
|
|
127
162
|
beginProgress();
|
package/dist/index.js
CHANGED
|
@@ -29,28 +29,27 @@ program.command('pack', { isDefault: true })
|
|
|
29
29
|
.action(async (dir, options) => {
|
|
30
30
|
globalState.dir = dir;
|
|
31
31
|
globalState.args = options;
|
|
32
|
-
if (!options.onlyoptim && !options.onlycomp) {
|
|
33
|
-
console.log(kleur.bgGreen(kleur.black(`\n PASS 1 - Compressing embeddables `)));
|
|
34
|
-
await compress("**/*.{css,svg,js}");
|
|
35
|
-
}
|
|
36
32
|
if (!options.onlycomp) {
|
|
37
|
-
console.log(
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(kleur.bgGreen(kleur.black(` PASS 1 - Optimizing `)));
|
|
38
35
|
await optimize();
|
|
39
36
|
}
|
|
40
37
|
if (!options.onlyoptim) {
|
|
41
|
-
console.log(
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(kleur.bgGreen(kleur.black(` PASS 2 - Compressing the rest`)));
|
|
42
40
|
await compress("**/**");
|
|
43
41
|
}
|
|
44
42
|
printDetails();
|
|
45
43
|
});
|
|
46
44
|
program.parse();
|
|
47
45
|
function printDetails() {
|
|
48
|
-
const dataTable = [['
|
|
46
|
+
const dataTable = [['Action', 'Compressed', 'Original', 'Compressed', 'Gain']];
|
|
49
47
|
const config = {
|
|
50
48
|
columns: [
|
|
51
49
|
{ alignment: 'left' },
|
|
52
50
|
{ alignment: 'right' },
|
|
53
51
|
{ alignment: 'right' },
|
|
52
|
+
{ alignment: 'right' },
|
|
54
53
|
{ alignment: 'right' }
|
|
55
54
|
],
|
|
56
55
|
};
|
package/dist/optimize.js
CHANGED
|
@@ -83,7 +83,7 @@ async function processImage(file, $, imgElement) {
|
|
|
83
83
|
const absoluteImgPath = translateSrc(globalState.dir, path.dirname(file), attrib_src);
|
|
84
84
|
// file exists?
|
|
85
85
|
try {
|
|
86
|
-
|
|
86
|
+
await fs.stat(absoluteImgPath);
|
|
87
87
|
}
|
|
88
88
|
catch (e) {
|
|
89
89
|
console.error(`Missing img ${absoluteImgPath}`);
|
|
@@ -93,23 +93,51 @@ async function processImage(file, $, imgElement) {
|
|
|
93
93
|
const meta = await sharpFile.metadata();
|
|
94
94
|
const originalData = await fs.readFile(absoluteImgPath);
|
|
95
95
|
/*
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
* Compress image to WebP
|
|
97
|
+
*/
|
|
98
|
+
let newImage;
|
|
99
|
+
if (!globalState.optimizedFiles.has(absoluteImgPath)) {
|
|
100
|
+
// Let's avoid to optimize same images twice
|
|
101
|
+
globalState.optimizedFiles.add(absoluteImgPath);
|
|
102
|
+
newImage = await compressImage(originalData, {}, true);
|
|
103
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < originalData.length) {
|
|
104
|
+
// Do we need to add an new extension?
|
|
105
|
+
const newExtension = `.${newImage.format}`;
|
|
106
|
+
const additionalExtension = path.extname(absoluteImgPath) === newExtension ? '' : newExtension;
|
|
107
|
+
const newFilename = absoluteImgPath + additionalExtension;
|
|
108
|
+
if (!globalState.args.nowrite) {
|
|
109
|
+
fs.writeFile(newFilename, newImage.data);
|
|
110
|
+
}
|
|
111
|
+
globalState.compressedFiles.add(newFilename);
|
|
112
|
+
// Report compression result
|
|
113
|
+
globalState.reportItem({
|
|
114
|
+
action: (newFilename !== absoluteImgPath) ? `${meta.format}->${newImage.format}` : path.extname(absoluteImgPath),
|
|
115
|
+
originalSize: originalData.length,
|
|
116
|
+
compressedSize: newImage.data.length
|
|
117
|
+
});
|
|
118
|
+
img.attr('src', attrib_src + additionalExtension);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/*
|
|
122
|
+
* Embed small images
|
|
123
|
+
*
|
|
124
|
+
* TODO this is only embedding images that have
|
|
125
|
+
* successfully be compressed. Should embed original
|
|
126
|
+
* image if it fits the size.
|
|
127
|
+
*/
|
|
98
128
|
let isEmbed = false;
|
|
99
|
-
if (
|
|
129
|
+
if (newImage && newImage.data.length <= options.image.embed_size) {
|
|
100
130
|
let datauri = undefined;
|
|
101
|
-
switch (
|
|
131
|
+
switch (newImage.format) {
|
|
102
132
|
case 'svg':
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
datauri = svgToMiniDataURI(newImage.data.toString());
|
|
134
|
+
break;
|
|
135
|
+
case 'webp':
|
|
136
|
+
datauri = `data:image/webp;base64,${newImage.data.toString('base64')}`;
|
|
106
137
|
break;
|
|
107
138
|
case 'jpg':
|
|
108
|
-
case 'jpeg':
|
|
109
139
|
case 'png':
|
|
110
|
-
|
|
111
|
-
//case 'gif':
|
|
112
|
-
// TODO
|
|
140
|
+
// TODO but not possible in current code
|
|
113
141
|
break;
|
|
114
142
|
}
|
|
115
143
|
if (datauri) {
|
|
@@ -117,31 +145,13 @@ async function processImage(file, $, imgElement) {
|
|
|
117
145
|
img.attr('src', datauri);
|
|
118
146
|
img.removeAttr('loading');
|
|
119
147
|
img.removeAttr('decoding');
|
|
148
|
+
globalState.reportItem({ action: `${newImage.format}->embed`, originalSize: originalData.length, compressedSize: newImage.data.length });
|
|
120
149
|
}
|
|
121
150
|
}
|
|
122
151
|
if (isEmbed) {
|
|
123
152
|
// Image is embed, no need for more processing
|
|
124
153
|
return;
|
|
125
154
|
}
|
|
126
|
-
/*
|
|
127
|
-
* Compress image
|
|
128
|
-
*/
|
|
129
|
-
if (!globalState.compressedFiles.has(absoluteImgPath)) {
|
|
130
|
-
// Image not already compressed before
|
|
131
|
-
const result = {
|
|
132
|
-
file: absoluteImgPath,
|
|
133
|
-
originalSize: originalData.length,
|
|
134
|
-
compressedSize: originalData.length
|
|
135
|
-
};
|
|
136
|
-
const newImage = await compressImage(originalData, {});
|
|
137
|
-
if (newImage && newImage.length < originalData.length) {
|
|
138
|
-
if (!globalState.args.nowrite) {
|
|
139
|
-
fs.writeFile(absoluteImgPath, newImage);
|
|
140
|
-
}
|
|
141
|
-
result.compressedSize = newImage.length;
|
|
142
|
-
}
|
|
143
|
-
globalState.addFile(result);
|
|
144
|
-
}
|
|
145
155
|
//
|
|
146
156
|
// Stop here if svg
|
|
147
157
|
//
|
|
@@ -168,7 +178,7 @@ async function processImage(file, $, imgElement) {
|
|
|
168
178
|
// Generate image set
|
|
169
179
|
const ext = path.extname(attrib_src);
|
|
170
180
|
const fullbasename = attrib_src.slice(0, -(ext.length));
|
|
171
|
-
const imageSrc = (addition) => `${fullbasename}${addition}
|
|
181
|
+
const imageSrc = (addition) => `${fullbasename}${addition}.webp`;
|
|
172
182
|
// Start from original image
|
|
173
183
|
let new_srcset = '';
|
|
174
184
|
// Start reduction
|
|
@@ -178,20 +188,24 @@ async function processImage(file, $, imgElement) {
|
|
|
178
188
|
let valueH = Math.trunc(valueW / ratio);
|
|
179
189
|
while (valueW > options.image.srcset_min_width) {
|
|
180
190
|
const src = imageSrc(`@${valueW}w`);
|
|
191
|
+
console.log(`Generating srcset ${src}`);
|
|
181
192
|
const absoluteFilename = translateSrc(globalState.dir, path.dirname(file), src);
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
193
|
+
// Don't generate srcset file twice
|
|
194
|
+
if (!globalState.compressedFiles.has(absoluteFilename)) {
|
|
195
|
+
// Add file to list avoid recompression
|
|
196
|
+
globalState.compressedFiles.add(absoluteFilename);
|
|
197
|
+
const compressedImage = await compressImage(originalData, { width: valueW, height: valueH }, true);
|
|
198
|
+
if ((compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) && !globalState.args.nowrite) {
|
|
199
|
+
fs.writeFile(absoluteFilename, compressedImage.data);
|
|
200
|
+
}
|
|
185
201
|
}
|
|
186
|
-
// Add file to list avoid recompression
|
|
187
|
-
globalState.compressedFiles.add(absoluteFilename);
|
|
188
202
|
new_srcset += `, ${src} ${valueW}w`;
|
|
189
203
|
// reduce size
|
|
190
204
|
valueW -= step;
|
|
191
205
|
valueH = Math.trunc(valueW / ratio);
|
|
192
206
|
}
|
|
193
207
|
if (new_srcset) {
|
|
194
|
-
img.attr('srcset', `${
|
|
208
|
+
img.attr('srcset', `${img.attr('src')} ${w}w` + new_srcset);
|
|
195
209
|
}
|
|
196
210
|
}
|
|
197
211
|
}
|
package/dist/options.d.ts
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
export declare type WebpOptions = {
|
|
2
|
+
effort?: number;
|
|
3
|
+
mode?: 'lossless' | 'lossly';
|
|
4
|
+
quality?: number;
|
|
5
|
+
};
|
|
1
6
|
export declare type Options = {
|
|
2
7
|
image: {
|
|
3
8
|
embed_size: number;
|
|
4
9
|
srcset_min_width: number;
|
|
5
10
|
srcset_max_width: number;
|
|
11
|
+
jpeg: {
|
|
12
|
+
options?: {
|
|
13
|
+
quality?: number;
|
|
14
|
+
mozjpeg?: boolean;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
png: {
|
|
18
|
+
options?: {
|
|
19
|
+
compressionLevel?: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
webp: {
|
|
23
|
+
options_lossless?: WebpOptions;
|
|
24
|
+
options_lossly?: WebpOptions;
|
|
25
|
+
};
|
|
6
26
|
};
|
|
7
27
|
};
|
|
8
28
|
declare const default_options: Options;
|
package/dist/options.js
CHANGED
|
@@ -2,7 +2,30 @@ const default_options = {
|
|
|
2
2
|
image: {
|
|
3
3
|
embed_size: 400,
|
|
4
4
|
srcset_min_width: 400,
|
|
5
|
-
srcset_max_width: 5120
|
|
5
|
+
srcset_max_width: 5120,
|
|
6
|
+
jpeg: {
|
|
7
|
+
options: {
|
|
8
|
+
quality: 80,
|
|
9
|
+
mozjpeg: true
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
png: {
|
|
13
|
+
options: {
|
|
14
|
+
compressionLevel: 9,
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
webp: {
|
|
18
|
+
options_lossless: {
|
|
19
|
+
effort: 4,
|
|
20
|
+
quality: 80,
|
|
21
|
+
mode: 'lossless'
|
|
22
|
+
},
|
|
23
|
+
options_lossly: {
|
|
24
|
+
effort: 4,
|
|
25
|
+
quality: 80,
|
|
26
|
+
mode: 'lossly'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
6
29
|
}
|
|
7
30
|
};
|
|
8
31
|
export default default_options;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare type Args = {
|
|
2
|
+
nowrite?: boolean;
|
|
3
|
+
};
|
|
4
|
+
export declare type Result = {
|
|
5
|
+
file: string;
|
|
6
|
+
originalSize: number;
|
|
7
|
+
compressedSize: number;
|
|
8
|
+
};
|
|
9
|
+
export declare type Summary = {
|
|
10
|
+
nbFiles: number;
|
|
11
|
+
nbFilesCompressed: number;
|
|
12
|
+
dataLenUncompressed: number;
|
|
13
|
+
dataLenCompressed: number;
|
|
14
|
+
};
|
|
15
|
+
export declare class GlobalState {
|
|
16
|
+
dir: string;
|
|
17
|
+
args: Args;
|
|
18
|
+
compressedFiles: Set<string>;
|
|
19
|
+
compressedFilesResult: Result[];
|
|
20
|
+
summary: Summary;
|
|
21
|
+
summaryByExtension: Record<string, Summary>;
|
|
22
|
+
addFile(r: Result): void;
|
|
23
|
+
}
|
|
24
|
+
declare const state: GlobalState;
|
|
25
|
+
export default state;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
export class GlobalState {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.dir = 'dist';
|
|
5
|
+
this.args = {};
|
|
6
|
+
this.compressedFiles = new Set();
|
|
7
|
+
this.compressedFilesResult = [];
|
|
8
|
+
this.summary = {
|
|
9
|
+
nbFiles: 0,
|
|
10
|
+
nbFilesCompressed: 0,
|
|
11
|
+
dataLenCompressed: 0,
|
|
12
|
+
dataLenUncompressed: 0
|
|
13
|
+
};
|
|
14
|
+
this.summaryByExtension = {};
|
|
15
|
+
}
|
|
16
|
+
addFile(r) {
|
|
17
|
+
const isCompressed = r.compressedSize < r.originalSize ? 1 : 0;
|
|
18
|
+
this.compressedFiles.add(r.file);
|
|
19
|
+
this.compressedFilesResult.push(r);
|
|
20
|
+
this.summary.nbFiles++;
|
|
21
|
+
state.summary.nbFilesCompressed += isCompressed;
|
|
22
|
+
state.summary.dataLenUncompressed += r.originalSize;
|
|
23
|
+
state.summary.dataLenCompressed += r.compressedSize;
|
|
24
|
+
const ext = path.extname(r.file);
|
|
25
|
+
if (ext) {
|
|
26
|
+
let summary = this.summaryByExtension[ext];
|
|
27
|
+
if (!summary) {
|
|
28
|
+
summary = {
|
|
29
|
+
nbFiles: 0,
|
|
30
|
+
nbFilesCompressed: 0,
|
|
31
|
+
dataLenUncompressed: 0,
|
|
32
|
+
dataLenCompressed: 0
|
|
33
|
+
};
|
|
34
|
+
this.summaryByExtension[ext] = summary;
|
|
35
|
+
}
|
|
36
|
+
summary.nbFiles++;
|
|
37
|
+
summary.nbFilesCompressed += isCompressed;
|
|
38
|
+
summary.dataLenUncompressed += r.originalSize;
|
|
39
|
+
summary.dataLenCompressed += r.compressedSize;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
;
|
|
43
|
+
}
|
|
44
|
+
const state = new GlobalState();
|
|
45
|
+
export default state;
|
package/dist/state.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export declare type Args = {
|
|
2
2
|
nowrite?: boolean;
|
|
3
3
|
};
|
|
4
|
-
export declare type
|
|
5
|
-
|
|
4
|
+
export declare type ReportItem = {
|
|
5
|
+
action: string;
|
|
6
6
|
originalSize: number;
|
|
7
7
|
compressedSize: number;
|
|
8
8
|
};
|
|
@@ -16,10 +16,10 @@ export declare class GlobalState {
|
|
|
16
16
|
dir: string;
|
|
17
17
|
args: Args;
|
|
18
18
|
compressedFiles: Set<string>;
|
|
19
|
-
|
|
19
|
+
optimizedFiles: Set<string>;
|
|
20
20
|
summary: Summary;
|
|
21
21
|
summaryByExtension: Record<string, Summary>;
|
|
22
|
-
|
|
22
|
+
reportItem(r: ReportItem): void;
|
|
23
23
|
}
|
|
24
24
|
declare const state: GlobalState;
|
|
25
25
|
export default state;
|
package/dist/state.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import path from "path";
|
|
2
1
|
export class GlobalState {
|
|
3
2
|
constructor() {
|
|
4
3
|
this.dir = 'dist';
|
|
5
4
|
this.args = {};
|
|
6
5
|
this.compressedFiles = new Set();
|
|
7
|
-
this.
|
|
6
|
+
this.optimizedFiles = new Set();
|
|
8
7
|
this.summary = {
|
|
9
8
|
nbFiles: 0,
|
|
10
9
|
nbFilesCompressed: 0,
|
|
@@ -13,17 +12,14 @@ export class GlobalState {
|
|
|
13
12
|
};
|
|
14
13
|
this.summaryByExtension = {};
|
|
15
14
|
}
|
|
16
|
-
|
|
15
|
+
reportItem(r) {
|
|
17
16
|
const isCompressed = r.compressedSize < r.originalSize ? 1 : 0;
|
|
18
|
-
this.compressedFiles.add(r.file);
|
|
19
|
-
this.compressedFilesResult.push(r);
|
|
20
17
|
this.summary.nbFiles++;
|
|
21
18
|
state.summary.nbFilesCompressed += isCompressed;
|
|
22
19
|
state.summary.dataLenUncompressed += r.originalSize;
|
|
23
20
|
state.summary.dataLenCompressed += r.compressedSize;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let summary = this.summaryByExtension[ext];
|
|
21
|
+
if (r.action) {
|
|
22
|
+
let summary = this.summaryByExtension[r.action];
|
|
27
23
|
if (!summary) {
|
|
28
24
|
summary = {
|
|
29
25
|
nbFiles: 0,
|
|
@@ -31,7 +27,7 @@ export class GlobalState {
|
|
|
31
27
|
dataLenUncompressed: 0,
|
|
32
28
|
dataLenCompressed: 0
|
|
33
29
|
};
|
|
34
|
-
this.summaryByExtension[
|
|
30
|
+
this.summaryByExtension[r.action] = summary;
|
|
35
31
|
}
|
|
36
32
|
summary.nbFiles++;
|
|
37
33
|
summary.nbFilesCompressed += isCompressed;
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|