-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
Conversation
* @struct | ||
* @constructor | ||
*/ | ||
function ReportGenerator() {} |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Checking functionality. This is mostly working great! Some things I found:
|
|
||
document.addEventListener('dragover', e => { | ||
e.stopPropagation(); | ||
e.preventDefault(); |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
done. Those no-op
done
yeah, it was preserved from v1 :P Fixed now though
everybody loves z-index! done. |
2e57b13
to
7d363f6
Compare
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
@@ -0,0 +1,63 @@ | |||
/** | |||
* @license Copyright 2016 Google Inc. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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....
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uses ReportGenerator....?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
lighthouse-viewer/gulpfile.js
Outdated
.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'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
lighthouse-viewer/gulpfile.js
Outdated
'node_modules/idb-keyval/dist/idb-keyval-min.js', | ||
]); | ||
|
||
const versionStr = `window.LH_CURRENT_VERSION = '${lighthousePackage.version}';`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
disable save-as-gist based on providing a callback for saving
yeah, this should maintain the earlier behavior where if both aren't present it requires reauthorizing. |
updated! |
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 aViewerUiFeatures
, a subclass ofReportUiFeatures
, 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'sindex.html
. We could pull it out into its own file, but then would either need to be put in the page withinnerHTML
or with an XHR withresponseType=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 thefs.readFileSync
versions. All the solutions I've explored so far ended up being pretty awkward, so, happy to figure that out in a followup.