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

Viewer: Lighthouse 2.0 support #2521

Merged
merged 13 commits into from
Jun 22, 2017
Merged

Viewer: Lighthouse 2.0 support #2521

merged 13 commits into from
Jun 22, 2017

Conversation

brendankenny
Copy link
Member

@brendankenny brendankenny commented Jun 16, 2017

Fixes #2338

Mostly porting, all the features should be exactly the same in practice, but minor refactoring so viewer only adds functionality around the report rather than being a version of the report with different build options. This separation mostly existed before, this just makes it explicit.

  • LighthouseReportViewer is the host page. It has a placeholder on screen and it takes all sorts of input to get a json report (as before: paste, drag-and-drop, post message, gist url, gist id in the query string). Notably it only deals with loading a json report from somewhere.

  • viewer has a copy of the v2 report renderer classes. When a new json is to be loaded, viewer's _replaceReportHtml() creates a new copy of these and passes the json in, just like the regular report does. The only difference is that it uses a ViewerUiFeatures, a subclass of ReportUiFeatures, which disables the ability to send to viewer (since it already is in viewer), adds the option to save to gist, and has a custom "save as html" option.

    viewer's report JS, CSS, and <template>s are pulled from the static strings in ReportGeneratorV2, which means when new files are added to the report we don't have to add them to the viewer build step too. The template file is a bit weird because it's inlined in viewer's index.html. We could pull it out into its own file, but then would either need to be put in the page with innerHTML or with an XHR with responseType=document. Open to options there.

  • To save the report from viewer, like before viewer needs to keep a copy of ReportGenerator around because it can't just copy itself (you'd get a copy of viewer, not just the report). ReportGeneratorV2 is browserified and also included in the JS bundle.

    The downside of this is that all the report rendering scripts are included in the page twice, once in a string and once as code, and the ones in strings aren't minified (adding about 100KB to the bundle). We could minifiy with a smarter transform than brfs, or we could do something tricky to dedupe them like have the report generator use the scripts that the page includes instead of the fs.readFileSync versions. All the solutions I've explored so far ended up being pretty awkward, so, happy to figure that out in a followup.

* @struct
* @constructor
*/
function ReportGenerator() {}
Copy link
Member Author

Choose a reason for hiding this comment

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

rather than trying to compile ReportGenerator (with its fs use), just use externs for the one method we care about

*/
constructor(dom, categoryRenderer, uiFeatures = null) {
constructor(dom, categoryRenderer) {
Copy link
Member Author

Choose a reason for hiding this comment

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

reportUIFeatures is removed from the report-renderer constructor, which should make devtools subclassing without it slightly easier cc @paulirish

/**
* Manages drag and drop file input for the page.
*/
class DragAndDrop {
Copy link
Member Author

Choose a reason for hiding this comment

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

slightly simpler than the old FileUploader. Handles only drag and dropping of files (the hidden input is now just in the page and its handler and readFile are now in the viewer main class)

* returns null (and will not trigger sign in).
* @return {!Promise<?string>}
*/
getAccessTokenIfLoggedIn() {
Copy link
Member Author

Choose a reason for hiding this comment

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

I believe this is a somewhat more explicit version of the old ready promise. Having a firebase user also seemed only to be an implementation detail, so it's now entirely internal to the class

Copy link
Contributor

Choose a reason for hiding this comment

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

I think there were cases where you'd get a user but they wouldn't have a token. Or there would be a user but the token wouldn't be refreshed or something. Can't remember exactly.

@ebidel
Copy link
Contributor

ebidel commented Jun 17, 2017

Checking functionality. This is mostly working great!

Some things I found:

  • Pasting a json file doesnt seem to work. Pasting a gist url does :)
  • Save as HTML -> open that HTML file in a new tab -> blank tab.
  • Can't remember if v1 did this, but dragging in a new json file should nuke ?gist=ID if it's in the URL.
  • If you try to drop in a new json file on top of an existing repo, there's a layering issue with the left nav:

screen shot 2017-06-16 at 5 20 18 pm

@ebidel ebidel added the viewer label Jun 17, 2017
@ebidel ebidel changed the title port viewer to use report v2 Viewer: Lighthouse 2.0 support Jun 17, 2017

document.addEventListener('dragover', e => {
e.stopPropagation();
e.preventDefault();
Copy link
Collaborator

Choose a reason for hiding this comment

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

as an aside, what do each of these e.stopProgagation/preventDefaults do/why are they necessary? I built another drag/drop recently and I got away without some of them, but I don't know what exactly is required to pull this off...

cc: @ebidel for the original impl :)

Copy link
Contributor

Choose a reason for hiding this comment

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

This dude told me to do it: https://www.html5rocks.com/en/tutorials/dnd/basics/

TBH, I don't think we need them anymore in a dragover handler. Back in the day, there were browser quirks and I noted that e.preventDefault() was needed to even start a drag. But we're also dragging files (as opposed to links or something in-page) and at the document level. MDN mentions e.preventDefault in dragover, but I betcha that's not accurate.

Anyways, doesn't hurt to be explicit the method is handing the event.

if (this._saveGistCallback) {
this._saveGistCallback(this.json);
} else {
throw new Error('Cannot save gist from gist');
Copy link
Collaborator

Choose a reason for hiding this comment

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

this error message doesn't make as much sense to me...

Copy link
Member Author

Choose a reason for hiding this comment

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

the gist also gazes into you (done)

const reportCss = streamFromString(ReportGenerator.reportCss, 'report-styles.css');
const viewerCss = gulp.src('app/styles/viewer.css');

return streamqueue({objectMode: true}, reportCss, viewerCss)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't really know what all is happening here, but I'm sure it's all necessary and makes sense to you :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I can add some comments. It's getting the report CSS from ReportGenerator.reportCss instead of from file, so that we don't have to keep up to date references to report resources here, we can just defer to whatever ReportGenerator includes.

Gulp makes it a little unwieldy to just insert a string into the object stream, of course.

Copy link
Contributor

@ebidel ebidel Jun 21, 2017

Choose a reason for hiding this comment

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

is most of this to get around require() usage? Is there a reason you got ride of require? Kinda nice to have explicit deps defined. It was a pain to get working, but it was working :)

Copy link
Member Author

Choose a reason for hiding this comment

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

is most of this to get around require() usage? Is there a reason you got ride of require? Kinda nice to have explicit deps defined. It was a pain to get working, but it was working :)

I guess it sort of is to get around it, but (I believe) it's necessary now. Viewer code is mixed with report renderer code in Viewer, so require() is a non-starter unless we want to convert all the report renderer code to using it too (which I don't believe we can).

The downside is, yeah, you have to look at ReportGenerator and this gulpfile to check what's included. On the other hand, we have less fragility in files trying to depend on things in node code, browserified node code, and/or native browser APIs.

Biggest complexity introduced in this file is that it delegates to ReportGenerator to pull in the report dependencies. e.g. instead of the old version including '../lighthouse-core/report/styles/report.css' in the gulp.src, it pulls it in as a string from ReportGenerator.reportCss. It makes the gulp stream nonsense more complicated, but it's nice from a DRY/maintenance perspective since any changes to report dependencies will automatically be pulled in here.

@brendankenny
Copy link
Member Author

Pasting a json file doesnt seem to work. Pasting a gist url does :)

done. Those no-op catches make that tricky to spot :)

Save as HTML -> open that HTML file in a new tab -> blank tab.

done

Can't remember if v1 did this, but dragging in a new json file should nuke ?gist=ID if it's in the URL.

yeah, it was preserved from v1 :P Fixed now though

If you try to drop in a new json file on top of an existing repo, there's a layering issue with the left nav

everybody loves z-index! done.

@codecov
Copy link

codecov bot commented Jun 20, 2017

Codecov Report

Merging #2521 into master will decrease coverage by 0.48%.
The diff coverage is 29.82%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2521      +/-   ##
==========================================
- Coverage   84.52%   84.03%   -0.49%     
==========================================
  Files         169      169              
  Lines        5614     5613       -1     
  Branches      774      776       +2     
==========================================
- Hits         4745     4717      -28     
- Misses        855      882      +27     
  Partials       14       14
Impacted Files Coverage Δ
...ouse-core/report/v2/renderer/report-ui-features.js 1.41% <0%> (-0.25%) ⬇️
lighthouse-core/report/v2/report-generator.js 86.04% <0%> (-13.96%) ⬇️
...hthouse-core/report/v2/renderer/report-renderer.js 98.46% <100%> (+1.4%) ⬆️
lighthouse-viewer/app/src/drag-and-drop.js 59.25% <59.25%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cea443e...7d363f6. Read the comment docs.

@@ -0,0 +1,63 @@
/**
* @license Copyright 2016 Google Inc. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

});
/**
* Promise which resolves after the first check of auth state. After this,
* _accessToken will be set if user logged in and has access token.
Copy link
Contributor

Choose a reason for hiding this comment

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

if the user is logged in....

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

* returns null (and will not trigger sign in).
* @return {!Promise<?string>}
*/
getAccessTokenIfLoggedIn() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there were cases where you'd get a user but they wouldn't have a token. Or there would be a user but the token wouldn't be refreshed or something. Can't remember exactly.

return idb.set('accessToken', this.accessToken).then(_ => {
return this.accessToken;
return idbKeyval.set('accessToken', this._accessToken).then(_ => {
return this._accessToken;
Copy link
Contributor

Choose a reason for hiding this comment

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

we can go 1 line on this

Copy link
Member Author

Choose a reason for hiding this comment

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

done

*/
class GithubAPI {
class GithubApi {
Copy link
Contributor

Choose a reason for hiding this comment

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

grooooosss

* @param {?function(!ReportRenderer.ReportJSON)} saveGistCallback
*/
constructor(dom, saveGistCallback) {
super(dom);
Copy link
Contributor

Choose a reason for hiding this comment

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

dom is so super 💪

}

/**
* Returns the html that recreates this report. Uses ReportGenerator
Copy link
Contributor

Choose a reason for hiding this comment

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

Uses ReportGenerator....?

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

* @override
*/
getReportHtml() {
return new ReportGenerator().generateReportHtml(this.json);
Copy link
Contributor

Choose a reason for hiding this comment

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

would be be able to just use this in main report? Instead of pulling the outerHTML. That would allow us to get rid of _resetUIState() too.

Copy link
Member Author

Choose a reason for hiding this comment

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

would be be able to just use this in main report? Instead of pulling the outerHTML. That would allow us to get rid of _resetUIState() too.

We could consider that in a follow up. I avoided it here because it means ReportGenerator has to be browserified before Lighthouse can be run, taking us back to the report build step we just ditched by getting rid of pre-compiled handlebars templates.

I do have a separate idea for getting rid of _resetUIState(), though: basically we'd track which templates had been imported in a JS variable instead of an attribute. outerHTML wouldn't care anymore, and the equivalent of dom.resetTemplates() could be called in renderReport at the same time it does container.textContent = ''; so that they'd be cloned correctly again.

(though I am personally digging the fact that all our uses of the v2 report just builds the report rendering classes new each time. If we only want to go that way, no need for resetting the Dom class, it'll just be a new instance every time)

.pipe(license()) // Add license to top.
.pipe(gulp.dest(`${config.dist}/src`));
gulp.task('compile-js', () => {
const filename = __dirname + '/../lighthouse-core/report/v2/report-generator.js';
Copy link
Contributor

Choose a reason for hiding this comment

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

how big is the viewer bundle now? without fb and such.

Copy link
Member Author

Choose a reason for hiding this comment

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

how big is the viewer bundle now? without fb and such.

183KB

(About 100KB of that is the unminified js pulled in by ReportGenerator as a string, which can be brought down to about 25K with a simple uglify step, but we'd need to replace brfs with something that can chain transforms like that...good fodder for a followup)

'node_modules/idb-keyval/dist/idb-keyval-min.js',
]);

const versionStr = `window.LH_CURRENT_VERSION = '${lighthousePackage.version}';`;
Copy link
Contributor

Choose a reason for hiding this comment

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

add comment

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@brendankenny
Copy link
Member Author

I think there were cases where you'd get a user but they wouldn't have a token. Or there would be a user but the token wouldn't be refreshed or something. Can't remember exactly.

yeah, this should maintain the earlier behavior where if both aren't present it requires reauthorizing.

@brendankenny
Copy link
Member Author

updated!

@ebidel
Copy link
Contributor

ebidel commented Jun 22, 2017

@ebidel ebidel merged commit 2b31a44 into master Jun 22, 2017
@ebidel ebidel deleted the viewer branch June 22, 2017 22:55
@brendankenny
Copy link
Member Author

brendankenny commented Jun 22, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants