@caweb/css-audit-webpack-plugin 2.0.2 → 2.1.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/css-audit.config.js +1 -1
- package/index.js +143 -224
- package/package.json +5 -2
package/css-audit.config.js
CHANGED
package/index.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { sync as resolveBin } from 'resolve-bin';
|
|
7
7
|
import spawn from 'cross-spawn';
|
|
8
|
-
import { getAllFilesSync } from 'get-all-files'
|
|
9
8
|
import EntryDependency from "webpack/lib/dependencies/EntryDependency.js";
|
|
9
|
+
import { flagExists, getArgVal, getAllFlags } from '@caweb/webpack/lib/args.js';
|
|
10
10
|
|
|
11
|
-
import path from 'path';
|
|
11
|
+
import path, { resolve } from 'path';
|
|
12
12
|
import fs from 'fs';
|
|
13
13
|
import deepmerge from 'deepmerge';
|
|
14
14
|
import chalk from 'chalk';
|
|
@@ -22,38 +22,26 @@ const boldGreen = chalk.bold.green;
|
|
|
22
22
|
const boldBlue = chalk.bold.hex('#03a7fc');
|
|
23
23
|
const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
|
|
25
|
+
const pluginName = 'CAWebCSSAuditPlugin';
|
|
26
|
+
|
|
25
27
|
// CSS Audit Plugin
|
|
26
|
-
class
|
|
28
|
+
class CAWebCSSAuditPlugin {
|
|
27
29
|
config = {};
|
|
28
30
|
|
|
29
31
|
constructor(opts = {}) {
|
|
30
|
-
// the default publicPath is always the outputFolder
|
|
31
|
-
DefaultConfig.publicPath = DefaultConfig.outputFolder;
|
|
32
|
-
|
|
33
|
-
// the default output folder is always relative to the current working directory.
|
|
34
|
-
DefaultConfig.outputFolder = path.join( process.cwd(), DefaultConfig.outputFolder );
|
|
35
|
-
|
|
36
|
-
// if opts.outputFolder is defined
|
|
37
|
-
if( opts.outputFolder && ! path.isAbsolute(opts.outputFolder) ){
|
|
38
|
-
opts.publicPath = opts.outputFolder;
|
|
39
|
-
|
|
40
|
-
// we join the current working directory with the opts.outputFolder
|
|
41
|
-
opts.outputFolder = path.join( process.cwd(), opts.outputFolder );
|
|
42
|
-
}
|
|
43
|
-
|
|
44
32
|
this.config = deepmerge(DefaultConfig, opts);
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
apply(compiler) {
|
|
48
36
|
const staticDir = {
|
|
49
|
-
directory: this.config.outputFolder,
|
|
50
|
-
publicPath: encodeURI(this.config.
|
|
37
|
+
directory: path.join( process.cwd(), this.config.outputFolder ),
|
|
38
|
+
publicPath: encodeURI(this.config.outputFolder).replace(':', ''),
|
|
51
39
|
watch: true
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
let { devServer } = compiler.options;
|
|
55
43
|
let auditUrl = `${devServer.server}://${devServer.host}:${devServer.port}`;
|
|
56
|
-
let nodeModulePath = encodeURI(
|
|
44
|
+
let nodeModulePath = encodeURI(staticDir.publicPath) + '/node_modules';
|
|
57
45
|
let pathRewrite = {};
|
|
58
46
|
pathRewrite[`^${nodeModulePath}`] = '';
|
|
59
47
|
|
|
@@ -76,128 +64,95 @@ class CSSAuditPlugin {
|
|
|
76
64
|
}else{
|
|
77
65
|
devServer.static = [].concat(devServer.static, staticDir );
|
|
78
66
|
}
|
|
79
|
-
|
|
67
|
+
|
|
68
|
+
// add url to devServer Open
|
|
80
69
|
// if dev server allows for multiple pages to be opened
|
|
81
70
|
// add filename.html to open property.
|
|
82
71
|
if( Array.isArray(devServer.open) ){
|
|
83
|
-
devServer.open.push(`${
|
|
72
|
+
devServer.open.push(`${auditUrl}${this.config.outputFolder}/${this.config.filename}.html`)
|
|
84
73
|
}else if( 'object' === typeof devServer.open && Array.isArray(devServer.open.target) ){
|
|
85
|
-
devServer.open.target.push(`${
|
|
74
|
+
devServer.open.target.push(`${auditUrl}${this.config.outputFolder}/${this.config.filename}.html`)
|
|
86
75
|
}
|
|
87
|
-
|
|
88
|
-
// we always make sure the output folder exists
|
|
89
|
-
fs.mkdirSync( staticDir.directory, { recursive: true } );
|
|
90
|
-
|
|
91
76
|
// Wait for configuration preset plugins to apply all configure webpack defaults
|
|
92
|
-
compiler.hooks.initialize.tap(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
77
|
+
// compiler.hooks.initialize.tap(pluginName, () => {
|
|
78
|
+
// compiler.hooks.compilation.tap(
|
|
79
|
+
// pluginName,
|
|
80
|
+
// (compilation, { normalModuleFactory }) => {
|
|
81
|
+
// compilation.dependencyFactories.set(
|
|
82
|
+
// EntryDependency,
|
|
83
|
+
// normalModuleFactory
|
|
84
|
+
// );
|
|
85
|
+
// }
|
|
86
|
+
// );
|
|
87
|
+
|
|
88
|
+
// // const { entry, options, context } = {
|
|
89
|
+
// // entry: path.join(staticDir.directory, 'css-audit.update.js'),
|
|
90
|
+
// // options: {
|
|
91
|
+
// // name: 'css-audit.update'
|
|
92
|
+
// // },
|
|
93
|
+
// // context: staticDir.directory
|
|
94
|
+
// // };
|
|
95
|
+
|
|
96
|
+
// // const dep = new EntryDependency(entry);
|
|
97
|
+
// // dep.loc = {
|
|
98
|
+
// // name: options.name
|
|
99
|
+
// // };
|
|
115
100
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
101
|
+
// // fs.writeFileSync(
|
|
102
|
+
// // path.join(staticDir.directory, `css-audit.update.js`),
|
|
103
|
+
// // `` // required for hot-update to compile on our page, blank script for now
|
|
104
|
+
// // );
|
|
105
|
+
|
|
106
|
+
// // compiler.hooks.thisCompilation.tap(pluginName,
|
|
107
|
+
// // /**
|
|
108
|
+
// // * Hook into the webpack compilation
|
|
109
|
+
// // * @param {Compilation} compilation
|
|
110
|
+
// // */
|
|
111
|
+
// // (compilation) => {
|
|
127
112
|
|
|
128
|
-
|
|
113
|
+
// // compiler.hooks.make.tapAsync(pluginName, (compilation, callback) => {
|
|
129
114
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
115
|
+
// // compilation.addEntry(
|
|
116
|
+
// // context,
|
|
117
|
+
// // dep,
|
|
118
|
+
// // options,
|
|
119
|
+
// // err => {
|
|
120
|
+
// // callback(err);
|
|
121
|
+
// // });
|
|
122
|
+
// // });
|
|
138
123
|
|
|
139
124
|
|
|
140
125
|
|
|
141
|
-
|
|
126
|
+
// // });
|
|
127
|
+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
142
128
|
|
|
143
|
-
compiler.hooks.done.tapAsync(
|
|
129
|
+
compiler.hooks.done.tapAsync(pluginName,
|
|
144
130
|
(stats, callback) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// if key already exists, push value to array
|
|
163
|
-
if( audits.hasOwnProperty(key) ){
|
|
164
|
-
audits[key].push(value);
|
|
165
|
-
}else{
|
|
166
|
-
// otherwise, if the audit is an array create a new array with the value
|
|
167
|
-
audits[key] = ! Array.isArray(audit) ? value : [value];
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
let result = this.audit(files, {
|
|
172
|
-
format: this.config.format,
|
|
173
|
-
filename: this.config.filename,
|
|
174
|
-
outputFolder: this.config.outputFolder,
|
|
175
|
-
...audits,
|
|
176
|
-
} );
|
|
177
|
-
|
|
178
|
-
if( result ){
|
|
179
|
-
// we have to inject the css-audit.update.js file into the head in order for the webpack-dev-server scripts to load.
|
|
180
|
-
let pageContent = fs.readFileSync(path.join(staticDir.directory, `${this.config.filename}.html`))
|
|
181
|
-
|
|
182
|
-
fs.writeFileSync(
|
|
183
|
-
path.join(staticDir.directory, `${this.config.filename}.html`),
|
|
184
|
-
pageContent.toString().replace('</head>', `<script src="./css-audit.update.js"></script>\n</head>`)
|
|
185
|
-
)
|
|
131
|
+
console.log('<i> \x1b[32m[webpack-dev-middleware] Running CSS Auditor...\x1b[0m');
|
|
132
|
+
/**
|
|
133
|
+
* We run the css audit
|
|
134
|
+
*
|
|
135
|
+
* we scan the output.publicPath if its set and not set to 'auto'
|
|
136
|
+
* otherwise we scan the output.path
|
|
137
|
+
*/
|
|
138
|
+
let scanDir = compiler.options.output.publicPath && 'auto' !== compiler.options.output.publicPath ?
|
|
139
|
+
compiler.options.output.publicPath :
|
|
140
|
+
compiler.options.output.path;
|
|
141
|
+
|
|
142
|
+
// get all .css files
|
|
143
|
+
let files = fs.readdirSync( scanDir, { recursive: true } ).filter( f => f.endsWith( '.css' ) ).map( f => path.join( scanDir, f ) );
|
|
144
|
+
|
|
145
|
+
if( this.audit(files, this.config ) ){
|
|
146
|
+
console.log(`<i> \x1b[32m[webpack-dev-middleware] CSS Auditor completed successfully. Report can be viewed at \x1b[34m ${new URL(`${auditUrl}${staticDir.publicPath}/${this.config.filename}.html`).toString()}\x1b[0m`);
|
|
186
147
|
}
|
|
187
|
-
|
|
188
|
-
console.log(`<i> ${boldGreen('[webpack-dev-middleware] CSS Audit can be viewed at')} ${ boldBlue(new URL(`${auditUrl}${staticDir.publicPath}/${this.config.filename}.html`).toString()) }`);
|
|
189
|
-
|
|
190
|
-
callback();
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
148
|
|
|
149
|
+
|
|
150
|
+
});
|
|
194
151
|
});
|
|
152
|
+
// });
|
|
195
153
|
|
|
196
154
|
}
|
|
197
155
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
156
|
/**
|
|
202
157
|
* Run WordPress CSS Audit
|
|
203
158
|
*
|
|
@@ -222,143 +177,107 @@ class CSSAuditPlugin {
|
|
|
222
177
|
format,
|
|
223
178
|
filename,
|
|
224
179
|
outputFolder,
|
|
225
|
-
|
|
226
|
-
important,
|
|
227
|
-
displayNone,
|
|
228
|
-
selectors,
|
|
229
|
-
mediaQueries,
|
|
230
|
-
typography,
|
|
231
|
-
propertyValues
|
|
180
|
+
audits
|
|
232
181
|
}){
|
|
233
182
|
|
|
234
|
-
|
|
235
|
-
|
|
183
|
+
// outputFolder should not be absolute
|
|
184
|
+
outputFolder = path.isAbsolute(outputFolder) ? path.join( process.cwd(), outputFolder ) : outputFolder;
|
|
236
185
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// we always make sure the output folder exists
|
|
241
|
-
fs.mkdirSync( outputFolder, { recursive: true } );
|
|
242
|
-
|
|
243
|
-
files.forEach( (paths, i) => {
|
|
244
|
-
let resolvePath = path.resolve(paths);
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
// if given path is a directory
|
|
248
|
-
if( fs.statSync(resolvePath).isDirectory() ){
|
|
249
|
-
|
|
250
|
-
// get all .css files
|
|
251
|
-
getAllFilesSync(resolvePath).toArray().forEach(f => {
|
|
252
|
-
if( f.endsWith('.css') ){
|
|
253
|
-
filesToBeAudited.push(f)
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
// if given path is a file and a .css file
|
|
257
|
-
}else if( fs.statSync(paths).isFile() && (paths.endsWith('.css') || paths.endsWith('.scss')) ){
|
|
258
|
-
filesToBeAudited.push(paths)
|
|
259
|
-
}
|
|
260
|
-
// invalid path/file
|
|
261
|
-
} catch (error) {
|
|
262
|
-
filesWithIssues.push(paths)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if( ! filesToBeAudited.length ){
|
|
268
|
-
console.log('No file(s) or directory path(s) were given or default directory was not found.')
|
|
269
|
-
console.log('Auditor did not execute.');
|
|
186
|
+
// if no files are passed, exit
|
|
187
|
+
if( ! files || ! files.length ){
|
|
188
|
+
console.log( '\x1b[31mNo CSS files found to audit.\nAuditor did not execute.\x1b[0m' );
|
|
270
189
|
|
|
190
|
+
// ensure the output folder exists before moving files
|
|
191
|
+
fs.mkdirSync( outputFolder, { recursive: true } );
|
|
192
|
+
|
|
193
|
+
// copy no files sample report
|
|
271
194
|
fs.copyFileSync(
|
|
272
195
|
path.join(currentPath, 'sample', 'no-files.html'),
|
|
273
|
-
path.join(
|
|
196
|
+
path.join(outputFolder, `${filename}.html`),
|
|
274
197
|
)
|
|
275
198
|
|
|
276
|
-
return
|
|
199
|
+
return;
|
|
277
200
|
}
|
|
278
201
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
202
|
+
// pass all audits except propertyValues since those take a value there may be multiple
|
|
203
|
+
let propertyValues = [];
|
|
204
|
+
let auditArgs = audits.map( (audit) => {
|
|
205
|
+
if('string' === typeof audit) {
|
|
206
|
+
return `--${audit}`;
|
|
207
|
+
}else if( Array.isArray(audit) && 'property-values' === audit[0] ){
|
|
208
|
+
// propertyValues += audit[1].split(',').map( v => v.trim() ).join(',');
|
|
209
|
+
propertyValues = propertyValues.concat( audit[1].split(',').map( v => v.trim() ) );
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}).filter( Boolean );
|
|
213
|
+
|
|
214
|
+
// push property values separately
|
|
215
|
+
if( propertyValues ){
|
|
216
|
+
auditArgs.push(`--property-values=${propertyValues.join(',')}`);
|
|
217
|
+
}
|
|
287
218
|
|
|
288
|
-
//
|
|
289
|
-
if(
|
|
290
|
-
|
|
219
|
+
// add the format if set
|
|
220
|
+
if( format ){
|
|
221
|
+
auditArgs.push( `--format=${format}` );
|
|
291
222
|
}
|
|
292
|
-
|
|
223
|
+
|
|
293
224
|
/**
|
|
294
225
|
* the css audit uses the filename for the title, rather than the project name
|
|
295
226
|
* we fix that by passing the project name for the file name
|
|
296
227
|
* then renaming the file to the intended file name.
|
|
297
228
|
*/
|
|
298
|
-
|
|
299
|
-
colors && ! processArgs.includes('--no-colors') ? '--colors' : '',
|
|
300
|
-
important && ! processArgs.includes('--no-important') ? '--important' : '',
|
|
301
|
-
displayNone && ! processArgs.includes('--no-display-none') ? '--display-none' : '',
|
|
302
|
-
selectors && ! processArgs.includes('--no-selectors') ? '--selectors' : '',
|
|
303
|
-
// mediaQueries && ! processArgs.includes('--no-media-queries') ? '--media-queries' : '',
|
|
304
|
-
typography && ! processArgs.includes('--no-typography') ? '--typography' : '',
|
|
305
|
-
format ? `--format=${format}` : '',
|
|
306
|
-
filename ? `--filename=${path.basename(process.cwd())}` : ''
|
|
307
|
-
].filter( e => e)
|
|
308
|
-
|
|
309
|
-
if( propertyValues && ! processArgs.includes('--no-property-values') ){
|
|
310
|
-
propertyValues.forEach((p) => {
|
|
311
|
-
auditArgs.push(`--property-values=${p.replace(' ',',')}`)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
229
|
+
auditArgs.push( `--filename=${path.basename(process.cwd())}` );
|
|
314
230
|
|
|
315
231
|
let { stdout, stderr } = spawn.sync(
|
|
316
232
|
'node',
|
|
317
233
|
[
|
|
318
|
-
resolveBin(
|
|
319
|
-
|
|
320
|
-
...filesToBeAudited,
|
|
234
|
+
resolveBin(currentPath, {executable: 'auditor'}),
|
|
235
|
+
...files,
|
|
321
236
|
...auditArgs
|
|
322
237
|
],
|
|
323
238
|
{
|
|
324
|
-
shell: false,
|
|
325
239
|
stdio: 'pipe',
|
|
326
|
-
cwd:
|
|
240
|
+
cwd: path.join( currentPath, 'bin', 'auditor' ), // has to be set to the bin/auditor directory
|
|
327
241
|
}
|
|
328
242
|
)
|
|
329
243
|
|
|
244
|
+
// if there was an error with the audit process
|
|
330
245
|
if( stderr && stderr.toString() ){
|
|
331
|
-
console.log( stderr.toString() )
|
|
246
|
+
console.log( 'CSS Audit Error: ', stderr.toString() );
|
|
332
247
|
}
|
|
333
248
|
|
|
334
249
|
if( stdout && stdout.toString() ){
|
|
250
|
+
// the css audit tool always outputs to its own public directory
|
|
251
|
+
let defaultOutputPath = path.join(currentPath, 'bin', 'auditor', 'public');
|
|
252
|
+
|
|
253
|
+
// rename the file back to the intended file name instead of the project name
|
|
254
|
+
fs.renameSync(
|
|
255
|
+
path.join(defaultOutputPath, `${path.basename(process.cwd())}.html`),
|
|
256
|
+
path.join(defaultOutputPath, `${filename}.html`)
|
|
257
|
+
)
|
|
335
258
|
|
|
336
|
-
|
|
337
|
-
|
|
259
|
+
// ensure the output folder exists before moving files
|
|
260
|
+
fs.mkdirSync( outputFolder, { recursive: true } );
|
|
338
261
|
|
|
262
|
+
// move all files except .gitkeep to the output folder
|
|
263
|
+
fs.readdirSync( defaultOutputPath ).filter( f => ! f.endsWith('.gitkeep')).forEach( (f) => {
|
|
339
264
|
fs.renameSync(
|
|
340
|
-
path.join(defaultOutputPath,
|
|
341
|
-
|
|
265
|
+
path.join( defaultOutputPath, f ),
|
|
266
|
+
path.join( outputFolder, f )
|
|
342
267
|
)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
console.log( msg );
|
|
357
|
-
console.log( path.resolve(outputFile) )
|
|
358
|
-
// otherwise it's being applied during the webpack process.
|
|
359
|
-
}else{
|
|
360
|
-
return msg;
|
|
361
|
-
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// clean up the default output message
|
|
271
|
+
let msg = stdout.toString().replace('undefined', '').replace('template', 'css audit');
|
|
272
|
+
|
|
273
|
+
// the command was ran via cli
|
|
274
|
+
if( 'audit' === process.argv[2] ){
|
|
275
|
+
console.log( msg );
|
|
276
|
+
console.log( path.join(outputFolder, `${filename}.html`) )
|
|
277
|
+
// otherwise it's being applied during the webpack process.
|
|
278
|
+
}else{
|
|
279
|
+
return msg;
|
|
280
|
+
}
|
|
362
281
|
|
|
363
282
|
}
|
|
364
283
|
|
|
@@ -367,4 +286,4 @@ class CSSAuditPlugin {
|
|
|
367
286
|
} // end of class
|
|
368
287
|
|
|
369
288
|
|
|
370
|
-
export default
|
|
289
|
+
export default CAWebCSSAuditPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caweb/css-audit-webpack-plugin",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CAWebPublishing Webpack Plugin to run WordPress CSS Audit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -36,9 +36,11 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"webpack": "webpack",
|
|
38
38
|
"postinstall": "cd bin/auditor && npm ci",
|
|
39
|
+
"serve": "webpack serve --config ./node_modules/@caweb/webpack/webpack.config.js ./tests/webpack.tests.js --merge",
|
|
39
40
|
"test": "echo \"Error: run tests from root\" && exit 0"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
43
|
+
"@caweb/webpack": "^1.6.3",
|
|
42
44
|
"chalk": "^5.6.2",
|
|
43
45
|
"cross-spawn": "^7.0.6",
|
|
44
46
|
"deepmerge": "^4.3.1",
|
|
@@ -47,7 +49,8 @@
|
|
|
47
49
|
"twig": "^1.17.1"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"webpack": "^
|
|
52
|
+
"@caweb/html-webpack-plugin": "^2.1.0",
|
|
53
|
+
"webpack": "^5.104.1",
|
|
51
54
|
"webpack-cli": "^6.0.1"
|
|
52
55
|
}
|
|
53
56
|
}
|