@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.
- package/aceconfig.js +3 -3
- package/index.js +180 -120
- 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: '
|
|
39
|
-
outputFolder: "
|
|
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
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
50
|
-
let
|
|
51
|
-
let
|
|
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
|
-
|
|
54
|
-
{
|
|
55
|
-
|
|
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(`${
|
|
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(`${
|
|
93
|
+
devServer.open.target.push(`${staticDir.publicPath}/${this.config.outputFilename}.html`)
|
|
64
94
|
}
|
|
65
95
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
110
|
+
pluginName,
|
|
77
111
|
(compilation, { normalModuleFactory }) => {
|
|
78
112
|
compilation.dependencyFactories.set(
|
|
79
113
|
EntryDependency,
|
|
80
114
|
normalModuleFactory
|
|
81
115
|
);
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
116
|
|
|
85
|
-
|
|
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.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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(`${
|
|
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 =
|
|
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
|
-
|
|
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
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
fs.renameSync(path.join(outputDir, originalFileName), reportedFile );
|
|
250
|
-
fs.renameSync(path.join(outputDir, originalJsonFileName), reportedJSon );
|
|
309
|
+
});
|
|
251
310
|
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|