UNPKG

13.7 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8var path = _interopRequireWildcard(require("path"));
9
10var os = _interopRequireWildcard(require("os"));
11
12var _schemaUtils = require("schema-utils");
13
14var _serializeJavascript = _interopRequireDefault(require("serialize-javascript"));
15
16var _worker = _interopRequireDefault(require("./worker"));
17
18var _pluginOptions = _interopRequireDefault(require("./plugin-options.json"));
19
20var _utils = require("./utils.js");
21
22function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
24function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
25
26function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
27
28/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
29
30/** @typedef {import("webpack").WebpackPluginInstance} WebpackPluginInstance */
31
32/** @typedef {import("webpack").Compiler} Compiler */
33
34/** @typedef {import("webpack").Compilation} Compilation */
35
36/** @typedef {import("webpack").WebpackError} WebpackError */
37
38/** @typedef {import("webpack").Asset} Asset */
39
40/** @typedef {import("webpack").AssetInfo} AssetInfo */
41
42/** @typedef {import("./utils.js").imageminMinify} ImageminMinifyFunction */
43
44/** @typedef {import("./utils.js").squooshMinify} SquooshMinifyFunction */
45
46/** @typedef {RegExp | string} Rule */
47
48/** @typedef {Rule[] | Rule} Rules */
49
50/**
51 * @callback FilterFn
52 * @param {Buffer} source `Buffer` of source file.
53 * @param {string} sourcePath Absolute path to source.
54 * @returns {boolean}
55 */
56
57/**
58 * @typedef {Object} ImageminOptions
59 * @property {import("imagemin").Options["plugins"] | [string, Record<string, any>]} plugins
60 * @property {Array<Record<string, any>>} [pluginsMeta]
61 */
62
63/**
64 * @typedef {Object.<string, any>} SquooshOptions
65 */
66
67/**
68 * @typedef {Object} WorkerResult
69 * @property {string} filename
70 * @property {Buffer} data
71 * @property {Array<Error>} warnings
72 * @property {Array<Error>} errors
73 * @property {AssetInfo} info
74 */
75
76/**
77 * @template T
78 * @callback TransformerFunction
79 * @param {WorkerResult} original
80 * @param {T | undefined} options
81 * @returns {Promise<WorkerResult>}
82 */
83
84/**
85 * @typedef {Object} PathData
86 * @property {string} [filename]
87 */
88
89/**
90 * @callback FilenameFn
91 * @param {PathData} pathData
92 * @param {AssetInfo} [assetInfo]
93 * @returns {string}
94 */
95
96/**
97 * @template T
98 * @typedef {Object} Transformer
99 * @property {TransformerFunction<T>} implementation
100 * @property {T} [options]
101 * @property {FilterFn} [filter]
102 * @property {string | FilenameFn} [filename]
103 * @property {string} [preset]
104 */
105
106/**
107 * @template T
108 * @typedef {Omit<Transformer<T>, "preset">} Minimizer
109 */
110
111/**
112 * @template T
113 * @typedef {Transformer<T>} Generator
114 */
115
116/**
117 * @template T
118 * @typedef {Object} InternalWorkerOptions
119 * @property {string} filename
120 * @property {Buffer} input
121 * @property {Transformer<T> | Transformer<T>[]} transformer
122 * @property {string} [severityError]
123 * @property {Function} [generateFilename]
124 */
125
126/**
127 * @template T
128 * @typedef {import("./loader").LoaderOptions<T>} InternalLoaderOptions
129 */
130
131/**
132 * @template T
133 * @typedef {Object} PluginOptions
134 * @property {Rules} [test] Test to match files against.
135 * @property {Rules} [include] Files to include.
136 * @property {Rules} [exclude] Files to exclude.
137 * @property {Minimizer<T> | Minimizer<T>[]} [minimizer] Allows to setup the minimizer.
138 * @property {Generator<T>[]} [generator] Allows to set the generator.
139 * @property {boolean} [loader] Automatically adding `imagemin-loader`.
140 * @property {number} [concurrency] Maximum number of concurrency optimization processes in one time.
141 * @property {string} [severityError] Allows to choose how errors are displayed.
142 * @property {boolean} [deleteOriginalAssets] Allows to remove original assets. Useful for converting to a `webp` and remove original assets.
143 */
144
145/**
146 * @template T
147 * @extends {WebpackPluginInstance}
148 */
149class ImageMinimizerPlugin {
150 /**
151 * @param {PluginOptions<T>} [options={}] Plugin options.
152 */
153 constructor(options = {}) {
154 (0, _schemaUtils.validate)(
155 /** @type {Schema} */
156 _pluginOptions.default, options, {
157 name: "Image Minimizer Plugin",
158 baseDataPath: "options"
159 });
160 const {
161 minimizer,
162 test = /\.(jpe?g|png|gif|tif|webp|svg|avif|jxl)$/i,
163 include,
164 exclude,
165 severityError,
166 generator,
167 loader = true,
168 concurrency,
169 deleteOriginalAssets = true
170 } = options;
171
172 if (!minimizer && !generator) {
173 throw new Error("Not configured 'minimizer' or 'generator' options, please setup them");
174 }
175 /**
176 * @private
177 */
178
179
180 this.options = {
181 minimizer,
182 generator,
183 severityError,
184 exclude,
185 include,
186 loader,
187 concurrency,
188 test,
189 deleteOriginalAssets
190 };
191 }
192 /**
193 * @private
194 * @param {Compiler} compiler
195 * @param {Compilation} compilation
196 * @param {Record<string, import("webpack").sources.Source>} assets
197 * @returns {Promise<void>}
198 */
199
200
201 async optimize(compiler, compilation, assets) {
202 if (!this.options.minimizer) {
203 return;
204 }
205
206 const cache = compilation.getCache("ImageMinimizerWebpackPlugin");
207 const assetsForMinify = await Promise.all(Object.keys(assets).filter(name => {
208 const {
209 info
210 } =
211 /** @type {Asset} */
212 compilation.getAsset(name); // Skip double minimize assets from child compilation
213
214 if (info.minimized || info.generated) {
215 return false;
216 }
217
218 if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(undefined, this.options)(name)) {
219 return false;
220 } // // Exclude already optimized assets from `image-minimizer-webpack-loader`
221 // if (this.options.loader && moduleAssets.has(name)) {
222 // const newInfo = moduleAssets.get(name) || {};
223 //
224 // compilation.updateAsset(name, source, newInfo);
225 //
226 // return false;
227 // }
228
229
230 return true;
231 }).map(async name => {
232 const {
233 info,
234 source
235 } =
236 /** @type {Asset} */
237 compilation.getAsset(name);
238 const cacheName = (0, _serializeJavascript.default)({
239 name,
240 minimizer: this.options.minimizer,
241 generator: this.options.generator
242 });
243 const eTag = cache.getLazyHashedEtag(source);
244 const cacheItem = cache.getItemCache(cacheName, eTag);
245 const output = await cacheItem.getPromise();
246 return {
247 name,
248 info,
249 inputSource: source,
250 output,
251 cacheItem
252 };
253 }));
254 const cpus = os.cpus() || {
255 length: 1
256 };
257 const limit = this.options.concurrency || Math.max(1, cpus.length - 1);
258 const {
259 RawSource
260 } = compiler.webpack.sources;
261 const scheduledTasks = [];
262
263 for (const asset of assetsForMinify) {
264 scheduledTasks.push(async () => {
265 const {
266 name,
267 inputSource,
268 cacheItem
269 } = asset;
270 let {
271 output
272 } = asset;
273 let input;
274 const sourceFromInputSource = inputSource.source();
275
276 if (!output) {
277 input = sourceFromInputSource;
278
279 if (!Buffer.isBuffer(input)) {
280 input = Buffer.from(input);
281 }
282
283 const minifyOptions =
284 /** @type {InternalWorkerOptions<T>} */
285 {
286 filename: name,
287 input,
288 severityError: this.options.severityError,
289 transformer: this.options.minimizer,
290 generateFilename: compilation.getAssetPath.bind(compilation)
291 };
292 output = await (0, _worker.default)(minifyOptions);
293 output.source = new RawSource(output.data);
294 await cacheItem.storePromise({
295 source: output.source,
296 info: output.info,
297 filename: output.filename,
298 warnings: output.warnings,
299 errors: output.errors
300 });
301 }
302
303 if (output.warnings.length > 0) {
304 /** @type {[WebpackError]} */
305 output.warnings.forEach(warning => {
306 compilation.warnings.push(warning);
307 });
308 }
309
310 if (output.errors.length > 0) {
311 /** @type {[WebpackError]} */
312 output.errors.forEach(error => {
313 compilation.errors.push(error);
314 });
315 }
316
317 if (compilation.getAsset(output.filename)) {
318 compilation.updateAsset(output.filename, output.source, output.info);
319 } else {
320 compilation.emitAsset(output.filename, output.source, output.info);
321
322 if (this.options.deleteOriginalAssets) {
323 compilation.deleteAsset(name);
324 }
325 }
326 });
327 }
328
329 await (0, _utils.throttleAll)(limit, scheduledTasks);
330 }
331 /**
332 * @param {import("webpack").Compiler} compiler
333 */
334
335
336 apply(compiler) {
337 const pluginName = this.constructor.name;
338
339 if (this.options.loader) {
340 compiler.hooks.compilation.tap({
341 name: pluginName
342 }, compilation => {
343 // Collect asset and update info from old loaders
344 compilation.hooks.moduleAsset.tap({
345 name: pluginName
346 }, (module, file) => {
347 const newInfo = module && module.buildMeta && module.buildMeta.imageMinimizerPluginInfo;
348
349 if (newInfo) {
350 const asset =
351 /** @type {Asset} */
352 compilation.getAsset(file);
353 compilation.updateAsset(file, asset.source, newInfo);
354 }
355 }); // Collect asset modules and update info for asset modules
356
357 compilation.hooks.assetPath.tap({
358 name: pluginName
359 }, (filename, data, info) => {
360 const newInfo = data && // @ts-ignore
361 data.module && // @ts-ignore
362 data.module.buildMeta && // @ts-ignore
363 data.module.buildMeta.imageMinimizerPluginInfo;
364
365 if (newInfo) {
366 Object.assign(info || {}, newInfo);
367 }
368
369 return filename;
370 });
371 });
372 compiler.hooks.afterPlugins.tap({
373 name: pluginName
374 }, () => {
375 const {
376 minimizer,
377 generator,
378 test,
379 include,
380 exclude,
381 severityError
382 } = this.options;
383 const loader =
384 /** @type {InternalLoaderOptions<T>} */
385 {
386 test,
387 include,
388 exclude,
389 enforce: "pre",
390 loader: require.resolve(path.join(__dirname, "loader.js")),
391 options:
392 /** @type {import("./loader").LoaderOptions<T>} */
393 {
394 generator,
395 minimizer,
396 severityError
397 }
398 };
399 const dataURILoader =
400 /** @type {InternalLoaderOptions<T>} */
401 {
402 scheme: /^data$/,
403 enforce: "pre",
404 loader: require.resolve(path.join(__dirname, "loader.js")),
405 options:
406 /** @type {import("./loader").LoaderOptions<T>} */
407 {
408 generator,
409 minimizer,
410 severityError
411 }
412 };
413 compiler.options.module.rules.push(loader);
414 compiler.options.module.rules.push(dataURILoader);
415 });
416 }
417
418 compiler.hooks.compilation.tap(pluginName, compilation => {
419 compilation.hooks.processAssets.tapPromise({
420 name: pluginName,
421 stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
422 additionalAssets: true
423 }, assets => this.optimize(compiler, compilation, assets));
424 compilation.hooks.statsPrinter.tap(pluginName, stats => {
425 stats.hooks.print.for("asset.info.minimized").tap("image-minimizer-webpack-plugin", (minimized, {
426 green,
427 formatFlag
428 }) => minimized ?
429 /** @type {Function} */
430 green(
431 /** @type {Function} */
432 formatFlag("minimized")) : "");
433 stats.hooks.print.for("asset.info.generated").tap("image-minimizer-webpack-plugin", (generated, {
434 green,
435 formatFlag
436 }) => generated ?
437 /** @type {Function} */
438 green(
439 /** @type {Function} */
440 formatFlag("generated")) : "");
441 });
442 });
443 }
444
445}
446
447ImageMinimizerPlugin.loader = require.resolve("./loader");
448ImageMinimizerPlugin.imageminNormalizeConfig = _utils.imageminNormalizeConfig;
449ImageMinimizerPlugin.imageminMinify = _utils.imageminMinify;
450ImageMinimizerPlugin.imageminGenerate = _utils.imageminGenerate;
451ImageMinimizerPlugin.squooshMinify = _utils.squooshMinify;
452ImageMinimizerPlugin.squooshGenerate = _utils.squooshGenerate;
453var _default = ImageMinimizerPlugin;
454exports.default = _default;
\No newline at end of file