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 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | class ImageMinimizerPlugin {
|
187 | |
188 |
|
189 |
|
190 | constructor(options = {}) {
|
191 | validate(
|
192 |
|
193 | schema, options, {
|
194 | name: "Image Minimizer Plugin",
|
195 | baseDataPath: "options"
|
196 | });
|
197 | const {
|
198 | minimizer,
|
199 | test = /\.(jpe?g|png|gif|tif|webp|svg|avif|jxl)$/i,
|
200 | include,
|
201 | exclude,
|
202 | severityError,
|
203 | generator,
|
204 | loader = true,
|
205 | concurrency,
|
206 | deleteOriginalAssets = true
|
207 | } = options;
|
208 |
|
209 | if (!minimizer && !generator) {
|
210 | throw new Error("Not configured 'minimizer' or 'generator' options, please setup them");
|
211 | }
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 | this.options = {
|
218 | minimizer,
|
219 | generator,
|
220 | severityError,
|
221 | exclude,
|
222 | include,
|
223 | loader,
|
224 | concurrency,
|
225 | test,
|
226 | deleteOriginalAssets
|
227 | };
|
228 | }
|
229 | |
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | async optimize(compiler, compilation, assets) {
|
239 | const minimizers = typeof this.options.minimizer !== "undefined" ? Array.isArray(this.options.minimizer) ? this.options.minimizer : [this.options.minimizer] : [];
|
240 | const generators = Array.isArray(this.options.generator) ? this.options.generator.filter(item => {
|
241 | if (item.type === "asset") {
|
242 | return true;
|
243 | }
|
244 |
|
245 | return false;
|
246 | }) : [];
|
247 |
|
248 | if (minimizers.length === 0 && generators.length === 0) {
|
249 | return;
|
250 | }
|
251 |
|
252 | const cache = compilation.getCache("ImageMinimizerWebpackPlugin");
|
253 | const assetsForTransformers = (await Promise.all(Object.keys(assets).filter(name => {
|
254 | const {
|
255 | info
|
256 | } =
|
257 |
|
258 | compilation.getAsset(name);
|
259 |
|
260 | if (info.minimized || info.generated) {
|
261 | return false;
|
262 | }
|
263 |
|
264 | if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(undefined, this.options)(name)) {
|
265 | return false;
|
266 | }
|
267 |
|
268 | return true;
|
269 | }).map(async name => {
|
270 | const {
|
271 | info,
|
272 | source
|
273 | } =
|
274 |
|
275 | compilation.getAsset(name);
|
276 | |
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | const getFromCache = async transformer => {
|
283 | const cacheName = serialize({
|
284 | name,
|
285 | transformer
|
286 | });
|
287 | const eTag = cache.getLazyHashedEtag(source);
|
288 | const cacheItem = cache.getItemCache(cacheName, eTag);
|
289 | const output = await cacheItem.getPromise();
|
290 | return {
|
291 | name,
|
292 | info,
|
293 | inputSource: source,
|
294 | output,
|
295 | cacheItem,
|
296 | transformer
|
297 | };
|
298 | };
|
299 | |
300 |
|
301 |
|
302 |
|
303 |
|
304 | const tasks = [];
|
305 |
|
306 | if (generators.length > 0) {
|
307 | tasks.push(...(await Promise.all(generators.map(generator => getFromCache(generator)))));
|
308 | }
|
309 |
|
310 | if (minimizers.length > 0) {
|
311 | tasks.push(await getFromCache(
|
312 |
|
313 | minimizers));
|
314 | }
|
315 |
|
316 | return tasks;
|
317 | }))).flat();
|
318 | const cpus = os.cpus() || {
|
319 | length: 1
|
320 | };
|
321 | const limit = this.options.concurrency || Math.max(1, cpus.length - 1);
|
322 | const {
|
323 | RawSource
|
324 | } = compiler.webpack.sources;
|
325 | const scheduledTasks = [];
|
326 |
|
327 | for (const asset of assetsForTransformers) {
|
328 | scheduledTasks.push(async () => {
|
329 | const {
|
330 | name,
|
331 | inputSource,
|
332 | cacheItem,
|
333 | transformer
|
334 | } = asset;
|
335 | let {
|
336 | output
|
337 | } = asset;
|
338 | let input;
|
339 | const sourceFromInputSource = inputSource.source();
|
340 |
|
341 | if (!output) {
|
342 | input = sourceFromInputSource;
|
343 |
|
344 | if (!Buffer.isBuffer(input)) {
|
345 | input = Buffer.from(input);
|
346 | }
|
347 |
|
348 | const minifyOptions =
|
349 |
|
350 | {
|
351 | filename: name,
|
352 | input,
|
353 | severityError: this.options.severityError,
|
354 | transformer,
|
355 | generateFilename: compilation.getAssetPath.bind(compilation)
|
356 | };
|
357 | output = await worker(minifyOptions);
|
358 | output.source = new RawSource(output.data);
|
359 | await cacheItem.storePromise({
|
360 | source: output.source,
|
361 | info: output.info,
|
362 | filename: output.filename,
|
363 | warnings: output.warnings,
|
364 | errors: output.errors
|
365 | });
|
366 | }
|
367 |
|
368 | if (output.warnings.length > 0) {
|
369 |
|
370 | output.warnings.forEach(warning => {
|
371 | compilation.warnings.push(warning);
|
372 | });
|
373 | }
|
374 |
|
375 | if (output.errors.length > 0) {
|
376 |
|
377 | output.errors.forEach(error => {
|
378 | compilation.errors.push(error);
|
379 | });
|
380 | }
|
381 |
|
382 | if (compilation.getAsset(output.filename)) {
|
383 | compilation.updateAsset(output.filename,
|
384 |
|
385 | output.source, output.info);
|
386 | } else {
|
387 | compilation.emitAsset(output.filename,
|
388 |
|
389 | output.source, output.info);
|
390 |
|
391 | if (this.options.deleteOriginalAssets) {
|
392 | compilation.deleteAsset(name);
|
393 | }
|
394 | }
|
395 | });
|
396 | }
|
397 |
|
398 | await throttleAll(limit, scheduledTasks);
|
399 | }
|
400 | |
401 |
|
402 |
|
403 |
|
404 |
|
405 | setupAll() {
|
406 | if (typeof this.options.generator !== "undefined") {
|
407 | const {
|
408 | generator
|
409 | } = this.options;
|
410 |
|
411 | for (const item of generator) {
|
412 | if (typeof item.implementation.setup !== "undefined") {
|
413 | item.implementation.setup();
|
414 | }
|
415 | }
|
416 | }
|
417 |
|
418 | if (typeof this.options.minimizer !== "undefined") {
|
419 | const minimizers = Array.isArray(this.options.minimizer) ? this.options.minimizer : [this.options.minimizer];
|
420 |
|
421 | for (const item of minimizers) {
|
422 | if (typeof item.implementation.setup !== "undefined") {
|
423 | item.implementation.setup();
|
424 | }
|
425 | }
|
426 | }
|
427 | }
|
428 | |
429 |
|
430 |
|
431 |
|
432 |
|
433 | async teardownAll() {
|
434 | if (typeof this.options.generator !== "undefined") {
|
435 | const {
|
436 | generator
|
437 | } = this.options;
|
438 |
|
439 | for (const item of generator) {
|
440 | if (typeof item.implementation.teardown !== "undefined") {
|
441 |
|
442 | await item.implementation.teardown();
|
443 | }
|
444 | }
|
445 | }
|
446 |
|
447 | if (typeof this.options.minimizer !== "undefined") {
|
448 | const minimizers = Array.isArray(this.options.minimizer) ? this.options.minimizer : [this.options.minimizer];
|
449 |
|
450 | for (const item of minimizers) {
|
451 | if (typeof item.implementation.teardown !== "undefined") {
|
452 |
|
453 | await item.implementation.teardown();
|
454 | }
|
455 | }
|
456 | }
|
457 | }
|
458 | |
459 |
|
460 |
|
461 |
|
462 |
|
463 | apply(compiler) {
|
464 | const pluginName = this.constructor.name;
|
465 | this.setupAll();
|
466 |
|
467 | if (this.options.loader) {
|
468 | compiler.hooks.compilation.tap({
|
469 | name: pluginName
|
470 | }, compilation => {
|
471 |
|
472 | compilation.hooks.moduleAsset.tap({
|
473 | name: pluginName
|
474 | }, (module, file) => {
|
475 | const newInfo = module && module.buildMeta && module.buildMeta.imageMinimizerPluginInfo;
|
476 |
|
477 | if (newInfo) {
|
478 | const asset =
|
479 |
|
480 | compilation.getAsset(file);
|
481 | compilation.updateAsset(file, asset.source, newInfo);
|
482 | }
|
483 | });
|
484 |
|
485 | compilation.hooks.assetPath.tap({
|
486 | name: pluginName
|
487 | }, (filename, data, info) => {
|
488 | const newInfo = data &&
|
489 | data.module &&
|
490 | data.module.buildMeta &&
|
491 | data.module.buildMeta.imageMinimizerPluginInfo;
|
492 |
|
493 | if (newInfo) {
|
494 | Object.assign(info || {}, newInfo);
|
495 | }
|
496 |
|
497 | return filename;
|
498 | });
|
499 | });
|
500 | compiler.hooks.afterPlugins.tap({
|
501 | name: pluginName
|
502 | }, () => {
|
503 | const {
|
504 | minimizer,
|
505 | generator,
|
506 | test,
|
507 | include,
|
508 | exclude,
|
509 | severityError
|
510 | } = this.options;
|
511 | const minimizerForLoader = minimizer;
|
512 | let generatorForLoader = generator;
|
513 |
|
514 | if (Array.isArray(generatorForLoader)) {
|
515 | const importGenerators = generatorForLoader.filter(item => {
|
516 | if (typeof item.type === "undefined" || item.type === "import") {
|
517 | return true;
|
518 | }
|
519 |
|
520 | return false;
|
521 | });
|
522 | generatorForLoader = importGenerators.length > 0 ?
|
523 |
|
524 | importGenerators : undefined;
|
525 | }
|
526 |
|
527 | if (!minimizerForLoader && !generatorForLoader) {
|
528 | return;
|
529 | }
|
530 |
|
531 | const loader =
|
532 |
|
533 | {
|
534 | test,
|
535 | include,
|
536 | exclude,
|
537 | enforce: "pre",
|
538 | loader: require.resolve(path.join(__dirname, "loader.js")),
|
539 | options:
|
540 |
|
541 | {
|
542 | generator: generatorForLoader,
|
543 | minimizer: minimizerForLoader,
|
544 | severityError
|
545 | }
|
546 | };
|
547 | const dataURILoader =
|
548 |
|
549 | {
|
550 | scheme: /^data$/,
|
551 | mimetype: /^image\/.+/i,
|
552 | enforce: "pre",
|
553 | loader: require.resolve(path.join(__dirname, "loader.js")),
|
554 | options:
|
555 |
|
556 | {
|
557 | generator: generatorForLoader,
|
558 | minimizer: minimizerForLoader,
|
559 | severityError
|
560 | }
|
561 | };
|
562 | compiler.options.module.rules.push(loader);
|
563 | compiler.options.module.rules.push(dataURILoader);
|
564 | });
|
565 | }
|
566 |
|
567 | compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
568 | compilation.hooks.afterSeal.tapPromise({
|
569 | name: pluginName
|
570 | }, async () => {
|
571 | await this.teardownAll();
|
572 | });
|
573 | });
|
574 | compiler.hooks.compilation.tap(pluginName, compilation => {
|
575 | compilation.hooks.processAssets.tapPromise({
|
576 | name: pluginName,
|
577 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
|
578 | additionalAssets: true
|
579 | }, async assets => {
|
580 | await this.optimize(compiler, compilation, assets);
|
581 | });
|
582 | compilation.hooks.statsPrinter.tap(pluginName, stats => {
|
583 | stats.hooks.print.for("asset.info.minimized").tap("image-minimizer-webpack-plugin", (minimized, {
|
584 | green,
|
585 | formatFlag
|
586 | }) => minimized ?
|
587 |
|
588 | green(
|
589 |
|
590 | formatFlag("minimized")) : "");
|
591 | stats.hooks.print.for("asset.info.generated").tap("image-minimizer-webpack-plugin", (generated, {
|
592 | green,
|
593 | formatFlag
|
594 | }) => generated ?
|
595 |
|
596 | green(
|
597 |
|
598 | formatFlag("generated")) : "");
|
599 | });
|
600 | });
|
601 | }
|
602 |
|
603 | }
|
604 |
|
605 | ImageMinimizerPlugin.loader = require.resolve("./loader");
|
606 | ImageMinimizerPlugin.imageminNormalizeConfig = imageminNormalizeConfig;
|
607 | ImageMinimizerPlugin.imageminMinify = imageminMinify;
|
608 | ImageMinimizerPlugin.imageminGenerate = imageminGenerate;
|
609 | ImageMinimizerPlugin.squooshMinify = squooshMinify;
|
610 | ImageMinimizerPlugin.squooshGenerate = squooshGenerate;
|
611 | module.exports = ImageMinimizerPlugin; |
\ | No newline at end of file |