diff --git a/lighthouse_runner.js b/lighthouse_runner.js index fa8d82f..9c9dfab 100644 --- a/lighthouse_runner.js +++ b/lighthouse_runner.js @@ -7,6 +7,9 @@ const path = require('path'); const { URL } = require('url'); const open = require('open'); const os = require('os'); +const csvStringify = require('csv-stringify/lib/sync'); +const { reportToRow, reportToRowHeaders } = require('./reportToRow'); + let autoOpen = false; let port; let outputMode; @@ -100,17 +103,28 @@ const processResults = (processObj) => { } else { filePath = path.join(tempFilePath, replacedUrl + '.mobile.report.' + opts.output); } + // // @TODO ASYNC IS BETTER BUT WE ARENT CURRENTLY AWAITING THE PROMISE PROPERLY // https://stackoverflow.com/questions/34811222/writefile-no-such-file-or-directory - fs.writeFile(filePath, report, { + // fs.writeFile(filePath, report, { + // encoding: 'utf-8' + // }, (err) => { + // if (err) throw err; + // if (opts.emulatedFormFactor && opts.emulatedFormFactor === 'desktop') { + // console.log('Wrote desktop report: ', currentUrl, 'at: ', tempFilePath); + // } else { + // console.log('Wrote mobile report: ', currentUrl, 'at: ', tempFilePath); + // } + // }); + + fs.writeFileSync(filePath, report, { encoding: 'utf-8' - }, (err) => { - if (err) throw err; - if (opts.emulatedFormFactor && opts.emulatedFormFactor === 'desktop') { - console.log('Wrote desktop report: ', currentUrl, 'at: ', tempFilePath); - } else { - console.log('Wrote mobile report: ', currentUrl, 'at: ', tempFilePath); - } }); + if (opts.emulatedFormFactor && opts.emulatedFormFactor === 'desktop') { + console.log('Wrote desktop report: ', currentUrl, 'at: ', tempFilePath); + } else { + console.log('Wrote mobile report: ', currentUrl, 'at: ', tempFilePath); + } + }; /** * Helper function to queue up async promises. @@ -248,32 +262,33 @@ const aggregateCSVReports = (directoryPath) => { let mobileAggregatePath = path.join(directoryPath, mobileAggregateReportName); let desktopWriteStream = fs.createWriteStream(desktopAggregatePath, { flags: 'a' }); let mobileWriteStream = fs.createWriteStream(mobileAggregatePath, { flags: 'a' }); - let desktopCounter = 0; - let mobileCounter = 0; + + let desktopRows = [ + reportToRowHeaders + ]; + let mobileRows = [ + reportToRowHeaders + ]; + try { files.forEach(fileName => { if (fileName !== desktopAggregateReportName && fileName !== mobileAggregateReportName) { let filePath = path.join(directoryPath, fileName); let fileContents = fs.readFileSync(filePath, { encoding: 'utf-8' }); + console.log(`Bundling ${fileName} into aggregated report`); + const newRow = reportToRow(fileContents); if (fileName.includes('.desktop')) { - if (desktopCounter === 0) { - desktopWriteStream.write(fileContents + '\n'); - desktopCounter++; - } else { - let newContents = fileContents.split('\n').slice(1).join('\n'); - desktopWriteStream.write(newContents + '\n'); - } + desktopRows.push(newRow); } else if (fileName.includes('.mobile')) { - if (mobileCounter === 0) { - mobileWriteStream.write(fileContents + '\n'); - mobileCounter++; - } else { - let newContents = fileContents.split('\n').slice(1).join('\n'); - mobileWriteStream.write(newContents + '\n'); - } + mobileRows.push(newRow); } } }); + desktopWriteStream.write(csvStringify(desktopRows)); + console.log('Wrote desktop aggregate report'); + mobileWriteStream.write(csvStringify(mobileRows)); + console.log('Wrote mobile aggregate report'); + } catch (e) { console.error(e); @@ -282,7 +297,6 @@ const aggregateCSVReports = (directoryPath) => { return true; } - /** * Opens generated reports in your preferred browser as an explorable list * @param {Number} port Port used by Express diff --git a/package-lock.json b/package-lock.json index fc9d8be..1651a97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2603,6 +2603,32 @@ "cssom": "0.3.x" } }, + "csv": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/csv/-/csv-5.3.2.tgz", + "integrity": "sha512-odDyucr9OgJTdGM2wrMbJXbOkJx3nnUX3Pt8SFOwlAMOpsUQlz1dywvLMXJWX/4Ib0rjfOsaawuuwfI5ucqBGQ==", + "requires": { + "csv-generate": "^3.2.4", + "csv-parse": "^4.8.8", + "csv-stringify": "^5.3.6", + "stream-transform": "^2.0.1" + } + }, + "csv-generate": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.2.4.tgz", + "integrity": "sha512-qNM9eqlxd53TWJeGtY1IQPj90b563Zx49eZs8e0uMyEvPgvNVmX1uZDtdzAcflB3PniuH9creAzcFOdyJ9YGvA==" + }, + "csv-parse": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.11.1.tgz", + "integrity": "sha512-cH2BG5Gd0u4G8qVI/jGXJSP2+El7Vy91/ZD3ehKALAWids1aIKOPhZ1ZVJzUrs2zTn6aGumVPBlbHsI91kI83A==" + }, + "csv-stringify": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.5.0.tgz", + "integrity": "sha512-G05575DSO/9vFzQxZN+Srh30cNyHk0SM0ePyiTChMD5WVt7GMTVPBQf4rtgMF6mqhNCJUPw4pN8LDe8MF9EYOA==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -4990,6 +5016,11 @@ "is-plain-obj": "^1.1.0" } }, + "mixme": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.3.5.tgz", + "integrity": "sha512-SyV9uPETRig5ZmYev0ANfiGeB+g6N2EnqqEfBbCGmmJ6MgZ3E4qv5aPbnHVdZ60KAHHXV+T3sXopdrnIXQdmjQ==" + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -7447,6 +7478,14 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stream-transform": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.0.2.tgz", + "integrity": "sha512-J+D5jWPF/1oX+r9ZaZvEXFbu7znjxSkbNAHJ9L44bt/tCVuOEWZlDqU9qJk7N2xBU1S+K2DPpSKeR/MucmCA1Q==", + "requires": { + "mixme": "^0.3.1" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", diff --git a/package.json b/package.json index 23cc500..7d9029d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "chrome-launcher": "^0.13.0", "commander": "^5.0.0", + "csv": "^5.3.2", "express": "^4.17.1", "lighthouse": "^6.0.0", "open": "7.0.4", diff --git a/reportToRow.js b/reportToRow.js new file mode 100644 index 0000000..3948406 --- /dev/null +++ b/reportToRow.js @@ -0,0 +1,174 @@ +const csvParse = require('csv-parse/lib/sync'); + +const reportToRowHeaders = [ + "Requested URL", + "Final URL", + "Performance: First Contentful Paint (numeric)", + "Performance: Speed Index (numeric)", + "Performance: Largest Contentful Paint (numeric)", + "Performance: Time to Interactive (numeric)", + "Performance: Total Blocking Time (numeric)", + "Performance: Cumulative Layout Shift (numeric)", + "Performance: First CPU Idle (numeric)", + "Performance: Max Potential First Input Delay (numeric)", + "Performance: First Meaningful Paint (numeric)", + "Performance: Estimated Input Latency (numeric)", + "Performance: Eliminate render-blocking resources (numeric)", + "Performance: Properly size images (numeric)", + "Performance: Defer offscreen images (numeric)", + "Performance: Minify CSS (numeric)", + "Performance: Minify JavaScript (numeric)", + "Performance: Remove unused CSS (numeric)", + "Performance: Remove unused JavaScript (numeric)", + "Performance: Efficiently encode images (numeric)", + "Performance: Serve images in next-gen formats (numeric)", + "Performance: Enable text compression (numeric)", + "Performance: Preconnect to required origins (numeric)", + "Performance: Initial server response time was short (binary)", + "Performance: Avoid multiple page redirects (numeric)", + "Performance: Preload key requests (numeric)", + "Performance: Use video formats for animated content (numeric)", + "Performance: Avoids enormous network payloads (numeric)", + "Performance: Serve static assets with an efficient cache policy (numeric)", + "Performance: Avoids an excessive DOM size (numeric)", + "Performance: Avoid chaining critical requests (informative)", + "Performance: User Timing marks and measures (notApplicable)", + "Performance: JavaScript execution time (numeric)", + "Performance: Minimizes main-thread work (numeric)", + "Performance: Ensure text remains visible during webfont load (binary)", + "Performance: Performance budget (notApplicable)", + "Performance: Timing budget (notApplicable)", + "Performance: Keep request counts low and transfer sizes small (informative)", + "Performance: Minimize third-party usage (binary)", + "Performance: Largest Contentful Paint element (informative)", + "Performance: Avoid large layout shifts (informative)", + "Performance: Uses HTTP/2 for its own resources (binary)", + "Performance: Uses passive listeners to improve scrolling performance (binary)", + "Performance: Avoids `document.write()` (binary)", + "Performance: Avoid long main-thread tasks (informative)", + "Performance: Network Requests (informative)", + "Performance: Network Round Trip Times (informative)", + "Performance: Server Backend Latencies (informative)", + "Performance: Tasks (informative)", + "Performance: Diagnostics (informative)", + "Performance: Metrics (informative)", + "Performance: Screenshot Thumbnails (informative)", + "Performance: Final Screenshot (informative)", + "Accessibility: `[accesskey]` values are unique (notApplicable)", + "Accessibility: `[aria-*]` attributes match their roles (notApplicable)", + "Accessibility: `[aria-hidden='true']` is not present on the document `
` (binary)", + "Accessibility: `[aria-hidden='true]` elements do not contain focusable descendents (notApplicable)", + "Accessibility: ARIA input fields have accessible names (notApplicable)", + "Accessibility: `[role]`s have all required `[aria-*]` attributes (notApplicable)", + "Accessibility: Elements with an ARIA `[role]` that require children to contain a specific `[role]` have all required children. (notApplicable)", + "Accessibility: `[role]`s are contained by their required parent element (notApplicable)", + "Accessibility: `[role]` values are valid (notApplicable)", + "Accessibility: ARIA toggle fields have accessible names (notApplicable)", + "Accessibility: `[aria-*]` attributes have valid values (notApplicable)", + "Accessibility: `[aria-*]` attributes are valid and not misspelled (notApplicable)", + "Accessibility: Buttons have an accessible name (notApplicable)", + "Accessibility: The page contains a heading, skip link, or landmark region (binary)", + "Accessibility: Background and foreground colors have a sufficient contrast ratio (binary)", + "Accessibility: `