Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

treemap: first pass on Lighthouse Treemap #11545

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions build/build-treemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @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 GhPagesApp = require('./gh-pages-app.js');

/**
* Build treemap app, optionally deploying to gh-pages if `--deploy` flag was set.
*/
async function run() {
const app = new GhPagesApp({
name: 'treemap',
appDir: `${__dirname}/../lighthouse-treemap/app`,
html: {path: 'index.html'},
stylesheets: [
{path: 'styles/*'},
],
javascripts: [
{path: 'src/*'},
],
assets: [
{path: 'images/**/*'},
{path: 'debug.json'},
],
});

await app.build();

const argv = process.argv.slice(2);
if (argv.includes('--deploy')) {
await app.deploy();
}
}

run();
3 changes: 2 additions & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ git push --follow-tags
# Publish to npm.
npm publish

# Publish viewer.
# Publish viewer and treemap.
yarn deploy-viewer
yarn deploy-treemap
```

### Extensions
Expand Down
10 changes: 9 additions & 1 deletion lighthouse-core/audits/script-treemap-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,15 @@ class ScriptTreemapDataAudit extends Audit {
unusedBytes: unusedJavascriptSummary.sourcesWastedBytes[source],
};

const key = ModuleDuplication.normalizeSource(source);
// ModuleDuplication uses keys without the source root prepended, but
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed an issue with the duplication stuff. Need to make a separate PR for this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean split this fix out into a separate PR with tests or there's another fix not shown here that needs to happen in a different PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First one. This is the fix.

// bundle.sizes uses keys with it prepended, so we remove the source root before
// using it with duplicationByPath.
let sourceWithoutSourceRoot = source;
if (bundle.rawMap.sourceRoot && source.startsWith(bundle.rawMap.sourceRoot)) {
sourceWithoutSourceRoot = source.replace(bundle.rawMap.sourceRoot, '');
}

const key = ModuleDuplication.normalizeSource(sourceWithoutSourceRoot);
if (duplicationByPath.has(key)) sourceData.duplicatedNormalizedModuleName = key;

sourcesData[source] = sourceData;
Expand Down
54 changes: 40 additions & 14 deletions lighthouse-core/report/html/renderer/report-ui-features.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

/** @typedef {import('./dom')} DOM */

const VIEWER_ORIGIN = 'http://localhost:8000';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a good way to control when to use localhost vs when to use the hosted app. Important for local development. Any ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could append +dev to our version marker when in local development that unlocks some extra stuff in the report?

const TREEMAP_URL = `${VIEWER_ORIGIN}/treemap/`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is only when local though right? when deployed won't it be ${VIEWER_ORIGIN}/lighthouse/treemap/?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the path and origin will be environment dependent


/**
* @param {HTMLTableElement} tableEl
* @return {Array<HTMLElement>}
Expand Down Expand Up @@ -457,8 +460,7 @@ class ReportUIFeatures {
break;
}
case 'open-viewer': {
const viewerPath = '/lighthouse/viewer/';
ReportUIFeatures.openTabAndSendJsonReport(this.json, viewerPath);
ReportUIFeatures.openTabAndSendJsonReportToViewer(this.json);
break;
}
case 'save-gist': {
Expand All @@ -478,6 +480,20 @@ class ReportUIFeatures {
self.print();
}

_openTreemap() {
const treemapDebugData = /** @type {LH.Audit.Details.DebugData} */ (
this.json.audits['script-treemap-data'].details);
if (!treemapDebugData) return;

const windowName = `treemap-${this.json.requestedUrl}`;
// TODO how to use this type ...?
/** @type {import('../../../../lighthouse-treemap/types/treemap').Treemap.Options} */
const treemapOptions = {
lhr: this.json,
};
ReportUIFeatures.openTabAndSendData(treemapOptions, TREEMAP_URL, windowName);
}

/**
* Keyup handler for the document.
* @param {KeyboardEvent} e
Expand All @@ -492,33 +508,43 @@ class ReportUIFeatures {
/**
* Opens a new tab to the online viewer and sends the local page's JSON results
* to the online viewer using postMessage.
* @param {LH.Result} reportJson
* @param {string} viewerPath
* @param {LH.Result} json
* @protected
*/
static openTabAndSendJsonReportToViewer(json) {
// The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly
// @ts-ignore - If this is a v2 LHR, use old `generatedTime`.
const fallbackFetchTime = /** @type {string} */ (json.generatedTime);
const fetchTime = json.fetchTime || fallbackFetchTime;
const windowName = `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`;
ReportUIFeatures.openTabAndSendData(json, `${VIEWER_ORIGIN}/lighthouse/viewer/`, windowName);
}

/**
* Opens a new tab to an external page and sends data using postMessage.
* @param {Object} data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param {Object} data
* @template {{lhresult: LH.Result}} T
* @param {T} data

(we should probably update viewer to accept lhr though 😄 )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lhresult?

Planning to add a mode property to the data sent to treemap app. At that point, there's no overlap in types, hence object. We don't do nothing but serialize this value, so it seems fine to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lhresult?

Isn't that what viewer uses?

At that point, there's no overlap in types, hence object.

They both require an LHR, there could be some overlap in types if we wanted to.

We don't do nothing but serialize this value, so it seems fine to me.

I'm separately advocating elsewhere in this PR that we do do something with it ;)

* @param {string} url
* @param {string} windowName
* @protected
*/
static openTabAndSendJsonReport(reportJson, viewerPath) {
const VIEWER_ORIGIN = 'https://googlechrome.github.io';
static openTabAndSendData(data, url, windowName) {
const origin = new URL(url).origin;
// Chrome doesn't allow us to immediately postMessage to a popup right
// after it's created. Normally, we could also listen for the popup window's
// load event, however it is cross-domain and won't fire. Instead, listen
// for a message from the target app saying "I'm open".
const json = reportJson;
window.addEventListener('message', function msgHandler(messageEvent) {
if (messageEvent.origin !== VIEWER_ORIGIN) {
if (messageEvent.origin !== origin) {
return;
}
if (popup && messageEvent.data.opened) {
popup.postMessage({lhresults: json}, VIEWER_ORIGIN);
popup.postMessage(data, origin);
window.removeEventListener('message', msgHandler);
}
});

// The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly
// @ts-expect-error - If this is a v2 LHR, use old `generatedTime`.
const fallbackFetchTime = /** @type {string} */ (json.generatedTime);
const fetchTime = json.fetchTime || fallbackFetchTime;
const windowName = `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`;
const popup = window.open(`${VIEWER_ORIGIN}${viewerPath}`, windowName);
const popup = window.open(url, windowName);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions lighthouse-treemap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Lighthouse Treemap Viewer

## Development

```sh
yarn serve-treemap

# in separate terminal, start build watch
# dependency: `brew install entr`
find lighthouse-treemap | entr -s 'DEBUG=1 yarn build-treemap'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a new dependency we need to add to CONTRIBUTING.md?

$ which entr
entr not found

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that doesn't seem important to add there. I'll just add a comment here brew install entr.

open http://localhost:8000/treemap/?debug
```
21,497 changes: 21,497 additions & 0 deletions lighthouse-treemap/app/debug.json

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions lighthouse-treemap/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<!--
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.
-->

<!doctype html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>Lighthouse Treemap</title>
<link rel="icon"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEhklEQVR4AWJxL/BhIAesev1U5tcflpncgNrKIsqNIwzC9feMpDUzs70kOczMzMzJJcxwCTMzncPMnOwtzBwzMzPb0vRfeZPp0VhPS5I39V5fdiXV1/VD+9QC7OVn9BsyH1XIoEI1PfmJvLFowVV564+34DFUHudbmfDh4kVXh//7XwE+WjS/YfXZe3yr4j2rqj1AIhSB7hZ8ZtPZu/zw8cK523U4wE1/rvPfWrz4zs0m9ZdC9yUJAlASdBAgocRegfF/f3/h/PuaFsxMdwjAR0vm1+06eMMfIrhLqTWqdH4EumU2SPfMhigJAlRQbZrgrRsl9U+Y2DYDFCz3ILC9kiAiqSrMwbWT0nceEnR+9Kggc2zjOJCASDENkg0a5HfZZgDP81CM3CrQs2Z1+o7DJ6ePr8sK0AOCHv5Jjdt3evyYSaZ351VIStIxPRAUtrBYbxC6w+BZ0ivVSBKkIhJhemSyZpfB00EiPO2VjzYkxhcqXQqCWCShGplvi3y0QxqbuBurMjyJeWnkHZuAEgIQGsUBqwrfjZ+IlBgKyRJzVVYF8O6qFWdh86YzQzMrZigYmxAyfvHgLZQ/LC1CbeniW2Hkqr/PH16SgvGuf2/uzNMBwJA/njxizGPtSyAf7EziJCMGRDRdhoAC4PL1A/SrKQMAAQkEfpJAcRQdrBJ7gNwjSpJsdwK+CANBkqa1LgQB4IicV9nYUct7gaxuDJUErQIiEAiMxLVOFlKzIktPpT0ggpdpC/8YAHnxbgkUY4tAAFSR7AAXNyAAWHJrA/kHGjzg5nleuwFO7Nd/IoDw4Pm58+4jNLmYG0wRA5bErc2Mr3Y+dXTDW1VvwqbJkzMCHQ4S1GTCBOIgUHJrGdEwqzR+jAp/o2qAZelUDoQnruEEdDclJI6576AlNVfc+22XN/+Y1vnJD0Yind6UpEEvn/Hqq15EYjCW7jZCJEpnNvDgkyelDjs106kuux2AAXCSobULOWP8mLhYlpoDMK4qAFXJGk+grtH8YXVz5KJblqaG1+VUdTc0I290bmUQAriGITRbdQnom0aoFj8kx1+wMD2ifncAXUQE4SkDqN1hE0jEophs1SUwZAOhUAiMCLwRtamtTZtbbmZErSAUHbSysaoEmnrsakiMiUAURi283gN6wans9oX8rOCrj7/JP35DFD+iQ7Au/K2KE1jzx6ujjUnXFH9KjEq6ZlhsTBICrNLJf47Pv/pkHzvup1w4dmUbEei0+bcXRqJuh5kVARQ8byyYxOwNGr7A87xh1tp8sGT+uMInrwi++Xj7TQz2d27NvwEkrOflAFQGIDA5khASBCGdO2/Z/MnLPwYfv5TFhjW7QhVKAB6afwe2LpFlFsCnlQEosgQgDsdOG1/LKeNqJS4JCSPJ/i+TakwEARor7gER1Iva5JmPOJK0RUqmoPnnlzFCtmIAhAAQEIQRgDaiYPIauNXcnDlRIrWNFY3hm7PG9YRqr7IV7HrCgAC17befjEvRq2nGhAHtBqDpOuI/I1diUUAMYIxEdyejBJqLnNoszGZtfiX/CztGv2mq+sdaAAAAAElFTkSuQmCC">
<meta name="theme-color" content="#304ffe">
<link rel="stylesheet" href="styles/treemap.css">
</head>

<body>
<main class="lh-main lh-vars">
<div class="panel panel--settings">
<header>
<div class="lh-header--section">
<style>
.lh-topbar__logo {
width: 24px;
height: 24px;
user-select: none;
flex: none;
}
</style>

<div>
<!-- Lighthouse logo. Stolen from templates.html -->
<svg class="lh-topbar__logo" viewBox="0 0 24 24">
<defs>
<linearGradient x1="57.456%" y1="13.086%" x2="18.259%" y2="72.322%" id="lh-topbar__logo--a">
<stop stop-color="#262626" stop-opacity=".1" offset="0%"/>
<stop stop-color="#262626" stop-opacity="0" offset="100%"/>
</linearGradient>
<linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="lh-topbar__logo--b">
<stop stop-color="#262626" stop-opacity=".1" offset="0%"/>
<stop stop-color="#262626" stop-opacity="0" offset="100%"/>
</linearGradient>
<linearGradient x1="58.764%" y1="65.756%" x2="36.939%" y2="50.14%" id="lh-topbar__logo--c">
<stop stop-color="#262626" stop-opacity=".1" offset="0%"/>
<stop stop-color="#262626" stop-opacity="0" offset="100%"/>
</linearGradient>
<linearGradient x1="41.635%" y1="20.358%" x2="72.863%" y2="85.424%" id="lh-topbar__logo--d">
<stop stop-color="#FFF" stop-opacity=".1" offset="0%"/>
<stop stop-color="#FFF" stop-opacity="0" offset="100%"/>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<path d="M12 3l4.125 2.625v3.75H18v2.25h-1.688l1.5 9.375H6.188l1.5-9.375H6v-2.25h1.875V5.648L12 3zm2.201 9.938L9.54 14.633 9 18.028l5.625-2.062-.424-3.028zM12.005 5.67l-1.88 1.207v2.498h3.75V6.86l-1.87-1.19z" fill="#F44B21"/>
<path fill="#FFF" d="M14.201 12.938L9.54 14.633 9 18.028l5.625-2.062z"/>
<path d="M6 18c-2.042 0-3.95-.01-5.813 0l1.5-9.375h4.326L6 18z" fill="url(#lh-topbar__logo--a)" fill-rule="nonzero" transform="translate(6 3)"/>
<path fill="#FFF176" fill-rule="nonzero" d="M13.875 9.375v-2.56l-1.87-1.19-1.88 1.207v2.543z"/>
<path fill="url(#lh-topbar__logo--b)" fill-rule="nonzero" d="M0 6.375h6v2.25H0z" transform="translate(6 3)"/>
<path fill="url(#lh-topbar__logo--c)" fill-rule="nonzero" d="M6 6.375H1.875v-3.75L6 0z" transform="translate(6 3)"/>
<path fill="url(#lh-topbar__logo--d)" fill-rule="nonzero" d="M6 0l4.125 2.625v3.75H12v2.25h-1.688l1.5 9.375H.188l1.5-9.375H0v-2.25h1.875V2.648z" transform="translate(6 3)"/>
</g>
</svg>

<span class="lh-header--title lh-text-dim">Lighthouse Treemap</span>
</div>
</div>

<div class="lh-header--section">
<span class="lh-header--url-and-size">
<span class="lh-header--url"></span> • <span class="lh-header--size lh-text-dim"></span></span>
</span>
<div class="panel panel--modals">
<!-- View modes go here -->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use an id for component slots?

</div>
</div>
</header>
</div>

<div class="panel panel--treemap">
<!-- treemap goes here -->
</div>
</main>

<script src="src/treemap.js"></script>

<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');

ga('create', 'UA-85519014-2', 'auto');
ga('send', 'pageview');
</script>
</body>

</html>
Loading