@divriots/jampack 0.2.4 → 0.4.1
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 +37 -59
- package/dist/cache.d.ts +7 -0
- package/dist/cache.js +52 -0
- package/dist/compress.d.ts +4 -1
- package/dist/compress.js +45 -14
- package/dist/config.d.ts +2 -2
- package/dist/index.js +8 -1
- package/dist/optimize.js +143 -90
- package/dist/state.d.ts +5 -4
- package/dist/types.d.ts +2 -2
- package/dist/utils/resource.d.ts +1 -1
- package/dist/utils/resource.js +7 -3
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
|
|
1
3
|
```
|
|
2
4
|
__ __
|
|
3
5
|
|__|____ _____ ___________ ____ | | __
|
|
@@ -7,14 +9,17 @@
|
|
|
7
9
|
\______| \/ \/| | \/ \/ \/
|
|
8
10
|
|__|
|
|
9
11
|
```
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
10
15
|
[](https://npmjs.org/package/@divriots/jampack)
|
|
11
16
|
[](https://discord.gg/XkQxSU9)
|
|
12
17
|
|
|
13
|
-
#
|
|
18
|
+
# Jampack
|
|
14
19
|
|
|
15
20
|
Optimizes static websites for best user experience and best Core Web Vitals scores.
|
|
16
21
|
|
|
17
|
-
<div>
|
|
22
|
+
<div id="banner">
|
|
18
23
|
<div>Brought to you by</div>
|
|
19
24
|
<a href="https://divRIOTS.com#gh-light-mode-only">
|
|
20
25
|
<img width="150" height="40" src="https://divRIOTS.com/divriots.svg" alt="‹div›RIOTS" />
|
|
@@ -32,47 +37,11 @@ Optimizes static websites for best user experience and best Core Web Vitals scor
|
|
|
32
37
|
|
|
33
38
|
## Why `jampack`?
|
|
34
39
|
|
|
35
|
-
- **Better user experience
|
|
40
|
+
- **Better user experience** for your website visitors. Specially on low-end devices.
|
|
36
41
|
- **Better SEO**. Page experience now [affects ranking](https://developers.google.com/search/docs/appearance/page-experience).
|
|
37
42
|
- **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
43
|
- **Time saving**. Spend less time optimizing and let `jampack` perform the heaving-lifting for you.
|
|
39
44
|
|
|
40
|
-
## Optimizations
|
|
41
|
-
|
|
42
|
-
### PASS 1 - Optimizations
|
|
43
|
-
|
|
44
|
-
This pass takes all `*.{html|.htm}` files and parse them for optimisation.
|
|
45
|
-
|
|
46
|
-
| Optimization | Technical details | Related links | Available |
|
|
47
|
-
| -------------- | --------------------- |:------------:|:---------:|
|
|
48
|
-
| 🔽 `<img>` | | | |
|
|
49
|
-
| Optimize `png` to `WebP` | Given `foo.png`, creates a **near loss less** `foo.png.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
50
|
-
| Optimize `jpeg` to `WebP` | Given `foo.jpg`, creates a **lossly** `foo.jpg.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
51
|
-
| Optimize `svg` | | | ✅ |
|
|
52
|
-
| Generate responsive images and set `srcset` | Multiple smaller images are generated exclusively in `WebP` and `srcset` attribute is set accordingly. | [web.dev](https://web.dev/patterns/web-vitals-patterns/images/responsive-images/) | ✅ |
|
|
53
|
-
| Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
|
|
54
|
-
| Set `width` and `height` if missing | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
55
|
-
| Fix `width` or `height` if not numerical | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
56
|
-
| Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
|
|
57
|
-
| Set async decoding by default | Sets `decoding="async"`. | | ✅ |
|
|
58
|
-
| Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
|
|
59
|
-
| 🔽 `<picture>` | | | |
|
|
60
|
-
| - TODO | | | ❌ |
|
|
61
|
-
|
|
62
|
-
### PASS 2 - Compression
|
|
63
|
-
|
|
64
|
-
This pass compresses all untouched files without changing filename or format.
|
|
65
|
-
|
|
66
|
-
| Extension | Compressor | Some options |
|
|
67
|
-
| --------------- | --------------------- | ------------ |
|
|
68
|
-
| `.html`,`.htm` | html-minifier-terser | |
|
|
69
|
-
| `.css` | csso | |
|
|
70
|
-
| `.js` | swc | |
|
|
71
|
-
| `.svg` | svgo | |
|
|
72
|
-
| `.jpg`,`.jpeg` | sharp | |
|
|
73
|
-
| `.png` | sharp | |
|
|
74
|
-
| `.webp` | sharp | |
|
|
75
|
-
|
|
76
45
|
## Quick use
|
|
77
46
|
|
|
78
47
|
```sh
|
|
@@ -80,34 +49,20 @@ This pass compresses all untouched files without changing filename or format.
|
|
|
80
49
|
npx @divriots/jampack ./dist
|
|
81
50
|
```
|
|
82
51
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| Option | Description |
|
|
86
|
-
| ----------- | ------------------------------ |
|
|
87
|
-
| `--onlyoptim` | Only runs optimization (PASS 1) |
|
|
88
|
-
| `--onlycomp` | Only runs compression (PASS 2) |
|
|
89
|
-
| `--fast` | Go fast. Mostly no compression just checks for issues. |
|
|
90
|
-
| `--fail` | Exits with a non-zero return code if issues. |
|
|
91
|
-
| `--nowrite` | Don't write anything to disk (for testing) |
|
|
92
|
-
| `--exclude` | Files to exclude from processing. Expect glob format like `--exclude 'blog/**'` |
|
|
93
|
-
|
|
94
|
-
## Configuration
|
|
95
|
-
|
|
96
|
-
No configuration yet. All defaults.
|
|
52
|
+
For more options see [CLI options](https://jampack.divriots.com/cli-options).
|
|
97
53
|
|
|
98
|
-
##
|
|
54
|
+
## Documentation
|
|
99
55
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
### Add `loading="eager"` to your images above the fold
|
|
103
|
-
|
|
104
|
-
`jampack` sets all images to lazy by default. But, [images above the fold should not be lazy](https://web.dev/lazy-loading-images/#effects-on-largest-contentful-paint-lcp). Just add `loading="eager"` to your images above the fold and `jampack` will treat them as high priority.
|
|
56
|
+
Check the [Documentation side](https://jampack.divriots.com).
|
|
105
57
|
|
|
106
58
|
## `jampack` use in the wild
|
|
107
59
|
|
|
108
60
|
- [divRIOTS.com](https://divRIOTS.com)
|
|
109
61
|
- [Backlight.dev](https://backlight.dev)
|
|
110
62
|
- [story.to.design](https://story.to.design)
|
|
63
|
+
- [html.to.design](https://html.to.design/docs)
|
|
64
|
+
- [WebComponents.dev](https://WebComponents.dev)
|
|
65
|
+
- [LWC.studio](https://lwc.studio)
|
|
111
66
|
- [Components.studio](https://components.studio)
|
|
112
67
|
> Add yours here
|
|
113
68
|
|
|
@@ -119,3 +74,26 @@ No configuration yet. All defaults.
|
|
|
119
74
|
## License
|
|
120
75
|
|
|
121
76
|
This software is released under the terms of the [MIT license](https://github.com/divriots/jampack/blob/main/LICENSE).
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Optimizations
|
|
80
|
+
|
|
81
|
+
### PASS 1 - Optimizations
|
|
82
|
+
|
|
83
|
+
This pass takes all `*.{html|.htm}` files and parse them for optimisation.
|
|
84
|
+
|
|
85
|
+
| Optimization | Technical details | Related links | Available |
|
|
86
|
+
| -------------- | --------------------- |:------------:|:---------:|
|
|
87
|
+
| 🔽 `<img>` | | | |
|
|
88
|
+
| Optimize `png` to `WebP` | Given `foo.png`, creates a **near loss less** `foo.png.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
89
|
+
| Optimize `jpeg` to `WebP` | Given `foo.jpg`, creates a **lossly** `foo.jpg.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
90
|
+
| Optimize `svg` | | | ✅ |
|
|
91
|
+
| Generate responsive images and set `srcset` | Multiple smaller images are generated exclusively in `WebP` and `srcset` attribute is set accordingly. | [web.dev](https://web.dev/patterns/web-vitals-patterns/images/responsive-images/) | ✅ |
|
|
92
|
+
| Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
|
|
93
|
+
| Set `width` and `height` if missing | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
94
|
+
| Fix `width` or `height` if not numerical | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
95
|
+
| Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
|
|
96
|
+
| Set async decoding by default | Sets `decoding="async"`. | | ✅ |
|
|
97
|
+
| Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
|
|
98
|
+
| 🔽 `<picture>` | | | |
|
|
99
|
+
| - TODO | | | ❌ |
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Image } from "./types.js";
|
|
3
|
+
declare function cleanCache(): Promise<void>;
|
|
4
|
+
declare function computeCacheHash(buffer: Buffer, options?: any): string;
|
|
5
|
+
declare function getFromCacheCompressImage(hash: string): Promise<Image | undefined>;
|
|
6
|
+
declare function addToCacheCompressImage(hash: string, image: Image): Promise<void>;
|
|
7
|
+
export { cleanCache, computeCacheHash, getFromCacheCompressImage, addToCacheCompressImage };
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import hasha from "hasha";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import { VERSION } from './version.js';
|
|
5
|
+
import $state from './state.js';
|
|
6
|
+
const CACHE_FOLDER = ".jampack/cache";
|
|
7
|
+
async function cleanCache() {
|
|
8
|
+
try {
|
|
9
|
+
await fs.rm(CACHE_FOLDER, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
// Nothing to do, probably not present
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function computeCacheHash(buffer, options) {
|
|
16
|
+
if ($state.args.nocache) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
let hash = `${VERSION}/${hasha(buffer, { algorithm: 'sha256' })}`;
|
|
20
|
+
if (options) {
|
|
21
|
+
hash += "/" + hasha(JSON.stringify(options), { algorithm: 'md5' });
|
|
22
|
+
}
|
|
23
|
+
return hash;
|
|
24
|
+
}
|
|
25
|
+
function getLocation(hash) {
|
|
26
|
+
return path.join(CACHE_FOLDER, hash);
|
|
27
|
+
}
|
|
28
|
+
async function getFromCacheCompressImage(hash) {
|
|
29
|
+
if ($state.args.nocache) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const location = getLocation(hash);
|
|
33
|
+
try {
|
|
34
|
+
const data = await fs.readFile(path.join(location, 'data'));
|
|
35
|
+
const format = JSON.parse((await fs.readFile(path.join(location, 'format'))).toString());
|
|
36
|
+
return { data, format };
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Problem during cache loading or not in cache
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
async function addToCacheCompressImage(hash, image) {
|
|
44
|
+
if ($state.args.nocache) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const location = getLocation(hash);
|
|
48
|
+
await fs.mkdir(location, { recursive: true });
|
|
49
|
+
await fs.writeFile(path.join(location, 'data'), image.data);
|
|
50
|
+
await fs.writeFile(path.join(location, 'format'), JSON.stringify(image.format));
|
|
51
|
+
}
|
|
52
|
+
export { cleanCache, computeCacheHash, getFromCacheCompressImage, addToCacheCompressImage };
|
package/dist/compress.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import sharp from 'sharp';
|
|
3
3
|
import type { Image } from './types.js';
|
|
4
|
-
export declare const compressImage: (data: Buffer,
|
|
4
|
+
export declare const compressImage: (data: Buffer, options: {
|
|
5
|
+
resize?: sharp.ResizeOptions;
|
|
6
|
+
toFormat?: 'webp' | 'pjpg' | 'unchanged';
|
|
7
|
+
}) => Promise<Image | undefined>;
|
|
5
8
|
export declare function compress(exclude: string): Promise<void>;
|
package/dist/compress.js
CHANGED
|
@@ -10,6 +10,17 @@ import $state from './state.js';
|
|
|
10
10
|
import { globby } from 'globby';
|
|
11
11
|
import config from './config.js';
|
|
12
12
|
import ora from 'ora';
|
|
13
|
+
import { getFromCacheCompressImage, addToCacheCompressImage, computeCacheHash } from './cache.js';
|
|
14
|
+
async function compressJS(code) {
|
|
15
|
+
const newjsresult = await swc.minify(code, { compress: true, mangle: true });
|
|
16
|
+
return newjsresult.code;
|
|
17
|
+
}
|
|
18
|
+
async function compressInlineJS(code) {
|
|
19
|
+
const newCode = await compressJS(code);
|
|
20
|
+
if (newCode && newCode.length < code.length)
|
|
21
|
+
return newCode;
|
|
22
|
+
return code;
|
|
23
|
+
}
|
|
13
24
|
const processFile = async (file, stats) => {
|
|
14
25
|
let writeData = undefined;
|
|
15
26
|
try {
|
|
@@ -28,7 +39,7 @@ const processFile = async (file, stats) => {
|
|
|
28
39
|
case '.html':
|
|
29
40
|
case '.htm':
|
|
30
41
|
const htmldata = await fs.readFile(file);
|
|
31
|
-
const newhtmlData = await htmlminifier(htmldata.toString(), { minifyCSS: true, minifyJS:
|
|
42
|
+
const newhtmlData = await htmlminifier(htmldata.toString(), { minifyCSS: true, minifyJS: compressInlineJS, sortClassName: true, sortAttributes: true });
|
|
32
43
|
writeData = newhtmlData;
|
|
33
44
|
break;
|
|
34
45
|
case '.css':
|
|
@@ -40,9 +51,9 @@ const processFile = async (file, stats) => {
|
|
|
40
51
|
break;
|
|
41
52
|
case '.js':
|
|
42
53
|
const jsdata = await fs.readFile(file);
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
writeData =
|
|
54
|
+
const newJS = await compressJS(jsdata.toString());
|
|
55
|
+
if (newJS && newJS.length < jsdata.length) {
|
|
56
|
+
writeData = newJS;
|
|
46
57
|
}
|
|
47
58
|
break;
|
|
48
59
|
}
|
|
@@ -74,7 +85,15 @@ function createWebpOptions(opt) {
|
|
|
74
85
|
effort: opt.effort
|
|
75
86
|
};
|
|
76
87
|
}
|
|
77
|
-
export const compressImage = async (data,
|
|
88
|
+
export const compressImage = async (data, options) => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const cacheHash = computeCacheHash(data, options);
|
|
91
|
+
const imageFromCache = await getFromCacheCompressImage(cacheHash);
|
|
92
|
+
if (imageFromCache) {
|
|
93
|
+
return imageFromCache;
|
|
94
|
+
}
|
|
95
|
+
// Load modifiable toFormat
|
|
96
|
+
let toFormat = options.toFormat || 'unchanged';
|
|
78
97
|
if (!config.image.compress)
|
|
79
98
|
return undefined;
|
|
80
99
|
let sharpFile = await sharp(data, { animated: true });
|
|
@@ -106,7 +125,7 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
106
125
|
return { format: 'svg', data: Buffer.from(newData.data, 'utf8') };
|
|
107
126
|
case 'png':
|
|
108
127
|
outputFormat = 'png';
|
|
109
|
-
if (
|
|
128
|
+
if (toFormat === 'unchanged') {
|
|
110
129
|
sharpFile = sharpFile.png(config.image.png.options || {});
|
|
111
130
|
}
|
|
112
131
|
doSharp = true;
|
|
@@ -114,7 +133,7 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
114
133
|
case 'jpeg':
|
|
115
134
|
case 'jpg':
|
|
116
135
|
outputFormat = 'jpg';
|
|
117
|
-
if (
|
|
136
|
+
if (toFormat === 'unchanged') {
|
|
118
137
|
sharpFile = sharpFile.jpeg(config.image.jpeg.options || {});
|
|
119
138
|
}
|
|
120
139
|
doSharp = true;
|
|
@@ -123,24 +142,36 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
123
142
|
outputFormat = 'webp';
|
|
124
143
|
sharpFile = sharpFile.webp(createWebpOptions(config.image.webp.options_lossly) || {});
|
|
125
144
|
doSharp = true;
|
|
126
|
-
|
|
145
|
+
toFormat = 'unchanged'; // Don't need to convert to webP again
|
|
127
146
|
break;
|
|
128
147
|
}
|
|
129
148
|
if (doSharp) {
|
|
130
|
-
if (
|
|
149
|
+
if (toFormat === 'webp') {
|
|
131
150
|
sharpFile = sharpFile.webp(createWebpOptions(meta.format === 'png' ? config.image.webp.options_lossless : config.image.webp.options_lossly));
|
|
132
151
|
outputFormat = 'webp';
|
|
133
152
|
}
|
|
134
|
-
if (
|
|
135
|
-
sharpFile = sharpFile.
|
|
153
|
+
else if (toFormat === 'pjpg') {
|
|
154
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options, progressive: true } || {});
|
|
155
|
+
outputFormat = 'jpg';
|
|
156
|
+
}
|
|
157
|
+
if (((_a = options.resize) === null || _a === void 0 ? void 0 : _a.width) && ((_b = options.resize) === null || _b === void 0 ? void 0 : _b.height)) {
|
|
158
|
+
sharpFile = sharpFile.resize({ ...options.resize, fit: 'fill', withoutEnlargement: true });
|
|
136
159
|
}
|
|
137
|
-
|
|
160
|
+
//
|
|
161
|
+
// Output image processed
|
|
162
|
+
//
|
|
163
|
+
// Add to cache
|
|
164
|
+
const outputImage = { format: outputFormat, data: await sharpFile.toBuffer() };
|
|
165
|
+
await addToCacheCompressImage(cacheHash, outputImage);
|
|
166
|
+
// Go
|
|
167
|
+
return outputImage;
|
|
138
168
|
}
|
|
169
|
+
// Unknown input format or error while processing
|
|
139
170
|
return undefined;
|
|
140
171
|
};
|
|
141
|
-
const compressImageFile = async (file
|
|
172
|
+
const compressImageFile = async (file) => {
|
|
142
173
|
const buffer = await fs.readFile(file);
|
|
143
|
-
return compressImage(buffer, {}
|
|
174
|
+
return compressImage(buffer, {});
|
|
144
175
|
};
|
|
145
176
|
export async function compress(exclude) {
|
|
146
177
|
const spinner = ora(getProgressText()).start();
|
package/dist/config.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,13 +9,14 @@ import { fast } from './config.js';
|
|
|
9
9
|
import { printTitle } from './logger.js';
|
|
10
10
|
import { exit } from 'process';
|
|
11
11
|
import kleur from 'kleur';
|
|
12
|
+
import { cleanCache } from './cache.js';
|
|
12
13
|
const logo = ` __ __
|
|
13
14
|
|__|____ _____ ___________ ____ | | __
|
|
14
15
|
| \\__ \\ / \\\\____ \\__ \\ _/ ___\\| |/ /
|
|
15
16
|
| |/ __ \\| Y Y \\ |_> > __ \\\\ \\___| <
|
|
16
17
|
/\\__| (____ /__|_| / __(____ /\\___ >__|_ \\
|
|
17
18
|
\\______| \\/ \\/| | \\/ \\/ \\/
|
|
18
|
-
|__| by ‹div›RIOTS (c)2022
|
|
19
|
+
|__| by ‹div›RIOTS (c)2022-2023
|
|
19
20
|
`;
|
|
20
21
|
console.log(logo);
|
|
21
22
|
const program = new Command();
|
|
@@ -32,6 +33,8 @@ program.command('pack', { isDefault: true })
|
|
|
32
33
|
.option('--fail', 'Exits with a non-zero return code if issues.')
|
|
33
34
|
.option('--onlyoptim', 'Only optimize (PASS 1).')
|
|
34
35
|
.option('--onlycomp', 'Only compress (PASS 2).')
|
|
36
|
+
.option('--cleancache', 'Clean cache before running')
|
|
37
|
+
.option('--nocache', 'Run with no use of cache')
|
|
35
38
|
.action(async (dir, options) => {
|
|
36
39
|
$state.dir = dir;
|
|
37
40
|
$state.args = options;
|
|
@@ -39,6 +42,10 @@ program.command('pack', { isDefault: true })
|
|
|
39
42
|
if (options.fast) {
|
|
40
43
|
fast();
|
|
41
44
|
}
|
|
45
|
+
// Clean cache
|
|
46
|
+
if (options.cleancache) {
|
|
47
|
+
cleanCache();
|
|
48
|
+
}
|
|
42
49
|
if (!options.onlycomp) {
|
|
43
50
|
printTitle('PASS 1 - Optimizing');
|
|
44
51
|
console.time('Done');
|
package/dist/optimize.js
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import { globby } from
|
|
2
|
-
import * as path from
|
|
3
|
-
import * as fs from
|
|
4
|
-
import cheerio from
|
|
5
|
-
import { isNumeric } from
|
|
6
|
-
import config from
|
|
7
|
-
import { compressImage } from
|
|
8
|
-
import svgToMiniDataURI from
|
|
9
|
-
import $state from
|
|
10
|
-
import kleur from
|
|
11
|
-
import ora from
|
|
12
|
-
import { isLocal, Resource, translateSrc } from
|
|
1
|
+
import { globby } from "globby";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import cheerio from "cheerio";
|
|
5
|
+
import { isNumeric } from "./utils.js";
|
|
6
|
+
import config from "./config.js";
|
|
7
|
+
import { compressImage } from "./compress.js";
|
|
8
|
+
import svgToMiniDataURI from "mini-svg-data-uri";
|
|
9
|
+
import $state from "./state.js";
|
|
10
|
+
import kleur from "kleur";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { isLocal, Resource, translateSrc } from "./utils/resource.js";
|
|
13
13
|
async function analyse(file) {
|
|
14
|
-
console.log(
|
|
14
|
+
console.log("▶ " + file);
|
|
15
15
|
const html = (await fs.readFile(path.join($state.dir, file))).toString();
|
|
16
|
-
const $ = cheerio.load(html, {});
|
|
17
|
-
const
|
|
16
|
+
const $ = cheerio.load(html, { withStartIndices: true, decodeEntities: false });
|
|
17
|
+
const theFold = getTheFold($);
|
|
18
|
+
const imgs = $("img");
|
|
18
19
|
const imgsArray = [];
|
|
19
20
|
imgs.each(async (index, imgElement) => {
|
|
20
21
|
imgsArray.push(imgElement);
|
|
21
22
|
});
|
|
22
23
|
// Process images sequentially
|
|
23
|
-
const spinnerImg = ora({ prefixText:
|
|
24
|
+
const spinnerImg = ora({ prefixText: " " }).start();
|
|
24
25
|
for (let i = 0; i < imgsArray.length; i++) {
|
|
25
26
|
const imgElement = imgsArray[i];
|
|
26
|
-
spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr(
|
|
27
|
-
|
|
27
|
+
spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr("src")} `);
|
|
28
|
+
const isAboveTheFold = imgElement.startIndex < theFold;
|
|
29
|
+
await processImage(file, $, imgElement, isAboveTheFold);
|
|
28
30
|
}
|
|
29
31
|
// Reset spinner
|
|
30
32
|
spinnerImg.text = kleur.dim(`<img> [${imgsArray.length}/${imgsArray.length}]`);
|
|
@@ -32,72 +34,102 @@ async function analyse(file) {
|
|
|
32
34
|
const issues = $state.issues.get(file);
|
|
33
35
|
if (issues) {
|
|
34
36
|
spinnerImg.fail();
|
|
35
|
-
console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ?
|
|
37
|
+
console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ? "s" : ""}`));
|
|
36
38
|
}
|
|
37
39
|
else {
|
|
38
40
|
spinnerImg.succeed();
|
|
39
41
|
}
|
|
42
|
+
// Remove the fold
|
|
43
|
+
if (theFold) {
|
|
44
|
+
$("the-fold").remove();
|
|
45
|
+
}
|
|
40
46
|
if (!$state.args.nowrite) {
|
|
41
47
|
await fs.writeFile(path.join($state.dir, file), $.html());
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
|
-
|
|
50
|
+
function getTheFold($) {
|
|
51
|
+
const theFolds = $("the-fold");
|
|
52
|
+
if (!theFolds[0]) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
return theFolds[0].startIndex;
|
|
57
|
+
}
|
|
58
|
+
async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
|
|
45
59
|
try {
|
|
46
60
|
const img = $(imgElement);
|
|
47
61
|
/*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const attrib_src = img.attr(
|
|
62
|
+
* Attribute 'src'
|
|
63
|
+
*/
|
|
64
|
+
const attrib_src = img.attr("src");
|
|
51
65
|
if (!attrib_src) {
|
|
52
|
-
$state.reportIssue(htmlfile, {
|
|
66
|
+
$state.reportIssue(htmlfile, {
|
|
67
|
+
type: "warn",
|
|
68
|
+
msg: `Missing [src] on img - processing skipped.`,
|
|
69
|
+
});
|
|
53
70
|
return;
|
|
54
71
|
}
|
|
55
72
|
/*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const attrib_alt = img.attr(
|
|
73
|
+
* Attribute 'alt'
|
|
74
|
+
*/
|
|
75
|
+
const attrib_alt = img.attr("alt");
|
|
59
76
|
if (attrib_alt === undefined) {
|
|
60
|
-
$state.reportIssue(htmlfile, {
|
|
61
|
-
|
|
77
|
+
$state.reportIssue(htmlfile, {
|
|
78
|
+
type: "a11y",
|
|
79
|
+
msg: `Missing [alt] on img src="${attrib_src}" - Adding alt="" meanwhile.`,
|
|
80
|
+
});
|
|
81
|
+
img.attr("alt", "");
|
|
62
82
|
}
|
|
63
|
-
if (attrib_src.startsWith(
|
|
83
|
+
if (attrib_src.startsWith("data:")) {
|
|
64
84
|
// Data URI image
|
|
65
85
|
// TODO: try to compress it
|
|
66
86
|
return;
|
|
67
87
|
}
|
|
68
88
|
/*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const attr_loading = img.attr(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
* Attribute 'loading'
|
|
90
|
+
*/
|
|
91
|
+
const attr_loading = img.attr("loading");
|
|
92
|
+
if (isAboveTheFold) {
|
|
93
|
+
img.removeAttr("loading");
|
|
94
|
+
img.attr("fetchpriority", "high");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
switch (attr_loading) {
|
|
98
|
+
case undefined:
|
|
99
|
+
// Go lazy by default
|
|
100
|
+
img.attr("loading", "lazy");
|
|
101
|
+
break;
|
|
102
|
+
case "eager":
|
|
103
|
+
img.removeAttr("loading");
|
|
104
|
+
break;
|
|
105
|
+
case "lazy":
|
|
106
|
+
// Don't touch it
|
|
107
|
+
break;
|
|
108
|
+
default:
|
|
109
|
+
$state.reportIssue(htmlfile, {
|
|
110
|
+
type: "invalid",
|
|
111
|
+
msg: `Invalid [loading]="${attr_loading}" on img src="${attrib_src}"`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
115
|
/*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
img.attr(
|
|
116
|
+
* Attribute 'decoding'
|
|
117
|
+
*/
|
|
118
|
+
img.attr("decoding", "async");
|
|
90
119
|
if (!isLocal(attrib_src)) {
|
|
91
120
|
// Image not local, don't touch it
|
|
92
121
|
return;
|
|
93
122
|
}
|
|
94
123
|
/*
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
* Loading image
|
|
125
|
+
*/
|
|
97
126
|
const originalImage = await Resource.loadResource($state.dir, htmlfile, attrib_src);
|
|
98
127
|
// No file -> give up
|
|
99
128
|
if (!originalImage) {
|
|
100
|
-
$state.reportIssue(htmlfile, {
|
|
129
|
+
$state.reportIssue(htmlfile, {
|
|
130
|
+
type: "erro",
|
|
131
|
+
msg: `Can't find img on disk src="${attrib_src}"`,
|
|
132
|
+
});
|
|
101
133
|
return;
|
|
102
134
|
}
|
|
103
135
|
/*
|
|
@@ -107,11 +139,18 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
107
139
|
if (!$state.optimizedFiles.has(originalImage.filePathAbsolute)) {
|
|
108
140
|
// Let's avoid to optimize same images twice
|
|
109
141
|
$state.optimizedFiles.add(originalImage.filePathAbsolute);
|
|
110
|
-
newImage = await compressImage(await originalImage.getData(), {
|
|
111
|
-
|
|
142
|
+
newImage = await compressImage(await originalImage.getData(), {
|
|
143
|
+
toFormat: isAboveTheFold ? "pjpg" : "webp",
|
|
144
|
+
});
|
|
145
|
+
if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
|
|
146
|
+
(newImage.data.length < (await originalImage.getLen()) ||
|
|
147
|
+
isAboveTheFold) // Progressive jpg above the fold should get replaced even if bigger
|
|
148
|
+
) {
|
|
112
149
|
// Do we need to add an new extension?
|
|
113
150
|
const newExtension = `.${newImage.format}`;
|
|
114
|
-
const additionalExtension = path.extname(originalImage.filePathAbsolute) === newExtension
|
|
151
|
+
const additionalExtension = path.extname(originalImage.filePathAbsolute) === newExtension
|
|
152
|
+
? ""
|
|
153
|
+
: newExtension;
|
|
115
154
|
const newFilename = originalImage.filePathAbsolute + additionalExtension;
|
|
116
155
|
if (!$state.args.nowrite) {
|
|
117
156
|
fs.writeFile(newFilename, newImage.data);
|
|
@@ -119,11 +158,13 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
119
158
|
$state.compressedFiles.add(newFilename);
|
|
120
159
|
// Report compression result
|
|
121
160
|
$state.reportSummary({
|
|
122
|
-
action:
|
|
161
|
+
action: newFilename !== originalImage.filePathAbsolute
|
|
162
|
+
? `${await originalImage.getExt()}->${newImage.format}`
|
|
163
|
+
: path.extname(originalImage.filePathAbsolute),
|
|
123
164
|
originalSize: await originalImage.getLen(),
|
|
124
|
-
compressedSize: newImage.data.length
|
|
165
|
+
compressedSize: newImage.data.length,
|
|
125
166
|
});
|
|
126
|
-
img.attr(
|
|
167
|
+
img.attr("src", attrib_src + additionalExtension);
|
|
127
168
|
}
|
|
128
169
|
}
|
|
129
170
|
/*
|
|
@@ -137,23 +178,27 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
137
178
|
if (newImage && newImage.data.length <= config.image.embed_size) {
|
|
138
179
|
let datauri = undefined;
|
|
139
180
|
switch (newImage.format) {
|
|
140
|
-
case
|
|
181
|
+
case "svg":
|
|
141
182
|
datauri = svgToMiniDataURI(newImage.data.toString());
|
|
142
183
|
break;
|
|
143
|
-
case
|
|
144
|
-
datauri = `data:image/webp;base64,${newImage.data.toString(
|
|
184
|
+
case "webp":
|
|
185
|
+
datauri = `data:image/webp;base64,${newImage.data.toString("base64")}`;
|
|
145
186
|
break;
|
|
146
|
-
case
|
|
147
|
-
case
|
|
187
|
+
case "jpg":
|
|
188
|
+
case "png":
|
|
148
189
|
// TODO but not possible in current code
|
|
149
190
|
break;
|
|
150
191
|
}
|
|
151
192
|
if (datauri) {
|
|
152
193
|
isEmbed = true;
|
|
153
|
-
img.attr(
|
|
154
|
-
img.removeAttr(
|
|
155
|
-
img.removeAttr(
|
|
156
|
-
$state.reportSummary({
|
|
194
|
+
img.attr("src", datauri);
|
|
195
|
+
img.removeAttr("loading");
|
|
196
|
+
img.removeAttr("decoding");
|
|
197
|
+
$state.reportSummary({
|
|
198
|
+
action: `${newImage.format}->embed`,
|
|
199
|
+
originalSize: await originalImage.getLen(),
|
|
200
|
+
compressedSize: newImage.data.length,
|
|
201
|
+
});
|
|
157
202
|
}
|
|
158
203
|
}
|
|
159
204
|
/*
|
|
@@ -167,13 +212,13 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
167
212
|
//
|
|
168
213
|
// Stop here if svg
|
|
169
214
|
//
|
|
170
|
-
if (await originalImage.getExt() ===
|
|
215
|
+
if ((await originalImage.getExt()) === "svg") {
|
|
171
216
|
return;
|
|
172
217
|
}
|
|
173
218
|
/*
|
|
174
219
|
* Attribute 'srcset'
|
|
175
220
|
*/
|
|
176
|
-
const attr_srcset = img.attr(
|
|
221
|
+
const attr_srcset = img.attr("srcset");
|
|
177
222
|
if (attr_srcset) {
|
|
178
223
|
// If srcset is set, don't touch it.
|
|
179
224
|
// The compress pass will compress the images
|
|
@@ -182,10 +227,10 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
182
227
|
else {
|
|
183
228
|
// Generate image set
|
|
184
229
|
const ext = path.extname(attrib_src);
|
|
185
|
-
const fullbasename = attrib_src.slice(0, -
|
|
230
|
+
const fullbasename = attrib_src.slice(0, -ext.length);
|
|
186
231
|
const imageSrc = (addition) => `${fullbasename}${addition}.webp`;
|
|
187
232
|
// Start from original image
|
|
188
|
-
let new_srcset =
|
|
233
|
+
let new_srcset = "";
|
|
189
234
|
// Start reduction
|
|
190
235
|
const step = 300; //px
|
|
191
236
|
const ratio = w / h;
|
|
@@ -198,7 +243,7 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
198
243
|
if (!$state.compressedFiles.has(absoluteFilename)) {
|
|
199
244
|
// Add file to list avoid recompression
|
|
200
245
|
$state.compressedFiles.add(absoluteFilename);
|
|
201
|
-
const compressedImage = await compressImage(await originalImage.getData(), { width: valueW, height: valueH },
|
|
246
|
+
const compressedImage = await compressImage(await originalImage.getData(), { resize: { width: valueW, height: valueH }, toFormat: "webp" });
|
|
202
247
|
if ((compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) && !$state.args.nowrite) {
|
|
203
248
|
fs.writeFile(absoluteFilename, compressedImage.data);
|
|
204
249
|
}
|
|
@@ -209,17 +254,21 @@ async function processImage(htmlfile, $, imgElement) {
|
|
|
209
254
|
valueH = Math.trunc(valueW / ratio);
|
|
210
255
|
}
|
|
211
256
|
if (new_srcset) {
|
|
212
|
-
img.attr(
|
|
257
|
+
img.attr("srcset", `${img.attr("src")} ${w}w` + new_srcset);
|
|
213
258
|
}
|
|
214
259
|
}
|
|
215
260
|
}
|
|
216
261
|
catch (e) {
|
|
217
|
-
$state.reportIssue(htmlfile, {
|
|
262
|
+
$state.reportIssue(htmlfile, {
|
|
263
|
+
type: "erro",
|
|
264
|
+
msg: e.message ||
|
|
265
|
+
`Unexpected error while processing image: ${JSON.stringify(e)}`,
|
|
266
|
+
});
|
|
218
267
|
}
|
|
219
268
|
}
|
|
220
269
|
async function setImageSize(img, image) {
|
|
221
|
-
let width = img.attr(
|
|
222
|
-
let height = img.attr(
|
|
270
|
+
let width = img.attr("width");
|
|
271
|
+
let height = img.attr("height");
|
|
223
272
|
let width_new = undefined;
|
|
224
273
|
let height_new = undefined;
|
|
225
274
|
// Check valid values
|
|
@@ -249,7 +298,7 @@ async function setImageSize(img, image) {
|
|
|
249
298
|
const w = parseInt(width, 10);
|
|
250
299
|
const h = parseInt(height, 10);
|
|
251
300
|
// Is ratio equal?
|
|
252
|
-
const providedRatio = Math.round(w / h * 10) / 10;
|
|
301
|
+
const providedRatio = Math.round((w / h) * 10) / 10;
|
|
253
302
|
const imageRatio = Math.round(originalRatio * 10) / 10;
|
|
254
303
|
if (providedRatio !== imageRatio) {
|
|
255
304
|
//console.warn(`Image aspect ratio in HTML (${providedRatio}) differs from image aspect ratio (${imageRatio}) - fix width and height or let jampack fill them.`);
|
|
@@ -268,33 +317,38 @@ async function setImageSize(img, image) {
|
|
|
268
317
|
}
|
|
269
318
|
else {
|
|
270
319
|
// No width or height provided - set both to image size
|
|
271
|
-
if (await image.getExt() ===
|
|
320
|
+
if ((await image.getExt()) === "svg") {
|
|
272
321
|
// svg with no height and width has special sizing by browsers
|
|
273
322
|
// They size it inside 300x150 unless they have width and height
|
|
274
323
|
// attributes
|
|
275
324
|
// Load svg
|
|
276
325
|
const c = cheerio.load(await image.getData(), {});
|
|
277
|
-
const svg = c(
|
|
278
|
-
const svg_viewbox = svg.attr(
|
|
279
|
-
const svg_width = svg.attr(
|
|
280
|
-
const svg_height = svg.attr(
|
|
326
|
+
const svg = c("svg").first();
|
|
327
|
+
const svg_viewbox = svg.attr("viewbox"); // bug in cheerio here, should be "viewBox"
|
|
328
|
+
const svg_width = svg.attr("width");
|
|
329
|
+
const svg_height = svg.attr("height");
|
|
281
330
|
// Calculate aspect ratio from viewbox
|
|
282
331
|
let svg_aspectratio_from_viewbox = undefined;
|
|
283
332
|
if (svg_viewbox) {
|
|
284
|
-
const box = svg_viewbox.split(
|
|
333
|
+
const box = svg_viewbox.split(" ");
|
|
285
334
|
const w = parseInt(box[2], 10);
|
|
286
335
|
const h = parseInt(box[3], 10);
|
|
287
336
|
svg_aspectratio_from_viewbox = w / h;
|
|
288
337
|
}
|
|
289
338
|
// Set size
|
|
290
339
|
//
|
|
291
|
-
if (svg_width &&
|
|
340
|
+
if (svg_width &&
|
|
341
|
+
isNumeric(svg_width) &&
|
|
342
|
+
svg_height &&
|
|
343
|
+
isNumeric(svg_height)) {
|
|
292
344
|
// height and width are present
|
|
293
345
|
// use them
|
|
294
346
|
width_new = parseInt(svg_width, 10);
|
|
295
347
|
height_new = parseInt(svg_height, 10);
|
|
296
348
|
}
|
|
297
|
-
else if (svg_width === undefined &&
|
|
349
|
+
else if (svg_width === undefined &&
|
|
350
|
+
svg_height === undefined &&
|
|
351
|
+
svg_aspectratio_from_viewbox) {
|
|
298
352
|
// no height and no width but viewbox is present
|
|
299
353
|
// fit it in default browser box 300x150
|
|
300
354
|
if (svg_aspectratio_from_viewbox >= 2) {
|
|
@@ -324,21 +378,20 @@ async function setImageSize(img, image) {
|
|
|
324
378
|
if (width_new !== undefined && height_new !== undefined) {
|
|
325
379
|
const result_w = Math.round(width_new);
|
|
326
380
|
const result_h = Math.round(height_new);
|
|
327
|
-
img.attr(
|
|
328
|
-
img.attr(
|
|
381
|
+
img.attr("width", result_w.toFixed(0));
|
|
382
|
+
img.attr("height", result_h.toFixed(0));
|
|
329
383
|
return [result_w, result_h];
|
|
330
384
|
}
|
|
331
385
|
// Something when wrong
|
|
332
386
|
throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
|
|
333
387
|
}
|
|
334
388
|
export async function optimize(exclude) {
|
|
335
|
-
const glob = [
|
|
389
|
+
const glob = ["**/*.{htm,html}"];
|
|
336
390
|
if (exclude)
|
|
337
|
-
glob.push(
|
|
391
|
+
glob.push("!" + exclude);
|
|
338
392
|
const paths = await globby(glob, { cwd: $state.dir });
|
|
339
393
|
// Sequential async
|
|
340
394
|
for (const file of paths) {
|
|
341
395
|
await analyse(file);
|
|
342
396
|
}
|
|
343
|
-
;
|
|
344
397
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type Args = {
|
|
2
2
|
nowrite?: boolean;
|
|
3
|
+
nocache?: boolean;
|
|
3
4
|
};
|
|
4
|
-
export
|
|
5
|
+
export type ReportItem = {
|
|
5
6
|
action: string;
|
|
6
7
|
originalSize: number;
|
|
7
8
|
compressedSize: number;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
type Summary = {
|
|
10
11
|
nbFiles: number;
|
|
11
12
|
nbFilesCompressed: number;
|
|
12
13
|
dataLenUncompressed: number;
|
|
13
14
|
dataLenCompressed: number;
|
|
14
15
|
};
|
|
15
|
-
|
|
16
|
+
type Issue = {
|
|
16
17
|
type: 'invalid' | 'a11y' | 'perf' | 'erro' | 'warn';
|
|
17
18
|
msg: string;
|
|
18
19
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
export
|
|
3
|
-
export
|
|
2
|
+
export type ImageFormat = 'webp' | 'svg' | 'jpg' | 'png' | undefined;
|
|
3
|
+
export type Image = {
|
|
4
4
|
format: ImageFormat;
|
|
5
5
|
data: Buffer;
|
|
6
6
|
};
|
package/dist/utils/resource.d.ts
CHANGED
package/dist/utils/resource.js
CHANGED
|
@@ -74,10 +74,14 @@ export class Resource {
|
|
|
74
74
|
}
|
|
75
75
|
static async loadResource(projectRoot, relativeFile, src) {
|
|
76
76
|
if (!isLocal(src)) {
|
|
77
|
-
throw new Error('
|
|
77
|
+
throw new Error('src should be local');
|
|
78
78
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
79
|
+
const u = url.parse(src);
|
|
80
|
+
if (!u.pathname) {
|
|
81
|
+
throw new Error(`Invalid src format "${src}"`);
|
|
82
|
+
}
|
|
83
|
+
const relativePath = path.join(projectRoot, src.startsWith('/') ? '' : path.dirname(relativeFile), u.pathname);
|
|
84
|
+
let absolutePath = path.resolve(relativePath);
|
|
81
85
|
if (await fileExists(absolutePath)) {
|
|
82
86
|
return new Resource(src, absolutePath);
|
|
83
87
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION: string;
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divriots/jampack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Packer for static websites",
|
|
5
5
|
"author": "Georges Gomes @georges-gomes",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"/dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@swc/core": "^1.3.
|
|
19
|
+
"@swc/core": "^1.3.21",
|
|
20
20
|
"@types/cheerio": "^0.22.31",
|
|
21
21
|
"@types/commander": "^2.12.2",
|
|
22
22
|
"@types/csso": "^5.0.0",
|
|
@@ -25,26 +25,27 @@
|
|
|
25
25
|
"@types/sharp": "^0.31.0",
|
|
26
26
|
"@types/svgo": "^2.6.4",
|
|
27
27
|
"@types/table": "^6.3.2",
|
|
28
|
-
"chalk": "^5.
|
|
28
|
+
"chalk": "^5.1.2",
|
|
29
29
|
"cheerio": "0.22.0",
|
|
30
30
|
"commander": "^9.4.1",
|
|
31
31
|
"csso": "^5.0.5",
|
|
32
32
|
"file-type": "^18.0.0",
|
|
33
33
|
"globby": "^13.1.2",
|
|
34
|
-
"
|
|
34
|
+
"hasha": "^5.2.2",
|
|
35
|
+
"html-minifier-terser": "^7.1.0",
|
|
35
36
|
"kleur": "^4.1.5",
|
|
36
37
|
"mini-svg-data-uri": "^1.4.4",
|
|
37
38
|
"ora": "^6.1.2",
|
|
38
|
-
"sharp": "^0.31.
|
|
39
|
+
"sharp": "^0.31.2",
|
|
39
40
|
"svgo": "^2.8.0",
|
|
40
|
-
"table": "^6.8.
|
|
41
|
+
"table": "^6.8.1"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^16.
|
|
44
|
+
"@types/node": "^16.11.59",
|
|
44
45
|
"shx": "^0.3.3",
|
|
45
46
|
"ts-node": "^10.2.1",
|
|
46
|
-
"tslib": "^2.
|
|
47
|
-
"typescript": "^4.
|
|
47
|
+
"tslib": "^2.4.1",
|
|
48
|
+
"typescript": "^4.9.3"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
51
|
"watch": "pnpm build --watch",
|