1 | "use strict";
|
2 |
|
3 | const path = require("path");
|
4 |
|
5 | const os = require("os");
|
6 |
|
7 | const {
|
8 | validate
|
9 | } = require("schema-utils");
|
10 |
|
11 | const serialize = require("serialize-javascript");
|
12 |
|
13 | const worker = require("./worker");
|
14 |
|
15 | const schema = require("./plugin-options.json");
|
16 |
|
17 | const {
|
18 | throttleAll,
|
19 | imageminNormalizeConfig,
|
20 | imageminMinify,
|
21 | imageminGenerate,
|
22 | squooshMinify,
|
23 | squooshGenerate
|
24 | } = require("./utils.js");
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | class ImageMinimizerPlugin {
|
173 | |
174 |
|
175 |
|
176 | constructor(options = {}) {
|
177 | validate(
|
178 |
|
179 | schema, options, {
|
180 | name: "Image Minimizer Plugin",
|
181 | baseDataPath: "options"
|
182 | });
|
183 | const {
|
184 | minimizer,
|
185 | test = /\.(jpe?g|png|gif|tif|webp|svg|avif|jxl)$/i,
|
186 | include,
|
187 | exclude,
|
188 | severityError,
|
189 | generator,
|
190 | loader = true,
|
191 | concurrency,
|
192 | deleteOriginalAssets = true
|
193 | } = options;
|
194 |
|
195 | if (!minimizer && !generator) {
|
196 | throw new Error("Not configured 'minimizer' or 'generator' options, please setup them");
|
197 | }
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 | this.options = {
|
204 | minimizer,
|
205 | generator,
|
206 | severityError,
|
207 | exclude,
|
208 | include,
|
209 | loader,
|
210 | concurrency,
|
211 | test,
|
212 | deleteOriginalAssets
|
213 | };
|
214 | }
|
215 | |
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | async optimize(compiler, compilation, assets) {
|
225 | if (!this.options.minimizer) {
|
226 | return;
|
227 | }
|
228 |
|
229 | const cache = compilation.getCache("ImageMinimizerWebpackPlugin");
|
230 | const assetsForMinify = await Promise.all(Object.keys(assets).filter(name => {
|
231 | const {
|
232 | info
|
233 | } =
|
234 |
|
235 | compilation.getAsset(name);
|
236 |
|
237 | if (info.minimized || info.generated) {
|
238 | return false;
|
239 | }
|
240 |
|
241 | if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(undefined, this.options)(name)) {
|
242 | return false;
|
243 | }
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | return true;
|
254 | }).map(async name => {
|
255 | const {
|
256 | info,
|
257 | source
|
258 | } =
|
259 |
|
260 | compilation.getAsset(name);
|
261 | const cacheName = serialize({
|
262 | name,
|
263 | minimizer: this.options.minimizer,
|
264 | generator: this.options.generator
|
265 | });
|
266 | const eTag = cache.getLazyHashedEtag(source);
|
267 | const cacheItem = cache.getItemCache(cacheName, eTag);
|
268 | const output = await cacheItem.getPromise();
|
269 | return {
|
270 | name,
|
271 | info,
|
272 | inputSource: source,
|
273 | output,
|
274 | cacheItem
|
275 | };
|
276 | }));
|
277 | const cpus = os.cpus() || {
|
278 | length: 1
|
279 | };
|
280 | const limit = this.options.concurrency || Math.max(1, cpus.length - 1);
|
281 | const {
|
282 | RawSource
|
283 | } = compiler.webpack.sources;
|
284 | const scheduledTasks = [];
|
285 |
|
286 | for (const asset of assetsForMinify) {
|
287 | scheduledTasks.push(async () => {
|
288 | const {
|
289 | name,
|
290 | inputSource,
|
291 | cacheItem
|
292 | } = asset;
|
293 | let {
|
294 | output
|
295 | } = asset;
|
296 | let input;
|
297 | const sourceFromInputSource = inputSource.source();
|
298 |
|
299 | if (!output) {
|
300 | input = sourceFromInputSource;
|
301 |
|
302 | if (!Buffer.isBuffer(input)) {
|
303 | input = Buffer.from(input);
|
304 | }
|
305 |
|
306 | const minifyOptions =
|
307 |
|
308 | {
|
309 | filename: name,
|
310 | input,
|
311 | severityError: this.options.severityError,
|
312 | transformer: this.options.minimizer,
|
313 | generateFilename: compilation.getAssetPath.bind(compilation)
|
314 | };
|
315 | output = await worker(minifyOptions);
|
316 | output.source = new RawSource(output.data);
|
317 | await cacheItem.storePromise({
|
318 | source: output.source,
|
319 | info: output.info,
|
320 | filename: output.filename,
|
321 | warnings: output.warnings,
|
322 | errors: output.errors
|
323 | });
|
324 | }
|
325 |
|
326 | if (output.warnings.length > 0) {
|
327 |
|
328 | output.warnings.forEach(warning => {
|
329 | compilation.warnings.push(warning);
|
330 | });
|
331 | }
|
332 |
|
333 | if (output.errors.length > 0) {
|
334 |
|
335 | output.errors.forEach(error => {
|
336 | compilation.errors.push(error);
|
337 | });
|
338 | }
|
339 |
|
340 | if (compilation.getAsset(output.filename)) {
|
341 | compilation.updateAsset(output.filename, output.source, output.info);
|
342 | } else {
|
343 | compilation.emitAsset(output.filename, output.source, output.info);
|
344 |
|
345 | if (this.options.deleteOriginalAssets) {
|
346 | compilation.deleteAsset(name);
|
347 | }
|
348 | }
|
349 | });
|
350 | }
|
351 |
|
352 | await throttleAll(limit, scheduledTasks);
|
353 | }
|
354 | |
355 |
|
356 |
|
357 |
|
358 |
|
359 | setupAll() {
|
360 | if (typeof this.options.generator !== "undefined") {
|
361 | const {
|
362 | generator
|
363 | } = this.options;
|
364 |
|
365 | for (const item of generator) {
|
366 | if (typeof item.implementation.setup !== "undefined") {
|
367 | item.implementation.setup();
|
368 | }
|
369 | }
|
370 | }
|
371 |
|
372 | if (typeof this.options.minimizer !== "undefined") {
|
373 | const minimizers = Array.isArray(this.options.minimizer) ? this.options.minimizer : [this.options.minimizer];
|
374 |
|
375 | for (const item of minimizers) {
|
376 | if (typeof item.implementation.setup !== "undefined") {
|
377 | item.implementation.setup();
|
378 | }
|
379 | }
|
380 | }
|
381 | }
|
382 | |
383 |
|
384 |
|
385 |
|
386 |
|
387 | async teardownAll() {
|
388 | if (typeof this.options.generator !== "undefined") {
|
389 | const {
|
390 | generator
|
391 | } = this.options;
|
392 |
|
393 | for (const item of generator) {
|
394 | if (typeof item.implementation.teardown !== "undefined") {
|
395 |
|
396 | await item.implementation.teardown();
|
397 | }
|
398 | }
|
399 | }
|
400 |
|
401 | if (typeof this.options.minimizer !== "undefined") {
|
402 | const minimizers = Array.isArray(this.options.minimizer) ? this.options.minimizer : [this.options.minimizer];
|
403 |
|
404 | for (const item of minimizers) {
|
405 | if (typeof item.implementation.teardown !== "undefined") {
|
406 |
|
407 | await item.implementation.teardown();
|
408 | }
|
409 | }
|
410 | }
|
411 | }
|
412 | |
413 |
|
414 |
|
415 |
|
416 |
|
417 | apply(compiler) {
|
418 | const pluginName = this.constructor.name;
|
419 | this.setupAll();
|
420 |
|
421 | if (this.options.loader) {
|
422 | compiler.hooks.compilation.tap({
|
423 | name: pluginName
|
424 | }, compilation => {
|
425 |
|
426 | compilation.hooks.moduleAsset.tap({
|
427 | name: pluginName
|
428 | }, (module, file) => {
|
429 | const newInfo = module && module.buildMeta && module.buildMeta.imageMinimizerPluginInfo;
|
430 |
|
431 | if (newInfo) {
|
432 | const asset =
|
433 |
|
434 | compilation.getAsset(file);
|
435 | compilation.updateAsset(file, asset.source, newInfo);
|
436 | }
|
437 | });
|
438 |
|
439 | compilation.hooks.assetPath.tap({
|
440 | name: pluginName
|
441 | }, (filename, data, info) => {
|
442 | const newInfo = data &&
|
443 | data.module &&
|
444 | data.module.buildMeta &&
|
445 | data.module.buildMeta.imageMinimizerPluginInfo;
|
446 |
|
447 | if (newInfo) {
|
448 | Object.assign(info || {}, newInfo);
|
449 | }
|
450 |
|
451 | return filename;
|
452 | });
|
453 | });
|
454 | compiler.hooks.afterPlugins.tap({
|
455 | name: pluginName
|
456 | }, () => {
|
457 | const {
|
458 | minimizer,
|
459 | generator,
|
460 | test,
|
461 | include,
|
462 | exclude,
|
463 | severityError
|
464 | } = this.options;
|
465 | const loader =
|
466 |
|
467 | {
|
468 | test,
|
469 | include,
|
470 | exclude,
|
471 | enforce: "pre",
|
472 | loader: require.resolve(path.join(__dirname, "loader.js")),
|
473 | options:
|
474 |
|
475 | {
|
476 | generator,
|
477 | minimizer,
|
478 | severityError
|
479 | }
|
480 | };
|
481 | const dataURILoader =
|
482 |
|
483 | {
|
484 | scheme: /^data$/,
|
485 | enforce: "pre",
|
486 | loader: require.resolve(path.join(__dirname, "loader.js")),
|
487 | options:
|
488 |
|
489 | {
|
490 | generator,
|
491 | minimizer,
|
492 | severityError
|
493 | }
|
494 | };
|
495 | compiler.options.module.rules.push(loader);
|
496 | compiler.options.module.rules.push(dataURILoader);
|
497 | });
|
498 | }
|
499 |
|
500 | compiler.hooks.compilation.tap(pluginName, compilation => {
|
501 | compilation.hooks.processAssets.tapPromise({
|
502 | name: pluginName,
|
503 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
|
504 | additionalAssets: true
|
505 | }, async assets => {
|
506 | await this.optimize(compiler, compilation, assets);
|
507 | await this.teardownAll();
|
508 | });
|
509 | compilation.hooks.statsPrinter.tap(pluginName, stats => {
|
510 | stats.hooks.print.for("asset.info.minimized").tap("image-minimizer-webpack-plugin", (minimized, {
|
511 | green,
|
512 | formatFlag
|
513 | }) => minimized ?
|
514 |
|
515 | green(
|
516 |
|
517 | formatFlag("minimized")) : "");
|
518 | stats.hooks.print.for("asset.info.generated").tap("image-minimizer-webpack-plugin", (generated, {
|
519 | green,
|
520 | formatFlag
|
521 | }) => generated ?
|
522 |
|
523 | green(
|
524 |
|
525 | formatFlag("generated")) : "");
|
526 | });
|
527 | });
|
528 | }
|
529 |
|
530 | }
|
531 |
|
532 | ImageMinimizerPlugin.loader = require.resolve("./loader");
|
533 | ImageMinimizerPlugin.imageminNormalizeConfig = imageminNormalizeConfig;
|
534 | ImageMinimizerPlugin.imageminMinify = imageminMinify;
|
535 | ImageMinimizerPlugin.imageminGenerate = imageminGenerate;
|
536 | ImageMinimizerPlugin.squooshMinify = squooshMinify;
|
537 | ImageMinimizerPlugin.squooshGenerate = squooshGenerate;
|
538 | module.exports = ImageMinimizerPlugin; |
\ | No newline at end of file |