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

misc(treemap): initialize app structure #11635

Merged
merged 14 commits into from
Nov 13, 2020
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
run: xvfb-run --auto-servernum yarn test-docs
- name: yarn test-viewer
run: xvfb-run --auto-servernum yarn test-viewer
- name: yarn test-treemap
run: xvfb-run --auto-servernum yarn test-treemap

- run: yarn diff:sample-json
- run: yarn type-check
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ script:
- yarn smoke:cicoverage
- yarn test-clients
- yarn test-viewer
- yarn test-treemap
- yarn test-lantern
- yarn test-bundle
- yarn i18n:checks
Expand Down
39 changes: 39 additions & 0 deletions build/build-treemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @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 fs = require('fs');
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: [
fs.readFileSync(require.resolve('webtreemap-cdt'), 'utf8'),
{path: 'src/*'},
],
assets: [
{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
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ module.exports = {
testMatch: [
'**/lighthouse-core/**/*-test.js',
'**/lighthouse-cli/**/*-test.js',
'**/lighthouse-treemap/**/*-test.js',
'**/lighthouse-treemap/**/*-test-pptr.js',
'**/lighthouse-viewer/**/*-test.js',
'**/lighthouse-viewer/**/*-test-pptr.js',
'**/clients/test/**/*-test.js',
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-cli/test/fixtures/static-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class Server {
return;
}

if (filePath.startsWith('/dist/gh-pages/viewer')) {
if (filePath.startsWith('/dist/gh-pages')) {
// Rewrite lighthouse-viewer paths to point to that location.
absoluteFilePath = path.join(__dirname, '/../../../', filePath);
}
Expand Down
37 changes: 11 additions & 26 deletions lighthouse-core/audits/script-treemap-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,19 @@
* Creates treemap data for treemap app.
*/

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');

/**
* @typedef {RootNodeContainer[]} TreemapData
*/

/**
* Ex: https://gist.github.com/connorjclark/0ef1099ae994c075e36d65fecb4d26a7
* @typedef RootNodeContainer
* @property {string} name Arbitrary name identifier. Usually a script url.
* @property {Node} node
* @typedef {LH.Treemap.RootNodeContainer[]} TreemapData
*/

/**
* @typedef Node
* @property {string} name Arbitrary name identifier. Usually a path component from a source map.
* @property {number} resourceBytes
* @property {number=} unusedBytes
* @property {string=} duplicatedNormalizedModuleName If present, this module is a duplicate. String is normalized source path. See ModuleDuplication.normalizeSource
* @property {Node[]=} children
* @typedef {Omit<LH.Treemap.Node, 'name'|'children'>} SourceData
*/

/**
* @typedef {Omit<Node, 'name'|'children'>} SourceData
*/
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');

class ScriptTreemapDataAudit extends Audit {
/**
Expand All @@ -61,12 +46,12 @@ class ScriptTreemapDataAudit extends Audit {
* same data as the sum of all descendant leaf nodes.
* @param {string} sourceRoot
* @param {Record<string, SourceData>} sourcesData
* @return {Node}
* @return {LH.Treemap.Node}
*/
static prepareTreemapNodes(sourceRoot, sourcesData) {
/**
* @param {string} name
* @return {Node}
* @return {LH.Treemap.Node}
*/
function newNode(name) {
return {
Expand Down Expand Up @@ -124,7 +109,7 @@ class ScriptTreemapDataAudit extends Audit {

/**
* Collapse nodes that have only one child.
* @param {Node} node
* @param {LH.Treemap.Node} node
*/
function collapseAll(node) {
while (node.children && node.children.length === 1) {
Expand All @@ -151,7 +136,7 @@ class ScriptTreemapDataAudit extends Audit {
* @return {Promise<TreemapData>}
*/
static async makeRootNodes(artifacts, context) {
/** @type {RootNodeContainer[]} */
/** @type {LH.Treemap.RootNodeContainer[]} */
const rootNodeContainers = [];

let inlineScriptLength = 0;
Expand Down Expand Up @@ -199,7 +184,7 @@ class ScriptTreemapDataAudit extends Audit {
const unusedJavascriptSummary = await UnusedJavaScriptSummary.request(
{url: scriptElement.src, scriptCoverages, bundle}, context);

/** @type {Node} */
/** @type {LH.Treemap.Node} */
let node;
if (unusedJavascriptSummary.sourcesWastedBytes && !('errorMessage' in bundle.sizes)) {
// Create nodes for each module in a bundle.
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'
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.

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

<body>
<main>
<!-- Inject LHR here. -->
</main>

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

<!-- Google Analytics -->
<script>
(function (i, s, o, g, r, a, m) {
Copy link
Contributor

Choose a reason for hiding this comment

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

A comment here that summarizes what's happening could be helpful if it's not too much trouble. Personally, at first glance this bit threw me for a loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is meant-to-be-unreadable code for google analytics :) it's what they give to you for copy/paste.

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');
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 the same analytics account as the viewer? I guess that's what we want when served over same domain anyway

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ya

ga('send', 'pageview');
</script>
</body>

</html>
78 changes: 78 additions & 0 deletions lighthouse-treemap/app/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* @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';

/* eslint-env browser */

/* globals webtreemap TreemapUtil */

/**
* Allows for saving the document and loading with data intact.
* @param {LH.Treemap.Options} options
*/
function injectOptions(options) {
if (window.__treemapOptions) return;
Copy link
Collaborator

Choose a reason for hiding this comment

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

definitely fits now, so not worth discussing much, but can I reserve the right to object to options if this turns into state in future PRs? 😃


const scriptEl = document.createElement('script');
scriptEl.textContent = `
window.__treemapOptions = ${JSON.stringify(options)};
`;
document.head.append(scriptEl);
}

/**
* @param {LH.Treemap.Options} options
*/
function init(options) {
// ==== temporary
TreemapUtil.find('main').textContent = JSON.stringify(options.lhr);
// eslint-disable-next-line no-console
console.log({webtreemap});
// ==== temporary

injectOptions(options);

// eslint-disable-next-line no-console
console.log('window.__treemapOptions', window.__treemapOptions);
}

/**
* @param {string} message
*/
function showError(message) {
document.body.textContent = message;
}

async function main() {
if (window.__treemapOptions) {
// Prefer the hardcoded options from a saved HTML file above all.
init(window.__treemapOptions);
} else if (new URLSearchParams(window.location.search).has('debug')) {
const response = await fetch('debug.json');
init(await response.json());
} else {
window.addEventListener('message', e => {
if (e.source !== self.opener) return;

/** @type {LH.Treemap.Options} */
const options = e.data;
const {lhr} = options;
if (!lhr) return showError('Error: Invalid options');

const documentUrl = lhr.requestedUrl;
if (!documentUrl) return showError('Error: Invalid options');

init(options);
});
}

// If the page was opened as a popup, tell the opening window we're ready.
if (self.opener && !self.opener.closed) {
self.opener.postMessage({opened: true}, '*');
}
}

document.addEventListener('DOMContentLoaded', main);
72 changes: 72 additions & 0 deletions lighthouse-treemap/app/src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @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';

/* eslint-env browser */

/** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElementByTagName */

class TreemapUtil {
/**
* @template {string} T
* @param {T} name
* @param {string=} className
* @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node.
* @return {HTMLElementByTagName[T]}
*/
static createElement(name, className, attrs = {}) {
patrickhulce marked this conversation as resolved.
Show resolved Hide resolved
const element = document.createElement(name);
if (className) {
element.className = className;
}
Object.keys(attrs).forEach(key => {
const value = attrs[key];
if (typeof value !== 'undefined') {
element.setAttribute(key, value);
}
});
return element;
}

/**
* @template {string} T
* @param {Element} parentElem
* @param {T} elementName
* @param {string=} className
* @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node.
* @return {HTMLElementByTagName[T]}
*/
static createChildOf(parentElem, elementName, className, attrs) {
const element = this.createElement(elementName, className, attrs);
parentElem.appendChild(element);
return element;
}

/**
* Guaranteed context.querySelector. Always returns an element or throws if
* nothing matches query.
* @param {string} query
* @param {ParentNode=} context
* @return {HTMLElement}
*/
static find(query, context = document) {
/** @type {?HTMLElement} */
const result = context.querySelector(query);
if (result === null) {
throw new Error(`query ${query} not found`);
}
return result;
}
}

// node export for testing.
if (typeof module !== 'undefined' && module.exports) {
module.exports = TreemapUtil;
}
10 changes: 10 additions & 0 deletions lighthouse-treemap/app/styles/treemap.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @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.
*/

body {
margin: 0;
overflow-y: hidden;
}
Loading