@rsbuild/plugin-image-compress 1.0.0 → 1.0.2-beta.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 +1 -1
- package/README.md +68 -9
- package/dist/{index.mjs → index.cjs} +77 -43
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.js +47 -69
- package/package.json +38 -22
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024 Rspack Contrib
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,19 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# @rsbuild/plugin-image-compress
|
|
2
|
+
|
|
3
|
+
An Rsbuild plugin to compress images via [@napi-rs/image](https://www.npmjs.com/package/@napi-rs/image) and [SVGO](https://www.npmjs.com/package/svgo).
|
|
4
|
+
|
|
5
|
+
With the image compression plugin, image assets used in the project can be compressed to reduce the output size without affecting the visual appearance of the image.
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://npmjs.com/package/@rsbuild/plugin-image-compress">
|
|
9
|
+
<img src="https://img.shields.io/npm/v/@rsbuild/plugin-image-compress?style=flat-square&colorA=564341&colorB=EDED91" alt="npm version" />
|
|
10
|
+
</a>
|
|
11
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="license" />
|
|
3
12
|
</p>
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Install:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm add @rsbuild/plugin-image-compress -D
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Add plugin to your `rsbuild.config.ts`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// rsbuild.config.ts
|
|
26
|
+
import { pluginImageCompress } from "@rsbuild/plugin-image-compress";
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
plugins: [pluginImageCompress()],
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
The plugin accepts an array of compressor configuration options, each of which can be either a string or an object. The string can be the name of a built-in compressor and its default configuration enabled.
|
|
36
|
+
Or use the object format configuration and specify the compressor in the `use` field. The remaining fields of the object will be used as compressor configuration options.
|
|
37
|
+
|
|
38
|
+
By default, the plugin will enable `jpeg`, `png`, `ico` image compressors, which are equivalent to the following two examples:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
pluginImageCompress(["jpeg", "png", "ico"]);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
pluginImageCompress([{ use: "jpeg" }, { use: "png" }, { use: "ico" }]);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The default configuration can be overridden by specifying a configuration option.
|
|
49
|
+
For example, to allow the jpeg compressor to recognize new extension name and to set the quality of the png compressor.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
pluginImageCompress([
|
|
53
|
+
{ use: "jpeg", test: /\.(?:jpg|jpeg|jpe)$/ },
|
|
54
|
+
{ use: "png", minQuality: 50 },
|
|
55
|
+
"ico",
|
|
56
|
+
]);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The default `png` compressor is lossy.
|
|
60
|
+
If you want to replace it with a lossless compressor, you can use the following configuration.
|
|
6
61
|
|
|
7
|
-
|
|
62
|
+
```js
|
|
63
|
+
pluginImageCompress(["jpeg", "pngLossless", "ico"]);
|
|
64
|
+
```
|
|
8
65
|
|
|
9
|
-
|
|
66
|
+
The list of configuration options will eventually be converted to the corresponding bundler loader configuration, so compressors follow the same bottom-to-top matching rule.
|
|
10
67
|
|
|
11
|
-
|
|
68
|
+
For example, the `png` compressor will take precedence over the `pngLossless` compressor for the following configuration:
|
|
12
69
|
|
|
13
|
-
|
|
70
|
+
```js
|
|
71
|
+
pluginImageCompress(["jpeg", "pngLossless", "ico", "png"]);
|
|
72
|
+
```
|
|
14
73
|
|
|
15
|
-
|
|
74
|
+
For more information on compressors, please visit [@napi-rs/image](https://image.napi.rs/docs).
|
|
16
75
|
|
|
17
76
|
## License
|
|
18
77
|
|
|
19
|
-
|
|
78
|
+
[MIT](./LICENSE).
|
|
@@ -1,37 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
8
29
|
|
|
9
30
|
// src/index.ts
|
|
10
|
-
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
34
|
+
PLUGIN_IMAGE_COMPRESS_NAME: () => PLUGIN_IMAGE_COMPRESS_NAME,
|
|
35
|
+
pluginImageCompress: () => pluginImageCompress
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
var import_node_assert2 = __toESM(require("assert"), 1);
|
|
11
39
|
|
|
12
40
|
// src/minimizer.ts
|
|
13
|
-
|
|
41
|
+
var import_node_buffer2 = require("buffer");
|
|
14
42
|
|
|
15
43
|
// src/shared/codecs.ts
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
losslessCompressPng,
|
|
20
|
-
pngQuantize,
|
|
21
|
-
Transformer
|
|
22
|
-
} from "@napi-rs/image";
|
|
23
|
-
import svgo from "svgo";
|
|
44
|
+
var import_node_buffer = require("buffer");
|
|
45
|
+
var import_image = require("@napi-rs/image");
|
|
46
|
+
var import_svgo = __toESM(require("svgo"), 1);
|
|
24
47
|
var jpegCodec = {
|
|
25
48
|
handler(buf, options) {
|
|
26
|
-
return compressJpeg(buf, options);
|
|
49
|
+
return (0, import_image.compressJpeg)(buf, options);
|
|
27
50
|
},
|
|
28
51
|
defaultOptions: {
|
|
29
|
-
test: /\.(jpg|jpeg)$/
|
|
52
|
+
test: /\.(?:jpg|jpeg)$/
|
|
30
53
|
}
|
|
31
54
|
};
|
|
32
55
|
var pngCodec = {
|
|
33
56
|
handler(buf, options) {
|
|
34
|
-
return pngQuantize(buf, options);
|
|
57
|
+
return (0, import_image.pngQuantize)(buf, options);
|
|
35
58
|
},
|
|
36
59
|
defaultOptions: {
|
|
37
60
|
test: /\.png$/
|
|
@@ -39,7 +62,7 @@ var pngCodec = {
|
|
|
39
62
|
};
|
|
40
63
|
var pngLosslessCodec = {
|
|
41
64
|
handler(buf, options) {
|
|
42
|
-
return losslessCompressPng(buf, options);
|
|
65
|
+
return (0, import_image.losslessCompressPng)(buf, options);
|
|
43
66
|
},
|
|
44
67
|
defaultOptions: {
|
|
45
68
|
test: /\.png$/
|
|
@@ -47,16 +70,16 @@ var pngLosslessCodec = {
|
|
|
47
70
|
};
|
|
48
71
|
var icoCodec = {
|
|
49
72
|
handler(buf) {
|
|
50
|
-
return new Transformer(buf).ico();
|
|
73
|
+
return new import_image.Transformer(buf).ico();
|
|
51
74
|
},
|
|
52
75
|
defaultOptions: {
|
|
53
|
-
test: /\.(ico|icon)$/
|
|
76
|
+
test: /\.(?:ico|icon)$/
|
|
54
77
|
}
|
|
55
78
|
};
|
|
56
79
|
var svgCodec = {
|
|
57
80
|
async handler(buf, options) {
|
|
58
|
-
const result =
|
|
59
|
-
return Buffer.from(result.data);
|
|
81
|
+
const result = import_svgo.default.optimize(buf.toString(), options);
|
|
82
|
+
return import_node_buffer.Buffer.from(result.data);
|
|
60
83
|
},
|
|
61
84
|
defaultOptions: {
|
|
62
85
|
test: /\.svg$/
|
|
@@ -72,22 +95,24 @@ var codecs = {
|
|
|
72
95
|
var codecs_default = codecs;
|
|
73
96
|
|
|
74
97
|
// src/minimizer.ts
|
|
75
|
-
var
|
|
76
|
-
var
|
|
98
|
+
var IMAGE_MINIMIZER_PLUGIN_NAME = "@rsbuild/plugin-image-compress/minimizer";
|
|
99
|
+
var ImageMinimizerPlugin = class {
|
|
77
100
|
constructor(options) {
|
|
78
|
-
this.name =
|
|
101
|
+
this.name = IMAGE_MINIMIZER_PLUGIN_NAME;
|
|
79
102
|
this.options = options;
|
|
80
103
|
}
|
|
81
104
|
async optimize(compiler, compilation, assets) {
|
|
82
|
-
const cache = compilation.getCache(
|
|
105
|
+
const cache = compilation.getCache(IMAGE_MINIMIZER_PLUGIN_NAME);
|
|
83
106
|
const { RawSource } = compiler.webpack.sources;
|
|
84
107
|
const { matchObject } = compiler.webpack.ModuleFilenameHelpers;
|
|
85
108
|
const buildError = (error, file, context) => {
|
|
86
109
|
const cause = error instanceof Error ? error : new Error();
|
|
87
|
-
const message = file && context ? `"${file}" in "${context}" from
|
|
110
|
+
const message = file && context ? `"${file}" in "${context}" from Image Minimizer:
|
|
88
111
|
${cause.message}` : cause.message;
|
|
89
112
|
const ret = new compiler.webpack.WebpackError(message);
|
|
90
|
-
error instanceof Error
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
ret.error = error;
|
|
115
|
+
}
|
|
91
116
|
return ret;
|
|
92
117
|
};
|
|
93
118
|
const codec = codecs_default[this.options.use];
|
|
@@ -99,17 +124,22 @@ ${cause.message}` : cause.message;
|
|
|
99
124
|
const opts = { ...codec.defaultOptions, ...this.options };
|
|
100
125
|
const handleAsset = async (name) => {
|
|
101
126
|
const info = compilation.getAsset(name)?.info;
|
|
102
|
-
|
|
127
|
+
const fileName = name.split("?")[0];
|
|
128
|
+
if (info?.minimized || !matchObject(opts, fileName)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const asset = compilation.getAsset(name);
|
|
132
|
+
if (!asset) {
|
|
103
133
|
return;
|
|
104
134
|
}
|
|
105
|
-
const { source: inputSource } =
|
|
135
|
+
const { source: inputSource } = asset;
|
|
106
136
|
const eTag = cache.getLazyHashedEtag(inputSource);
|
|
107
137
|
const cacheItem = cache.getItemCache(name, eTag);
|
|
108
138
|
let result = await cacheItem.getPromise();
|
|
109
139
|
try {
|
|
110
140
|
if (!result) {
|
|
111
141
|
const input = inputSource.source();
|
|
112
|
-
const buf = await codec.handler(
|
|
142
|
+
const buf = await codec.handler(import_node_buffer2.Buffer.from(input), opts);
|
|
113
143
|
result = { source: new RawSource(buf) };
|
|
114
144
|
await cacheItem.storePromise(result);
|
|
115
145
|
}
|
|
@@ -127,6 +157,7 @@ ${cause.message}` : cause.message;
|
|
|
127
157
|
{
|
|
128
158
|
name: this.name,
|
|
129
159
|
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
|
|
160
|
+
// @ts-expect-error unsupported by Rspack
|
|
130
161
|
additionalAssets: true
|
|
131
162
|
},
|
|
132
163
|
(assets) => this.optimize(compiler, compilation, assets)
|
|
@@ -143,12 +174,12 @@ ${cause.message}` : cause.message;
|
|
|
143
174
|
};
|
|
144
175
|
|
|
145
176
|
// src/shared/utils.ts
|
|
146
|
-
|
|
177
|
+
var import_node_assert = __toESM(require("assert"), 1);
|
|
147
178
|
var withDefaultOptions = (opt) => {
|
|
148
179
|
const options = typeof opt === "string" ? { use: opt } : opt;
|
|
149
180
|
const { defaultOptions } = codecs_default[options.use];
|
|
150
181
|
const ret = { ...defaultOptions, ...options };
|
|
151
|
-
|
|
182
|
+
(0, import_node_assert.default)("test" in ret);
|
|
152
183
|
return ret;
|
|
153
184
|
};
|
|
154
185
|
|
|
@@ -161,7 +192,7 @@ var castOptions = (args) => {
|
|
|
161
192
|
}
|
|
162
193
|
const ret = [];
|
|
163
194
|
for (const arg of args) {
|
|
164
|
-
|
|
195
|
+
(0, import_node_assert2.default)(!Array.isArray(arg));
|
|
165
196
|
ret.push(arg);
|
|
166
197
|
}
|
|
167
198
|
return ret;
|
|
@@ -171,22 +202,25 @@ var normalizeOptions = (options) => {
|
|
|
171
202
|
const normalized = opts.map((opt) => withDefaultOptions(opt));
|
|
172
203
|
return normalized;
|
|
173
204
|
};
|
|
205
|
+
var PLUGIN_IMAGE_COMPRESS_NAME = "rsbuild:image-compress";
|
|
174
206
|
var pluginImageCompress = (...args) => ({
|
|
175
|
-
name:
|
|
207
|
+
name: PLUGIN_IMAGE_COMPRESS_NAME,
|
|
176
208
|
setup(api) {
|
|
177
209
|
const opts = normalizeOptions(castOptions(args));
|
|
178
|
-
api.modifyBundlerChain((chain, {
|
|
179
|
-
if (
|
|
210
|
+
api.modifyBundlerChain((chain, { isDev }) => {
|
|
211
|
+
if (isDev) {
|
|
180
212
|
return;
|
|
181
213
|
}
|
|
182
214
|
chain.optimization.minimize(true);
|
|
183
215
|
for (const opt of opts) {
|
|
184
|
-
chain.optimization.minimizer(`image-compress-${opt.use}`).use(
|
|
216
|
+
chain.optimization.minimizer(`image-compress-${opt.use}`).use(ImageMinimizerPlugin, [opt]);
|
|
185
217
|
}
|
|
186
218
|
});
|
|
187
219
|
}
|
|
188
220
|
});
|
|
189
|
-
export
|
|
221
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
+
0 && (module.exports = {
|
|
190
223
|
DEFAULT_OPTIONS,
|
|
224
|
+
PLUGIN_IMAGE_COMPRESS_NAME,
|
|
191
225
|
pluginImageCompress
|
|
192
|
-
};
|
|
226
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RsbuildPlugin } from '@rsbuild/core';
|
|
2
|
+
import { JpegCompressOptions, PngQuantOptions, PNGLosslessOptions } from '@napi-rs/image';
|
|
3
|
+
import { Config } from 'svgo';
|
|
4
|
+
|
|
5
|
+
type OneOrMany<T> = T | T[];
|
|
6
|
+
interface CodecBaseOptions {
|
|
7
|
+
jpeg: JpegCompressOptions;
|
|
8
|
+
png: PngQuantOptions;
|
|
9
|
+
pngLossless: PNGLosslessOptions;
|
|
10
|
+
ico: Record<string, unknown>;
|
|
11
|
+
svg: Config;
|
|
12
|
+
}
|
|
13
|
+
interface BaseCompressOptions<T extends Codecs> {
|
|
14
|
+
use: T;
|
|
15
|
+
test?: OneOrMany<RegExp>;
|
|
16
|
+
include?: OneOrMany<RegExp>;
|
|
17
|
+
exclude?: OneOrMany<RegExp>;
|
|
18
|
+
}
|
|
19
|
+
type FinalOptionCollection = {
|
|
20
|
+
[K in Codecs]: BaseCompressOptions<K> & CodecBaseOptions[K];
|
|
21
|
+
};
|
|
22
|
+
type Codecs = keyof CodecBaseOptions;
|
|
23
|
+
type OptionCollection = {
|
|
24
|
+
[K in Codecs]: K | FinalOptionCollection[K];
|
|
25
|
+
};
|
|
26
|
+
type Options = OptionCollection[Codecs];
|
|
27
|
+
|
|
28
|
+
type PluginImageCompressOptions = Options[];
|
|
29
|
+
declare const DEFAULT_OPTIONS: Codecs[];
|
|
30
|
+
interface IPluginImageCompress {
|
|
31
|
+
(...options: Options[]): RsbuildPlugin;
|
|
32
|
+
(options: Options[]): RsbuildPlugin;
|
|
33
|
+
}
|
|
34
|
+
declare const PLUGIN_IMAGE_COMPRESS_NAME = "rsbuild:image-compress";
|
|
35
|
+
/** Options enable by default: {@link DEFAULT_OPTIONS} */
|
|
36
|
+
declare const pluginImageCompress: IPluginImageCompress;
|
|
37
|
+
|
|
38
|
+
export { DEFAULT_OPTIONS, type IPluginImageCompress, PLUGIN_IMAGE_COMPRESS_NAME, type PluginImageCompressOptions, pluginImageCompress };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { RsbuildPlugin } from '@rsbuild/
|
|
1
|
+
import { RsbuildPlugin } from '@rsbuild/core';
|
|
2
2
|
import { JpegCompressOptions, PngQuantOptions, PNGLosslessOptions } from '@napi-rs/image';
|
|
3
3
|
import { Config } from 'svgo';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type OneOrMany<T> = T | T[];
|
|
6
6
|
interface CodecBaseOptions {
|
|
7
7
|
jpeg: JpegCompressOptions;
|
|
8
8
|
png: PngQuantOptions;
|
|
9
9
|
pngLossless: PNGLosslessOptions;
|
|
10
|
-
ico:
|
|
10
|
+
ico: Record<string, unknown>;
|
|
11
11
|
svg: Config;
|
|
12
12
|
}
|
|
13
13
|
interface BaseCompressOptions<T extends Codecs> {
|
|
14
14
|
use: T;
|
|
15
|
-
test?:
|
|
16
|
-
include?:
|
|
17
|
-
exclude?:
|
|
15
|
+
test?: OneOrMany<RegExp>;
|
|
16
|
+
include?: OneOrMany<RegExp>;
|
|
17
|
+
exclude?: OneOrMany<RegExp>;
|
|
18
18
|
}
|
|
19
19
|
type FinalOptionCollection = {
|
|
20
20
|
[K in Codecs]: BaseCompressOptions<K> & CodecBaseOptions[K];
|
|
@@ -31,7 +31,8 @@ interface IPluginImageCompress {
|
|
|
31
31
|
(...options: Options[]): RsbuildPlugin;
|
|
32
32
|
(options: Options[]): RsbuildPlugin;
|
|
33
33
|
}
|
|
34
|
+
declare const PLUGIN_IMAGE_COMPRESS_NAME = "rsbuild:image-compress";
|
|
34
35
|
/** Options enable by default: {@link DEFAULT_OPTIONS} */
|
|
35
36
|
declare const pluginImageCompress: IPluginImageCompress;
|
|
36
37
|
|
|
37
|
-
export { DEFAULT_OPTIONS, IPluginImageCompress, PluginImageCompressOptions, pluginImageCompress };
|
|
38
|
+
export { DEFAULT_OPTIONS, type IPluginImageCompress, PLUGIN_IMAGE_COMPRESS_NAME, type PluginImageCompressOptions, pluginImageCompress };
|
package/dist/index.js
CHANGED
|
@@ -1,59 +1,29 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
1
|
// src/index.ts
|
|
31
|
-
|
|
32
|
-
__export(src_exports, {
|
|
33
|
-
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
34
|
-
pluginImageCompress: () => pluginImageCompress
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(src_exports);
|
|
37
|
-
var import_assert2 = __toESM(require("assert"));
|
|
2
|
+
import assert2 from "node:assert";
|
|
38
3
|
|
|
39
4
|
// src/minimizer.ts
|
|
40
|
-
|
|
5
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
41
6
|
|
|
42
7
|
// src/shared/codecs.ts
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
8
|
+
import { Buffer } from "node:buffer";
|
|
9
|
+
import {
|
|
10
|
+
Transformer,
|
|
11
|
+
compressJpeg,
|
|
12
|
+
losslessCompressPng,
|
|
13
|
+
pngQuantize
|
|
14
|
+
} from "@napi-rs/image";
|
|
15
|
+
import svgo from "svgo";
|
|
46
16
|
var jpegCodec = {
|
|
47
17
|
handler(buf, options) {
|
|
48
|
-
return
|
|
18
|
+
return compressJpeg(buf, options);
|
|
49
19
|
},
|
|
50
20
|
defaultOptions: {
|
|
51
|
-
test: /\.(jpg|jpeg)$/
|
|
21
|
+
test: /\.(?:jpg|jpeg)$/
|
|
52
22
|
}
|
|
53
23
|
};
|
|
54
24
|
var pngCodec = {
|
|
55
25
|
handler(buf, options) {
|
|
56
|
-
return
|
|
26
|
+
return pngQuantize(buf, options);
|
|
57
27
|
},
|
|
58
28
|
defaultOptions: {
|
|
59
29
|
test: /\.png$/
|
|
@@ -61,7 +31,7 @@ var pngCodec = {
|
|
|
61
31
|
};
|
|
62
32
|
var pngLosslessCodec = {
|
|
63
33
|
handler(buf, options) {
|
|
64
|
-
return
|
|
34
|
+
return losslessCompressPng(buf, options);
|
|
65
35
|
},
|
|
66
36
|
defaultOptions: {
|
|
67
37
|
test: /\.png$/
|
|
@@ -69,16 +39,16 @@ var pngLosslessCodec = {
|
|
|
69
39
|
};
|
|
70
40
|
var icoCodec = {
|
|
71
41
|
handler(buf) {
|
|
72
|
-
return new
|
|
42
|
+
return new Transformer(buf).ico();
|
|
73
43
|
},
|
|
74
44
|
defaultOptions: {
|
|
75
|
-
test: /\.(ico|icon)$/
|
|
45
|
+
test: /\.(?:ico|icon)$/
|
|
76
46
|
}
|
|
77
47
|
};
|
|
78
48
|
var svgCodec = {
|
|
79
49
|
async handler(buf, options) {
|
|
80
|
-
const result =
|
|
81
|
-
return
|
|
50
|
+
const result = svgo.optimize(buf.toString(), options);
|
|
51
|
+
return Buffer.from(result.data);
|
|
82
52
|
},
|
|
83
53
|
defaultOptions: {
|
|
84
54
|
test: /\.svg$/
|
|
@@ -94,22 +64,24 @@ var codecs = {
|
|
|
94
64
|
var codecs_default = codecs;
|
|
95
65
|
|
|
96
66
|
// src/minimizer.ts
|
|
97
|
-
var
|
|
98
|
-
var
|
|
67
|
+
var IMAGE_MINIMIZER_PLUGIN_NAME = "@rsbuild/plugin-image-compress/minimizer";
|
|
68
|
+
var ImageMinimizerPlugin = class {
|
|
99
69
|
constructor(options) {
|
|
100
|
-
this.name =
|
|
70
|
+
this.name = IMAGE_MINIMIZER_PLUGIN_NAME;
|
|
101
71
|
this.options = options;
|
|
102
72
|
}
|
|
103
73
|
async optimize(compiler, compilation, assets) {
|
|
104
|
-
const cache = compilation.getCache(
|
|
74
|
+
const cache = compilation.getCache(IMAGE_MINIMIZER_PLUGIN_NAME);
|
|
105
75
|
const { RawSource } = compiler.webpack.sources;
|
|
106
76
|
const { matchObject } = compiler.webpack.ModuleFilenameHelpers;
|
|
107
77
|
const buildError = (error, file, context) => {
|
|
108
78
|
const cause = error instanceof Error ? error : new Error();
|
|
109
|
-
const message = file && context ? `"${file}" in "${context}" from
|
|
79
|
+
const message = file && context ? `"${file}" in "${context}" from Image Minimizer:
|
|
110
80
|
${cause.message}` : cause.message;
|
|
111
81
|
const ret = new compiler.webpack.WebpackError(message);
|
|
112
|
-
error instanceof Error
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
ret.error = error;
|
|
84
|
+
}
|
|
113
85
|
return ret;
|
|
114
86
|
};
|
|
115
87
|
const codec = codecs_default[this.options.use];
|
|
@@ -120,19 +92,23 @@ ${cause.message}` : cause.message;
|
|
|
120
92
|
}
|
|
121
93
|
const opts = { ...codec.defaultOptions, ...this.options };
|
|
122
94
|
const handleAsset = async (name) => {
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
95
|
+
const info = compilation.getAsset(name)?.info;
|
|
96
|
+
const fileName = name.split("?")[0];
|
|
97
|
+
if (info?.minimized || !matchObject(opts, fileName)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const asset = compilation.getAsset(name);
|
|
101
|
+
if (!asset) {
|
|
126
102
|
return;
|
|
127
103
|
}
|
|
128
|
-
const { source: inputSource } =
|
|
104
|
+
const { source: inputSource } = asset;
|
|
129
105
|
const eTag = cache.getLazyHashedEtag(inputSource);
|
|
130
106
|
const cacheItem = cache.getItemCache(name, eTag);
|
|
131
107
|
let result = await cacheItem.getPromise();
|
|
132
108
|
try {
|
|
133
109
|
if (!result) {
|
|
134
110
|
const input = inputSource.source();
|
|
135
|
-
const buf = await codec.handler(
|
|
111
|
+
const buf = await codec.handler(Buffer2.from(input), opts);
|
|
136
112
|
result = { source: new RawSource(buf) };
|
|
137
113
|
await cacheItem.storePromise(result);
|
|
138
114
|
}
|
|
@@ -150,6 +126,7 @@ ${cause.message}` : cause.message;
|
|
|
150
126
|
{
|
|
151
127
|
name: this.name,
|
|
152
128
|
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
|
|
129
|
+
// @ts-expect-error unsupported by Rspack
|
|
153
130
|
additionalAssets: true
|
|
154
131
|
},
|
|
155
132
|
(assets) => this.optimize(compiler, compilation, assets)
|
|
@@ -166,12 +143,12 @@ ${cause.message}` : cause.message;
|
|
|
166
143
|
};
|
|
167
144
|
|
|
168
145
|
// src/shared/utils.ts
|
|
169
|
-
|
|
146
|
+
import assert from "node:assert";
|
|
170
147
|
var withDefaultOptions = (opt) => {
|
|
171
148
|
const options = typeof opt === "string" ? { use: opt } : opt;
|
|
172
149
|
const { defaultOptions } = codecs_default[options.use];
|
|
173
150
|
const ret = { ...defaultOptions, ...options };
|
|
174
|
-
(
|
|
151
|
+
assert("test" in ret);
|
|
175
152
|
return ret;
|
|
176
153
|
};
|
|
177
154
|
|
|
@@ -184,7 +161,7 @@ var castOptions = (args) => {
|
|
|
184
161
|
}
|
|
185
162
|
const ret = [];
|
|
186
163
|
for (const arg of args) {
|
|
187
|
-
(
|
|
164
|
+
assert2(!Array.isArray(arg));
|
|
188
165
|
ret.push(arg);
|
|
189
166
|
}
|
|
190
167
|
return ret;
|
|
@@ -194,23 +171,24 @@ var normalizeOptions = (options) => {
|
|
|
194
171
|
const normalized = opts.map((opt) => withDefaultOptions(opt));
|
|
195
172
|
return normalized;
|
|
196
173
|
};
|
|
174
|
+
var PLUGIN_IMAGE_COMPRESS_NAME = "rsbuild:image-compress";
|
|
197
175
|
var pluginImageCompress = (...args) => ({
|
|
198
|
-
name:
|
|
176
|
+
name: PLUGIN_IMAGE_COMPRESS_NAME,
|
|
199
177
|
setup(api) {
|
|
200
178
|
const opts = normalizeOptions(castOptions(args));
|
|
201
|
-
api.modifyBundlerChain((chain, {
|
|
202
|
-
if (
|
|
179
|
+
api.modifyBundlerChain((chain, { isDev }) => {
|
|
180
|
+
if (isDev) {
|
|
203
181
|
return;
|
|
204
182
|
}
|
|
205
183
|
chain.optimization.minimize(true);
|
|
206
184
|
for (const opt of opts) {
|
|
207
|
-
chain.optimization.minimizer(`image-compress-${opt.use}`).use(
|
|
185
|
+
chain.optimization.minimizer(`image-compress-${opt.use}`).use(ImageMinimizerPlugin, [opt]);
|
|
208
186
|
}
|
|
209
187
|
});
|
|
210
188
|
}
|
|
211
189
|
});
|
|
212
|
-
|
|
213
|
-
0 && (module.exports = {
|
|
190
|
+
export {
|
|
214
191
|
DEFAULT_OPTIONS,
|
|
192
|
+
PLUGIN_IMAGE_COMPRESS_NAME,
|
|
215
193
|
pluginImageCompress
|
|
216
|
-
}
|
|
194
|
+
};
|
package/package.json
CHANGED
|
@@ -1,46 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsbuild/plugin-image-compress",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"
|
|
5
|
-
"homepage": "https://rsbuild.dev",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/web-infra-dev/rsbuild",
|
|
9
|
-
"directory": "packages/plugin-image-compress"
|
|
10
|
-
},
|
|
3
|
+
"version": "1.0.2-beta.0",
|
|
4
|
+
"repository": "https://github.com/rspack-contrib/rsbuild-plugin-image-compress",
|
|
11
5
|
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
12
7
|
"exports": {
|
|
13
8
|
".": {
|
|
14
9
|
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.
|
|
16
|
-
"
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
17
12
|
}
|
|
18
13
|
},
|
|
19
14
|
"main": "./dist/index.js",
|
|
15
|
+
"module": "./dist/index.mjs",
|
|
20
16
|
"types": "./dist/index.d.ts",
|
|
21
17
|
"files": [
|
|
22
18
|
"dist"
|
|
23
19
|
],
|
|
20
|
+
"simple-git-hooks": {
|
|
21
|
+
"pre-commit": "npx nano-staged"
|
|
22
|
+
},
|
|
23
|
+
"nano-staged": {
|
|
24
|
+
"*.{js,jsx,ts,tsx,mjs,cjs}": [
|
|
25
|
+
"biome check --write --no-errors-on-unmatched"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
24
28
|
"dependencies": {
|
|
25
|
-
"@napi-rs/image": "^1.
|
|
26
|
-
"svgo": "^3.
|
|
27
|
-
"@rsbuild/shared": "1.0.0"
|
|
29
|
+
"@napi-rs/image": "^1.9.2",
|
|
30
|
+
"svgo": "^3.3.2"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"
|
|
33
|
+
"@biomejs/biome": "^1.8.3",
|
|
34
|
+
"@playwright/test": "^1.44.1",
|
|
35
|
+
"@rsbuild/core": "1.0.0-alpha.9",
|
|
36
|
+
"@types/node": "^20.14.1",
|
|
37
|
+
"nano-staged": "^0.8.0",
|
|
38
|
+
"playwright": "^1.44.1",
|
|
39
|
+
"simple-git-hooks": "^2.11.1",
|
|
40
|
+
"tsup": "^8.0.2",
|
|
41
|
+
"typescript": "^5.5.2"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@rsbuild/core": "1.x || 1.0.0-0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"@rsbuild/core": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
36
50
|
},
|
|
37
51
|
"publishConfig": {
|
|
38
52
|
"access": "public",
|
|
39
|
-
"provenance": true,
|
|
40
53
|
"registry": "https://registry.npmjs.org/"
|
|
41
54
|
},
|
|
42
55
|
"scripts": {
|
|
43
|
-
"build": "
|
|
44
|
-
"dev": "
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"lint": "biome check .",
|
|
59
|
+
"lint:write": "biome check . --write",
|
|
60
|
+
"test": "playwright test"
|
|
45
61
|
}
|
|
46
62
|
}
|