@caweb/a11y-webpack-plugin 1.0.9 → 1.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.
Files changed (3) hide show
  1. package/aceconfig.js +3 -3
  2. package/index.js +180 -120
  3. package/package.json +4 -1
package/aceconfig.js CHANGED
@@ -35,10 +35,10 @@ export default {
35
35
  ],
36
36
  failLevels: failLevels.filter(e=>e),
37
37
  reportLevels: reportLevels.filter(e=>e),
38
- outputFilename: 'a11y',
39
- outputFolder: "public",
38
+ outputFilename: 'reports',
39
+ outputFolder: "/audits/a11y",
40
40
  outputFormat: [
41
- 'html'
41
+ 'html', 'json'
42
42
  ],
43
43
  outputFilenameTimestamp: false
44
44
  }
package/index.js CHANGED
@@ -14,6 +14,11 @@ import deepmerge from 'deepmerge';
14
14
  import chalk from 'chalk';
15
15
  import { fileURLToPath, URL } from 'url';
16
16
 
17
+ /**
18
+ * Internal dependencies
19
+ */
20
+ import { landingPage, reporter } from './reporter.js';
21
+
17
22
  // default configuration
18
23
  import {default as DefaultConfig} from './aceconfig.js';
19
24
 
@@ -22,138 +27,157 @@ const boldGreen = chalk.bold.green;
22
27
  const boldBlue = chalk.bold.hex('#03a7fc');
23
28
  const currentPath = path.dirname(fileURLToPath(import.meta.url));
24
29
 
30
+ const pluginName = 'CAWebA11yWebpackPlugin';
31
+
25
32
  // IBM Accessibility Checker Plugin
26
33
  class A11yPlugin {
27
34
  config = {}
28
35
 
29
36
  constructor(opts = {}) {
30
- // outputFolder must be resolved
31
- if( opts.outputFolder ){
32
- opts.outputFolder = path.join(process.cwd(), opts.outputFolder);
37
+ // the default publicPath is always the outputFolder
38
+ DefaultConfig.publicPath = DefaultConfig.outputFolder;
39
+
40
+ // the default output folder is always relative to the current working directory.
41
+ DefaultConfig.outputFolder = path.join( process.cwd(), DefaultConfig.outputFolder );
42
+
43
+ // if opts.outputFolder is defined
44
+ if( opts.outputFolder && ! path.isAbsolute(opts.outputFolder) ){
45
+ opts.publicPath = opts.outputFolder;
46
+
47
+ // we join the current working directory with the opts.outputFolder
48
+ opts.outputFolder = path.join( process.cwd(), opts.outputFolder );
33
49
  }
34
- this.config = deepmerge(
35
- DefaultConfig,
36
- {
37
- outputFolder: path.join(currentPath, DefaultConfig.outputFolder)
38
- },
39
- opts
40
- );
50
+
51
+ this.config = deepmerge(DefaultConfig, opts);
41
52
  }
42
53
 
54
+
43
55
  apply(compiler) {
44
56
  const staticDir = {
45
57
  directory: this.config.outputFolder,
58
+ publicPath: encodeURI(this.config.publicPath).replace(':', ''),
46
59
  watch: true
47
60
  }
48
61
 
49
- let { devServer, output } = compiler.options;
50
- let hostUrl = 'localhost' === devServer.host ? `http://${devServer.host}`: devServer.host;
51
- let hostPort = devServer.port;
62
+ let { devServer } = compiler.options;
63
+ let auditUrl = `${devServer.server}://${devServer.host}:${devServer.port}`;
64
+ let nodeModulePath = encodeURI(this.config.publicPath).replace(':', '') + '/node_modules';
65
+ let pathRewrite = {};
66
+ pathRewrite[`^${nodeModulePath}`] = '';
67
+
68
+ let proxy = {
69
+ context: [ nodeModulePath ],
70
+ target: auditUrl,
71
+ pathRewrite,
72
+ };
73
+
74
+ // we add the proxy to the devServer
75
+ if( Array.isArray(devServer.proxy) ){
76
+ devServer.proxy.push(proxy)
77
+ }else{
78
+ devServer.proxy = [].concat(devServer.proxy, proxy );
79
+ }
52
80
 
53
- if( hostPort && 80 !== hostPort )
54
- {
55
- hostUrl = `${hostUrl}:${hostPort}`;
81
+ // add our static directory to the devServer
82
+ if( Array.isArray(devServer.static) ){
83
+ devServer.static.push(staticDir)
84
+ }else{
85
+ devServer.static = [].concat(devServer.static, staticDir );
56
86
  }
57
87
 
58
88
  // if dev server allows for multiple pages to be opened
59
89
  // add outputFilename.html to open property.
60
90
  if( Array.isArray(devServer.open) ){
61
- devServer.open.push(`${hostUrl}/${this.config.outputFilename}.html`)
91
+ devServer.open.push(`${staticDir.publicPath}/${this.config.outputFilename}.html`)
62
92
  }else if( 'object' === typeof devServer.open && Array.isArray(devServer.open.target) ){
63
- devServer.open.target.push(`${hostUrl}/${this.config.outputFilename}.html`)
93
+ devServer.open.target.push(`${staticDir.publicPath}/${this.config.outputFilename}.html`)
64
94
  }
65
95
 
66
- // add our static directory
67
- if( Array.isArray(devServer.static) ){
68
- devServer.static.push(staticDir)
69
- }else{
70
- devServer.static = [].concat(devServer.static, staticDir );
71
- }
72
-
73
- // Wait for configuration preset plugins to apply all configure webpack defaults
74
- compiler.hooks.initialize.tap('IBM Accessibility Plugin', () => {
96
+ // we always make sure the output folder exists
97
+ fs.mkdirSync( staticDir.directory, { recursive: true } );
98
+
99
+ // Hot Module Replacement
100
+ if( compiler?.options?.devServer?.hot ){
101
+ // we create a blank file for the hot update to compile on our page.
102
+ // this is required for the hot-update to work.
103
+ fs.writeFileSync(
104
+ path.join(staticDir.directory, `a11y.update.js`),
105
+ `` // required for hot-update to compile on our page, blank script for now
106
+ );
107
+
108
+ // we add the entry to the dependency factory during compilation
75
109
  compiler.hooks.compilation.tap(
76
- "IBM Accessibility Plugin",
110
+ pluginName,
77
111
  (compilation, { normalModuleFactory }) => {
78
112
  compilation.dependencyFactories.set(
79
113
  EntryDependency,
80
114
  normalModuleFactory
81
115
  );
82
- }
83
- );
84
116
 
85
- const { entry, options, context } = {
86
- entry: path.join( this.config.outputFolder, 'a11y.update.js'),
87
- options: {
88
- name: 'a11y.update'
89
- },
90
- context: 'a11y'
91
- };
92
-
93
- const dep = new EntryDependency(entry);
94
- dep.loc = {
95
- name: options.name
96
- };
97
- if( ! fs.existsSync(path.resolve(this.config.outputFolder))){
98
- fs.mkdirSync( path.resolve(this.config.outputFolder), {recursive: true} );
99
- }
100
-
101
- fs.writeFileSync(
102
- path.join(this.config.outputFolder, `a11y.update.js`),
103
- `` // required for hot-update to compile on our page, blank script for now
117
+ }
104
118
  );
105
119
 
106
-
107
- compiler.hooks.thisCompilation.tap('IBM Accessibility Plugin',
108
- /**
109
- * Hook into the webpack compilation
110
- * @param {Compilation} compilation
111
- */
112
- (compilation) => {
113
-
114
- compiler.hooks.make.tapAsync("IBM Accessibility Plugin", (compilation, callback) => {
115
-
116
- compilation.addEntry(
117
- context,
118
- dep,
119
- options,
120
- err => {
121
- callback(err);
122
- });
120
+ // we add the entry before the compilation ends
121
+ compiler.hooks.make.tapAsync(
122
+ pluginName,
123
+ (compilation, callback) => {
124
+ const { entry, options, context } = {
125
+ entry: path.join( staticDir.directory, 'a11y.update.js'),
126
+ options: {
127
+ name: 'a11y.update'
128
+ },
129
+ context: 'a11y'
130
+ };
131
+
132
+ const dep = new EntryDependency(entry);
133
+ dep.loc = {
134
+ name: options.name
135
+ };
136
+
137
+ compilation.addEntry(
138
+ context,
139
+ dep,
140
+ options,
141
+ err => {
142
+ callback(err);
123
143
  });
144
+ });
145
+ }
124
146
 
125
- });
126
-
147
+ compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
148
+ // We can audit the html files now that the compilation is done.
149
+ // we hook into the done hook to run the accessibility checker.
127
150
  compiler.hooks.done.tapAsync(
128
- 'IBM Accessibility Plugin',
129
- /**
130
- * Hook into the process assets hook
131
- * @param {any} _
132
- * @param {(err?: Error) => void} callback
133
- */
151
+ pluginName,
134
152
  (stats, callback) => {
135
-
153
+
136
154
  console.log(`<i> ${boldGreen('[webpack-dev-middleware] Running IBM Accessibility scan...')}`);
137
155
 
138
- let result = this.a11yCheck('auto' === output.publicPath ? output.path : output.publicPath, this.config );
156
+ // we check the compilers output.publicPath
157
+ // if it is not set, we scan the output.path as the publicPath
158
+ let result = this.a11yCheck(
159
+ ! compiler.options.output.publicPath ||
160
+ 'auto' === compiler.options.output.publicPath ?
161
+ compiler.options.output.path :
162
+ compiler.options.output.publicPath,
163
+ this.config
164
+ );
139
165
 
140
- if( result ){
141
- // we have to inject the a11y.update.js file into the head in order for the webpack-dev-server scripts to load.
142
- let pageContent = fs.readFileSync(path.join(staticDir.directory, `${this.config.outputFilename}.html`))
166
+ // if( result ){
167
+ // // we have to inject the a11y.update.js file into the head in order for the webpack-dev-server scripts to load.
168
+ // // let pageContent = fs.readFileSync(path.join(staticDir.directory, `${this.config.outputFilename}.html`))
143
169
 
144
- fs.writeFileSync(
145
- path.join(staticDir.directory, `${this.config.outputFilename}.html`),
146
- pageContent.toString().replace('</head>', `<script src="./a11y.update.js"></script>\n</head>`)
147
- )
148
- }
170
+ // // fs.writeFileSync(
171
+ // // path.join(staticDir.directory, `${this.config.outputFilename}.html`),
172
+ // // pageContent.toString().replace('</head>', `<script src="./a11y.update.js"></script>\n</head>`)
173
+ // // )
174
+ // }
149
175
 
150
- console.log(`<i> ${boldGreen('[webpack-dev-middleware] IBM Accessibilty Report can be viewed at')} ${ boldBlue(new URL(`${hostUrl}/${this.config.outputFilename}.html`).toString()) }`);
176
+ console.log(`<i> ${boldGreen('[webpack-dev-middleware] IBM Accessibilty Report can be viewed at')} ${ boldBlue(new URL(`${auditUrl}/${staticDir.publicPath}/${this.config.outputFilename}.html`).toString()) }`);
151
177
 
152
- callback();
153
178
  });
154
-
155
- });
156
-
179
+ })
180
+
157
181
  }
158
182
 
159
183
  /**
@@ -184,6 +208,12 @@ class A11yPlugin {
184
208
  outputFilenameTimestamp
185
209
  }){
186
210
 
211
+
212
+ let htmlOutput = outputFormat && outputFormat.includes('html');
213
+
214
+ // we remove the html output since we generate our own html based on the json output.
215
+ // outputFormat = outputFormat.filter(o => 'html' !== o );
216
+
187
217
  let acheckerArgs = [
188
218
  '--ruleArchive',
189
219
  ruleArchive,
@@ -202,25 +232,11 @@ class A11yPlugin {
202
232
  url
203
233
  ];
204
234
 
205
- let isValid = false;
206
-
207
- if( fs.existsSync( url ) ){
208
- if( fs.statSync(url).isDirectory() && path.join( url, 'index.html') ){
209
- url = path.join( url, 'index.html')
210
- }
211
- isValid = true;
212
- }else{
213
- isValid = 'localhost' === new URL(url).hostname || isUrl( url )
214
- }
235
+ let isValid = fs.existsSync( url ) || 'localhost' === new URL(url).hostname || isUrl( url );
236
+ let isDirectory = fs.existsSync( url ) && fs.statSync(url).isDirectory();
215
237
 
216
238
  if( isValid ){
217
- let originalFileName = `${fs.existsSync( url ) ?
218
- path.resolve(url).replace(':', '_') :
219
- url.replace(/http[s]+:\/\//, '')}.html`;
220
- let originalJsonFileName = `${fs.existsSync( url ) ?
221
- path.resolve(url).replace(':', '_') :
222
- url.replace(/http[s]+:\/\//, '')}.json`;
223
-
239
+
224
240
  let outputDir = path.resolve('.', outputFolder );
225
241
 
226
242
  let {stderr, stdout} = spawn.sync(
@@ -236,27 +252,71 @@ class A11yPlugin {
236
252
  }
237
253
 
238
254
  if( stdout && stdout.toString()){
239
- let reportedFile = path.join(outputDir, originalFileName );
240
- let reportedJSon = path.join(outputDir, originalJsonFileName );
255
+ let auditIndex = [];
256
+
257
+ // we iterate thru the output directory in reverse order,
258
+ // this way we can remove any empty directories at the end, since files are cycled first.
259
+ fs.readdirSync(outputDir, {recursive: true}).reverse().forEach( file => {
260
+ // process each json file file
261
+ if( fs.statSync(path.join(outputDir, file)).isFile() ){
262
+ if ( file.startsWith('summary_') ){
263
+ // remove the summary files
264
+ fs.rmSync( path.join(outputDir, file) )
265
+ return;
266
+ }
267
+
268
+ let oldName = file;
269
+ let newName = file;
270
+
271
+ // remove the original output directory from the file name
272
+ newName = isDirectory ? file.replace(url.replace(':', '_'), '') : file;
273
+
274
+ newName = newName.replace(/^\\/, '');
241
275
 
242
- // if output file name option was passed
243
- if( outputFilename ){
276
+ // for some reason .html files have an extra .html in the name
277
+ newName = newName.endsWith('.html.html') ? newName.replace('.html.html', '.html') : newName;
278
+
279
+ // if the new name is not the same as the old name.
280
+ if( newName !== file ){
281
+ // rename the file
282
+ fs.renameSync(
283
+ path.join(outputDir, oldName ),
284
+ path.join(outputDir, newName) );
285
+ }
286
+
287
+ // we add the file to the audit index
288
+ if( ! auditIndex.includes(newName) && newName.endsWith('.html') ){
289
+ auditIndex.push( newName );
290
+ }
291
+
292
+ // if we are generating html output, we need to generate the html file.
293
+ if( htmlOutput ){
294
+ // let jsonObj = JSON.parse(fs.readFileSync(path.join(outputDir, newName)));
244
295
 
245
- reportedFile = path.join( outputDir, `${outputFilename}.html` );
246
- reportedJSon = path.join( outputDir, `${outputFilename}.json` );
296
+ // we generate the html file
297
+ // reporter( jsonObj, { outputFolder, outputFilename: newName.replace(/\.json$/, '') } );
298
+ }
299
+
300
+ }else if ( fs.statSync(path.join(outputDir, file)).isDirectory() ){
301
+ // process each directory
302
+ // delete any empty directories.
303
+ if( 0 === fs.readdirSync(path.join(outputDir, file)).length ){
304
+ // remove the directory
305
+ fs.rmSync(path.join(outputDir, file), {recursive: true});
306
+ }
307
+ }
247
308
 
248
- // rename the output files
249
- fs.renameSync(path.join(outputDir, originalFileName), reportedFile );
250
- fs.renameSync(path.join(outputDir, originalJsonFileName), reportedJSon );
309
+ });
251
310
 
252
- // delete any empty directories.
253
- fs.rmSync( path.join(outputDir, originalFileName.split(path.sep).shift()), {recursive: true} )
254
- }
311
+ // we generate the landing page.
312
+ landingPage( auditIndex, {outputFolder, outputFilename} );
255
313
 
314
+ // we generate the .
256
315
  if( 'a11y' === process.argv[2] ){
257
- console.log( reportedFile )
316
+ // console.log( reportedFile )
258
317
  }else{
259
- return reportedFile;
318
+ return true;
319
+ // return reportedFile;
260
320
  }
261
321
  }
262
322
  }else{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/a11y-webpack-plugin",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "CAWebPublishing Webpack Plugin to run Accessibility Scans",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -38,5 +38,8 @@
38
38
  "devDependencies": {
39
39
  "webpack": "^5.96.1",
40
40
  "webpack-cli": "^5.1.4"
41
+ },
42
+ "peerDependencies": {
43
+ "@caweb/template": "^1.0.2"
41
44
  }
42
45
  }