@caweb/css-audit-webpack-plugin 1.0.12 → 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/bin/auditor/.github/workflows/build-report.yml +15 -14
- package/bin/auditor/.github/workflows/merge-trunk-to-report.yml +5 -4
- package/bin/auditor/.github/workflows/node.yaml +6 -9
- package/bin/auditor/.nvmrc +1 -1
- package/bin/auditor/package-lock.json +9954 -5695
- package/bin/auditor/package.json +16 -18
- package/bin/auditor/src/__tests__/selectors.js +14 -0
- package/bin/auditor/src/audits/media-queries.js +2 -4
- package/bin/auditor/src/audits/selectors.js +6 -4
- package/bin/auditor/src/formats/html.js +15 -15
- package/bin/auditor/src/utils/__tests__/get-specificity.js +23 -0
- package/bin/auditor/src/utils/get-specificity.js +30 -14
- package/index.js +24 -25
- package/package.json +7 -6
package/bin/auditor/package.json
CHANGED
|
@@ -12,21 +12,19 @@
|
|
|
12
12
|
"author": "WordPress CSS Contributors",
|
|
13
13
|
"license": "GPL-2.0-or-later",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@wordpress/eslint-plugin": "
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"prettier": "npm:wp-prettier@^2.0.5",
|
|
29
|
-
"tinycolor2": "1.4.2"
|
|
15
|
+
"@wordpress/eslint-plugin": "^22.0.0",
|
|
16
|
+
"chalk": "5.4.1",
|
|
17
|
+
"cli-table3": "0.6.5",
|
|
18
|
+
"cosmiconfig": "9.0.0",
|
|
19
|
+
"css-tree": "3.1.0",
|
|
20
|
+
"cssom": "0.5.0",
|
|
21
|
+
"eslint": "^8.57.1",
|
|
22
|
+
"glob": "11.0.3",
|
|
23
|
+
"minimist": "1.2.8",
|
|
24
|
+
"postcss": "8.5.6",
|
|
25
|
+
"postcss-values-parser": "6.0.2",
|
|
26
|
+
"prettier": "npm:wp-prettier@3.0.3",
|
|
27
|
+
"tinycolor2": "1.6.0"
|
|
30
28
|
},
|
|
31
29
|
"eslintConfig": {
|
|
32
30
|
"extends": [
|
|
@@ -49,8 +47,8 @@
|
|
|
49
47
|
},
|
|
50
48
|
"prettier": "@wordpress/prettier-config",
|
|
51
49
|
"devDependencies": {
|
|
52
|
-
"handlebars": "4.7.
|
|
53
|
-
"jest": "
|
|
54
|
-
"
|
|
50
|
+
"handlebars": "4.7.8",
|
|
51
|
+
"jest": "30.0.3",
|
|
52
|
+
"twig": "^1.17.1"
|
|
55
53
|
}
|
|
56
54
|
}
|
|
@@ -63,4 +63,18 @@ describe( 'Audit: Selectors', () => {
|
|
|
63
63
|
const { value } = results.find( ( { id } ) => 'count' === id );
|
|
64
64
|
expect( value ).toBe( 2 );
|
|
65
65
|
} );
|
|
66
|
+
|
|
67
|
+
it( 'should handle modern CSS', () => {
|
|
68
|
+
expect( () => {
|
|
69
|
+
audit( [
|
|
70
|
+
{
|
|
71
|
+
name: 'a.css',
|
|
72
|
+
content: `h1, h2 { color: green; }
|
|
73
|
+
div:not([hidden]) { color: black; }
|
|
74
|
+
body :is(h1, h2) { color: red; }
|
|
75
|
+
body :where(h1, h2) { color: orange; }`,
|
|
76
|
+
},
|
|
77
|
+
] );
|
|
78
|
+
} ).not.toThrow( SyntaxError );
|
|
79
|
+
} );
|
|
66
80
|
} );
|
|
@@ -17,11 +17,9 @@ module.exports = function ( files = [] ) {
|
|
|
17
17
|
csstree.walk( ast, {
|
|
18
18
|
visit: 'MediaQuery',
|
|
19
19
|
enter( node ) {
|
|
20
|
-
|
|
21
|
-
allQueries.push( csstree.generate( node ) );
|
|
22
|
-
}
|
|
20
|
+
allQueries.push( csstree.generate( node ) );
|
|
23
21
|
csstree.walk( node, {
|
|
24
|
-
visit: '
|
|
22
|
+
visit: 'Feature',
|
|
25
23
|
enter( sizeNode ) {
|
|
26
24
|
if (
|
|
27
25
|
sizeNode.name === 'max-width' ||
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
const csstree = require( 'css-tree' );
|
|
4
5
|
const { parse } = require( 'postcss' );
|
|
5
6
|
|
|
6
7
|
const { getSpecificityArray } = require( '../utils/get-specificity' );
|
|
@@ -12,10 +13,11 @@ module.exports = function ( files = [] ) {
|
|
|
12
13
|
files.forEach( ( { name, content } ) => {
|
|
13
14
|
const root = parse( content, { from: name } );
|
|
14
15
|
root.walkRules( function ( { selector } ) {
|
|
15
|
-
const selectorList =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const selectorList = csstree.parse( selector, {
|
|
17
|
+
context: 'selectorList',
|
|
18
|
+
} );
|
|
19
|
+
selectorList.children.forEach( ( _selector ) => {
|
|
20
|
+
const selectorName = csstree.generate( _selector );
|
|
19
21
|
const [ a, b, c ] = getSpecificityArray( selectorName );
|
|
20
22
|
const sum = 100 * a + 10 * b + c; // eslint-disable-line no-mixed-operators
|
|
21
23
|
selectors.push( {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const fs = require( 'fs
|
|
1
|
+
const fs = require( 'node:fs' );
|
|
2
2
|
const path = require( 'path' );
|
|
3
|
-
const
|
|
3
|
+
const Twig = require( 'twig' );
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Internal dependencies
|
|
@@ -23,9 +23,6 @@ function getTemplateFile( name ) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
module.exports = function ( reports ) {
|
|
26
|
-
const loader = new TwingLoaderFilesystem( templatePath );
|
|
27
|
-
const twing = new TwingEnvironment( loader, { debug: true } );
|
|
28
|
-
|
|
29
26
|
const reportName = getArg( '--filename' );
|
|
30
27
|
const reportTemplate = getTemplateFile( reportName );
|
|
31
28
|
const reportDestDir = path.join( __dirname, '..', '..', 'public' );
|
|
@@ -38,15 +35,18 @@ module.exports = function ( reports ) {
|
|
|
38
35
|
// Copy CSS src to /public
|
|
39
36
|
const cssSrc = path.join( __dirname, 'html', 'style.css' );
|
|
40
37
|
const cssDest = path.join( reportDestDir, 'style.css' );
|
|
41
|
-
fs.
|
|
38
|
+
fs.copyFileSync( cssSrc, cssDest );
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
Twig.renderFile(
|
|
41
|
+
path.resolve( __dirname, 'html', reportTemplate ),
|
|
42
|
+
context,
|
|
43
|
+
( error, output ) => {
|
|
44
|
+
if ( error ) {
|
|
45
|
+
console.error( e );
|
|
46
|
+
} else {
|
|
47
|
+
console.log( `Generated template for ${ reportName }.` );
|
|
48
|
+
fs.writeFileSync( reportDest, output );
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
52
|
};
|
|
@@ -14,6 +14,8 @@ describe( 'Calculate Specificity', () => {
|
|
|
14
14
|
it( 'should calculate for pseudo-classes', () => {
|
|
15
15
|
expect( getSpecificity( ':checked' ) ).toBe( 10 );
|
|
16
16
|
expect( getSpecificity( 'a:link' ) ).toBe( 11 );
|
|
17
|
+
expect( getSpecificity( 'body:lang(en)' ) ).toBe( 11 );
|
|
18
|
+
expect( getSpecificity( 'body:lang(en,ja)' ) ).toBe( 11 );
|
|
17
19
|
} );
|
|
18
20
|
|
|
19
21
|
it( 'should calculate for class selectors', () => {
|
|
@@ -36,4 +38,25 @@ describe( 'Calculate Specificity', () => {
|
|
|
36
38
|
getSpecificity( 'li > a[href*="en-US"] > .inline-warning' )
|
|
37
39
|
).toBe( 22 );
|
|
38
40
|
} );
|
|
41
|
+
|
|
42
|
+
it( 'should calculate for :is selectors', () => {
|
|
43
|
+
expect( getSpecificity( ':is(h1)' ) ).toBe( 1 );
|
|
44
|
+
expect( getSpecificity( ':is(h1, .class)' ) ).toBe( 10 );
|
|
45
|
+
expect( getSpecificity( ':is(h1, .class, #id)' ) ).toBe( 100 );
|
|
46
|
+
expect( getSpecificity( 'span:is(h1)' ) ).toBe( 2 );
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
it( 'should calculate for :where selectors', () => {
|
|
50
|
+
expect( getSpecificity( ':where(h1)' ) ).toBe( 0 );
|
|
51
|
+
expect( getSpecificity( ':where(h1, .class)' ) ).toBe( 0 );
|
|
52
|
+
expect( getSpecificity( ':where(h1, .class, #id)' ) ).toBe( 0 );
|
|
53
|
+
expect( getSpecificity( 'span:where(h1)' ) ).toBe( 1 );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'should calculate for :not selectors', () => {
|
|
57
|
+
expect( getSpecificity( ':not(h1)' ) ).toBe( 1 );
|
|
58
|
+
expect( getSpecificity( ':not(h1, .class)' ) ).toBe( 10 );
|
|
59
|
+
expect( getSpecificity( ':not(h1, .class, #id)' ) ).toBe( 100 );
|
|
60
|
+
expect( getSpecificity( 'span:not(h1)' ) ).toBe( 2 );
|
|
61
|
+
} );
|
|
39
62
|
} );
|
|
@@ -14,10 +14,28 @@ function calculateSpecificity( [ a, b, c ], selector ) {
|
|
|
14
14
|
if ( ! selector.type ) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
if ( 'PseudoClassSelector' === selector.type && 'lang' !== selector.name ) {
|
|
19
|
+
if ( 'where' === selector.name ) {
|
|
20
|
+
return [ a, b, c ];
|
|
21
|
+
}
|
|
22
|
+
if ( selector.children ) {
|
|
23
|
+
let maxSpec = [ 0, 0, 0 ];
|
|
24
|
+
let max = -1;
|
|
25
|
+
selector.children.forEach( ( list ) => {
|
|
26
|
+
if ( 'SelectorList' === list.type ) {
|
|
27
|
+
list.children.forEach( ( s ) => {
|
|
28
|
+
const _selector = csstree.generate( s );
|
|
29
|
+
const result = getSpecificity( _selector );
|
|
30
|
+
if ( result > max ) {
|
|
31
|
+
maxSpec = getSpecificityArray( _selector );
|
|
32
|
+
max = result;
|
|
33
|
+
}
|
|
34
|
+
} );
|
|
35
|
+
}
|
|
36
|
+
} );
|
|
37
|
+
return [ a + maxSpec[ 0 ], b + maxSpec[ 1 ], c + maxSpec[ 2 ] ];
|
|
38
|
+
}
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
switch ( selector.type ) {
|
|
@@ -65,11 +83,10 @@ function calculateSpecificity( [ a, b, c ], selector ) {
|
|
|
65
83
|
function getSpecificity( selector ) {
|
|
66
84
|
const node = csstree.parse( selector, { context: 'selector' } );
|
|
67
85
|
const selectorList = node.children.toArray();
|
|
68
|
-
const [ a, b, c ] = selectorList.reduce(
|
|
69
|
-
|
|
70
|
-
0,
|
|
71
|
-
|
|
72
|
-
] );
|
|
86
|
+
const [ a, b, c ] = selectorList.reduce(
|
|
87
|
+
calculateSpecificity,
|
|
88
|
+
[ 0, 0, 0 ]
|
|
89
|
+
);
|
|
73
90
|
return 100 * a + 10 * b + c;
|
|
74
91
|
}
|
|
75
92
|
|
|
@@ -82,11 +99,10 @@ function getSpecificity( selector ) {
|
|
|
82
99
|
function getSpecificityArray( selector ) {
|
|
83
100
|
const node = csstree.parse( selector, { context: 'selector' } );
|
|
84
101
|
const selectorList = node.children.toArray();
|
|
85
|
-
const [ a, b, c ] = selectorList.reduce(
|
|
86
|
-
|
|
87
|
-
0,
|
|
88
|
-
|
|
89
|
-
] );
|
|
102
|
+
const [ a, b, c ] = selectorList.reduce(
|
|
103
|
+
calculateSpecificity,
|
|
104
|
+
[ 0, 0, 0 ]
|
|
105
|
+
);
|
|
90
106
|
return [ a, b, c ];
|
|
91
107
|
}
|
|
92
108
|
|
package/index.js
CHANGED
|
@@ -24,14 +24,14 @@ const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
|
|
24
24
|
|
|
25
25
|
// CSS Audit Plugin
|
|
26
26
|
class CSSAuditPlugin {
|
|
27
|
-
config = {}
|
|
27
|
+
config = {};
|
|
28
28
|
|
|
29
29
|
constructor(opts = {}) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
opts.outputFolder = path.join(
|
|
34
|
-
//
|
|
30
|
+
|
|
31
|
+
// if no outputFolder is defined fallback to the default path
|
|
32
|
+
if( ! opts.outputFolder ){
|
|
33
|
+
opts.outputFolder = path.join(process.cwd(), 'audits' )
|
|
34
|
+
// if outputfolder is set and not absolute
|
|
35
35
|
}else if( ! path.isAbsolute(opts.outputFolder) ){
|
|
36
36
|
opts.outputFolder = path.join(process.cwd(), opts.outputFolder );
|
|
37
37
|
}
|
|
@@ -44,21 +44,15 @@ class CSSAuditPlugin {
|
|
|
44
44
|
directory: this.config.outputFolder,
|
|
45
45
|
watch: true
|
|
46
46
|
}
|
|
47
|
-
let { devServer
|
|
48
|
-
let
|
|
49
|
-
let hostPort = devServer.port;
|
|
50
|
-
|
|
51
|
-
if( hostPort && 80 !== hostPort )
|
|
52
|
-
{
|
|
53
|
-
hostUrl = `${hostUrl}:${hostPort}`;
|
|
54
|
-
}
|
|
47
|
+
let { devServer } = compiler.options;
|
|
48
|
+
let auditUrl = `${devServer.server}://${devServer.host}:${devServer.port}`;
|
|
55
49
|
|
|
56
50
|
// if dev server allows for multiple pages to be opened
|
|
57
51
|
// add css-audit.html to open property.
|
|
58
52
|
if( Array.isArray(devServer.open) ){
|
|
59
|
-
devServer.open.push(`${
|
|
53
|
+
devServer.open.push(`${auditUrl}/${this.config.filename}.html`)
|
|
60
54
|
}else if( 'object' === typeof devServer.open && Array.isArray(devServer.open.target) ){
|
|
61
|
-
devServer.open.target.push(`${
|
|
55
|
+
devServer.open.target.push(`${auditUrl}/${this.config.filename}.html`)
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
// add our static directory
|
|
@@ -146,7 +140,7 @@ class CSSAuditPlugin {
|
|
|
146
140
|
)
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
console.log(`<i> ${boldGreen('[webpack-dev-middleware] CSS Audit can be viewed at')} ${ boldBlue(new URL(`${
|
|
143
|
+
console.log(`<i> ${boldGreen('[webpack-dev-middleware] CSS Audit can be viewed at')} ${ boldBlue(new URL(`${auditUrl}/${this.config.filename}.html`).toString()) }`);
|
|
150
144
|
|
|
151
145
|
callback();
|
|
152
146
|
}
|
|
@@ -156,6 +150,9 @@ class CSSAuditPlugin {
|
|
|
156
150
|
|
|
157
151
|
}
|
|
158
152
|
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
159
156
|
/**
|
|
160
157
|
* Run WordPress CSS Audit
|
|
161
158
|
*
|
|
@@ -191,10 +188,10 @@ class CSSAuditPlugin {
|
|
|
191
188
|
|
|
192
189
|
let filesToBeAudited = [];
|
|
193
190
|
let filesWithIssues = [];
|
|
194
|
-
|
|
191
|
+
|
|
195
192
|
// the css audit tool always outputs to its own public directory
|
|
196
193
|
let defaultOutputPath = path.join(currentPath, 'bin', 'auditor', 'public');
|
|
197
|
-
|
|
194
|
+
|
|
198
195
|
// we always make sure the output folder exists
|
|
199
196
|
fs.mkdirSync( outputFolder, { recursive: true } );
|
|
200
197
|
|
|
@@ -258,7 +255,7 @@ class CSSAuditPlugin {
|
|
|
258
255
|
important && ! processArgs.includes('--no-important') ? '--important' : '',
|
|
259
256
|
displayNone && ! processArgs.includes('--no-display-none') ? '--display-none' : '',
|
|
260
257
|
selectors && ! processArgs.includes('--no-selectors') ? '--selectors' : '',
|
|
261
|
-
mediaQueries && ! processArgs.includes('--no-media-queries') ? '--media-queries' : '',
|
|
258
|
+
// mediaQueries && ! processArgs.includes('--no-media-queries') ? '--media-queries' : '',
|
|
262
259
|
typography && ! processArgs.includes('--no-typography') ? '--typography' : '',
|
|
263
260
|
format ? `--format=${format}` : '',
|
|
264
261
|
filename ? `--filename=${path.basename(process.cwd())}` : ''
|
|
@@ -269,25 +266,27 @@ class CSSAuditPlugin {
|
|
|
269
266
|
auditArgs.push(`--property-values=${p.replace(' ',',')}`)
|
|
270
267
|
})
|
|
271
268
|
}
|
|
272
|
-
|
|
269
|
+
|
|
273
270
|
let { stdout, stderr } = spawn.sync(
|
|
274
|
-
'node
|
|
271
|
+
'node',
|
|
275
272
|
[
|
|
273
|
+
resolveBin('@caweb/css-audit-webpack-plugin', {executable: 'auditor'}),
|
|
274
|
+
// '--',
|
|
276
275
|
...filesToBeAudited,
|
|
277
276
|
...auditArgs
|
|
278
277
|
],
|
|
279
278
|
{
|
|
279
|
+
shell: false,
|
|
280
280
|
stdio: 'pipe',
|
|
281
281
|
cwd: fs.existsSync(path.join(process.cwd(), 'css-audit.config.cjs')) ? process.cwd() : currentPath
|
|
282
282
|
}
|
|
283
283
|
)
|
|
284
|
-
|
|
284
|
+
|
|
285
285
|
if( stderr && stderr.toString() ){
|
|
286
286
|
console.log( stderr.toString() )
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
if( stdout && stdout.toString() ){
|
|
290
|
-
|
|
291
290
|
|
|
292
291
|
// rename the file back to the intended file name instead of the project name
|
|
293
292
|
let outputFile = path.join(outputFolder, `${filename}.html`);
|
|
@@ -305,7 +304,7 @@ class CSSAuditPlugin {
|
|
|
305
304
|
)
|
|
306
305
|
}
|
|
307
306
|
|
|
308
|
-
let msg = stdout.toString().replace('undefined', '');
|
|
307
|
+
let msg = stdout.toString().replace('undefined', '').replace('template', 'css audit');
|
|
309
308
|
|
|
310
309
|
// the command was ran via cli
|
|
311
310
|
if( 'audit' === process.argv[2] ){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caweb/css-audit-webpack-plugin",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CAWebPublishing Webpack Plugin to run WordPress CSS Audit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -40,14 +40,15 @@
|
|
|
40
40
|
"test": "echo \"Error: run tests from root\" && exit 0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"chalk": "^5.
|
|
44
|
-
"cross-spawn": "^7.0.
|
|
43
|
+
"chalk": "^5.4.1",
|
|
44
|
+
"cross-spawn": "^7.0.6",
|
|
45
45
|
"deepmerge": "^4.3.1",
|
|
46
46
|
"get-all-files": "^5.0.0",
|
|
47
|
-
"resolve-bin": "^1.0.1"
|
|
47
|
+
"resolve-bin": "^1.0.1",
|
|
48
|
+
"twig": "^1.17.1"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"webpack": "^5.
|
|
51
|
-
"webpack-cli": "^
|
|
51
|
+
"webpack": "^5.99.9",
|
|
52
|
+
"webpack-cli": "^6.0.1"
|
|
52
53
|
}
|
|
53
54
|
}
|