@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.
@@ -9,7 +9,7 @@ export function hasOwnProperty( obj, prop ) {
9
9
 
10
10
  export default {
11
11
  format: 'html',
12
- filename: 'index',
12
+ filename: 'reports',
13
13
  outputFolder: '/audits/css',
14
14
  audits: [
15
15
  'colors',
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 CSSAuditPlugin {
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.publicPath).replace(':', ''),
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(this.config.publicPath).replace(':', '') + '/node_modules';
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(`${staticDir.publicPath}/${this.config.filename}.html`)
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(`${staticDir.publicPath}/${this.config.filename}.html`)
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('CSS Audit Plugin', () => {
93
- compiler.hooks.compilation.tap(
94
- "CSS Audit Plugin",
95
- (compilation, { normalModuleFactory }) => {
96
- compilation.dependencyFactories.set(
97
- EntryDependency,
98
- normalModuleFactory
99
- );
100
- }
101
- );
102
-
103
- const { entry, options, context } = {
104
- entry: path.join(staticDir.directory, 'css-audit.update.js'),
105
- options: {
106
- name: 'css-audit.update'
107
- },
108
- context: staticDir.directory
109
- };
110
-
111
- const dep = new EntryDependency(entry);
112
- dep.loc = {
113
- name: options.name
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
- fs.writeFileSync(
117
- path.join(staticDir.directory, `css-audit.update.js`),
118
- `` // required for hot-update to compile on our page, blank script for now
119
- );
120
-
121
- compiler.hooks.thisCompilation.tap('CSS Audit Plugin',
122
- /**
123
- * Hook into the webpack compilation
124
- * @param {Compilation} compilation
125
- */
126
- (compilation) => {
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
- compiler.hooks.make.tapAsync("CSS Audit Plugin", (compilation, callback) => {
113
+ // // compiler.hooks.make.tapAsync(pluginName, (compilation, callback) => {
129
114
 
130
- compilation.addEntry(
131
- context,
132
- dep,
133
- options,
134
- err => {
135
- callback(err);
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('CSS Audit Plugin',
129
+ compiler.hooks.done.tapAsync(pluginName,
144
130
  (stats, callback) => {
145
- let files = [];
146
- getAllFilesSync(compiler.options.output.path).toArray().forEach(f => {
147
- // we skip any Right to Left style sheets
148
- if( f.endsWith('.css') && ! f.endsWith('-rtl.css') ){
149
- files.push(f)
150
- }
151
- })
152
- console.log(`<i> ${boldGreen('[webpack-dev-middleware] Running CSS Audit...')}`);
153
-
154
- let audits = {};
155
- this.config.audits.forEach( (audit) => {
156
- let key = 'string' === typeof audit ? audit : audit[0];
157
- let value = 'string' === typeof audit ? true : audit[1];
158
-
159
- // fix key
160
- key = key.replace(/-\w/g, (m) => m[1].toUpperCase());
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
- colors,
226
- important,
227
- displayNone,
228
- selectors,
229
- mediaQueries,
230
- typography,
231
- propertyValues
180
+ audits
232
181
  }){
233
182
 
234
- let filesToBeAudited = [];
235
- let filesWithIssues = [];
183
+ // outputFolder should not be absolute
184
+ outputFolder = path.isAbsolute(outputFolder) ? path.join( process.cwd(), outputFolder ) : outputFolder;
236
185
 
237
- // the css audit tool always outputs to its own public directory
238
- let defaultOutputPath = path.join(currentPath, 'bin', 'auditor', 'public');
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(defaultOutputPath, `${filename}.html`),
196
+ path.join(outputFolder, `${filename}.html`),
274
197
  )
275
198
 
276
- return false;
199
+ return;
277
200
  }
278
201
 
279
- /**
280
- * We combine process arguments from argv, argv0
281
- */
282
- const processArgs = [
283
- ...process.argv,
284
- ...process.argv0.split(' '),
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
- // we also add args from env.NODE_OPTIONS
289
- if( process.env.NODE_OPTIONS ){
290
- processArgs.push( ...process.env.NODE_OPTIONS.split(' ').filter(e=>e).map((o) => o.replaceAll("'", '')) )
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
- let auditArgs = [
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('@caweb/css-audit-webpack-plugin', {executable: 'auditor'}),
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: fs.existsSync(path.join(process.cwd(), 'css-audit.config.cjs')) ? process.cwd() : currentPath
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
- // rename the file back to the intended file name instead of the project name
337
- let outputFile = path.join(outputFolder, `${filename}.html`);
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, `${path.basename(process.cwd())}.html`),
341
- outputFile
265
+ path.join( defaultOutputPath, f ),
266
+ path.join( outputFolder, f )
342
267
  )
343
-
344
- // we also move the style.css as well case the output path is different than the default.
345
- if( fs.existsSync( path.join( defaultOutputPath, 'style.css' ) ) ){
346
- fs.renameSync(
347
- path.join( defaultOutputPath, 'style.css' ),
348
- path.join( outputFolder, 'style.css' )
349
- )
350
- }
351
-
352
- let msg = stdout.toString().replace('undefined', '').replace('template', 'css audit');
353
-
354
- // the command was ran via cli
355
- if( 'audit' === process.argv[2] ){
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 CSSAuditPlugin;
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.2",
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": "^5.102.1",
52
+ "@caweb/html-webpack-plugin": "^2.1.0",
53
+ "webpack": "^5.104.1",
51
54
  "webpack-cli": "^6.0.1"
52
55
  }
53
56
  }