@caweb/webpack 1.3.16 → 1.3.18

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/jshint-webpack-plugin",
3
- "version": "1.0.9",
3
+ "version": "2.0.0",
4
4
  "description": "CAWebPublishing Webpack Plugin to run JSHint",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -12,6 +12,9 @@
12
12
  "README.md"
13
13
  ],
14
14
  "scripts": {
15
+ "webpack": "webpack",
16
+ "build": "webpack build --config node_modules/@caweb/html-webpack-plugin/webpack.config.js",
17
+ "serve": "set NODE_OPTIONS='--no-audit --no-a11y --no-jshint' && webpack serve --config node_modules/@caweb/html-webpack-plugin/webpack.config.js ./scripts/webpack.test.js --merge",
15
18
  "test": "echo \"Error: run tests from root\" && exit 0"
16
19
  },
17
20
  "author": "CAWebPublishing",
@@ -35,11 +38,13 @@
35
38
  ],
36
39
  "dependencies": {
37
40
  "get-all-files": "^5.0.0",
41
+ "handlebars": "^4.7.8",
38
42
  "html-format": "^1.1.7",
39
- "jshint": "^2.13.6"
43
+ "jshint": "^2.13.6",
44
+ "webpack": "^5.97.1",
45
+ "webpack-cli": "^6.0.1"
40
46
  },
41
- "devDependencies": {
42
- "webpack": "^5.96.1",
43
- "webpack-cli": "^5.1.4"
47
+ "peerDependencies": {
48
+ "@caweb/html-webpack-plugin": "^1.5.19"
44
49
  }
45
50
  }
@@ -2,6 +2,22 @@
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const htmlFormat = require('html-format');
5
+ const HandleBars = require('handlebars');
6
+
7
+ const endsWith = require('@caweb/html-webpack-plugin/helpers/logic/endsWith.js');
8
+
9
+ const templateDir = path.resolve('node_modules', '@caweb', 'html-webpack-plugin', 'sample' );
10
+
11
+ let templatePartials = {
12
+ 'header': 'structural/header.html',
13
+ 'footer': 'structural/footer.html',
14
+ '../components/alerts.html': 'components/alerts.html',
15
+ './utility-header.html': 'structural/utility-header.html',
16
+ './branding.html': 'structural/branding.html',
17
+ './mobile-controls.html': 'structural/mobile-controls.html',
18
+ './navigation.html': 'structural/navigation.html',
19
+ './search.html': 'structural/search.html'
20
+ }
5
21
 
6
22
  let sortedReport = {
7
23
  errors: [],
@@ -9,106 +25,10 @@ let sortedReport = {
9
25
  info: []
10
26
  };
11
27
 
12
- let title = process.cwd().split('\\').pop();
28
+ let title = `JSHint Report for ${process.cwd().split('\\').pop()}`;
13
29
 
14
30
  let output = [];
15
31
 
16
- /**
17
- * Adds Head to output.
18
- */
19
- function addHead(){
20
-
21
- output.push(
22
- '<head>',
23
- '<meta charset="UTF-8">',
24
- '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
25
- `<title>JSHint Report for ${title}</title>`,
26
- '<script src="./jshint.js"></script>',
27
- '<link rel="stylesheet" href="./jshint.css">',
28
- '</head>'
29
- )
30
- }
31
-
32
- /**
33
- * Adds Header to output.
34
- */
35
- function addHeader(){
36
- let navItems = []
37
-
38
- // add dark mode switch just for fun.
39
- navItems.push(
40
- '<li>',
41
- '<button onclick="jsHint.toggleDarkMode()" class="btn button__theme-toggle" aria-label="Dark Mode"></button>',
42
- '</li>'
43
- );
44
- // open header and add title
45
- output.push(
46
- '<header id="top" class="site__header">',
47
- `<h1>JSHint Report for ${title}</h1>`,
48
- )
49
-
50
- // add navigation if items were found
51
- if( navItems.length ){
52
- output.push(
53
- '<nav class="nav">',
54
- '<ul>',
55
- ...navItems,
56
- '</ul>',
57
- '</nav>',
58
- )
59
- }
60
-
61
- // close the header.
62
- output.push(
63
- '</header>',
64
- )
65
-
66
- }
67
-
68
- /**
69
- * Adds Result Summary to output.
70
- * @param {Array} results
71
- */
72
- function addSummary(results){
73
- let items = [];
74
-
75
- // summary
76
- results.forEach(function(result) {
77
- var file = result.file;
78
- var error = result.error;
79
-
80
- items.push(
81
- '<li>',
82
- `<a href="#${file.replace(/[\\:\.]/g, '-').toLowerCase()}" target="_blank">${file}</a>: line ${error.line}, col ${error.character}, ${error.reason}`,
83
- '</li>'
84
- )
85
- })
86
-
87
- let summary = [];
88
- for(let [category] of Object.entries(sortedReport) ){
89
- if( sortedReport[category].length ){
90
- let cCase = capitalCase(category);
91
- // if there is just 1, drop the plural
92
- if( 1 === sortedReport[category].length ){
93
- cCase = cCase.slice(0, -1)
94
- }
95
- summary.push(`${sortedReport[category].length} ${cCase} Detected`)
96
- }
97
- }
98
-
99
- output.push(
100
- '<section class="hint" id="summary">',
101
- '<header class="hint__header">',
102
- `<h2 class="hint__title">Summary: <em>${summary.join(' & ')}</em></h2>`,
103
- '</header>',
104
- '<ol>',
105
- ...items,
106
- '</ol>',
107
- '</section>'
108
- );
109
-
110
- }
111
-
112
32
  /**
113
33
  * Process data
114
34
  *
@@ -229,43 +149,17 @@ function addBreakdown({
229
149
  })
230
150
  }
231
151
 
232
- output.push(
233
- `<section id="${file.replace(/[\\:\.]/g, '-').toLowerCase()}" class="hint">`,
234
- '<header class="hint__header">',
235
- `<h2 class="hint__title"><a href="${file}" class="link" target="_blank"></a>File: <em>${file}</em></h2>`,
236
- '<a class="btn back-to-top" href="#top" title="Back to top">⬆</a>',
237
- '</header>',
238
- `<h3>Functions: <span class="count">${functions.length}</span></h3>`,
239
- functionList.length ? `<ol>${functionList.join('\n')}</ol>` : '',
240
- `<h3>Errors: <span class="count">${errors ? errors.length : 0}</span></h3>`,
241
- errorList.length ? `<ol>${errorList.join('\n')}</ol>` : '',
242
- `<h3>Unused: <span class="count">${unused ? unused.length : 0}</span></h3>`,
243
- unusedList.length ? `<ol>${unusedList.join('\n')}</ol>` : '',
244
- '</section>'
245
- )
246
- }
247
-
248
- /**
249
- * Add file list sidebar
250
- */
251
- function addSidebar(data){
252
- let fileList = [];
253
-
254
- data.forEach(({file}) => {
255
- fileList.push(
256
- `<li><a href="#${ file.replace(/[\\:\.]/g, '-').toLowerCase() }">${file}</a></li>`
257
- )
258
- })
259
- output.push(
260
- '<aside>',
261
- '<section>',
262
- '<header class="hint__header"><h2 class="hint__title">Files</h2></header>',
263
- '<ol>',
264
- fileList.join('\n'),
265
- '</section>',
266
- '</ol>',
267
- '</aside>'
268
- )
152
+ return `<section id="${file.replace(/[\\:\.]/g, '-').toLowerCase()}" class="mb-5 border border-2">
153
+ <div class="bg-light p-4"><h4>File: <a href="file://${file}" target="_blank" class="fst-italic fs-md text-break">${file}</a></h4></div>
154
+ <div class="p-4">
155
+ <h5>Functions: <span class="bg-light rounded-circle p-2">${functions.length}</span></h5>
156
+ ${ functionList.length ? `<ol>${functionList.join('\n')}</ol>` : ''}
157
+ <h5>Errors: <span class="bg-light rounded-circle p-2">${errors ? errors.length : 0}</span></h5>
158
+ ${ errorList.length ? `<ol>${errorList.join('\n')}</ol>` : '' }
159
+ <h5>Unused: <span class="bg-light rounded-circle p-2">${unused ? unused.length : 0}</span></h5>
160
+ ${ unusedList.length ? `<ol>${unusedList.join('\n')}</ol>` : '' }
161
+ </div>
162
+ </section>`;
269
163
  }
270
164
 
271
165
  /**
@@ -280,384 +174,128 @@ function reporter(results, data, opts){
280
174
  path.resolve( process.env.JSHINT_OUTPUT_DIR ) :
281
175
  path.join(__dirname, 'public')
282
176
  let outputFilename = path.join(outputFolder, `${process.env.JSHINT_OUTPUT_FILENAME ?? 'jshint'}.html` );
283
-
284
- // process results.
285
- results.forEach((result) => {
286
- var file = result.file;
287
- var error = result.error;
288
-
289
- // add error to sorted report array
290
- switch( error.code.charAt(0) ){
291
- case 'E': // errors
292
- sortedReport.errors.push({...error, file: file})
293
- break;
294
- case 'I': // info
295
- sortedReport.info.push({...error, file: file})
296
- break;
297
- case 'W': // warnings
298
- sortedReport.warnings.push({...error, file: file})
299
- break;
300
- }
301
- })
177
+ let fileList = [];
178
+ let fileSummary = [];
179
+ let fileBreakdown = [];
180
+ let summaryHeader = [];
302
181
 
303
182
  try{
304
183
 
305
- // Add Head Section.
306
- addHead();
184
+ // process results.
185
+ results.forEach((result) => {
186
+ var file = result.file;
187
+ var error = result.error;
188
+
189
+ // add error to sorted report array
190
+ switch( error.code.charAt(0) ){
191
+ case 'E': // errors
192
+ sortedReport.errors.push({...error, file: file})
193
+ break;
194
+ case 'I': // info
195
+ sortedReport.info.push({...error, file: file})
196
+ break;
197
+ case 'W': // warnings
198
+ sortedReport.warnings.push({...error, file: file})
199
+ break;
200
+ }
307
201
 
308
- // Add Header Section.
309
- addHeader();
202
+ fileList.push(
203
+ `<li class="text-break mb-4"><a href="#${ file.replace(/[\\:\.]/g, '-').toLowerCase() }">${file}</a></li>`
204
+ )
310
205
 
311
- output.push ('<div id="main">');
206
+ fileSummary.push(
207
+ `<li class="mb-3"><a href="#${ file.replace(/[\\:\.]/g, '-').toLowerCase( )}">${file}</a>: line ${error.line}, col ${error.character}, ${error.reason}</li>`
208
+ )
312
209
 
313
- addSidebar(data);
314
-
315
- output.push ('<div id="information">');
316
-
317
- // Add Summary.
318
- addSummary(results);
210
+ })
319
211
 
320
- // process data.
321
212
  data.forEach((d) => {
322
- /*
323
- Only used to see what the data is passing
324
- for(let [prop, value] of Object.entries(d) ){
325
- output.push(prop)
326
- output.push('object' == typeof value ? value[0] : value)
327
- output.push('<br />')
328
- }
329
- */
330
- addBreakdown(d)
213
+ fileBreakdown.push( addBreakdown(d) );
331
214
  })
332
215
 
333
- output.push (
334
- '</div> <!-- Close information div -->',
335
- '</div> <!-- Close main div -->'
336
- );
216
+
217
+ for(let [category] of Object.entries(sortedReport) ){
218
+ if( sortedReport[category].length ){
219
+ let cCase = capitalCase(category);
220
+ // if there is just 1, drop the plural
221
+ if( 1 === sortedReport[category].length ){
222
+ cCase = cCase.slice(0, -1)
223
+ }
224
+ summaryHeader.push(`${sortedReport[category].length} ${cCase} Detected`)
225
+ }
226
+ }
227
+
228
+
229
+ fileList = Array.from( new Set( fileList ) );
230
+
231
+ output.push(
232
+ `<h1 class="page-title my-4">${title}</h1>`,
233
+ '<div class="container">',
234
+ '<div class="row">',
235
+ '<div id="sidebar" class="col-4">',
236
+ '<h2>Files</h2>',
237
+ '<ol class="border-end border-2 pe-3">',
238
+ ...fileList,
239
+ '</ol>',
240
+ '</div>',
241
+ '<!-- End of Sidebar Column -->',
242
+ '<div id="content" class="col-8">',
243
+ `<h2>Summary: <em>${summaryHeader.join(' & ')}</em></h2>`,
244
+ '<ol>',
245
+ ...fileSummary,
246
+ '</ol>',
247
+ ...fileBreakdown,
248
+ '</div>',
249
+ '<!-- End of Content Column -->',
250
+ '</div>',
251
+ '<!-- End of Row -->',
252
+ '</div>',
253
+ '<!-- End of Container -->'
254
+ )
255
+
337
256
 
338
257
  }catch(err){
339
258
  process.stderr.write(err.toString())
340
259
  process.exit(1)
341
260
  }
342
261
 
343
- // wrap the output in html tags.
344
- output = ['<html>', ...output, '</html>']
345
-
346
262
  fs.mkdirSync( outputFolder, {recursive: true} );
347
263
 
264
+ // Register partials.
265
+ Object.entries(templatePartials).forEach(([p, f]) => HandleBars.registerPartial(p, fs.readFileSync(path.resolve(templateDir, f )).toString() ) );
266
+
267
+ // Register custom helpers.
268
+ HandleBars.registerHelper('endsWith', (w,s,o) => { return w.endsWith(s); } )
269
+
270
+ let template = HandleBars.compile(fs.readFileSync(path.resolve(templateDir, 'default.html')).toString() )
271
+
348
272
  // write html file
349
- fs.writeFileSync(
273
+ fs.writeFileSync(
350
274
  outputFilename,
351
- htmlFormat(output.join('\n')," ".repeat(4), 250)
352
- );
353
-
354
- // write jshint.js file.
355
- fs.writeFileSync(
356
- path.join(outputFolder, 'jshint.js'),
357
- jsContent()
275
+ htmlFormat(
276
+ template({
277
+ title,
278
+ scheme: 'oceanside',
279
+ logo: 'https://caweb.cdt.ca.gov/wp-content/uploads/sites/221/2023/06/caweb-publishing-logo.png',
280
+ htmlWebpackPlugin: {
281
+ options: {
282
+ templateParameters: {
283
+ bodyHtmlSnippet: output.join('\n')
284
+ }
285
+ }
286
+ }
287
+ }),
288
+ " ".repeat(4), 250
289
+ )
358
290
  );
359
291
 
360
- // write jshint.css file.
361
- fs.writeFileSync(
362
- path.join(outputFolder, 'jshint.css'),
363
- cssContent()
364
- );
365
292
 
366
- // console.log( htmlFormat(output.join('\n')," ".repeat(4), 250) );
367
293
  }
368
294
 
369
295
  function capitalCase(str){
370
296
  return str.charAt(0).toUpperCase() + str.slice(1)
371
297
  }
372
298
 
373
- /**
374
- * Returns string content for css file
375
- * @returns string
376
- */
377
- function cssContent(){
378
- return `
379
- /*
380
- * JSHint
381
- */
382
- body {
383
- --body-margin: 2rem;
384
-
385
- /* Body colours */
386
- --body-bg: #fff;
387
- --body-fg: #111;
388
-
389
- /* Nav & link colours */
390
- --nav-bg: #eee;
391
- --nav-bg-hover: #bbb;
392
- --nav-fg: #11c;
393
-
394
- /* Heading colours */
395
- --h-fg: #222;
396
- --h-bg: #eee;
397
-
398
- /* Border style for bordered components */
399
- --component-border: 1px solid #aaa;
400
-
401
- /* Consistent component margins */
402
- --component-margin-sm: 1.5rem;
403
- --component-margin-lg: 3rem;
404
-
405
- /* Colour chips */
406
- --count-bg: #eee;
407
- --count-border: var(--component-border);
408
- }
409
- /*
410
- * Dark mode
411
- */
412
- body.is-dark-mode {
413
- /* Body colours */
414
- --body-bg: #111;
415
- --body-fg: #fff;
416
-
417
- /* Nav & link colours */
418
- --nav-bg: #444;
419
- --nav-bg-hover: #222;
420
- --nav-fg: #bcf;
421
-
422
- /* Heading colours */
423
- --h-fg: #eee;
424
- --h-bg: #222;
425
-
426
- /* Border style for bordered components */
427
- --component-border: 1px solid #666;
428
-
429
- /* Consistent component margins */
430
- --component-margin-sm: 1.5rem;
431
- --component-margin-lg: 3rem;
432
-
433
-
434
- /* Colour chips */
435
- --count-bg: #111;
436
- --count-border: var(--component-border);
437
- }
438
- /*
439
- * General
440
- */
441
- * {
442
- box-sizing: border-box;
443
- }
444
-
445
- html {
446
- scroll-behavior: smooth;
447
-
448
- @media (prefers-reduced-motion: reduce) {
449
- scroll-behavior: auto;
450
- }
451
- }
452
-
453
- body {
454
- background-color: var(--body-bg);
455
- color: var(--body-fg);
456
- font-family: sans-serif;
457
- margin: var(--body-margin);
458
- }
459
-
460
- a {
461
- color: var(--nav-fg);
462
- }
463
- p{
464
- margin: 0 0 .5em 0
465
- }
466
- h1, h2 {
467
- font-weight: normal;
468
- }
469
-
470
- h1, h2, h3 {
471
- color: var(--h-fg);
472
- margin: 0 0 var(--component-margin-sm) 0;
473
- }
474
- /*
475
- * Layout
476
- */
477
- #main{
478
- display: flex;
479
- }
480
- aside {
481
- margin-right: calc(1.5* var(--body-margin));
482
- padding-right: calc(1* var(--body-margin));
483
- border-right: var(--component-border);
484
- }
485
- #information{
486
- flex-grow: 1
487
- }
488
- /*
489
- * Hints
490
- */
491
- .hint {
492
- margin-bottom: var(--component-margin-lg);
493
- }
494
- .hint__header {
495
- align-items: baseline;
496
- background-color: var(--h-bg);
497
- display: flex;
498
- justify-content: space-between;
499
- margin-right: calc(-1 * var(--body-margin));
500
- margin-left: calc(-1 * var(--body-margin));
501
- margin-bottom: var(--component-margin-sm);
502
- padding: var(--component-margin-sm) var(--body-margin) 0;
503
- position: sticky;
504
- top: 0;
505
- z-index: 1;
506
- }
507
-
508
- /*
509
- * Lists
510
- */
511
- ul,
512
- ol {
513
- padding: 0 0 0 2em;
514
- }
515
- li {
516
- margin: 0 0 2em 0;
517
- }
518
- li li {
519
- margin: 0;
520
- }
521
- /*
522
- * Header, nav, 'back to top' button
523
- */
524
- .site__header {
525
- align-items: baseline;
526
- display: flex;
527
- flex-wrap: wrap;
528
- justify-content: space-between;
529
- }
530
- .nav {
531
- margin-bottom: var(--component-margin-sm);
532
- }
533
-
534
- .nav ul {
535
- display: flex;
536
- justify-content: flex-start;
537
- align-items: center;
538
- list-style: none;
539
- padding: 0;
540
- }
541
-
542
- .nav li {
543
- margin-bottom: var(--component-margin-sm);
544
- margin: 0 0.5em 0 0;
545
- }
546
-
547
- .nav a {
548
- display: block;
549
- height: 100%;
550
- }
551
-
552
- .nav a,
553
- .btn {
554
- background-color: var(--nav-bg);
555
- padding: 0.5em;
556
- text-decoration: none;
557
- transition: background-color 0.2s ease;
558
- }
559
-
560
- .nav a:hover,
561
- .nav a:focus,
562
- .btn:hover,
563
- .btn:focus {
564
- background-color: var(--nav-bg-hover);
565
- }
566
-
567
- .nav a:focus,
568
- .btn:focus {
569
- outline: 4px dashed var(--nav-fg);
570
- }
571
-
572
- .btn {
573
- font-size: 1.2em;
574
- margin-bottom: var(--component-margin-sm);
575
- text-align: center;
576
- }
577
- /*
578
- * Count component on colour chips
579
- */
580
- .count {
581
- background-color: var(--count-bg);
582
- border: var(--count-border);
583
- border-radius: 50%;
584
- box-sizing: border-box;
585
- color: var(--count-fg);
586
- display: inline-block;
587
- height: auto;
588
- min-width: 2.3em;
589
- padding: 0.5em;
590
- text-align: center;
591
-
592
- /* Counteract .chip:hover */
593
- font-weight: normal;
594
- text-shadow: none;
595
- }
596
- /*
597
- * Darkmode switch
598
- */
599
- .button__theme-toggle {
600
- background: transparent;
601
- border: none;
602
- cursor: pointer;
603
- margin-bottom: 0;
604
- }
605
-
606
- .button__theme-toggle::before {
607
- content: "🌛";
608
- font-size: 1.5rem;
609
- }
610
-
611
- body.is-dark-mode .button__theme-toggle::before {
612
- content: "🌞";
613
- }
614
-
615
- /*
616
- * Link Icon
617
- */
618
- .link{
619
- text-decoration: none;
620
- cursor: pointer;
621
- }
622
- .link::before{
623
- content: "🔗";
624
- }
625
- `;
626
- }
627
-
628
- /**
629
- * Returns string content for js file
630
- * @returns string
631
- */
632
- function jsContent(){
633
- return `
634
-
635
- const jsHint = {};
636
-
637
- document.addEventListener( 'DOMContentLoaded', () => {
638
- const button = document.querySelector( '.button__theme-toggle' );
639
-
640
- jsHint.toggleDarkMode = function() {
641
- const isDarkMode = document.body.classList.contains( 'is-dark-mode' );
642
- if ( isDarkMode ) {
643
- button.setAttribute( 'aria-pressed', 'false' );
644
- document.body.classList.remove( 'is-dark-mode' );
645
- } else {
646
- button.setAttribute( 'aria-pressed', 'true' );
647
- document.body.classList.add( 'is-dark-mode' );
648
- }
649
- }
650
-
651
- // Set is-dark-mode class if user has requested dark mode.
652
- if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches ){
653
- document.body.classList.add( 'is-dark-mode' );
654
- button.setAttribute( 'aria-pressed', 'true' );
655
- }
656
-
657
- });
658
- `;
659
- }
660
-
661
299
  module.exports = {
662
300
  reporter
663
301
  }