From 118e461aed27d2fec1a7d5235a71cad6da5f47e5 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 14 Aug 2020 22:10:31 -0500 Subject: [PATCH 01/25] new_audit: add treemap-data to experimental --- lighthouse-core/audits/treemap-data.js | 382 ++++++++++++ lighthouse-core/computed/resource-summary.js | 1 + lighthouse-core/config/experimental-config.js | 1 + .../__snapshots__/treemap-data-test.js.snap | 583 ++++++++++++++++++ .../byte-efficiency/unused-javascript-test.js | 46 +- .../test/audits/treemap-data-test.js | 289 +++++++++ .../test/gather/gather-runner-test.js | 10 +- lighthouse-core/test/test-utils.js | 59 ++ tsconfig.json | 1 + 9 files changed, 1336 insertions(+), 36 deletions(-) create mode 100644 lighthouse-core/audits/treemap-data.js create mode 100644 lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap create mode 100644 lighthouse-core/test/audits/treemap-data-test.js diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js new file mode 100644 index 000000000000..248194be41b8 --- /dev/null +++ b/lighthouse-core/audits/treemap-data.js @@ -0,0 +1,382 @@ +/** + * @license Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * Creates treemap data for webtreemap. + */ + +const Audit = require('./audit.js'); +const JsBundles = require('../computed/js-bundles.js'); +const UnusedJavaScriptSummary = require('../computed/unused-javascript-summary.js'); +const ModuleDuplication = require('../computed/module-duplication.js'); +const NetworkRecords = require('../computed/network-records.js'); +const ResourceSummary = require('../computed/resource-summary.js'); +const BootupTime = require('../audits/bootup-time.js'); +const MainThreadTasks = require('../computed/main-thread-tasks.js'); +const {taskGroups} = require('../lib/tracehouse/task-groups.js'); + +/** + * @typedef {Record} TreemapData + */ + +/** + * @typedef RootNode + * @property {string} name + * @property {Node} node + */ + +/** + * @typedef Node + * @property {string} name + * @property {number} resourceBytes + * @property {number=} unusedBytes + * @property {number=} executionTime + * @property {string=} duplicate + * @property {Node[]=} children + */ + +/** + * @typedef {Omit} SourceData + */ + +class TreemapDataAudit extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'treemap-data', + scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, + title: 'Treemap Data', + description: 'Used for treemap visualization.', + requiredArtifacts: + ['traces', 'devtoolsLogs', 'SourceMaps', 'ScriptElements', 'JsUsage', 'URL'], + }; + } + + /** + * @param {string} sourceRoot + * @param {Record} sourcesData + * @return {Node} + */ + static prepareTreemapNodes(sourceRoot, sourcesData) { + /** + * @param {string} name + * @return {Node} + */ + function newNode(name) { + return { + name, + resourceBytes: 0, + }; + } + + /** + * Given a slash-delimited path, traverse the Node structure and increment + * the data provided for each node in the chain. Creates nodes as needed. + * Ex: path/to/file.js will find or create "path" on `node`, increment the data fields, + * and continue with "to", and so on. + * @param {string} source + * @param {SourceData} data + * @param {Node} node + */ + function addNode(source, data, node) { + // Strip off the shared root. + const sourcePathSegments = source.replace(sourceRoot, '').split(/\/+/); + sourcePathSegments.forEach((sourcePathSegment, i) => { + const isLastSegment = i === sourcePathSegments.length - 1; + + let child = node.children && node.children.find(child => child.name === sourcePathSegment); + if (!child) { + child = newNode(sourcePathSegment); + node.children = node.children || []; + node.children.push(child); + } + node = child; + + // Now that we've found or created the next node in the path, apply the data. + node.resourceBytes += data.resourceBytes; + if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; + if (data.duplicate !== undefined && isLastSegment) { + node.duplicate = data.duplicate; + } + }); + } + + const rootNode = newNode(sourceRoot); + + // For every source file, apply the data to all components + // of the source path, creating nodes as necessary. + for (const [source, data] of Object.entries(sourcesData)) { + addNode(source || ``, data, rootNode); + + // Apply the data to the rootNode. + rootNode.resourceBytes += data.resourceBytes; + if (data.unusedBytes) rootNode.unusedBytes = (rootNode.unusedBytes || 0) + data.unusedBytes; + } + + /** + * Collapse nodes that have only one child. + * @param {*} node + */ + function collapse(node) { + while (node.children && node.children.length === 1) { + node.name += '/' + node.children[0].name; + node.children = node.children[0].children; + } + + if (node.children) { + for (const child of node.children) { + collapse(child); + } + } + } + collapse(rootNode); + + // TODO(cjamcl): Should this structure be flattened for space savings? + // Like DOM Snapshot. + // Less JSON (no super nested children, and no repeated property names). + + return rootNode; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async makeJavaScriptRootNodes(artifacts, context) { + /** @type {RootNode[]} */ + const rootNodes = []; + + const bundles = await JsBundles.request(artifacts, context); + const duplication = await ModuleDuplication.request(artifacts, context); + // TODO: this should be a computed artifact. + // const executionTimings = await TreemapDataAudit.getExecutionTimings(artifacts, context); + + /** @type {Array<{src: string, length: number, unusedJavascriptSummary?: import('../computed/unused-javascript-summary.js').Summary}>} */ + const scriptData = []; + const inlineScriptData = { + src: artifacts.URL.finalUrl, + length: 0, + }; + for (const scriptElement of artifacts.ScriptElements) { + // Normalize ScriptElements so that inline scripts show up as a single entity. + if (!scriptElement.src) { + inlineScriptData.length += (scriptElement.content || '').length; + continue; + } + + const url = scriptElement.src; + const bundle = bundles.find(bundle => url === bundle.script.src); + const scriptCoverages = artifacts.JsUsage[url]; + if (!bundle || !scriptCoverages) continue; + + scriptData.push({ + src: scriptElement.src, + length: (scriptElement.content || '').length, + unusedJavascriptSummary: + await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context), + }); + } + if (inlineScriptData.length) scriptData.unshift(inlineScriptData); + + for (const {src, length, unusedJavascriptSummary} of scriptData) { + const bundle = bundles.find(bundle => bundle.script.src === src); + const name = src; + + let node; + if (bundle && unusedJavascriptSummary && unusedJavascriptSummary.sourcesWastedBytes) { + /** @type {Record} */ + const sourcesData = {}; + for (const source of Object.keys(bundle.sizes.files)) { + /** @type {SourceData} */ + const sourceData = { + resourceBytes: bundle.sizes.files[source], + }; + + if (unusedJavascriptSummary && unusedJavascriptSummary.sourcesWastedBytes) { + sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes[source]; + } + + if (duplication) { + const key = ModuleDuplication._normalizeSource(source); + if (duplication.has(key)) sourceData.duplicate = key; + } + + sourcesData[source] = sourceData; + } + + node = this.prepareTreemapNodes(bundle.rawMap.sourceRoot || '', sourcesData); + } else if (unusedJavascriptSummary) { + node = { + name, + resourceBytes: unusedJavascriptSummary.totalBytes, + unusedBytes: unusedJavascriptSummary.wastedBytes, + executionTime: 0, + }; + } else { + // TODO ...? + node = { + name, + resourceBytes: length, + unusedBytes: 0, + executionTime: 0, + }; + } + + // const executionTiming = executionTimings.find(timing => timing.url === src); + // node.executionTime = executionTiming ? Math.round(executionTiming.total) : 0; + + rootNodes.push({ + name: name, + node, + }); + } + + return rootNodes; + } + + /** + * @param {LH.Artifacts.Bundle[]} bundles + * @param {string} url + * @param {LH.Artifacts['JsUsage']} JsUsage + * @param {LH.Audit.Context} context + */ + static async getUnusedJavascriptSummary(bundles, url, JsUsage, context) { + const bundle = bundles.find(bundle => url === bundle.script.src); + const scriptCoverages = JsUsage[url]; + if (!scriptCoverages) return; + + const unusedJsSumary = + await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context); + return unusedJsSumary; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async makeResourceSummaryRootNode(artifacts, context) { + const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; + const networkRecords = await NetworkRecords.request(devtoolsLog, context); + const origin = new URL(artifacts.URL.finalUrl).origin; + + const totalCount = networkRecords.length; + let totalSize = 0; + + /** @type {Node[]} */ + const children = []; + for (const networkRecord of networkRecords) { + const resourceType = ResourceSummary.determineResourceType(networkRecord); + + let child = children.find(child => child.name === resourceType); + if (!child) { + child = { + name: resourceType, + resourceBytes: 0, + children: [], + }; + children.push(child); + } + + totalSize += networkRecord.resourceSize; + child.resourceBytes += networkRecord.resourceSize; + + let name = networkRecord.url; + // TODO ... + if (name.startsWith(origin)) name = name.replace(origin, '/'); + child.children = child.children || []; + child.children.push({ + name, + resourceBytes: networkRecord.resourceSize, + }); + } + + return { + name: 'Resource Summary', + node: { + name: `${totalCount} requests`, + resourceBytes: totalSize, + children, + }, + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + */ + static async getExecutionTimings(artifacts, context) { + const trace = artifacts.traces[BootupTime.DEFAULT_PASS]; + const devtoolsLog = artifacts.devtoolsLogs[BootupTime.DEFAULT_PASS]; + const networkRecords = await NetworkRecords.request(devtoolsLog, context); + const tasks = await MainThreadTasks.request(trace, context); + const multiplier = context.settings.throttlingMethod === 'simulate' ? + context.settings.throttling.cpuSlowdownMultiplier : 1; + + const jsURLs = BootupTime.getJavaScriptURLs(networkRecords); + const executionTimings = BootupTime.getExecutionTimingsByURL(tasks, jsURLs); + + return Array.from(executionTimings) + .map(([url, timingByGroupId]) => { + // Add up the totalExecutionTime for all the taskGroups + let totalExecutionTimeForURL = 0; + for (const [groupId, timespanMs] of Object.entries(timingByGroupId)) { + timingByGroupId[groupId] = timespanMs * multiplier; + totalExecutionTimeForURL += timespanMs * multiplier; + } + + const scriptingTotal = timingByGroupId[taskGroups.scriptEvaluation.id] || 0; + const parseCompileTotal = timingByGroupId[taskGroups.scriptParseCompile.id] || 0; + + // Add up all the JavaScript time of shown URLs + // if (totalExecutionTimeForURL >= context.options.thresholdInMs) { + // } + + // hadExcessiveChromeExtension = hadExcessiveChromeExtension || + // (url.startsWith('chrome-extension:') && scriptingTotal > 100); + + return { + url: url, + total: totalExecutionTimeForURL, + // Highlight the JavaScript task costs + scripting: scriptingTotal, + scriptParseCompile: parseCompileTotal, + }; + }) + .sort((a, b) => b.total - a.total); + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + /** @type {TreemapData} */ + const treemapData = { + scripts: await TreemapDataAudit.makeJavaScriptRootNodes(artifacts, context), + resources: [await TreemapDataAudit.makeResourceSummaryRootNode(artifacts, context)], + }; + + /** @type {LH.Audit.Details.DebugData} */ + const details = { + type: 'debugdata', + treemapData, + }; + + return { + score: 1, + details, + }; + } +} + +module.exports = TreemapDataAudit; diff --git a/lighthouse-core/computed/resource-summary.js b/lighthouse-core/computed/resource-summary.js index 4ee005dd8f17..0071d086bb12 100644 --- a/lighthouse-core/computed/resource-summary.js +++ b/lighthouse-core/computed/resource-summary.js @@ -40,6 +40,7 @@ class ResourceSummary { * @return {Record} */ static summarize(networkRecords, mainResourceURL, context) { + /** @type {Record} */ const resourceSummary = { 'stylesheet': {count: 0, resourceSize: 0, transferSize: 0}, 'image': {count: 0, resourceSize: 0, transferSize: 0}, diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index c3fbdeca8181..4183657a5fb2 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -15,6 +15,7 @@ const config = { extends: 'lighthouse:default', audits: [ 'full-page-screenshot', + 'treemap-data', ], passes: [{ passName: 'defaultPass', diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap new file mode 100644 index 000000000000..6d35883f6405 --- /dev/null +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -0,0 +1,583 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TreemapData audit squoosh fixture js 1`] = ` +Array [ + Object { + "name": "https://squoosh.app/main-app.js", + "node": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "util.ts", + "resourceBytes": 4043, + "unusedBytes": 500, + }, + Object { + "name": "icons.tsx", + "resourceBytes": 2531, + "unusedBytes": 24, + }, + Object { + "name": "clean-modify.ts", + "resourceBytes": 331, + }, + ], + "name": "lib", + "resourceBytes": 6905, + "unusedBytes": 524, + }, + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 410, + }, + Object { + "name": "index.tsx", + "resourceBytes": 2176, + "unusedBytes": 37, + }, + ], + "name": "Options", + "resourceBytes": 2586, + "unusedBytes": 37, + }, + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "styles.css", + "resourceBytes": 75, + }, + Object { + "name": "index.ts", + "resourceBytes": 2088, + "unusedBytes": 42, + }, + ], + "name": "TwoUp", + "resourceBytes": 2163, + "unusedBytes": 42, + }, + Object { + "children": undefined, + "name": "PinchZoom/index.ts", + "resourceBytes": 3653, + "unusedBytes": 73, + }, + ], + "name": "custom-els", + "resourceBytes": 5816, + "unusedBytes": 115, + }, + Object { + "name": "style.scss", + "resourceBytes": 447, + }, + Object { + "name": "index.tsx", + "resourceBytes": 5199, + "unusedBytes": 73, + }, + ], + "name": "Output", + "resourceBytes": 11462, + "unusedBytes": 188, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 780, + }, + Object { + "name": "FileSize.tsx", + "resourceBytes": 445, + "unusedBytes": 21, + }, + Object { + "name": "index.tsx", + "resourceBytes": 1538, + "unusedBytes": 19, + }, + ], + "name": "results", + "resourceBytes": 2763, + "unusedBytes": 40, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 132, + }, + Object { + "children": Array [ + Object { + "name": "styles.css", + "resourceBytes": 105, + }, + Object { + "name": "index.ts", + "resourceBytes": 3461, + "unusedBytes": 28, + }, + ], + "name": "custom-els/MultiPanel", + "resourceBytes": 3566, + "unusedBytes": 28, + }, + Object { + "name": "result-cache.ts", + "resourceBytes": 611, + "unusedBytes": 20, + }, + Object { + "name": "index.tsx", + "resourceBytes": 8782, + "unusedBytes": 28, + }, + ], + "name": "compress", + "resourceBytes": 13091, + "unusedBytes": 76, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 200, + }, + Object { + "name": "index.tsx", + "resourceBytes": 566, + "unusedBytes": 9, + }, + ], + "name": "range", + "resourceBytes": 766, + "unusedBytes": 9, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 106, + }, + Object { + "name": "index.tsx", + "resourceBytes": 247, + "unusedBytes": 1, + }, + ], + "name": "checkbox", + "resourceBytes": 353, + "unusedBytes": 1, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 66, + }, + Object { + "name": "index.tsx", + "resourceBytes": 901, + "unusedBytes": 21, + }, + ], + "name": "expander", + "resourceBytes": 967, + "unusedBytes": 21, + }, + Object { + "children": Array [ + Object { + "name": "style.scss", + "resourceBytes": 103, + }, + Object { + "name": "index.tsx", + "resourceBytes": 291, + "unusedBytes": 21, + }, + ], + "name": "select", + "resourceBytes": 394, + "unusedBytes": 21, + }, + ], + "name": "components", + "resourceBytes": 32382, + "unusedBytes": 393, + }, + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "util.ts", + "resourceBytes": 159, + }, + Object { + "name": "quality-option.tsx", + "resourceBytes": 398, + "unusedBytes": 19, + }, + ], + "name": "generic", + "resourceBytes": 557, + "unusedBytes": 19, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 268, + "unusedBytes": 44, + }, + Object { + "name": "encoder.tsx", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-png", + "resourceBytes": 369, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 282, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 115, + "unusedBytes": 22, + }, + Object { + "name": "options.ts", + "resourceBytes": 35, + "unusedBytes": 35, + }, + ], + "name": "browser-jpeg", + "resourceBytes": 432, + "unusedBytes": 101, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 358, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 115, + "unusedBytes": 22, + }, + Object { + "name": "options.ts", + "resourceBytes": 34, + "unusedBytes": 34, + }, + ], + "name": "browser-webp", + "resourceBytes": 507, + "unusedBytes": 100, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 343, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-gif", + "resourceBytes": 444, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 347, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-tiff", + "resourceBytes": 448, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 349, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-jp2", + "resourceBytes": 450, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 343, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-bmp", + "resourceBytes": 444, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 349, + "unusedBytes": 44, + }, + Object { + "name": "encoder.ts", + "resourceBytes": 101, + "unusedBytes": 22, + }, + ], + "name": "browser-pdf", + "resourceBytes": 450, + "unusedBytes": 66, + }, + Object { + "name": "tiny.webp", + "resourceBytes": 89, + }, + Object { + "name": "processor.ts", + "resourceBytes": 2380, + "unusedBytes": 22, + }, + Object { + "children": undefined, + "name": "processor-worker/index.ts", + "resourceBytes": 50, + }, + Object { + "children": Array [ + Object { + "name": "util.ts", + "resourceBytes": 134, + }, + Object { + "name": "processor-sync.ts", + "resourceBytes": 462, + "unusedBytes": 13, + }, + Object { + "name": "processor-meta.ts", + "resourceBytes": 225, + }, + Object { + "name": "options.tsx", + "resourceBytes": 3970, + "unusedBytes": 53, + }, + ], + "name": "resize", + "resourceBytes": 4791, + "unusedBytes": 66, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 436, + "unusedBytes": 3, + }, + Object { + "name": "options.tsx", + "resourceBytes": 4416, + "unusedBytes": 21, + }, + ], + "name": "mozjpeg", + "resourceBytes": 4852, + "unusedBytes": 24, + }, + Object { + "children": Array [ + Object { + "name": "options.tsx", + "resourceBytes": 366, + "unusedBytes": 21, + }, + Object { + "name": "encoder-meta.ts", + "resourceBytes": 59, + }, + ], + "name": "optipng", + "resourceBytes": 425, + "unusedBytes": 21, + }, + Object { + "children": Array [ + Object { + "name": "encoder-meta.ts", + "resourceBytes": 660, + "unusedBytes": 660, + }, + Object { + "name": "options.tsx", + "resourceBytes": 5114, + "unusedBytes": 93, + }, + ], + "name": "webp", + "resourceBytes": 5774, + "unusedBytes": 753, + }, + Object { + "children": Array [ + Object { + "name": "options.tsx", + "resourceBytes": 1052, + "unusedBytes": 44, + }, + Object { + "name": "processor-meta.ts", + "resourceBytes": 40, + }, + ], + "name": "imagequant", + "resourceBytes": 1092, + "unusedBytes": 44, + }, + Object { + "children": undefined, + "name": "identity/encoder-meta.ts", + "resourceBytes": 46, + }, + Object { + "name": "encoders.ts", + "resourceBytes": 336, + }, + Object { + "name": "preprocessors.ts", + "resourceBytes": 75, + }, + Object { + "name": "decoders.ts", + "resourceBytes": 206, + }, + Object { + "children": undefined, + "name": "rotate/processor-meta.ts", + "resourceBytes": 18, + "unusedBytes": 18, + }, + Object { + "name": "input-processors.ts", + "resourceBytes": 11, + "unusedBytes": 11, + }, + ], + "name": "codecs", + "resourceBytes": 24246, + "unusedBytes": 1575, + }, + Object { + "children": Array [ + Object { + "name": "styles.css", + "resourceBytes": 180, + }, + Object { + "name": "index.ts", + "resourceBytes": 2138, + "unusedBytes": 293, + }, + ], + "name": "custom-els/RangeInput", + "resourceBytes": 2318, + "unusedBytes": 293, + }, + ], + "name": "src", + "resourceBytes": 65851, + "unusedBytes": 2785, + }, + Object { + "children": Array [ + Object { + "children": undefined, + "name": "comlink/comlink.js", + "resourceBytes": 4117, + "unusedBytes": 256, + }, + Object { + "children": undefined, + "name": "pretty-bytes/index.js", + "resourceBytes": 635, + "unusedBytes": 11, + }, + Object { + "children": undefined, + "name": "pointer-tracker/dist/PointerTracker.mjs", + "resourceBytes": 2672, + "unusedBytes": 13, + }, + Object { + "children": undefined, + "name": "linkstate/dist/linkstate.es.js", + "resourceBytes": 412, + "unusedBytes": 1, + }, + ], + "name": "node_modules", + "resourceBytes": 7836, + "unusedBytes": 281, + }, + ], + "name": "/webpack:/.", + "resourceBytes": 73687, + "unusedBytes": 3066, + }, + }, +] +`; diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js index 94902a755634..0fa464ed45a1 100644 --- a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js @@ -6,43 +6,35 @@ 'use strict'; const assert = require('assert').strict; -const fs = require('fs'); const UnusedJavaScript = require('../../../audits/byte-efficiency/unused-javascript.js'); const networkRecordsToDevtoolsLog = require('../../network-records-to-devtools-log.js'); +const {loadSourceMapFixture} = require('../../test-utils.js'); -function load(name) { - const dir = `${__dirname}/../../fixtures/source-maps`; - const mapJson = fs.readFileSync(`${dir}/${name}.js.map`, 'utf-8'); - const content = fs.readFileSync(`${dir}/${name}.js`, 'utf-8'); - const usageJson = fs.readFileSync(`${dir}/${name}.usage.json`, 'utf-8'); - const exportedUsage = JSON.parse(usageJson); - - // Usage is exported from DevTools, which simplifies the real format of the - // usage protocol. - const usage = { - url: exportedUsage.url, - functions: [ - { - ranges: exportedUsage.ranges.map((range, i) => { - return { - startOffset: range.start, - endOffset: range.end, - count: i % 2 === 0 ? 0 : 1, - }; - }), - }, - ], - }; +/* eslint-env jest */ - return {map: JSON.parse(mapJson), content, usage}; +/** + * @param {string} name + */ +function load(name) { + const data = loadSourceMapFixture(name); + if (!data.usage) throw new Error('exepcted usage'); + return {...data, usage: data.usage}; } -/* eslint-env jest */ - +/** + * @param {string} url + * @param {number} transferSize + * @param {LH.Crdp.Network.ResourceType} resourceType + */ function generateRecord(url, transferSize, resourceType) { return {url, transferSize, resourceType}; } +/** + * @param {string} url + * @param {Array<[number, number, number]>} ranges + * @return {Crdp.Profiler.ScriptCoverage} + */ function generateUsage(url, ranges) { const functions = ranges.map(range => { return { diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js new file mode 100644 index 000000000000..dada6ecd0f4c --- /dev/null +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -0,0 +1,289 @@ +/** + * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const TreemapData_ = require('../../audits/treemap-data.js'); +const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js'); +const {loadSourceMapFixture, makeParamsOptional} = require('../test-utils.js'); + +/* eslint-env jest */ + +const TreemapData = { + audit: makeParamsOptional(TreemapData_.audit), + prepareTreemapNodes: makeParamsOptional(TreemapData_.prepareTreemapNodes), +}; + +/** + * @param {string} name + */ +function load(name) { + const data = loadSourceMapFixture(name); + if (!data.usage) throw new Error('exepcted usage'); + return {...data, usage: data.usage}; +} + +/** + * @param {string} url + * @param {number} transferSize + * @param {LH.Crdp.Network.ResourceType} resourceType + */ +function generateRecord(url, transferSize, resourceType) { + return {url, transferSize, resourceType}; +} + +describe('TreemapData audit', () => { + describe('squoosh fixture', () => { + /** @type {import('../../audits/treemap-data.js').TreemapData} */ + let treemapData; + beforeAll(async () => { + const context = {computedCache: new Map()}; + const {map, content, usage} = load('squoosh'); + const mainUrl = 'https://squoosh.app'; + const scriptUrl = 'https://squoosh.app/main-app.js'; + const networkRecords = [generateRecord(scriptUrl, content.length, 'Script')]; + + const artifacts = { + URL: {requestedUrl: mainUrl, finalUrl: mainUrl}, + JsUsage: {[usage.url]: [usage]}, + devtoolsLogs: {defaultPass: networkRecordsToDevtoolsLog(networkRecords)}, + SourceMaps: [{scriptUrl: scriptUrl, map}], + ScriptElements: [{src: scriptUrl, content}], + }; + const results = await TreemapData.audit(artifacts, context); + + // @ts-expect-error: Debug data. + treemapData = results.details.treemapData; + }); + + it('basics', () => { + expect(Object.keys(treemapData)).toEqual(['scripts', 'resources']); + }); + + it('js', () => { + expect(treemapData.scripts).toMatchSnapshot(); + }); + + it('resource summary', () => { + expect(treemapData.resources).toMatchInlineSnapshot(` + Array [ + Object { + "name": "Resource Summary", + "node": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "//main-app.js", + "resourceBytes": 0, + }, + ], + "name": "script", + "resourceBytes": 0, + }, + ], + "name": "1 requests", + "resourceBytes": 0, + }, + }, + ] + `); + }); + }); + + describe('.prepareTreemapNodes', () => { + it('basics 1', () => { + const rootNode = TreemapData.prepareTreemapNodes('', {'main.js': {resourceBytes: 100}}); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": undefined, + "name": "/main.js", + "resourceBytes": 100, + } + `); + }); + + it('basics 2', () => { + const sourcesData = { + 'some/prefix/main.js': {resourceBytes: 100}, + 'a.js': {resourceBytes: 101}, + }; + const rootNode = TreemapData.prepareTreemapNodes('some/prefix', sourcesData); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "children": undefined, + "name": "/main.js", + "resourceBytes": 100, + }, + Object { + "name": "a.js", + "resourceBytes": 101, + }, + ], + "name": "some/prefix", + "resourceBytes": 201, + } + `); + }); + + it('basics 3', () => { + const sourcesData = { + 'lib/a.js': {resourceBytes: 100}, + 'main.js': {resourceBytes: 101}, + }; + const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "children": undefined, + "name": "lib/a.js", + "resourceBytes": 100, + }, + Object { + "name": "main.js", + "resourceBytes": 101, + }, + ], + "name": "", + "resourceBytes": 201, + } + `); + }); + + it('basics 4', () => { + const sourcesData = { + 'lib/folder/a.js': {resourceBytes: 100}, + 'lib/folder/b.js': {resourceBytes: 101}, + }; + const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": "a.js", + "resourceBytes": 100, + }, + Object { + "name": "b.js", + "resourceBytes": 101, + }, + ], + "name": "/lib/folder", + "resourceBytes": 201, + } + `); + }); + + it('unusedBytes', () => { + const sourcesData = { + 'lib/folder/a.js': {resourceBytes: 100, unusedBytes: 50}, + 'lib/folder/b.js': {resourceBytes: 101}, + 'lib/c.js': {resourceBytes: 100, unusedBytes: 25}, + }; + const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "name": "a.js", + "resourceBytes": 100, + "unusedBytes": 50, + }, + Object { + "name": "b.js", + "resourceBytes": 101, + }, + ], + "name": "folder", + "resourceBytes": 201, + "unusedBytes": 50, + }, + Object { + "name": "c.js", + "resourceBytes": 100, + "unusedBytes": 25, + }, + ], + "name": "/lib", + "resourceBytes": 301, + "unusedBytes": 75, + } + `); + }); + + it('duplicates', () => { + const sourcesData = { + 'lib/folder/a.js': {resourceBytes: 100, unusedBytes: 50}, + 'lib/node_modules/dep/a.js': {resourceBytes: 101, duplicate: 'dep/a.js'}, + 'node_modules/dep/a.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/a.js'}, + 'lib/node_modules/dep/b.js': {resourceBytes: 101, duplicate: 'dep/b.js'}, + 'node_modules/dep/b.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/b.js'}, + }; + const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + expect(rootNode).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "children": undefined, + "name": "folder/a.js", + "resourceBytes": 100, + "unusedBytes": 50, + }, + Object { + "children": Array [ + Object { + "duplicate": "dep/a.js", + "name": "a.js", + "resourceBytes": 101, + }, + Object { + "duplicate": "dep/b.js", + "name": "b.js", + "resourceBytes": 101, + }, + ], + "name": "node_modules/dep", + "resourceBytes": 202, + }, + ], + "name": "lib", + "resourceBytes": 302, + "unusedBytes": 50, + }, + Object { + "children": Array [ + Object { + "duplicate": "dep/a.js", + "name": "a.js", + "resourceBytes": 100, + "unusedBytes": 25, + }, + Object { + "duplicate": "dep/b.js", + "name": "b.js", + "resourceBytes": 100, + "unusedBytes": 25, + }, + ], + "name": "node_modules/dep", + "resourceBytes": 200, + "unusedBytes": 50, + }, + ], + "name": "", + "resourceBytes": 502, + "unusedBytes": 100, + } + `); + }); + }); +}); diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 0120527eebd2..506cf744a3ae 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -18,18 +18,10 @@ const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log. const Driver = require('../../gather/driver.js'); const Connection = require('../../gather/connections/connection.js'); const {createMockSendCommandFn} = require('./mock-commands.js'); +const {makeParamsOptional} = require('../test-utils.js'); jest.mock('../../lib/stack-collector.js', () => () => Promise.resolve([])); -/** - * @template {unknown[]} TParams - * @template TReturn - * @param {(...args: TParams) => TReturn} fn - */ -function makeParamsOptional(fn) { - return /** @type {(...args: RecursivePartial) => TReturn} */ (fn); -} - const GatherRunner = { afterPass: makeParamsOptional(GatherRunner_.afterPass), beginRecording: makeParamsOptional(GatherRunner_.beginRecording), diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js index d4c7f94f0e57..ae497ff8be45 100644 --- a/lighthouse-core/test/test-utils.js +++ b/lighthouse-core/test/test-utils.js @@ -78,6 +78,65 @@ function getProtoRoundTrip() { }; } +/** + * @typedef PartialScriptCoverage + * @property {string} scriptId + * @property {string} url + * @property {Array<{ranges: LH.Crdp.Profiler.CoverageRange[]}>} functions + */ + +/** + * @param {string} name + * @return {{map: LH.Artifacts.RawSourceMap, content: string, usage?: PartialScriptCoverage}} + */ +function loadSourceMapFixture(name) { + const dir = `${__dirname}/fixtures/source-maps`; + const mapJson = fs.readFileSync(`${dir}/${name}.js.map`, 'utf-8'); + const content = fs.readFileSync(`${dir}/${name}.js`, 'utf-8'); + + const usagePath = `${dir}/${name}.usage.json`; + let usage = undefined; + if (fs.existsSync(usagePath)) { + const usageJson = fs.readFileSync(`${dir}/${name}.usage.json`, 'utf-8'); + // Usage is exported from DevTools, which simplifies the real format of the + // usage protocol. + /** @type {{url: string, ranges: Array<{start: number, end: number, count: number}>}} */ + const exportedUsage = JSON.parse(usageJson); + usage = { + scriptId: '0', + url: exportedUsage.url, + functions: [ + { + ranges: exportedUsage.ranges.map((range, i) => { + return { + startOffset: range.start, + endOffset: range.end, + count: i % 2 === 0 ? 0 : 1, + }; + }), + }, + ], + }; + } + + return { + map: JSON.parse(mapJson), + content, + usage, + }; +} + +/** + * @template {unknown[]} TParams + * @template TReturn + * @param {(...args: TParams) => TReturn} fn + */ +function makeParamsOptional(fn) { + return /** @type {(...args: RecursivePartial) => TReturn} */ (fn); +} + module.exports = { getProtoRoundTrip, + loadSourceMapFixture, + makeParamsOptional, }; diff --git a/tsconfig.json b/tsconfig.json index 2017a56e7728..9c628ce4311d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,5 +32,6 @@ "lighthouse-core/test/scripts/lantern/constants-test.js", "lighthouse-core/test/gather/driver-test.js", "lighthouse-core/test/gather/gather-runner-test.js", + "lighthouse-core/test/audits/treemap-data-test.js" ], } From c6bed57949b2ff508850de5fd5ef8b004d8215fa Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 14 Aug 2020 22:13:17 -0500 Subject: [PATCH 02/25] remove execution time --- lighthouse-core/audits/treemap-data.js | 53 -------------------------- 1 file changed, 53 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 248194be41b8..d8728d46e03c 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -16,9 +16,6 @@ const UnusedJavaScriptSummary = require('../computed/unused-javascript-summary.j const ModuleDuplication = require('../computed/module-duplication.js'); const NetworkRecords = require('../computed/network-records.js'); const ResourceSummary = require('../computed/resource-summary.js'); -const BootupTime = require('../audits/bootup-time.js'); -const MainThreadTasks = require('../computed/main-thread-tasks.js'); -const {taskGroups} = require('../lib/tracehouse/task-groups.js'); /** * @typedef {Record} TreemapData @@ -156,8 +153,6 @@ class TreemapDataAudit extends Audit { const bundles = await JsBundles.request(artifacts, context); const duplication = await ModuleDuplication.request(artifacts, context); - // TODO: this should be a computed artifact. - // const executionTimings = await TreemapDataAudit.getExecutionTimings(artifacts, context); /** @type {Array<{src: string, length: number, unusedJavascriptSummary?: import('../computed/unused-javascript-summary.js').Summary}>} */ const scriptData = []; @@ -230,9 +225,6 @@ class TreemapDataAudit extends Audit { }; } - // const executionTiming = executionTimings.find(timing => timing.url === src); - // node.executionTime = executionTiming ? Math.round(executionTiming.total) : 0; - rootNodes.push({ name: name, node, @@ -309,51 +301,6 @@ class TreemapDataAudit extends Audit { }; } - /** - * @param {LH.Artifacts} artifacts - * @param {LH.Audit.Context} context - */ - static async getExecutionTimings(artifacts, context) { - const trace = artifacts.traces[BootupTime.DEFAULT_PASS]; - const devtoolsLog = artifacts.devtoolsLogs[BootupTime.DEFAULT_PASS]; - const networkRecords = await NetworkRecords.request(devtoolsLog, context); - const tasks = await MainThreadTasks.request(trace, context); - const multiplier = context.settings.throttlingMethod === 'simulate' ? - context.settings.throttling.cpuSlowdownMultiplier : 1; - - const jsURLs = BootupTime.getJavaScriptURLs(networkRecords); - const executionTimings = BootupTime.getExecutionTimingsByURL(tasks, jsURLs); - - return Array.from(executionTimings) - .map(([url, timingByGroupId]) => { - // Add up the totalExecutionTime for all the taskGroups - let totalExecutionTimeForURL = 0; - for (const [groupId, timespanMs] of Object.entries(timingByGroupId)) { - timingByGroupId[groupId] = timespanMs * multiplier; - totalExecutionTimeForURL += timespanMs * multiplier; - } - - const scriptingTotal = timingByGroupId[taskGroups.scriptEvaluation.id] || 0; - const parseCompileTotal = timingByGroupId[taskGroups.scriptParseCompile.id] || 0; - - // Add up all the JavaScript time of shown URLs - // if (totalExecutionTimeForURL >= context.options.thresholdInMs) { - // } - - // hadExcessiveChromeExtension = hadExcessiveChromeExtension || - // (url.startsWith('chrome-extension:') && scriptingTotal > 100); - - return { - url: url, - total: totalExecutionTimeForURL, - // Highlight the JavaScript task costs - scripting: scriptingTotal, - scriptParseCompile: parseCompileTotal, - }; - }) - .sort((a, b) => b.total - a.total); - } - /** * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context From bc6c57c82fa71d9bf71299d45356d345435a2891 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 14 Aug 2020 22:17:08 -0500 Subject: [PATCH 03/25] comments --- lighthouse-core/audits/treemap-data.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index d8728d46e03c..ff8367f192bb 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -216,7 +216,6 @@ class TreemapDataAudit extends Audit { executionTime: 0, }; } else { - // TODO ...? node = { name, resourceBytes: length, @@ -258,7 +257,6 @@ class TreemapDataAudit extends Audit { static async makeResourceSummaryRootNode(artifacts, context) { const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; const networkRecords = await NetworkRecords.request(devtoolsLog, context); - const origin = new URL(artifacts.URL.finalUrl).origin; const totalCount = networkRecords.length; let totalSize = 0; @@ -282,8 +280,6 @@ class TreemapDataAudit extends Audit { child.resourceBytes += networkRecord.resourceSize; let name = networkRecord.url; - // TODO ... - if (name.startsWith(origin)) name = name.replace(origin, '/'); child.children = child.children || []; child.children.push({ name, From 32a5c374dd2e78453e55a8c1399fbb964a5baedc Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 14 Aug 2020 22:19:26 -0500 Subject: [PATCH 04/25] rm script id --- lighthouse-core/test/test-utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js index ae497ff8be45..9268d4fc1753 100644 --- a/lighthouse-core/test/test-utils.js +++ b/lighthouse-core/test/test-utils.js @@ -80,7 +80,6 @@ function getProtoRoundTrip() { /** * @typedef PartialScriptCoverage - * @property {string} scriptId * @property {string} url * @property {Array<{ranges: LH.Crdp.Profiler.CoverageRange[]}>} functions */ @@ -103,7 +102,6 @@ function loadSourceMapFixture(name) { /** @type {{url: string, ranges: Array<{start: number, end: number, count: number}>}} */ const exportedUsage = JSON.parse(usageJson); usage = { - scriptId: '0', url: exportedUsage.url, functions: [ { From 4b60ca38b5bf7b5675c03effe235340144a8d680 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 14 Aug 2020 22:21:39 -0500 Subject: [PATCH 05/25] tweaks --- lighthouse-core/audits/treemap-data.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index ff8367f192bb..2337cdef0643 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -50,7 +50,7 @@ class TreemapDataAudit extends Audit { id: 'treemap-data', scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, title: 'Treemap Data', - description: 'Used for treemap visualization.', + description: 'Used for treemap app.', requiredArtifacts: ['traces', 'devtoolsLogs', 'SourceMaps', 'ScriptElements', 'JsUsage', 'URL'], }; @@ -119,7 +119,7 @@ class TreemapDataAudit extends Audit { /** * Collapse nodes that have only one child. - * @param {*} node + * @param {Node} node */ function collapse(node) { while (node.children && node.children.length === 1) { @@ -225,7 +225,7 @@ class TreemapDataAudit extends Audit { } rootNodes.push({ - name: name, + name, node, }); } @@ -233,22 +233,6 @@ class TreemapDataAudit extends Audit { return rootNodes; } - /** - * @param {LH.Artifacts.Bundle[]} bundles - * @param {string} url - * @param {LH.Artifacts['JsUsage']} JsUsage - * @param {LH.Audit.Context} context - */ - static async getUnusedJavascriptSummary(bundles, url, JsUsage, context) { - const bundle = bundles.find(bundle => url === bundle.script.src); - const scriptCoverages = JsUsage[url]; - if (!scriptCoverages) return; - - const unusedJsSumary = - await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context); - return unusedJsSumary; - } - /** * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context From e6b3eb6217f6ee250d272ae6c42c42585d9c0e62 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 15:33:12 -0500 Subject: [PATCH 06/25] first pass --- lighthouse-core/audits/treemap-data.js | 42 +++++++++++-------- .../computed/module-duplication.js | 4 +- .../byte-efficiency/unused-javascript-test.js | 2 +- .../test/audits/treemap-data-test.js | 2 +- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 2337cdef0643..b0394723fc71 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -7,7 +7,7 @@ /** * @fileoverview - * Creates treemap data for webtreemap. + * Creates treemap data for treemap app. */ const Audit = require('./audit.js'); @@ -18,22 +18,23 @@ const NetworkRecords = require('../computed/network-records.js'); const ResourceSummary = require('../computed/resource-summary.js'); /** + * A collection of root nodes, grouped by type. * @typedef {Record} TreemapData */ /** * @typedef RootNode - * @property {string} name + * @property {string} name Arbitrary name identifier. Usually a script url. * @property {Node} node */ /** * @typedef Node - * @property {string} name + * @property {string} name Arbitrary name identifier. Usually a path component from a source map. * @property {number} resourceBytes * @property {number=} unusedBytes * @property {number=} executionTime - * @property {string=} duplicate + * @property {string=} duplicate If present, this module is a duplicate. String is normalized source path. See ModuleDuplication.normalizeSource * @property {Node[]=} children */ @@ -50,13 +51,17 @@ class TreemapDataAudit extends Audit { id: 'treemap-data', scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, title: 'Treemap Data', - description: 'Used for treemap app.', + description: 'Used for treemap app', requiredArtifacts: ['traces', 'devtoolsLogs', 'SourceMaps', 'ScriptElements', 'JsUsage', 'URL'], }; } /** + * Returns a tree data structure where leaf nodes are sources (ie. real files from source tree) + * from a source map, and non-leaf nodes are directories. Leaf nodes have data + * for bytes, coverage, etc., when available, and non-leaf nodes have the + * same data as the sum of all descendant leaf nodes. * @param {string} sourceRoot * @param {Record} sourcesData * @return {Node} @@ -161,22 +166,26 @@ class TreemapDataAudit extends Audit { length: 0, }; for (const scriptElement of artifacts.ScriptElements) { - // Normalize ScriptElements so that inline scripts show up as a single entity. + // No src means script is inline. + // Combine these ScriptElements so that inline scripts show up as a single root node. if (!scriptElement.src) { inlineScriptData.length += (scriptElement.content || '').length; continue; } - const url = scriptElement.src; - const bundle = bundles.find(bundle => url === bundle.script.src); - const scriptCoverages = artifacts.JsUsage[url]; - if (!bundle || !scriptCoverages) continue; + const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src); + // No source map for this script, so skip the rest of this. + if (!bundle) continue; + const scriptCoverages = artifacts.JsUsage[scriptElement.src]; + if (!scriptCoverages) continue; + + const unusedJavascriptSummary = await UnusedJavaScriptSummary.request( + {url: scriptElement.src, scriptCoverages, bundle}, context); scriptData.push({ src: scriptElement.src, length: (scriptElement.content || '').length, - unusedJavascriptSummary: - await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context), + unusedJavascriptSummary, }); } if (inlineScriptData.length) scriptData.unshift(inlineScriptData); @@ -199,10 +208,8 @@ class TreemapDataAudit extends Audit { sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes[source]; } - if (duplication) { - const key = ModuleDuplication._normalizeSource(source); - if (duplication.has(key)) sourceData.duplicate = key; - } + const key = ModuleDuplication.normalizeSource(source); + if (duplication.has(key)) sourceData.duplicate = key; sourcesData[source] = sourceData; } @@ -263,10 +270,9 @@ class TreemapDataAudit extends Audit { totalSize += networkRecord.resourceSize; child.resourceBytes += networkRecord.resourceSize; - let name = networkRecord.url; child.children = child.children || []; child.children.push({ - name, + name: networkRecord.url, resourceBytes: networkRecord.resourceSize, }); } diff --git a/lighthouse-core/computed/module-duplication.js b/lighthouse-core/computed/module-duplication.js index d221c60d73cd..219335e6d3b8 100644 --- a/lighthouse-core/computed/module-duplication.js +++ b/lighthouse-core/computed/module-duplication.js @@ -15,7 +15,7 @@ class ModuleDuplication { /** * @param {string} source */ - static _normalizeSource(source) { + static normalizeSource(source) { // Trim trailing question mark - b/c webpack. source = source.replace(/\?$/, ''); @@ -101,7 +101,7 @@ class ModuleDuplication { const sourceKey = (rawMap.sourceRoot || '') + rawMap.sources[i]; const sourceSize = sizes.files[sourceKey]; sourceDataArray.push({ - source: ModuleDuplication._normalizeSource(rawMap.sources[i]), + source: ModuleDuplication.normalizeSource(rawMap.sources[i]), resourceSize: sourceSize, }); } diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js index 0fa464ed45a1..8e45edce02b7 100644 --- a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js @@ -17,7 +17,7 @@ const {loadSourceMapFixture} = require('../../test-utils.js'); */ function load(name) { const data = loadSourceMapFixture(name); - if (!data.usage) throw new Error('exepcted usage'); + if (!data.usage) throw new Error('expected usage'); return {...data, usage: data.usage}; } diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index dada6ecd0f4c..4d16b5a6b8f5 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -21,7 +21,7 @@ const TreemapData = { */ function load(name) { const data = loadSourceMapFixture(name); - if (!data.usage) throw new Error('exepcted usage'); + if (!data.usage) throw new Error('expected usage'); return {...data, usage: data.usage}; } From ac4e8a60a9d85670dd4cf53abdf68f6bebc2ca68 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 15:48:05 -0500 Subject: [PATCH 07/25] no scriptData --- lighthouse-core/audits/treemap-data.js | 38 ++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index b0394723fc71..b67baeb0df2f 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -159,17 +159,29 @@ class TreemapDataAudit extends Audit { const bundles = await JsBundles.request(artifacts, context); const duplication = await ModuleDuplication.request(artifacts, context); - /** @type {Array<{src: string, length: number, unusedJavascriptSummary?: import('../computed/unused-javascript-summary.js').Summary}>} */ - const scriptData = []; - const inlineScriptData = { - src: artifacts.URL.finalUrl, - length: 0, - }; + let inlineScriptLength = 0; for (const scriptElement of artifacts.ScriptElements) { // No src means script is inline. // Combine these ScriptElements so that inline scripts show up as a single root node. if (!scriptElement.src) { - inlineScriptData.length += (scriptElement.content || '').length; + inlineScriptLength += (scriptElement.content || '').length; + } + } + if (inlineScriptLength) { + const name = artifacts.URL.finalUrl; + rootNodes.push({ + name, + node: { + name, + resourceBytes: inlineScriptLength, + unusedBytes: 0, + executionTime: 0, + }, + }); + } + + for (const scriptElement of artifacts.ScriptElements) { + if (!scriptElement.src) { continue; } @@ -182,17 +194,9 @@ class TreemapDataAudit extends Audit { const unusedJavascriptSummary = await UnusedJavaScriptSummary.request( {url: scriptElement.src, scriptCoverages, bundle}, context); - scriptData.push({ - src: scriptElement.src, - length: (scriptElement.content || '').length, - unusedJavascriptSummary, - }); - } - if (inlineScriptData.length) scriptData.unshift(inlineScriptData); - for (const {src, length, unusedJavascriptSummary} of scriptData) { - const bundle = bundles.find(bundle => bundle.script.src === src); - const name = src; + const length = (scriptElement.content || '').length; + const name = scriptElement.src; let node; if (bundle && unusedJavascriptSummary && unusedJavascriptSummary.sourcesWastedBytes) { From b12967ed4fd31282f9a25b153bbcd5996376bed3 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 15:59:25 -0500 Subject: [PATCH 08/25] reverse ifs --- lighthouse-core/audits/treemap-data.js | 35 +++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index b67baeb0df2f..5b473dfe5279 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -33,7 +33,6 @@ const ResourceSummary = require('../computed/resource-summary.js'); * @property {string} name Arbitrary name identifier. Usually a path component from a source map. * @property {number} resourceBytes * @property {number=} unusedBytes - * @property {number=} executionTime * @property {string=} duplicate If present, this module is a duplicate. String is normalized source path. See ModuleDuplication.normalizeSource * @property {Node[]=} children */ @@ -175,7 +174,6 @@ class TreemapDataAudit extends Audit { name, resourceBytes: inlineScriptLength, unusedBytes: 0, - executionTime: 0, }, }); } @@ -199,19 +197,28 @@ class TreemapDataAudit extends Audit { const name = scriptElement.src; let node; - if (bundle && unusedJavascriptSummary && unusedJavascriptSummary.sourcesWastedBytes) { + if (!unusedJavascriptSummary) { + node = { + name, + resourceBytes: length, + unusedBytes: 0, + }; + } else if (!unusedJavascriptSummary.sourcesWastedBytes) { + node = { + name, + resourceBytes: unusedJavascriptSummary.totalBytes, + unusedBytes: unusedJavascriptSummary.wastedBytes, + }; + } else { /** @type {Record} */ const sourcesData = {}; for (const source of Object.keys(bundle.sizes.files)) { /** @type {SourceData} */ const sourceData = { resourceBytes: bundle.sizes.files[source], + unusedBytes: unusedJavascriptSummary.sourcesWastedBytes[source], }; - if (unusedJavascriptSummary && unusedJavascriptSummary.sourcesWastedBytes) { - sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes[source]; - } - const key = ModuleDuplication.normalizeSource(source); if (duplication.has(key)) sourceData.duplicate = key; @@ -219,20 +226,6 @@ class TreemapDataAudit extends Audit { } node = this.prepareTreemapNodes(bundle.rawMap.sourceRoot || '', sourcesData); - } else if (unusedJavascriptSummary) { - node = { - name, - resourceBytes: unusedJavascriptSummary.totalBytes, - unusedBytes: unusedJavascriptSummary.wastedBytes, - executionTime: 0, - }; - } else { - node = { - name, - resourceBytes: length, - unusedBytes: 0, - executionTime: 0, - }; } rootNodes.push({ From f81133433be77169d0ef10744140bb3b23093ce9 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 16:10:40 -0500 Subject: [PATCH 09/25] comments --- lighthouse-core/audits/treemap-data.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 5b473dfe5279..2223f02a94aa 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -147,6 +147,8 @@ class TreemapDataAudit extends Audit { } /** + * Returns a root node where the first level of nodes are script URLs. + * If a script has a source map, that node will be set by prepareTreemapNodes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context * @return {Promise} @@ -179,9 +181,7 @@ class TreemapDataAudit extends Audit { } for (const scriptElement of artifacts.ScriptElements) { - if (!scriptElement.src) { - continue; - } + if (!scriptElement.src) continue; const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src); // No source map for this script, so skip the rest of this. @@ -193,17 +193,9 @@ class TreemapDataAudit extends Audit { const unusedJavascriptSummary = await UnusedJavaScriptSummary.request( {url: scriptElement.src, scriptCoverages, bundle}, context); - const length = (scriptElement.content || '').length; const name = scriptElement.src; - let node; - if (!unusedJavascriptSummary) { - node = { - name, - resourceBytes: length, - unusedBytes: 0, - }; - } else if (!unusedJavascriptSummary.sourcesWastedBytes) { + if (!unusedJavascriptSummary.sourcesWastedBytes) { node = { name, resourceBytes: unusedJavascriptSummary.totalBytes, @@ -238,6 +230,9 @@ class TreemapDataAudit extends Audit { } /** + * Returns root node where the first level is network resource type, + * followed by the leaf nodes which are are all network requests. Data + * contained is the resourceBytes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context * @return {Promise} From a39a444a69d5c31e4a5692ecfff454df9009b86e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 16:28:12 -0500 Subject: [PATCH 10/25] fix case with no map or usage --- lighthouse-core/audits/treemap-data.js | 17 +++++++--- .../__snapshots__/treemap-data-test.js.snap | 9 +++++- .../test/audits/treemap-data-test.js | 32 ++++++++++++------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 2223f02a94aa..a1bd5b6bd59a 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -183,17 +183,23 @@ class TreemapDataAudit extends Audit { for (const scriptElement of artifacts.ScriptElements) { if (!scriptElement.src) continue; + const name = scriptElement.src; const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src); - // No source map for this script, so skip the rest of this. - if (!bundle) continue; - const scriptCoverages = artifacts.JsUsage[scriptElement.src]; - if (!scriptCoverages) continue; + if (!bundle || !scriptCoverages) { + rootNodes.push({ + name, + node: { + name, + resourceBytes: scriptElement.src.length, + }, + }); + continue; + } const unusedJavascriptSummary = await UnusedJavaScriptSummary.request( {url: scriptElement.src, scriptCoverages, bundle}, context); - const name = scriptElement.src; let node; if (!unusedJavascriptSummary.sourcesWastedBytes) { node = { @@ -291,6 +297,7 @@ class TreemapDataAudit extends Audit { resources: [await TreemapDataAudit.makeResourceSummaryRootNode(artifacts, context)], }; + // TODO: when out of experimental should make a new detail type. /** @type {LH.Audit.Details.DebugData} */ const details = { type: 'debugdata', diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap index 6d35883f6405..0ab4f5ea0bbc 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreemapData audit squoosh fixture js 1`] = ` +exports[`TreemapData audit squoosh fixture scripts 1`] = ` Array [ Object { "name": "https://squoosh.app/main-app.js", @@ -579,5 +579,12 @@ Array [ "unusedBytes": 3066, }, }, + Object { + "name": "https://sqoosh.app/blah.js", + "node": Object { + "name": "https://sqoosh.app/blah.js", + "resourceBytes": 26, + }, + }, ] `; diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index 4d16b5a6b8f5..bf57aaada27a 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -27,11 +27,11 @@ function load(name) { /** * @param {string} url - * @param {number} transferSize + * @param {number} resourceSize * @param {LH.Crdp.Network.ResourceType} resourceType */ -function generateRecord(url, transferSize, resourceType) { - return {url, transferSize, resourceType}; +function generateRecord(url, resourceSize, resourceType) { + return {url, resourceSize, resourceType}; } describe('TreemapData audit', () => { @@ -45,12 +45,18 @@ describe('TreemapData audit', () => { const scriptUrl = 'https://squoosh.app/main-app.js'; const networkRecords = [generateRecord(scriptUrl, content.length, 'Script')]; + // Add a script with no source map or usage. + const noSourceMapScript = {src: 'https://sqoosh.app/no-map-or-usage.js', content: '// hi'}; + networkRecords.push( + generateRecord(noSourceMapScript.src, noSourceMapScript.content.length, 'Script') + ); + const artifacts = { URL: {requestedUrl: mainUrl, finalUrl: mainUrl}, JsUsage: {[usage.url]: [usage]}, devtoolsLogs: {defaultPass: networkRecordsToDevtoolsLog(networkRecords)}, SourceMaps: [{scriptUrl: scriptUrl, map}], - ScriptElements: [{src: scriptUrl, content}], + ScriptElements: [{src: scriptUrl, content}, noSourceMapScript], }; const results = await TreemapData.audit(artifacts, context); @@ -62,11 +68,11 @@ describe('TreemapData audit', () => { expect(Object.keys(treemapData)).toEqual(['scripts', 'resources']); }); - it('js', () => { + it('scripts', () => { expect(treemapData.scripts).toMatchSnapshot(); }); - it('resource summary', () => { + it('resources', () => { expect(treemapData.resources).toMatchInlineSnapshot(` Array [ Object { @@ -76,16 +82,20 @@ describe('TreemapData audit', () => { Object { "children": Array [ Object { - "name": "//main-app.js", - "resourceBytes": 0, + "name": "https://squoosh.app/main-app.js", + "resourceBytes": 83748, + }, + Object { + "name": "https://sqoosh.app/blah.js", + "resourceBytes": 8, }, ], "name": "script", - "resourceBytes": 0, + "resourceBytes": 83756, }, ], - "name": "1 requests", - "resourceBytes": 0, + "name": "2 requests", + "resourceBytes": 83756, }, }, ] From f28ef7679279619e91c9e419573ff7c0fecb4a12 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 16:42:28 -0500 Subject: [PATCH 11/25] pr --- lighthouse-core/audits/treemap-data.js | 1 + .../audits/__snapshots__/treemap-data-test.js.snap | 6 +++--- lighthouse-core/test/audits/treemap-data-test.js | 8 ++++---- lighthouse-core/test/test-utils.js | 13 +++++-------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index a1bd5b6bd59a..0f442b786b49 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -23,6 +23,7 @@ const ResourceSummary = require('../computed/resource-summary.js'); */ /** + * Ex: https://gist.github.com/connorjclark/0ef1099ae994c075e36d65fecb4d26a7 * @typedef RootNode * @property {string} name Arbitrary name identifier. Usually a script url. * @property {Node} node diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap index 0ab4f5ea0bbc..87250efeb82c 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -580,10 +580,10 @@ Array [ }, }, Object { - "name": "https://sqoosh.app/blah.js", + "name": "https://sqoosh.app/no-map-or-usage.js", "node": Object { - "name": "https://sqoosh.app/blah.js", - "resourceBytes": 26, + "name": "https://sqoosh.app/no-map-or-usage.js", + "resourceBytes": 37, }, }, ] diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index bf57aaada27a..cc0e61b0ce1b 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -86,16 +86,16 @@ describe('TreemapData audit', () => { "resourceBytes": 83748, }, Object { - "name": "https://sqoosh.app/blah.js", - "resourceBytes": 8, + "name": "https://sqoosh.app/no-map-or-usage.js", + "resourceBytes": 5, }, ], "name": "script", - "resourceBytes": 83756, + "resourceBytes": 83753, }, ], "name": "2 requests", - "resourceBytes": 83756, + "resourceBytes": 83753, }, }, ] diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js index 9268d4fc1753..dd999660cb99 100644 --- a/lighthouse-core/test/test-utils.js +++ b/lighthouse-core/test/test-utils.js @@ -78,15 +78,9 @@ function getProtoRoundTrip() { }; } -/** - * @typedef PartialScriptCoverage - * @property {string} url - * @property {Array<{ranges: LH.Crdp.Profiler.CoverageRange[]}>} functions - */ - /** * @param {string} name - * @return {{map: LH.Artifacts.RawSourceMap, content: string, usage?: PartialScriptCoverage}} + * @return {{map: LH.Artifacts.RawSourceMap, content: string, usage?: LH.Crdp.Profiler.ScriptCoverage}} */ function loadSourceMapFixture(name) { const dir = `${__dirname}/fixtures/source-maps`; @@ -94,7 +88,7 @@ function loadSourceMapFixture(name) { const content = fs.readFileSync(`${dir}/${name}.js`, 'utf-8'); const usagePath = `${dir}/${name}.usage.json`; - let usage = undefined; + let usage; if (fs.existsSync(usagePath)) { const usageJson = fs.readFileSync(`${dir}/${name}.usage.json`, 'utf-8'); // Usage is exported from DevTools, which simplifies the real format of the @@ -102,9 +96,12 @@ function loadSourceMapFixture(name) { /** @type {{url: string, ranges: Array<{start: number, end: number, count: number}>}} */ const exportedUsage = JSON.parse(usageJson); usage = { + scriptId: 'FakeId', // Not used. url: exportedUsage.url, functions: [ { + functionName: 'FakeFunctionName', // Not used. + isBlockCoverage: false, // Not used. ranges: exportedUsage.ranges.map((range, i) => { return { startOffset: range.start, From 7008e5502ec4465d47f15d54fa25383a4b0db40a Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 19:09:30 -0500 Subject: [PATCH 12/25] load --- .../duplicated-javascript-test.js | 21 ++---- .../byte-efficiency/unused-javascript-test.js | 13 +--- .../test/audits/treemap-data-test.js | 13 +--- .../test/audits/valid-source-maps-test.js | 17 ++--- .../test/computed/js-bundles-test.js | 16 ++--- .../test/computed/module-duplication-test.js | 18 ++--- lighthouse-core/test/test-utils.js | 67 +++++++++++-------- 7 files changed, 65 insertions(+), 100 deletions(-) diff --git a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js index 942aeee1c323..03aaf9d47827 100644 --- a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js @@ -7,24 +7,15 @@ /* eslint-env jest */ -const fs = require('fs'); const DuplicatedJavascript = require('../../../audits/byte-efficiency/duplicated-javascript.js'); const trace = require('../../fixtures/traces/lcp-m78.json'); const devtoolsLog = require('../../fixtures/traces/lcp-m78.devtools.log.json'); - -function load(name) { - const mapJson = fs.readFileSync( - `${__dirname}/../../fixtures/source-maps/${name}.js.map`, - 'utf-8' - ); - const content = fs.readFileSync(`${__dirname}/../../fixtures/source-maps/${name}.js`, 'utf-8'); - return {map: JSON.parse(mapJson), content}; -} +const {loadSourceMapFixture} = require('../../test-utils.js'); describe('DuplicatedJavascript computed artifact', () => { it('works (simple)', async () => { const context = {computedCache: new Map(), options: {ignoreThresholdInBytes: 200}}; - const {map, content} = load('foo.min'); + const {map, content} = loadSourceMapFixture('foo.min'); const artifacts = { URL: {finalUrl: 'https://example.com'}, SourceMaps: [ @@ -49,8 +40,8 @@ describe('DuplicatedJavascript computed artifact', () => { it('works (complex)', async () => { const context = {computedCache: new Map(), options: {ignoreThresholdInBytes: 200}}; - const bundleData1 = load('coursehero-bundle-1'); - const bundleData2 = load('coursehero-bundle-2'); + const bundleData1 = loadSourceMapFixture('coursehero-bundle-1'); + const bundleData2 = loadSourceMapFixture('coursehero-bundle-2'); const artifacts = { URL: {finalUrl: 'https://example.com'}, SourceMaps: [ @@ -324,8 +315,8 @@ describe('DuplicatedJavascript computed artifact', () => { it('.audit', async () => { // Use a real trace fixture, but the bundle stuff. - const bundleData1 = load('coursehero-bundle-1'); - const bundleData2 = load('coursehero-bundle-2'); + const bundleData1 = loadSourceMapFixture('coursehero-bundle-1'); + const bundleData2 = loadSourceMapFixture('coursehero-bundle-2'); const artifacts = { URL: {finalUrl: 'https://www.paulirish.com'}, devtoolsLogs: { diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js index 8e45edce02b7..1992346ac9f6 100644 --- a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js @@ -8,19 +8,10 @@ const assert = require('assert').strict; const UnusedJavaScript = require('../../../audits/byte-efficiency/unused-javascript.js'); const networkRecordsToDevtoolsLog = require('../../network-records-to-devtools-log.js'); -const {loadSourceMapFixture} = require('../../test-utils.js'); +const {loadSourceMapAndUsageFixture} = require('../../test-utils.js'); /* eslint-env jest */ -/** - * @param {string} name - */ -function load(name) { - const data = loadSourceMapFixture(name); - if (!data.usage) throw new Error('expected usage'); - return {...data, usage: data.usage}; -} - /** * @param {string} url * @param {number} transferSize @@ -112,7 +103,7 @@ describe('UnusedJavaScript audit', () => { bundleSourceUnusedThreshold: 100, }, }; - const {map, content, usage} = load('squoosh'); + const {map, content, usage} = loadSourceMapAndUsageFixture('squoosh'); const url = 'https://squoosh.app/main-app.js'; const networkRecords = [generateRecord(url, content.length, 'Script')]; const artifacts = { diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index cc0e61b0ce1b..70240d03e136 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -7,7 +7,7 @@ const TreemapData_ = require('../../audits/treemap-data.js'); const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js'); -const {loadSourceMapFixture, makeParamsOptional} = require('../test-utils.js'); +const {loadSourceMapAndUsageFixture, makeParamsOptional} = require('../test-utils.js'); /* eslint-env jest */ @@ -16,15 +16,6 @@ const TreemapData = { prepareTreemapNodes: makeParamsOptional(TreemapData_.prepareTreemapNodes), }; -/** - * @param {string} name - */ -function load(name) { - const data = loadSourceMapFixture(name); - if (!data.usage) throw new Error('expected usage'); - return {...data, usage: data.usage}; -} - /** * @param {string} url * @param {number} resourceSize @@ -40,7 +31,7 @@ describe('TreemapData audit', () => { let treemapData; beforeAll(async () => { const context = {computedCache: new Map()}; - const {map, content, usage} = load('squoosh'); + const {map, content, usage} = loadSourceMapAndUsageFixture('squoosh'); const mainUrl = 'https://squoosh.app'; const scriptUrl = 'https://squoosh.app/main-app.js'; const networkRecords = [generateRecord(scriptUrl, content.length, 'Script')]; diff --git a/lighthouse-core/test/audits/valid-source-maps-test.js b/lighthouse-core/test/audits/valid-source-maps-test.js index fa72f3e840c3..c4f969488243 100644 --- a/lighthouse-core/test/audits/valid-source-maps-test.js +++ b/lighthouse-core/test/audits/valid-source-maps-test.js @@ -6,16 +6,11 @@ 'use strict'; /* eslint-env jest */ -function load(name) { - const mapJson = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js.map`, 'utf-8'); - const content = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js`, 'utf-8'); - return {map: JSON.parse(mapJson), content}; -} +const {loadSourceMapFixture} = require('../test-utils.js'); const ValidSourceMaps = require('../../audits/valid-source-maps.js'); -const fs = require('fs'); -const largeBundle = load('coursehero-bundle-1'); -const smallBundle = load('coursehero-bundle-2'); +const largeBundle = loadSourceMapFixture('coursehero-bundle-1'); +const smallBundle = loadSourceMapFixture('coursehero-bundle-2'); const LARGE_JS_BYTE_THRESHOLD = 500 * 1024; if (largeBundle.content.length < LARGE_JS_BYTE_THRESHOLD) { @@ -111,8 +106,8 @@ describe('Valid source maps audit', () => { }); it('discovers missing source map contents while passing', async () => { - const bundleNormal = load('squoosh'); - const bundleWithMissingContent = load('squoosh'); + const bundleNormal = loadSourceMapFixture('squoosh'); + const bundleWithMissingContent = loadSourceMapFixture('squoosh'); delete bundleWithMissingContent.map.sourcesContent[0]; const artifacts = { @@ -142,7 +137,7 @@ describe('Valid source maps audit', () => { }); it('discovers missing source map contents while failing', async () => { - const bundleWithMissingContent = load('squoosh'); + const bundleWithMissingContent = loadSourceMapFixture('squoosh'); delete bundleWithMissingContent.map.sourcesContent[0]; const artifacts = { diff --git a/lighthouse-core/test/computed/js-bundles-test.js b/lighthouse-core/test/computed/js-bundles-test.js index 5490e72e8fec..78a4421e65d6 100644 --- a/lighthouse-core/test/computed/js-bundles-test.js +++ b/lighthouse-core/test/computed/js-bundles-test.js @@ -6,14 +6,8 @@ 'use strict'; /* eslint-env jest */ -const fs = require('fs'); const JsBundles = require('../../computed/js-bundles.js'); - -function load(name) { - const mapJson = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js.map`, 'utf-8'); - const content = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js`, 'utf-8'); - return {map: JSON.parse(mapJson), content}; -} +const {loadSourceMapFixture} = require('../test-utils.js'); describe('JsBundles computed artifact', () => { it('collates script element and source map', async () => { @@ -34,7 +28,7 @@ describe('JsBundles computed artifact', () => { it('works (simple map)', async () => { // This map is from source-map-explorer. // https://github.com/danvk/source-map-explorer/tree/4b95f6e7dfe0058d791dcec2107fee43a1ebf02e/tests - const {map, content} = load('foo.min'); + const {map, content} = loadSourceMapFixture('foo.min'); const artifacts = { SourceMaps: [{scriptUrl: 'https://example.com/foo.min.js', map}], ScriptElements: [{src: 'https://example.com/foo.min.js', content}], @@ -80,7 +74,7 @@ describe('JsBundles computed artifact', () => { it('works (simple map) (null source)', async () => { // This map is from source-map-explorer. // https://github.com/danvk/source-map-explorer/tree/4b95f6e7dfe0058d791dcec2107fee43a1ebf02e/tests - const {map, content} = load('foo.min'); + const {map, content} = loadSourceMapFixture('foo.min'); map.sources[1] = null; const artifacts = { SourceMaps: [{scriptUrl: 'https://example.com/foo.min.js', map}], @@ -123,7 +117,7 @@ describe('JsBundles computed artifact', () => { }); it('works (complex map)', async () => { - const {map, content} = load('squoosh'); + const {map, content} = loadSourceMapFixture('squoosh'); const artifacts = { SourceMaps: [{scriptUrl: 'https://squoosh.app/main-app.js', map}], ScriptElements: [{src: 'https://squoosh.app/main-app.js', content}], @@ -240,7 +234,7 @@ describe('JsBundles computed artifact', () => { let content; beforeEach(() => { - data = load('foo.min'); + data = loadSourceMapFixture('foo.min'); map = data.map; content = data.content; }); diff --git a/lighthouse-core/test/computed/module-duplication-test.js b/lighthouse-core/test/computed/module-duplication-test.js index 0933d490e903..78310204b6b8 100644 --- a/lighthouse-core/test/computed/module-duplication-test.js +++ b/lighthouse-core/test/computed/module-duplication-test.js @@ -7,19 +7,13 @@ /* eslint-env jest */ -const fs = require('fs'); const ModuleDuplication = require('../../computed/module-duplication.js'); - -function load(name) { - const mapJson = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js.map`, 'utf-8'); - const content = fs.readFileSync(`${__dirname}/../fixtures/source-maps/${name}.js`, 'utf-8'); - return {map: JSON.parse(mapJson), content}; -} +const {loadSourceMapFixture} = require('../test-utils.js'); describe('ModuleDuplication computed artifact', () => { it('works (simple)', async () => { const context = {computedCache: new Map()}; - const {map, content} = load('foo.min'); + const {map, content} = loadSourceMapFixture('foo.min'); const artifacts = { SourceMaps: [ {scriptUrl: 'https://example.com/foo1.min.js', map}, @@ -36,8 +30,8 @@ describe('ModuleDuplication computed artifact', () => { it('works (complex)', async () => { const context = {computedCache: new Map()}; - const bundleData1 = load('coursehero-bundle-1'); - const bundleData2 = load('coursehero-bundle-2'); + const bundleData1 = loadSourceMapFixture('coursehero-bundle-1'); + const bundleData2 = loadSourceMapFixture('coursehero-bundle-2'); const artifacts = { SourceMaps: [ {scriptUrl: 'https://example.com/coursehero-bundle-1.js', map: bundleData1.map}, @@ -215,7 +209,7 @@ describe('ModuleDuplication computed artifact', () => { `); }); - it('_normalizeSource', () => { + it('normalizeSource', () => { const testCases = [ ['test.js', 'test.js'], ['node_modules/othermodule.js', 'node_modules/othermodule.js'], @@ -227,7 +221,7 @@ describe('ModuleDuplication computed artifact', () => { ['webpack.js?', 'webpack.js'], ]; for (const [input, expected] of testCases) { - expect(ModuleDuplication._normalizeSource(input)).toBe(expected); + expect(ModuleDuplication.normalizeSource(input)).toBe(expected); } }); diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js index dd999660cb99..6889e0aa72be 100644 --- a/lighthouse-core/test/test-utils.js +++ b/lighthouse-core/test/test-utils.js @@ -38,7 +38,7 @@ expect.extend({ // done for asymmetric matchers anyways. const thisObj = (this && this.utils) ? this : {isNot: false, promise: ''}; - + // @ts-ignore return toBeCloseTo.call(thisObj, ...args); }, }); @@ -80,43 +80,51 @@ function getProtoRoundTrip() { /** * @param {string} name - * @return {{map: LH.Artifacts.RawSourceMap, content: string, usage?: LH.Crdp.Profiler.ScriptCoverage}} + * @return {{map: LH.Artifacts.RawSourceMap, content: string}} */ function loadSourceMapFixture(name) { const dir = `${__dirname}/fixtures/source-maps`; const mapJson = fs.readFileSync(`${dir}/${name}.js.map`, 'utf-8'); const content = fs.readFileSync(`${dir}/${name}.js`, 'utf-8'); + return { + map: JSON.parse(mapJson), + content, + }; +} +/** + * @param {string} name + * @return {{map: LH.Artifacts.RawSourceMap, content: string, usage: LH.Crdp.Profiler.ScriptCoverage}} + */ +function loadSourceMapAndUsageFixture(name) { + const dir = `${__dirname}/fixtures/source-maps`; const usagePath = `${dir}/${name}.usage.json`; - let usage; - if (fs.existsSync(usagePath)) { - const usageJson = fs.readFileSync(`${dir}/${name}.usage.json`, 'utf-8'); - // Usage is exported from DevTools, which simplifies the real format of the - // usage protocol. - /** @type {{url: string, ranges: Array<{start: number, end: number, count: number}>}} */ - const exportedUsage = JSON.parse(usageJson); - usage = { - scriptId: 'FakeId', // Not used. - url: exportedUsage.url, - functions: [ - { - functionName: 'FakeFunctionName', // Not used. - isBlockCoverage: false, // Not used. - ranges: exportedUsage.ranges.map((range, i) => { - return { - startOffset: range.start, - endOffset: range.end, - count: i % 2 === 0 ? 0 : 1, - }; - }), - }, - ], - }; - } + const usageJson = fs.readFileSync(usagePath, 'utf-8'); + + // Usage is exported from DevTools, which simplifies the real format of the + // usage protocol. + /** @type {{url: string, ranges: Array<{start: number, end: number, count: number}>}} */ + const exportedUsage = JSON.parse(usageJson); + const usage = { + scriptId: 'FakeId', // Not used. + url: exportedUsage.url, + functions: [ + { + functionName: 'FakeFunctionName', // Not used. + isBlockCoverage: false, // Not used. + ranges: exportedUsage.ranges.map((range, i) => { + return { + startOffset: range.start, + endOffset: range.end, + count: i % 2 === 0 ? 0 : 1, + }; + }), + }, + ], + }; return { - map: JSON.parse(mapJson), - content, + ...loadSourceMapFixture(name), usage, }; } @@ -133,5 +141,6 @@ function makeParamsOptional(fn) { module.exports = { getProtoRoundTrip, loadSourceMapFixture, + loadSourceMapAndUsageFixture, makeParamsOptional, }; From 1eec2e566a82e58139efa16e7b6ddd39c026f6e8 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 25 Sep 2020 20:15:55 -0500 Subject: [PATCH 13/25] minor test --- .../audits/__snapshots__/treemap-data-test.js.snap | 2 +- lighthouse-core/test/audits/treemap-data-test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap index 87250efeb82c..8aee8088e4cf 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreemapData audit squoosh fixture scripts 1`] = ` +exports[`TreemapData audit squoosh fixture scripts 2`] = ` Array [ Object { "name": "https://squoosh.app/main-app.js", diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index 70240d03e136..95b161da1334 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -60,10 +60,24 @@ describe('TreemapData audit', () => { }); it('scripts', () => { + expect(treemapData.scripts.find(s => s.name === 'https://sqoosh.app/no-map-or-usage.js')) + .toMatchInlineSnapshot(` + Object { + "name": "https://sqoosh.app/no-map-or-usage.js", + "node": Object { + "name": "https://sqoosh.app/no-map-or-usage.js", + "resourceBytes": 37, + }, + } + `); + expect(treemapData.scripts).toMatchSnapshot(); }); it('resources', () => { + const scriptsNode = (treemapData.resources[0].node.children || [])[0]; + expect(scriptsNode.children || []).toHaveLength(2); + expect(treemapData.resources).toMatchInlineSnapshot(` Array [ Object { From 9c7e3a51f57028be794c58154723f06ae14b5a3f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 28 Sep 2020 13:14:53 -0500 Subject: [PATCH 14/25] tests --- .../test/audits/treemap-data-test.js | 101 ++++++++++++------ 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index 95b161da1334..6fd824502cbe 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -109,89 +109,120 @@ describe('TreemapData audit', () => { }); describe('.prepareTreemapNodes', () => { - it('basics 1', () => { - const rootNode = TreemapData.prepareTreemapNodes('', {'main.js': {resourceBytes: 100}}); + it('uses node data when available', () => { + const rootNode = TreemapData.prepareTreemapNodes('', { + 'a.js': {resourceBytes: 100}, + 'b.js': {resourceBytes: 100, duplicate: 'blah'}, + 'c.js': {resourceBytes: 100, unusedBytes: 50}, + }); expect(rootNode).toMatchInlineSnapshot(` Object { - "children": undefined, - "name": "/main.js", - "resourceBytes": 100, + "children": Array [ + Object { + "name": "a.js", + "resourceBytes": 100, + }, + Object { + "duplicate": "blah", + "name": "b.js", + "resourceBytes": 100, + }, + Object { + "name": "c.js", + "resourceBytes": 100, + "unusedBytes": 50, + }, + ], + "name": "", + "resourceBytes": 300, + "unusedBytes": 50, } `); }); - it('basics 2', () => { - const sourcesData = { - 'some/prefix/main.js': {resourceBytes: 100}, - 'a.js': {resourceBytes: 101}, - }; - const rootNode = TreemapData.prepareTreemapNodes('some/prefix', sourcesData); + it('creates directory node when multiple leaf nodes', () => { + const rootNode = TreemapData.prepareTreemapNodes('', { + 'folder/a.js': {resourceBytes: 100}, + 'folder/b.js': {resourceBytes: 100}, + }); expect(rootNode).toMatchInlineSnapshot(` Object { "children": Array [ Object { - "children": undefined, - "name": "/main.js", + "name": "a.js", "resourceBytes": 100, }, Object { - "name": "a.js", - "resourceBytes": 101, + "name": "b.js", + "resourceBytes": 100, }, ], - "name": "some/prefix", - "resourceBytes": 201, + "name": "/folder", + "resourceBytes": 200, } `); }); - it('basics 3', () => { - const sourcesData = { - 'lib/a.js': {resourceBytes: 100}, - 'main.js': {resourceBytes: 101}, - }; - const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + it('flattens directory node when single leaf nodes', () => { + const rootNode = TreemapData.prepareTreemapNodes('', { + 'root/folder1/a.js': {resourceBytes: 100}, + 'root/folder2/b.js': {resourceBytes: 100}, + }); expect(rootNode).toMatchInlineSnapshot(` Object { "children": Array [ Object { "children": undefined, - "name": "lib/a.js", + "name": "folder1/a.js", "resourceBytes": 100, }, Object { - "name": "main.js", - "resourceBytes": 101, + "children": undefined, + "name": "folder2/b.js", + "resourceBytes": 100, }, ], - "name": "", - "resourceBytes": 201, + "name": "/root", + "resourceBytes": 200, } `); }); - it('basics 4', () => { + it('source root replaces matching prefixes', () => { const sourcesData = { - 'lib/folder/a.js': {resourceBytes: 100}, - 'lib/folder/b.js': {resourceBytes: 101}, + 'some/prefix/main.js': {resourceBytes: 100, unusedBytes: 50}, + 'not/some/prefix/a.js': {resourceBytes: 101, unusedBytes: 51}, }; - const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + const rootNode = TreemapData.prepareTreemapNodes('some/prefix', sourcesData); expect(rootNode).toMatchInlineSnapshot(` Object { "children": Array [ Object { - "name": "a.js", + "children": undefined, + "name": "/main.js", "resourceBytes": 100, + "unusedBytes": 50, }, Object { - "name": "b.js", + "children": undefined, + "name": "not/a.js", "resourceBytes": 101, + "unusedBytes": 51, }, ], - "name": "/lib/folder", + "name": "some/prefix", "resourceBytes": 201, + "unusedBytes": 101, } `); + + expect(rootNode.name).toBe('some/prefix'); + expect(rootNode.resourceBytes).toBe(201); + expect(rootNode.unusedBytes).toBe(101); + + const children = rootNode.children || []; + expect(children[0].name).toBe('/main.js'); + expect(children[1].name).toBe('not/a.js'); }); it('unusedBytes', () => { From 5d72ee940cb457c1dedc36e7d19d1fd244b1d206 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 1 Oct 2020 13:07:53 -0500 Subject: [PATCH 15/25] pr --- lighthouse-core/audits/treemap-data.js | 47 ++++++++++++++------------ lighthouse-core/test/test-utils.js | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 0f442b786b49..5338d7b3b20d 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -1,5 +1,5 @@ /** - * @license Copyright 2020 Google Inc. All Rights Reserved. + * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -78,6 +78,8 @@ class TreemapDataAudit extends Audit { }; } + const rootNode = newNode(sourceRoot); + /** * Given a slash-delimited path, traverse the Node structure and increment * the data provided for each node in the chain. Creates nodes as needed. @@ -85,9 +87,10 @@ class TreemapDataAudit extends Audit { * and continue with "to", and so on. * @param {string} source * @param {SourceData} data - * @param {Node} node */ - function addNode(source, data, node) { + function addAllNodesInSourcePath(source, data) { + let node = rootNode; + // Strip off the shared root. const sourcePathSegments = source.replace(sourceRoot, '').split(/\/+/); sourcePathSegments.forEach((sourcePathSegment, i) => { @@ -104,18 +107,18 @@ class TreemapDataAudit extends Audit { // Now that we've found or created the next node in the path, apply the data. node.resourceBytes += data.resourceBytes; if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; + + // Leaf node might have duplication data. if (data.duplicate !== undefined && isLastSegment) { node.duplicate = data.duplicate; } }); } - const rootNode = newNode(sourceRoot); - // For every source file, apply the data to all components // of the source path, creating nodes as necessary. for (const [source, data] of Object.entries(sourcesData)) { - addNode(source || ``, data, rootNode); + addAllNodesInSourcePath(source || ``, data); // Apply the data to the rootNode. rootNode.resourceBytes += data.resourceBytes; @@ -126,7 +129,7 @@ class TreemapDataAudit extends Audit { * Collapse nodes that have only one child. * @param {Node} node */ - function collapse(node) { + function collapseAll(node) { while (node.children && node.children.length === 1) { node.name += '/' + node.children[0].name; node.children = node.children[0].children; @@ -134,11 +137,11 @@ class TreemapDataAudit extends Audit { if (node.children) { for (const child of node.children) { - collapse(child); + collapseAll(child); } } } - collapse(rootNode); + collapseAll(rootNode); // TODO(cjamcl): Should this structure be flattened for space savings? // Like DOM Snapshot. @@ -158,9 +161,6 @@ class TreemapDataAudit extends Audit { /** @type {RootNode[]} */ const rootNodes = []; - const bundles = await JsBundles.request(artifacts, context); - const duplication = await ModuleDuplication.request(artifacts, context); - let inlineScriptLength = 0; for (const scriptElement of artifacts.ScriptElements) { // No src means script is inline. @@ -181,6 +181,9 @@ class TreemapDataAudit extends Audit { }); } + const bundles = await JsBundles.request(artifacts, context); + const duplicationByPath = await ModuleDuplication.request(artifacts, context); + for (const scriptElement of artifacts.ScriptElements) { if (!scriptElement.src) continue; @@ -219,7 +222,7 @@ class TreemapDataAudit extends Audit { }; const key = ModuleDuplication.normalizeSource(source); - if (duplication.has(key)) sourceData.duplicate = key; + if (duplicationByPath.has(key)) sourceData.duplicate = key; sourcesData[source] = sourceData; } @@ -252,25 +255,25 @@ class TreemapDataAudit extends Audit { let totalSize = 0; /** @type {Node[]} */ - const children = []; + const resourceNodes = []; for (const networkRecord of networkRecords) { const resourceType = ResourceSummary.determineResourceType(networkRecord); - let child = children.find(child => child.name === resourceType); - if (!child) { - child = { + let resourceNode = resourceNodes.find(child => child.name === resourceType); + if (!resourceNode) { + resourceNode = { name: resourceType, resourceBytes: 0, children: [], }; - children.push(child); + resourceNodes.push(resourceNode); } totalSize += networkRecord.resourceSize; - child.resourceBytes += networkRecord.resourceSize; + resourceNode.resourceBytes += networkRecord.resourceSize; - child.children = child.children || []; - child.children.push({ + resourceNode.children = resourceNode.children || []; + resourceNode.children.push({ name: networkRecord.url, resourceBytes: networkRecord.resourceSize, }); @@ -281,7 +284,7 @@ class TreemapDataAudit extends Audit { node: { name: `${totalCount} requests`, resourceBytes: totalSize, - children, + children: resourceNodes, }, }; } diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js index 6889e0aa72be..14c485a5b4b7 100644 --- a/lighthouse-core/test/test-utils.js +++ b/lighthouse-core/test/test-utils.js @@ -38,7 +38,7 @@ expect.extend({ // done for asymmetric matchers anyways. const thisObj = (this && this.utils) ? this : {isNot: false, promise: ''}; - // @ts-ignore + // @ts-expect-error return toBeCloseTo.call(thisObj, ...args); }, }); From 8c05fc0d4cee348471c422202eca28c7d8daedd6 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 1 Oct 2020 13:31:58 -0500 Subject: [PATCH 16/25] pr --- lighthouse-core/audits/treemap-data.js | 30 +++++++++++-------- .../__snapshots__/treemap-data-test.js.snap | 2 +- .../test/audits/treemap-data-test.js | 17 +++++++---- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 5338d7b3b20d..066eaf6bb67a 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -91,6 +91,10 @@ class TreemapDataAudit extends Audit { function addAllNodesInSourcePath(source, data) { let node = rootNode; + // Apply the data to the rootNode. + rootNode.resourceBytes += data.resourceBytes; + if (data.unusedBytes) rootNode.unusedBytes = (rootNode.unusedBytes || 0) + data.unusedBytes; + // Strip off the shared root. const sourcePathSegments = source.replace(sourceRoot, '').split(/\/+/); sourcePathSegments.forEach((sourcePathSegment, i) => { @@ -107,7 +111,7 @@ class TreemapDataAudit extends Audit { // Now that we've found or created the next node in the path, apply the data. node.resourceBytes += data.resourceBytes; if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; - + // Leaf node might have duplication data. if (data.duplicate !== undefined && isLastSegment) { node.duplicate = data.duplicate; @@ -119,10 +123,6 @@ class TreemapDataAudit extends Audit { // of the source path, creating nodes as necessary. for (const [source, data] of Object.entries(sourcesData)) { addAllNodesInSourcePath(source || ``, data); - - // Apply the data to the rootNode. - rootNode.resourceBytes += data.resourceBytes; - if (data.unusedBytes) rootNode.unusedBytes = (rootNode.unusedBytes || 0) + data.unusedBytes; } /** @@ -176,7 +176,6 @@ class TreemapDataAudit extends Audit { node: { name, resourceBytes: inlineScriptLength, - unusedBytes: 0, }, }); } @@ -204,14 +203,11 @@ class TreemapDataAudit extends Audit { const unusedJavascriptSummary = await UnusedJavaScriptSummary.request( {url: scriptElement.src, scriptCoverages, bundle}, context); + /** @type {Node} */ let node; - if (!unusedJavascriptSummary.sourcesWastedBytes) { - node = { - name, - resourceBytes: unusedJavascriptSummary.totalBytes, - unusedBytes: unusedJavascriptSummary.wastedBytes, - }; - } else { + if (unusedJavascriptSummary.sourcesWastedBytes) { + // Create nodes for each module in a bundle. + /** @type {Record} */ const sourcesData = {}; for (const source of Object.keys(bundle.sizes.files)) { @@ -228,6 +224,14 @@ class TreemapDataAudit extends Audit { } node = this.prepareTreemapNodes(bundle.rawMap.sourceRoot || '', sourcesData); + } else { + // There was no source map for this script, so we can only produce a single node. + + node = { + name, + resourceBytes: unusedJavascriptSummary.totalBytes, + unusedBytes: unusedJavascriptSummary.wastedBytes, + }; } rootNodes.push({ diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap index 8aee8088e4cf..a53b4eac23cd 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreemapData audit squoosh fixture scripts 2`] = ` +exports[`TreemapData audit squoosh fixture has root nodes for scripts 3`] = ` Array [ Object { "name": "https://squoosh.app/main-app.js", diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index 6fd824502cbe..dc4156597a6b 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -55,11 +55,11 @@ describe('TreemapData audit', () => { treemapData = results.details.treemapData; }); - it('basics', () => { + it('has multiple root node types', () => { expect(Object.keys(treemapData)).toEqual(['scripts', 'resources']); }); - it('scripts', () => { + it('has root nodes for scripts', () => { expect(treemapData.scripts.find(s => s.name === 'https://sqoosh.app/no-map-or-usage.js')) .toMatchInlineSnapshot(` Object { @@ -71,11 +71,16 @@ describe('TreemapData audit', () => { } `); + expect(JSON.stringify(treemapData.scripts).length).toMatchInlineSnapshot(`6621`); expect(treemapData.scripts).toMatchSnapshot(); }); - it('resources', () => { - const scriptsNode = (treemapData.resources[0].node.children || [])[0]; + it('has root node for network resources', () => { + expect(treemapData.resources).toHaveLength(1); + const networkRequestsRootNode = treemapData.resources[0]; + + const scriptsNode = (networkRequestsRootNode.node.children || [])[0]; + expect(scriptsNode.name).toBe('script'); expect(scriptsNode.children || []).toHaveLength(2); expect(treemapData.resources).toMatchInlineSnapshot(` @@ -225,7 +230,7 @@ describe('TreemapData audit', () => { expect(children[1].name).toBe('not/a.js'); }); - it('unusedBytes', () => { + it('nodes have unusedBytes data', () => { const sourcesData = { 'lib/folder/a.js': {resourceBytes: 100, unusedBytes: 50}, 'lib/folder/b.js': {resourceBytes: 101}, @@ -264,7 +269,7 @@ describe('TreemapData audit', () => { `); }); - it('duplicates', () => { + it('nodes have duplicates data', () => { const sourcesData = { 'lib/folder/a.js': {resourceBytes: 100, unusedBytes: 50}, 'lib/node_modules/dep/a.js': {resourceBytes: 101, duplicate: 'dep/a.js'}, From 5976c3bd98d207f29b87d0580e52e9cbdc544550 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 1 Oct 2020 14:34:18 -0500 Subject: [PATCH 17/25] match object --- .../test/audits/treemap-data-test.js | 330 +++++++++--------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index dc4156597a6b..1b66789c7480 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -120,29 +120,29 @@ describe('TreemapData audit', () => { 'b.js': {resourceBytes: 100, duplicate: 'blah'}, 'c.js': {resourceBytes: 100, unusedBytes: 50}, }); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "name": "a.js", - "resourceBytes": 100, - }, - Object { - "duplicate": "blah", - "name": "b.js", - "resourceBytes": 100, - }, - Object { - "name": "c.js", - "resourceBytes": 100, - "unusedBytes": 50, - }, - ], - "name": "", - "resourceBytes": 300, - "unusedBytes": 50, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'name': 'a.js', + 'resourceBytes': 100, + }, + { + 'duplicate': 'blah', + 'name': 'b.js', + 'resourceBytes': 100, + }, + { + 'name': 'c.js', + 'resourceBytes': 100, + 'unusedBytes': 50, + }, + ], + 'name': '', + 'resourceBytes': 300, + 'unusedBytes': 50, + } + ); }); it('creates directory node when multiple leaf nodes', () => { @@ -150,22 +150,22 @@ describe('TreemapData audit', () => { 'folder/a.js': {resourceBytes: 100}, 'folder/b.js': {resourceBytes: 100}, }); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "name": "a.js", - "resourceBytes": 100, - }, - Object { - "name": "b.js", - "resourceBytes": 100, - }, - ], - "name": "/folder", - "resourceBytes": 200, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'name': 'a.js', + 'resourceBytes': 100, + }, + { + 'name': 'b.js', + 'resourceBytes': 100, + }, + ], + 'name': '/folder', + 'resourceBytes': 200, + } + ); }); it('flattens directory node when single leaf nodes', () => { @@ -173,24 +173,24 @@ describe('TreemapData audit', () => { 'root/folder1/a.js': {resourceBytes: 100}, 'root/folder2/b.js': {resourceBytes: 100}, }); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "children": undefined, - "name": "folder1/a.js", - "resourceBytes": 100, - }, - Object { - "children": undefined, - "name": "folder2/b.js", - "resourceBytes": 100, - }, - ], - "name": "/root", - "resourceBytes": 200, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'children': undefined, + 'name': 'folder1/a.js', + 'resourceBytes': 100, + }, + { + 'children': undefined, + 'name': 'folder2/b.js', + 'resourceBytes': 100, + }, + ], + 'name': '/root', + 'resourceBytes': 200, + } + ); }); it('source root replaces matching prefixes', () => { @@ -199,27 +199,27 @@ describe('TreemapData audit', () => { 'not/some/prefix/a.js': {resourceBytes: 101, unusedBytes: 51}, }; const rootNode = TreemapData.prepareTreemapNodes('some/prefix', sourcesData); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "children": undefined, - "name": "/main.js", - "resourceBytes": 100, - "unusedBytes": 50, - }, - Object { - "children": undefined, - "name": "not/a.js", - "resourceBytes": 101, - "unusedBytes": 51, - }, - ], - "name": "some/prefix", - "resourceBytes": 201, - "unusedBytes": 101, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'children': undefined, + 'name': '/main.js', + 'resourceBytes': 100, + 'unusedBytes': 50, + }, + { + 'children': undefined, + 'name': 'not/a.js', + 'resourceBytes': 101, + 'unusedBytes': 51, + }, + ], + 'name': 'some/prefix', + 'resourceBytes': 201, + 'unusedBytes': 101, + } + ); expect(rootNode.name).toBe('some/prefix'); expect(rootNode.resourceBytes).toBe(201); @@ -237,36 +237,36 @@ describe('TreemapData audit', () => { 'lib/c.js': {resourceBytes: 100, unusedBytes: 25}, }; const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "children": Array [ - Object { - "name": "a.js", - "resourceBytes": 100, - "unusedBytes": 50, - }, - Object { - "name": "b.js", - "resourceBytes": 101, - }, - ], - "name": "folder", - "resourceBytes": 201, - "unusedBytes": 50, - }, - Object { - "name": "c.js", - "resourceBytes": 100, - "unusedBytes": 25, - }, - ], - "name": "/lib", - "resourceBytes": 301, - "unusedBytes": 75, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'children': [ + { + 'name': 'a.js', + 'resourceBytes': 100, + 'unusedBytes': 50, + }, + { + 'name': 'b.js', + 'resourceBytes': 101, + }, + ], + 'name': 'folder', + 'resourceBytes': 201, + 'unusedBytes': 50, + }, + { + 'name': 'c.js', + 'resourceBytes': 100, + 'unusedBytes': 25, + }, + ], + 'name': '/lib', + 'resourceBytes': 301, + 'unusedBytes': 75, + } + ); }); it('nodes have duplicates data', () => { @@ -278,63 +278,63 @@ describe('TreemapData audit', () => { 'node_modules/dep/b.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/b.js'}, }; const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); - expect(rootNode).toMatchInlineSnapshot(` - Object { - "children": Array [ - Object { - "children": Array [ - Object { - "children": undefined, - "name": "folder/a.js", - "resourceBytes": 100, - "unusedBytes": 50, - }, - Object { - "children": Array [ - Object { - "duplicate": "dep/a.js", - "name": "a.js", - "resourceBytes": 101, - }, - Object { - "duplicate": "dep/b.js", - "name": "b.js", - "resourceBytes": 101, - }, - ], - "name": "node_modules/dep", - "resourceBytes": 202, - }, - ], - "name": "lib", - "resourceBytes": 302, - "unusedBytes": 50, - }, - Object { - "children": Array [ - Object { - "duplicate": "dep/a.js", - "name": "a.js", - "resourceBytes": 100, - "unusedBytes": 25, - }, - Object { - "duplicate": "dep/b.js", - "name": "b.js", - "resourceBytes": 100, - "unusedBytes": 25, - }, - ], - "name": "node_modules/dep", - "resourceBytes": 200, - "unusedBytes": 50, - }, - ], - "name": "", - "resourceBytes": 502, - "unusedBytes": 100, - } - `); + expect(rootNode).toMatchObject( + { + 'children': [ + { + 'children': [ + { + 'children': undefined, + 'name': 'folder/a.js', + 'resourceBytes': 100, + 'unusedBytes': 50, + }, + { + 'children': [ + { + 'duplicate': 'dep/a.js', + 'name': 'a.js', + 'resourceBytes': 101, + }, + { + 'duplicate': 'dep/b.js', + 'name': 'b.js', + 'resourceBytes': 101, + }, + ], + 'name': 'node_modules/dep', + 'resourceBytes': 202, + }, + ], + 'name': 'lib', + 'resourceBytes': 302, + 'unusedBytes': 50, + }, + { + 'children': [ + { + 'duplicate': 'dep/a.js', + 'name': 'a.js', + 'resourceBytes': 100, + 'unusedBytes': 25, + }, + { + 'duplicate': 'dep/b.js', + 'name': 'b.js', + 'resourceBytes': 100, + 'unusedBytes': 25, + }, + ], + 'name': 'node_modules/dep', + 'resourceBytes': 200, + 'unusedBytes': 50, + }, + ], + 'name': '', + 'resourceBytes': 502, + 'unusedBytes': 100, + } + ); }); }); }); From 27137af03f5c26d9cab8f90b085589f332f1c69f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 1 Oct 2020 14:37:38 -0500 Subject: [PATCH 18/25] root node container --- lighthouse-core/audits/treemap-data.js | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 066eaf6bb67a..86a93a07d190 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -18,13 +18,13 @@ const NetworkRecords = require('../computed/network-records.js'); const ResourceSummary = require('../computed/resource-summary.js'); /** - * A collection of root nodes, grouped by type. - * @typedef {Record} TreemapData + * A collection of root node containers, grouped by type. + * @typedef {Record} TreemapData */ /** * Ex: https://gist.github.com/connorjclark/0ef1099ae994c075e36d65fecb4d26a7 - * @typedef RootNode + * @typedef RootNodeContainer * @property {string} name Arbitrary name identifier. Usually a script url. * @property {Node} node */ @@ -78,7 +78,7 @@ class TreemapDataAudit extends Audit { }; } - const rootNode = newNode(sourceRoot); + const topNode = newNode(sourceRoot); /** * Given a slash-delimited path, traverse the Node structure and increment @@ -89,11 +89,11 @@ class TreemapDataAudit extends Audit { * @param {SourceData} data */ function addAllNodesInSourcePath(source, data) { - let node = rootNode; + let node = topNode; - // Apply the data to the rootNode. - rootNode.resourceBytes += data.resourceBytes; - if (data.unusedBytes) rootNode.unusedBytes = (rootNode.unusedBytes || 0) + data.unusedBytes; + // Apply the data to the topNode. + topNode.resourceBytes += data.resourceBytes; + if (data.unusedBytes) topNode.unusedBytes = (topNode.unusedBytes || 0) + data.unusedBytes; // Strip off the shared root. const sourcePathSegments = source.replace(sourceRoot, '').split(/\/+/); @@ -112,8 +112,8 @@ class TreemapDataAudit extends Audit { node.resourceBytes += data.resourceBytes; if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; - // Leaf node might have duplication data. - if (data.duplicate !== undefined && isLastSegment) { + // Only leaf nodes might have duplication data. + if (isLastSegment && data.duplicate !== undefined) { node.duplicate = data.duplicate; } }); @@ -141,25 +141,25 @@ class TreemapDataAudit extends Audit { } } } - collapseAll(rootNode); + collapseAll(topNode); // TODO(cjamcl): Should this structure be flattened for space savings? // Like DOM Snapshot. // Less JSON (no super nested children, and no repeated property names). - return rootNode; + return topNode; } /** - * Returns a root node where the first level of nodes are script URLs. + * Returns a root node container where the first level of nodes are script URLs. * If a script has a source map, that node will be set by prepareTreemapNodes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context - * @return {Promise} + * @return {Promise} */ static async makeJavaScriptRootNodes(artifacts, context) { - /** @type {RootNode[]} */ - const rootNodes = []; + /** @type {RootNodeContainer[]} */ + const rootNodeContainers = []; let inlineScriptLength = 0; for (const scriptElement of artifacts.ScriptElements) { @@ -171,7 +171,7 @@ class TreemapDataAudit extends Audit { } if (inlineScriptLength) { const name = artifacts.URL.finalUrl; - rootNodes.push({ + rootNodeContainers.push({ name, node: { name, @@ -190,7 +190,7 @@ class TreemapDataAudit extends Audit { const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src); const scriptCoverages = artifacts.JsUsage[scriptElement.src]; if (!bundle || !scriptCoverages) { - rootNodes.push({ + rootNodeContainers.push({ name, node: { name, @@ -234,13 +234,13 @@ class TreemapDataAudit extends Audit { }; } - rootNodes.push({ + rootNodeContainers.push({ name, node, }); } - return rootNodes; + return rootNodeContainers; } /** @@ -249,7 +249,7 @@ class TreemapDataAudit extends Audit { * contained is the resourceBytes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context - * @return {Promise} + * @return {Promise} */ static async makeResourceSummaryRootNode(artifacts, context) { const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; From 14469104e7e27c04d0d457025ebbec6a0d3cdbc1 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 1 Oct 2020 15:34:16 -0500 Subject: [PATCH 19/25] rm resource summary --- lighthouse-core/audits/treemap-data.js | 64 ++----------------- .../__snapshots__/treemap-data-test.js.snap | 2 +- .../test/audits/treemap-data-test.js | 49 ++------------ 3 files changed, 9 insertions(+), 106 deletions(-) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/treemap-data.js index 86a93a07d190..c48853c4a657 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/treemap-data.js @@ -14,12 +14,9 @@ const Audit = require('./audit.js'); const JsBundles = require('../computed/js-bundles.js'); const UnusedJavaScriptSummary = require('../computed/unused-javascript-summary.js'); const ModuleDuplication = require('../computed/module-duplication.js'); -const NetworkRecords = require('../computed/network-records.js'); -const ResourceSummary = require('../computed/resource-summary.js'); /** - * A collection of root node containers, grouped by type. - * @typedef {Record} TreemapData + * @typedef {RootNodeContainer[]} TreemapData */ /** @@ -151,13 +148,13 @@ class TreemapDataAudit extends Audit { } /** - * Returns a root node container where the first level of nodes are script URLs. + * Returns root node containers where the first level of nodes are script URLs. * If a script has a source map, that node will be set by prepareTreemapNodes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context * @return {Promise} */ - static async makeJavaScriptRootNodes(artifacts, context) { + static async makeRootNodes(artifacts, context) { /** @type {RootNodeContainer[]} */ const rootNodeContainers = []; @@ -243,56 +240,6 @@ class TreemapDataAudit extends Audit { return rootNodeContainers; } - /** - * Returns root node where the first level is network resource type, - * followed by the leaf nodes which are are all network requests. Data - * contained is the resourceBytes. - * @param {LH.Artifacts} artifacts - * @param {LH.Audit.Context} context - * @return {Promise} - */ - static async makeResourceSummaryRootNode(artifacts, context) { - const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; - const networkRecords = await NetworkRecords.request(devtoolsLog, context); - - const totalCount = networkRecords.length; - let totalSize = 0; - - /** @type {Node[]} */ - const resourceNodes = []; - for (const networkRecord of networkRecords) { - const resourceType = ResourceSummary.determineResourceType(networkRecord); - - let resourceNode = resourceNodes.find(child => child.name === resourceType); - if (!resourceNode) { - resourceNode = { - name: resourceType, - resourceBytes: 0, - children: [], - }; - resourceNodes.push(resourceNode); - } - - totalSize += networkRecord.resourceSize; - resourceNode.resourceBytes += networkRecord.resourceSize; - - resourceNode.children = resourceNode.children || []; - resourceNode.children.push({ - name: networkRecord.url, - resourceBytes: networkRecord.resourceSize, - }); - } - - return { - name: 'Resource Summary', - node: { - name: `${totalCount} requests`, - resourceBytes: totalSize, - children: resourceNodes, - }, - }; - } - /** * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context @@ -300,10 +247,7 @@ class TreemapDataAudit extends Audit { */ static async audit(artifacts, context) { /** @type {TreemapData} */ - const treemapData = { - scripts: await TreemapDataAudit.makeJavaScriptRootNodes(artifacts, context), - resources: [await TreemapDataAudit.makeResourceSummaryRootNode(artifacts, context)], - }; + const treemapData = await TreemapDataAudit.makeRootNodes(artifacts, context); // TODO: when out of experimental should make a new detail type. /** @type {LH.Audit.Details.DebugData} */ diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap index a53b4eac23cd..d56e44c7f30b 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreemapData audit squoosh fixture has root nodes for scripts 3`] = ` +exports[`TreemapData audit squoosh fixture has root nodes 3`] = ` Array [ Object { "name": "https://squoosh.app/main-app.js", diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/treemap-data-test.js index 1b66789c7480..385df51c71c4 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/treemap-data-test.js @@ -55,12 +55,8 @@ describe('TreemapData audit', () => { treemapData = results.details.treemapData; }); - it('has multiple root node types', () => { - expect(Object.keys(treemapData)).toEqual(['scripts', 'resources']); - }); - - it('has root nodes for scripts', () => { - expect(treemapData.scripts.find(s => s.name === 'https://sqoosh.app/no-map-or-usage.js')) + it('has root nodes', () => { + expect(treemapData.find(s => s.name === 'https://sqoosh.app/no-map-or-usage.js')) .toMatchInlineSnapshot(` Object { "name": "https://sqoosh.app/no-map-or-usage.js", @@ -71,45 +67,8 @@ describe('TreemapData audit', () => { } `); - expect(JSON.stringify(treemapData.scripts).length).toMatchInlineSnapshot(`6621`); - expect(treemapData.scripts).toMatchSnapshot(); - }); - - it('has root node for network resources', () => { - expect(treemapData.resources).toHaveLength(1); - const networkRequestsRootNode = treemapData.resources[0]; - - const scriptsNode = (networkRequestsRootNode.node.children || [])[0]; - expect(scriptsNode.name).toBe('script'); - expect(scriptsNode.children || []).toHaveLength(2); - - expect(treemapData.resources).toMatchInlineSnapshot(` - Array [ - Object { - "name": "Resource Summary", - "node": Object { - "children": Array [ - Object { - "children": Array [ - Object { - "name": "https://squoosh.app/main-app.js", - "resourceBytes": 83748, - }, - Object { - "name": "https://sqoosh.app/no-map-or-usage.js", - "resourceBytes": 5, - }, - ], - "name": "script", - "resourceBytes": 83753, - }, - ], - "name": "2 requests", - "resourceBytes": 83753, - }, - }, - ] - `); + expect(JSON.stringify(treemapData).length).toMatchInlineSnapshot(`6621`); + expect(treemapData).toMatchSnapshot(); }); }); From 97e548f1dedee0267be2740257ad49fb48db99e4 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 2 Oct 2020 18:40:17 -0500 Subject: [PATCH 20/25] rename --- ...treemap-data.js => script-treemap-data.js} | 10 +++---- lighthouse-core/config/experimental-config.js | 2 +- ...ta-test.js => script-treemap-data-test.js} | 26 +++++++++---------- tsconfig.json | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) rename lighthouse-core/audits/{treemap-data.js => script-treemap-data.js} (97%) rename lighthouse-core/test/audits/{treemap-data-test.js => script-treemap-data-test.js} (91%) diff --git a/lighthouse-core/audits/treemap-data.js b/lighthouse-core/audits/script-treemap-data.js similarity index 97% rename from lighthouse-core/audits/treemap-data.js rename to lighthouse-core/audits/script-treemap-data.js index c48853c4a657..b6091daa6324 100644 --- a/lighthouse-core/audits/treemap-data.js +++ b/lighthouse-core/audits/script-treemap-data.js @@ -39,15 +39,15 @@ const ModuleDuplication = require('../computed/module-duplication.js'); * @typedef {Omit} SourceData */ -class TreemapDataAudit extends Audit { +class ScriptTreemapDataAudit extends Audit { /** * @return {LH.Audit.Meta} */ static get meta() { return { - id: 'treemap-data', + id: 'script-treemap-data', scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, - title: 'Treemap Data', + title: 'Script Treemap Data', description: 'Used for treemap app', requiredArtifacts: ['traces', 'devtoolsLogs', 'SourceMaps', 'ScriptElements', 'JsUsage', 'URL'], @@ -247,7 +247,7 @@ class TreemapDataAudit extends Audit { */ static async audit(artifacts, context) { /** @type {TreemapData} */ - const treemapData = await TreemapDataAudit.makeRootNodes(artifacts, context); + const treemapData = await ScriptTreemapDataAudit.makeRootNodes(artifacts, context); // TODO: when out of experimental should make a new detail type. /** @type {LH.Audit.Details.DebugData} */ @@ -263,4 +263,4 @@ class TreemapDataAudit extends Audit { } } -module.exports = TreemapDataAudit; +module.exports = ScriptTreemapDataAudit; diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index 2eb4e686a8ad..2561a64f37f0 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -17,7 +17,7 @@ const config = { 'autocomplete', 'full-page-screenshot', 'large-javascript-libraries', - 'treemap-data', + 'script-treemap-data', ], passes: [{ passName: 'defaultPass', diff --git a/lighthouse-core/test/audits/treemap-data-test.js b/lighthouse-core/test/audits/script-treemap-data-test.js similarity index 91% rename from lighthouse-core/test/audits/treemap-data-test.js rename to lighthouse-core/test/audits/script-treemap-data-test.js index 385df51c71c4..18b37c45a32b 100644 --- a/lighthouse-core/test/audits/treemap-data-test.js +++ b/lighthouse-core/test/audits/script-treemap-data-test.js @@ -5,15 +5,15 @@ */ 'use strict'; -const TreemapData_ = require('../../audits/treemap-data.js'); +const ScriptTreemapData_ = require('../../audits/script-treemap-data.js'); const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js'); const {loadSourceMapAndUsageFixture, makeParamsOptional} = require('../test-utils.js'); /* eslint-env jest */ -const TreemapData = { - audit: makeParamsOptional(TreemapData_.audit), - prepareTreemapNodes: makeParamsOptional(TreemapData_.prepareTreemapNodes), +const ScriptTreemapData = { + audit: makeParamsOptional(ScriptTreemapData_.audit), + prepareTreemapNodes: makeParamsOptional(ScriptTreemapData_.prepareTreemapNodes), }; /** @@ -25,9 +25,9 @@ function generateRecord(url, resourceSize, resourceType) { return {url, resourceSize, resourceType}; } -describe('TreemapData audit', () => { +describe('ScriptTreemapData audit', () => { describe('squoosh fixture', () => { - /** @type {import('../../audits/treemap-data.js').TreemapData} */ + /** @type {import('../../audits/script-treemap-data.js').TreemapData} */ let treemapData; beforeAll(async () => { const context = {computedCache: new Map()}; @@ -49,7 +49,7 @@ describe('TreemapData audit', () => { SourceMaps: [{scriptUrl: scriptUrl, map}], ScriptElements: [{src: scriptUrl, content}, noSourceMapScript], }; - const results = await TreemapData.audit(artifacts, context); + const results = await ScriptTreemapData.audit(artifacts, context); // @ts-expect-error: Debug data. treemapData = results.details.treemapData; @@ -74,7 +74,7 @@ describe('TreemapData audit', () => { describe('.prepareTreemapNodes', () => { it('uses node data when available', () => { - const rootNode = TreemapData.prepareTreemapNodes('', { + const rootNode = ScriptTreemapData.prepareTreemapNodes('', { 'a.js': {resourceBytes: 100}, 'b.js': {resourceBytes: 100, duplicate: 'blah'}, 'c.js': {resourceBytes: 100, unusedBytes: 50}, @@ -105,7 +105,7 @@ describe('TreemapData audit', () => { }); it('creates directory node when multiple leaf nodes', () => { - const rootNode = TreemapData.prepareTreemapNodes('', { + const rootNode = ScriptTreemapData.prepareTreemapNodes('', { 'folder/a.js': {resourceBytes: 100}, 'folder/b.js': {resourceBytes: 100}, }); @@ -128,7 +128,7 @@ describe('TreemapData audit', () => { }); it('flattens directory node when single leaf nodes', () => { - const rootNode = TreemapData.prepareTreemapNodes('', { + const rootNode = ScriptTreemapData.prepareTreemapNodes('', { 'root/folder1/a.js': {resourceBytes: 100}, 'root/folder2/b.js': {resourceBytes: 100}, }); @@ -157,7 +157,7 @@ describe('TreemapData audit', () => { 'some/prefix/main.js': {resourceBytes: 100, unusedBytes: 50}, 'not/some/prefix/a.js': {resourceBytes: 101, unusedBytes: 51}, }; - const rootNode = TreemapData.prepareTreemapNodes('some/prefix', sourcesData); + const rootNode = ScriptTreemapData.prepareTreemapNodes('some/prefix', sourcesData); expect(rootNode).toMatchObject( { 'children': [ @@ -195,7 +195,7 @@ describe('TreemapData audit', () => { 'lib/folder/b.js': {resourceBytes: 101}, 'lib/c.js': {resourceBytes: 100, unusedBytes: 25}, }; - const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + const rootNode = ScriptTreemapData.prepareTreemapNodes('', sourcesData); expect(rootNode).toMatchObject( { 'children': [ @@ -236,7 +236,7 @@ describe('TreemapData audit', () => { 'lib/node_modules/dep/b.js': {resourceBytes: 101, duplicate: 'dep/b.js'}, 'node_modules/dep/b.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/b.js'}, }; - const rootNode = TreemapData.prepareTreemapNodes('', sourcesData); + const rootNode = ScriptTreemapData.prepareTreemapNodes('', sourcesData); expect(rootNode).toMatchObject( { 'children': [ diff --git a/tsconfig.json b/tsconfig.json index 088bd230f549..7081582269c5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,6 @@ "lighthouse-core/test/scripts/lantern/constants-test.js", "lighthouse-core/test/gather/driver-test.js", "lighthouse-core/test/gather/gather-runner-test.js", - "lighthouse-core/test/audits/treemap-data-test.js" + "lighthouse-core/test/audits/script-treemap-data-test.js" ], } From 556aa87818a0081c133d4b688dc6b4adc750f1d3 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Oct 2020 13:57:21 -0500 Subject: [PATCH 21/25] update --- lighthouse-core/audits/script-treemap-data.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lighthouse-core/audits/script-treemap-data.js b/lighthouse-core/audits/script-treemap-data.js index b6091daa6324..cd1248fbc4ea 100644 --- a/lighthouse-core/audits/script-treemap-data.js +++ b/lighthouse-core/audits/script-treemap-data.js @@ -31,7 +31,7 @@ const ModuleDuplication = require('../computed/module-duplication.js'); * @property {string} name Arbitrary name identifier. Usually a path component from a source map. * @property {number} resourceBytes * @property {number=} unusedBytes - * @property {string=} duplicate If present, this module is a duplicate. String is normalized source path. See ModuleDuplication.normalizeSource + * @property {string=} duplicatedNormalizedModuleName If present, this module is a duplicate. String is normalized source path. See ModuleDuplication.normalizeSource * @property {Node[]=} children */ @@ -110,8 +110,8 @@ class ScriptTreemapDataAudit extends Audit { if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; // Only leaf nodes might have duplication data. - if (isLastSegment && data.duplicate !== undefined) { - node.duplicate = data.duplicate; + if (isLastSegment && data.duplicatedNormalizedModuleName !== undefined) { + node.duplicatedNormalizedModuleName = data.duplicatedNormalizedModuleName; } }); } @@ -140,10 +140,6 @@ class ScriptTreemapDataAudit extends Audit { } collapseAll(topNode); - // TODO(cjamcl): Should this structure be flattened for space savings? - // Like DOM Snapshot. - // Less JSON (no super nested children, and no repeated property names). - return topNode; } @@ -215,7 +211,7 @@ class ScriptTreemapDataAudit extends Audit { }; const key = ModuleDuplication.normalizeSource(source); - if (duplicationByPath.has(key)) sourceData.duplicate = key; + if (duplicationByPath.has(key)) sourceData.duplicatedNormalizedModuleName = key; sourcesData[source] = sourceData; } From 754ce53d10c11b72db5f0aefd543c6e9976ef23e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Oct 2020 14:11:31 -0500 Subject: [PATCH 22/25] update tests --- ....snap => script-treemap-data-test.js.snap} | 2 +- .../test/audits/script-treemap-data-test.js | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) rename lighthouse-core/test/audits/__snapshots__/{treemap-data-test.js.snap => script-treemap-data-test.js.snap} (99%) diff --git a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap b/lighthouse-core/test/audits/__snapshots__/script-treemap-data-test.js.snap similarity index 99% rename from lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap rename to lighthouse-core/test/audits/__snapshots__/script-treemap-data-test.js.snap index d56e44c7f30b..1897a538b0e3 100644 --- a/lighthouse-core/test/audits/__snapshots__/treemap-data-test.js.snap +++ b/lighthouse-core/test/audits/__snapshots__/script-treemap-data-test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreemapData audit squoosh fixture has root nodes 3`] = ` +exports[`ScriptTreemapData audit squoosh fixture has root nodes 3`] = ` Array [ Object { "name": "https://squoosh.app/main-app.js", diff --git a/lighthouse-core/test/audits/script-treemap-data-test.js b/lighthouse-core/test/audits/script-treemap-data-test.js index 18b37c45a32b..de0e4e073fe1 100644 --- a/lighthouse-core/test/audits/script-treemap-data-test.js +++ b/lighthouse-core/test/audits/script-treemap-data-test.js @@ -76,7 +76,7 @@ describe('ScriptTreemapData audit', () => { it('uses node data when available', () => { const rootNode = ScriptTreemapData.prepareTreemapNodes('', { 'a.js': {resourceBytes: 100}, - 'b.js': {resourceBytes: 100, duplicate: 'blah'}, + 'b.js': {resourceBytes: 100, duplicatedNormalizedModuleName: 'blah'}, 'c.js': {resourceBytes: 100, unusedBytes: 50}, }); expect(rootNode).toMatchObject( @@ -87,7 +87,7 @@ describe('ScriptTreemapData audit', () => { 'resourceBytes': 100, }, { - 'duplicate': 'blah', + 'duplicatedNormalizedModuleName': 'blah', 'name': 'b.js', 'resourceBytes': 100, }, @@ -230,11 +230,13 @@ describe('ScriptTreemapData audit', () => { it('nodes have duplicates data', () => { const sourcesData = { + /* eslint-disable max-len */ 'lib/folder/a.js': {resourceBytes: 100, unusedBytes: 50}, - 'lib/node_modules/dep/a.js': {resourceBytes: 101, duplicate: 'dep/a.js'}, - 'node_modules/dep/a.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/a.js'}, - 'lib/node_modules/dep/b.js': {resourceBytes: 101, duplicate: 'dep/b.js'}, - 'node_modules/dep/b.js': {resourceBytes: 100, unusedBytes: 25, duplicate: 'dep/b.js'}, + 'lib/node_modules/dep/a.js': {resourceBytes: 101, duplicatedNormalizedModuleName: 'dep/a.js'}, + 'node_modules/dep/a.js': {resourceBytes: 100, unusedBytes: 25, duplicatedNormalizedModuleName: 'dep/a.js'}, + 'lib/node_modules/dep/b.js': {resourceBytes: 101, duplicatedNormalizedModuleName: 'dep/b.js'}, + 'node_modules/dep/b.js': {resourceBytes: 100, unusedBytes: 25, duplicatedNormalizedModuleName: 'dep/b.js'}, + /* eslint-enable max-len */ }; const rootNode = ScriptTreemapData.prepareTreemapNodes('', sourcesData); expect(rootNode).toMatchObject( @@ -251,12 +253,12 @@ describe('ScriptTreemapData audit', () => { { 'children': [ { - 'duplicate': 'dep/a.js', + 'duplicatedNormalizedModuleName': 'dep/a.js', 'name': 'a.js', 'resourceBytes': 101, }, { - 'duplicate': 'dep/b.js', + 'duplicatedNormalizedModuleName': 'dep/b.js', 'name': 'b.js', 'resourceBytes': 101, }, @@ -272,13 +274,13 @@ describe('ScriptTreemapData audit', () => { { 'children': [ { - 'duplicate': 'dep/a.js', + 'duplicatedNormalizedModuleName': 'dep/a.js', 'name': 'a.js', 'resourceBytes': 100, 'unusedBytes': 25, }, { - 'duplicate': 'dep/b.js', + 'duplicatedNormalizedModuleName': 'dep/b.js', 'name': 'b.js', 'resourceBytes': 100, 'unusedBytes': 25, From 5e971613c617dc664a04ce50fc9d92db45d4886b Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Oct 2020 14:21:36 -0500 Subject: [PATCH 23/25] deps: add @types/yargs-parser --- package.json | 1 + types/yargs-parser/index.d.ts | 22 ---------------------- yarn.lock | 5 +++++ 3 files changed, 6 insertions(+), 22 deletions(-) delete mode 100644 types/yargs-parser/index.d.ts diff --git a/package.json b/package.json index 8757430d2f79..8631163d51db 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "@types/update-notifier": "^4.1.0", "@types/ws": "^4.0.1", "@types/yargs": "^8.0.2", + "@types/yargs-parser": "^15.0.0", "@wardpeet/brfs": "2.1.0-0", "angular": "^1.7.4", "archiver": "^3.0.0", diff --git a/types/yargs-parser/index.d.ts b/types/yargs-parser/index.d.ts deleted file mode 100644 index f77e2ae57bf3..000000000000 --- a/types/yargs-parser/index.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ - -declare module 'yargs-parser' { - interface Options { - configuration: Configuration; - } - interface Configuration { - 'camel-case-expansion': boolean; - 'boolean-negation': boolean; - } - // from @types/yargs - interface Arguments { - [ argName: string ]: any; - } - function yargsParser(args: string, opts: Options): Arguments; - - export = yargsParser; -} diff --git a/yarn.lock b/yarn.lock index f46c74e964eb..1d59e49f79a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -697,6 +697,11 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228" integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg== +"@types/yargs-parser@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + "@types/yargs@^13.0.0": version "13.0.3" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.3.tgz#76482af3981d4412d65371a318f992d33464a380" From c2d78f13d6cb2bf5a186356bae023ad9e931300b Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 6 Oct 2020 15:56:40 -0500 Subject: [PATCH 24/25] pr --- lighthouse-core/audits/script-treemap-data.js | 12 +- .../test/audits/script-treemap-data-test.js | 182 +++++++++--------- 2 files changed, 98 insertions(+), 96 deletions(-) diff --git a/lighthouse-core/audits/script-treemap-data.js b/lighthouse-core/audits/script-treemap-data.js index cd1248fbc4ea..ac4d92f1a69d 100644 --- a/lighthouse-core/audits/script-treemap-data.js +++ b/lighthouse-core/audits/script-treemap-data.js @@ -95,7 +95,7 @@ class ScriptTreemapDataAudit extends Audit { // Strip off the shared root. const sourcePathSegments = source.replace(sourceRoot, '').split(/\/+/); sourcePathSegments.forEach((sourcePathSegment, i) => { - const isLastSegment = i === sourcePathSegments.length - 1; + const isLeaf = i === sourcePathSegments.length - 1; let child = node.children && node.children.find(child => child.name === sourcePathSegment); if (!child) { @@ -110,7 +110,7 @@ class ScriptTreemapDataAudit extends Audit { if (data.unusedBytes) node.unusedBytes = (node.unusedBytes || 0) + data.unusedBytes; // Only leaf nodes might have duplication data. - if (isLastSegment && data.duplicatedNormalizedModuleName !== undefined) { + if (isLeaf && data.duplicatedNormalizedModuleName !== undefined) { node.duplicatedNormalizedModuleName = data.duplicatedNormalizedModuleName; } }); @@ -119,7 +119,7 @@ class ScriptTreemapDataAudit extends Audit { // For every source file, apply the data to all components // of the source path, creating nodes as necessary. for (const [source, data] of Object.entries(sourcesData)) { - addAllNodesInSourcePath(source || ``, data); + addAllNodesInSourcePath(source, data); } /** @@ -148,7 +148,7 @@ class ScriptTreemapDataAudit extends Audit { * If a script has a source map, that node will be set by prepareTreemapNodes. * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context - * @return {Promise} + * @return {Promise} */ static async makeRootNodes(artifacts, context) { /** @type {RootNodeContainer[]} */ @@ -183,6 +183,9 @@ class ScriptTreemapDataAudit extends Audit { const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src); const scriptCoverages = artifacts.JsUsage[scriptElement.src]; if (!bundle || !scriptCoverages) { + // No bundle or coverage information, so simply make a single node + // detailing how big the script is. + rootNodeContainers.push({ name, node: { @@ -242,7 +245,6 @@ class ScriptTreemapDataAudit extends Audit { * @return {Promise} */ static async audit(artifacts, context) { - /** @type {TreemapData} */ const treemapData = await ScriptTreemapDataAudit.makeRootNodes(artifacts, context); // TODO: when out of experimental should make a new detail type. diff --git a/lighthouse-core/test/audits/script-treemap-data-test.js b/lighthouse-core/test/audits/script-treemap-data-test.js index de0e4e073fe1..5a7474d5b094 100644 --- a/lighthouse-core/test/audits/script-treemap-data-test.js +++ b/lighthouse-core/test/audits/script-treemap-data-test.js @@ -5,12 +5,12 @@ */ 'use strict'; +/* eslint-env jest */ + const ScriptTreemapData_ = require('../../audits/script-treemap-data.js'); const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js'); const {loadSourceMapAndUsageFixture, makeParamsOptional} = require('../test-utils.js'); -/* eslint-env jest */ - const ScriptTreemapData = { audit: makeParamsOptional(ScriptTreemapData_.audit), prepareTreemapNodes: makeParamsOptional(ScriptTreemapData_.prepareTreemapNodes), @@ -81,25 +81,25 @@ describe('ScriptTreemapData audit', () => { }); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'name': 'a.js', - 'resourceBytes': 100, + name: 'a.js', + resourceBytes: 100, }, { - 'duplicatedNormalizedModuleName': 'blah', - 'name': 'b.js', - 'resourceBytes': 100, + duplicatedNormalizedModuleName: 'blah', + name: 'b.js', + resourceBytes: 100, }, { - 'name': 'c.js', - 'resourceBytes': 100, - 'unusedBytes': 50, + name: 'c.js', + resourceBytes: 100, + unusedBytes: 50, }, ], - 'name': '', - 'resourceBytes': 300, - 'unusedBytes': 50, + name: '', + resourceBytes: 300, + unusedBytes: 50, } ); }); @@ -111,18 +111,18 @@ describe('ScriptTreemapData audit', () => { }); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'name': 'a.js', - 'resourceBytes': 100, + name: 'a.js', + resourceBytes: 100, }, { - 'name': 'b.js', - 'resourceBytes': 100, + name: 'b.js', + resourceBytes: 100, }, ], - 'name': '/folder', - 'resourceBytes': 200, + name: '/folder', + resourceBytes: 200, } ); }); @@ -134,20 +134,20 @@ describe('ScriptTreemapData audit', () => { }); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'children': undefined, - 'name': 'folder1/a.js', - 'resourceBytes': 100, + children: undefined, + name: 'folder1/a.js', + resourceBytes: 100, }, { - 'children': undefined, - 'name': 'folder2/b.js', - 'resourceBytes': 100, + children: undefined, + name: 'folder2/b.js', + resourceBytes: 100, }, ], - 'name': '/root', - 'resourceBytes': 200, + name: '/root', + resourceBytes: 200, } ); }); @@ -160,23 +160,23 @@ describe('ScriptTreemapData audit', () => { const rootNode = ScriptTreemapData.prepareTreemapNodes('some/prefix', sourcesData); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'children': undefined, - 'name': '/main.js', - 'resourceBytes': 100, - 'unusedBytes': 50, + children: undefined, + name: '/main.js', + resourceBytes: 100, + unusedBytes: 50, }, { - 'children': undefined, - 'name': 'not/a.js', - 'resourceBytes': 101, - 'unusedBytes': 51, + children: undefined, + name: 'not/a.js', + resourceBytes: 101, + unusedBytes: 51, }, ], - 'name': 'some/prefix', - 'resourceBytes': 201, - 'unusedBytes': 101, + name: 'some/prefix', + resourceBytes: 201, + unusedBytes: 101, } ); @@ -198,32 +198,32 @@ describe('ScriptTreemapData audit', () => { const rootNode = ScriptTreemapData.prepareTreemapNodes('', sourcesData); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'children': [ + children: [ { - 'name': 'a.js', - 'resourceBytes': 100, - 'unusedBytes': 50, + name: 'a.js', + resourceBytes: 100, + unusedBytes: 50, }, { - 'name': 'b.js', - 'resourceBytes': 101, + name: 'b.js', + resourceBytes: 101, }, ], - 'name': 'folder', - 'resourceBytes': 201, - 'unusedBytes': 50, + name: 'folder', + resourceBytes: 201, + unusedBytes: 50, }, { - 'name': 'c.js', - 'resourceBytes': 100, - 'unusedBytes': 25, + name: 'c.js', + resourceBytes: 100, + unusedBytes: 25, }, ], - 'name': '/lib', - 'resourceBytes': 301, - 'unusedBytes': 75, + name: '/lib', + resourceBytes: 301, + unusedBytes: 75, } ); }); @@ -241,59 +241,59 @@ describe('ScriptTreemapData audit', () => { const rootNode = ScriptTreemapData.prepareTreemapNodes('', sourcesData); expect(rootNode).toMatchObject( { - 'children': [ + children: [ { - 'children': [ + children: [ { - 'children': undefined, - 'name': 'folder/a.js', - 'resourceBytes': 100, - 'unusedBytes': 50, + children: undefined, + name: 'folder/a.js', + resourceBytes: 100, + unusedBytes: 50, }, { - 'children': [ + children: [ { - 'duplicatedNormalizedModuleName': 'dep/a.js', - 'name': 'a.js', - 'resourceBytes': 101, + duplicatedNormalizedModuleName: 'dep/a.js', + name: 'a.js', + resourceBytes: 101, }, { - 'duplicatedNormalizedModuleName': 'dep/b.js', - 'name': 'b.js', - 'resourceBytes': 101, + duplicatedNormalizedModuleName: 'dep/b.js', + name: 'b.js', + resourceBytes: 101, }, ], - 'name': 'node_modules/dep', - 'resourceBytes': 202, + name: 'node_modules/dep', + resourceBytes: 202, }, ], - 'name': 'lib', - 'resourceBytes': 302, - 'unusedBytes': 50, + name: 'lib', + resourceBytes: 302, + unusedBytes: 50, }, { - 'children': [ + children: [ { - 'duplicatedNormalizedModuleName': 'dep/a.js', - 'name': 'a.js', - 'resourceBytes': 100, - 'unusedBytes': 25, + duplicatedNormalizedModuleName: 'dep/a.js', + name: 'a.js', + resourceBytes: 100, + unusedBytes: 25, }, { - 'duplicatedNormalizedModuleName': 'dep/b.js', - 'name': 'b.js', - 'resourceBytes': 100, - 'unusedBytes': 25, + duplicatedNormalizedModuleName: 'dep/b.js', + name: 'b.js', + resourceBytes: 100, + unusedBytes: 25, }, ], - 'name': 'node_modules/dep', - 'resourceBytes': 200, - 'unusedBytes': 50, + name: 'node_modules/dep', + resourceBytes: 200, + unusedBytes: 50, }, ], - 'name': '', - 'resourceBytes': 502, - 'unusedBytes': 100, + name: '', + resourceBytes: 502, + unusedBytes: 100, } ); }); From 0eac4f6e13fdb7925b6a75d58c49c87060af2f59 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 6 Oct 2020 16:05:40 -0500 Subject: [PATCH 25/25] fixtest --- lighthouse-core/audits/script-treemap-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/audits/script-treemap-data.js b/lighthouse-core/audits/script-treemap-data.js index ac4d92f1a69d..24f42e54b3f5 100644 --- a/lighthouse-core/audits/script-treemap-data.js +++ b/lighthouse-core/audits/script-treemap-data.js @@ -201,7 +201,7 @@ class ScriptTreemapDataAudit extends Audit { /** @type {Node} */ let node; - if (unusedJavascriptSummary.sourcesWastedBytes) { + if (unusedJavascriptSummary.sourcesWastedBytes && !('errorMessage' in bundle.sizes)) { // Create nodes for each module in a bundle. /** @type {Record} */