UNPKG

5.35 kBJavaScriptView Raw
1"use strict";
2
3const {
4 validate
5} = require("schema-utils");
6const schema = require("./options.json");
7const {
8 minify
9} = require("./minify");
10
11/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
12/** @typedef {import("webpack").Compiler} Compiler */
13/** @typedef {import("webpack").Compilation} Compilation */
14/** @typedef {import("webpack").Asset} Asset */
15/** @typedef {import("webpack").WebpackError} WebpackError */
16
17/** @typedef {RegExp | string} Rule */
18/** @typedef {Rule[] | Rule} Rules */
19
20/**
21 * @typedef {Object} JSONOptions
22 * @property {(this: any, key: string, value: any) => any | (number | string)[] | null} [replacer]
23 * @property {string | number} [space]
24 */
25
26/**
27 * @typedef {Object} BasePluginOptions
28 * @property {Rule} [test]
29 * @property {Rule} [include]
30 * @property {Rule} [exclude]
31 * @property {JSONOptions} [minimizerOptions]
32 */
33
34/**
35 * @typedef {Object} MinimizedResult
36 * @property {string} code
37 */
38
39/**
40 * @typedef {Object} InternalOptions
41 * @property {string} input
42 * @property {JSONOptions} [minimizerOptions]
43 */
44
45/**
46 * @typedef {BasePluginOptions} InternalPluginOptions
47 */
48
49class JsonMinimizerPlugin {
50 /**
51 * @param {BasePluginOptions} [options]
52 */
53 constructor(options = {}) {
54 validate( /** @type {Schema} */schema, options, {
55 name: "Json Minimizer Plugin",
56 baseDataPath: "options"
57 });
58 const {
59 minimizerOptions = {},
60 test = /\.json(\?.*)?$/i,
61 include,
62 exclude
63 } = options;
64
65 /**
66 * @private
67 * @type {InternalPluginOptions}
68 */
69 this.options = {
70 test,
71 include,
72 exclude,
73 minimizerOptions
74 };
75 }
76
77 /**
78 * @param {any} error
79 * @param {string} file
80 * @param {string} context
81 * @returns {Error}
82 */
83 static buildError(error, file, context) {
84 return new Error(`"${file}" in "${context}" from Json Minimizer:\n${error}`);
85 }
86
87 /**
88 * @private
89 * @param {Compiler} compiler
90 * @param {Compilation} compilation
91 * @param {Record<string, import("webpack").sources.Source>} assets
92 * @returns {Promise<void>}
93 */
94 async optimize(compiler, compilation, assets) {
95 const cache = compilation.getCache("JsonMinimizerWebpackPlugin");
96 const assetsForMinify = await Promise.all(Object.keys(assets).filter(name => {
97 const {
98 info
99 } = /** @type {Asset} */compilation.getAsset(name);
100
101 // Skip double minimize assets from child compilation
102 if (info.minimized) {
103 return false;
104 }
105 if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(
106 // eslint-disable-next-line no-undefined
107 undefined, this.options)(name)) {
108 return false;
109 }
110 return true;
111 }).map(async name => {
112 const {
113 info,
114 source
115 } = /** @type {Asset} */
116 compilation.getAsset(name);
117 const eTag = cache.getLazyHashedEtag(source);
118 const cacheItem = cache.getItemCache(name, eTag);
119 const output = await cacheItem.getPromise();
120 return {
121 name,
122 info,
123 inputSource: source,
124 output,
125 cacheItem
126 };
127 }));
128 const {
129 RawSource
130 } = compiler.webpack.sources;
131 const scheduledTasks = [];
132 for (const asset of assetsForMinify) {
133 scheduledTasks.push((async () => {
134 const {
135 name,
136 inputSource,
137 cacheItem
138 } = asset;
139 let {
140 output
141 } = asset;
142 let input;
143 const sourceFromInputSource = inputSource.source();
144 if (!output) {
145 input = sourceFromInputSource;
146 if (Buffer.isBuffer(input)) {
147 input = input.toString();
148 }
149
150 /**
151 * @type {InternalOptions}
152 */
153 const options = {
154 input,
155 minimizerOptions: this.options.minimizerOptions
156 };
157 try {
158 output = await minify(options);
159 } catch (error) {
160 compilation.errors.push( /** @type {WebpackError} */
161 JsonMinimizerPlugin.buildError(error, name, compiler.context));
162 return;
163 }
164 output.source = new RawSource(output.code);
165 await cacheItem.storePromise({
166 source: output.source
167 });
168 }
169 const newInfo = {
170 minimized: true
171 };
172 const {
173 source
174 } = output;
175 compilation.updateAsset(name, source, newInfo);
176 })());
177 }
178 Promise.all(scheduledTasks);
179 }
180
181 /**
182 * @param {Compiler} compiler
183 * @returns {void}
184 */
185 apply(compiler) {
186 const pluginName = this.constructor.name;
187 compiler.hooks.compilation.tap(pluginName, compilation => {
188 compilation.hooks.processAssets.tapPromise({
189 name: pluginName,
190 stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
191 additionalAssets: true
192 }, assets => this.optimize(compiler, compilation, assets));
193 compilation.hooks.statsPrinter.tap(pluginName, stats => {
194 stats.hooks.print.for("asset.info.minimized").tap("json-minimizer-webpack-plugin", (minimized, {
195 green,
196 formatFlag
197 }) => minimized ? /** @type {Function} */green( /** @type {Function} */formatFlag("minimized")) : "");
198 });
199 });
200 }
201}
202module.exports = JsonMinimizerPlugin;
\No newline at end of file