Kuma: T - Document estimated level of effort for each possible performance refactor

Created on 4 May 2020  路  1Comment  路  Source: mdn/kuma

Acceptance Criteria

  • [x] Using report generated in #6894, provide estimated level of effort for each refactor.
  • [x] Make a call on whether we have any low hanging fruits that we want to implement soon. (Propose which fixes seem to have the most bang for their buck)

Most helpful comment

The following are thoughts based on this comment

inlinecss

What makes this hard is that if you compute the critical CSS based on static analysis it won't take into account the DOM changes that will happen, on document load, by JavaScript. The only sane solution to that is to run a headless browser like puppeteer and have its rendered CSSOM be the basis for the not-so-static analysis. Starting puppeteer is costly can be non-trivial to get working outside of running on a laptop. The costliness of starting it can be do use something like minimalcss-server which starts an instance of puppeteer (and minimalcss) and communicate via HTTP between Django and the Node server. This is similar to how we're currently doing SSR in kuma. Also, this is how www.peterbe.com works in that as soon as Django has used Jinja to generate the it starts a worker process that optimizes the built page. But that requires that you get all the static assets working and have the right HOST headers etc. Also, with our tiny amount of CDN caching, this operation would be vastly resource intensive and since we're using CloudFront as the "storage" of the generated HTML, we'd need a solution to invalidate the CDN which is potentially expensive and fragile.

A simpler solution would be to generate the critical CSS once and check it in somewhere. And we'd need to do it so it works on multiple pages without flaws. Then it would be trivial to inject it in the HTML using django-pipeline and some Jinja tricks. But it's fragile. We'd also need to limit to a set of pages that we know use all the DOM things.

Another option is to use something like leeoniya/dropcss which doesn't use puppeteer or an AST parser. It's fast and can be done dynamically on every generated page. It'd need to be node but can be executed via subprocess or a HTTP server like our current SSR service.

The results (i.e. the critical CSS payload) might not have to be perfect but it definitely adds fragility and complexity.

Conclusion/Estimate: 5 (out of 5)

noreact

Not really a feasible option since we use React for the header & banners (could be replaced with vanilla JS) and all the SPAs like the subscription management page. But it could be optional in that only the SPAs have React (e.g. subscription pages, site search, settings page). So we could ditch the react JS bundle on document pages but keep it on SPAs.

The noreact variable actually excluded the BCD signalling JS bundle which actually isn't React so perhaps the BCD signalling could stay.

We'd first need to rewrite the header & banners in vanilla JS and then do some careful testing that it doesn't break pages.

By the way, if we go down this path we wouldn't need the serialized JSON that is the page state data so the HTML would become smaller which is nice. Although, with gzip it's not a huge win.

Conclusion/Estimate: 4 (out of 5)

noiframe

The assumption is that you don't need or benefit from the interactive examples iframe on mobile devices because they're already really clunky on mobile devices.

But how do you know if the device is a mobile device in a CDN'ed page? You don't. We don't have a mobile static version (e.g. m.developer.mozilla.org) so you'd need to build a solution that only injects the iframe if you're on a non-mobile device based on media queries or something like window.innerHeight. But mostly people are using desktop so they'd have to have more complexity in JavaScript that re-renders the DOM after load which might cause flickering and make the page "jump" since the height of the iframe is bound to be different than what we'd have in the DOM during initial load.

We'd still probably use the preload HTML headers that downloads everything in advance if possible. But that's fragile since if you don't need what was preloaded it can cause errors.

Conclusion/Estimate: 4 (out of 5)

nobcdsignalling

Perhaps the implementation is not to ditch it entirely but to make it lazy-loaded based on an intersection observer. We can put an intersection observer on any BCD table and only inject the S bundle once you start viewing the BCD table.
To do this we should probably also first decouple all its CSS from the main CSS bundle so that the intersection observer would first inject the CSS into the DOM and then inject the JS bundle. It's not easy to guarantee that the CSS is available first.

Adding the extra JS code to do the intersection observer might add a small cost to the "first input delay" but since all BCD tables are almost always below the fold it won't be happening until way past initial load (unless you go to the BCD table with an anchor fragment on the URL.

Whatever we build could be made in such a way that it simply doesn't do anything if the browser doesn't support intersection observers so it'd be progressive enhancement only.

Conclusion/Estimate: 2 (out of 5)

ssrprism

First of all, the win is arguably small as a variant anyway.

Replacing the client-side syntax highlighting and doing it in KumaScript would make the HTML larger but the JS code smaller so you might just shift the costs.

A change like this in KumaScript means significant work in mdn/kumascript which is cumbersome as a developer experience so the effort might be making it harder than it needs to be.

Conclusion/Estimate: 2 (out of 5)

>All comments

The following are thoughts based on this comment

inlinecss

What makes this hard is that if you compute the critical CSS based on static analysis it won't take into account the DOM changes that will happen, on document load, by JavaScript. The only sane solution to that is to run a headless browser like puppeteer and have its rendered CSSOM be the basis for the not-so-static analysis. Starting puppeteer is costly can be non-trivial to get working outside of running on a laptop. The costliness of starting it can be do use something like minimalcss-server which starts an instance of puppeteer (and minimalcss) and communicate via HTTP between Django and the Node server. This is similar to how we're currently doing SSR in kuma. Also, this is how www.peterbe.com works in that as soon as Django has used Jinja to generate the it starts a worker process that optimizes the built page. But that requires that you get all the static assets working and have the right HOST headers etc. Also, with our tiny amount of CDN caching, this operation would be vastly resource intensive and since we're using CloudFront as the "storage" of the generated HTML, we'd need a solution to invalidate the CDN which is potentially expensive and fragile.

A simpler solution would be to generate the critical CSS once and check it in somewhere. And we'd need to do it so it works on multiple pages without flaws. Then it would be trivial to inject it in the HTML using django-pipeline and some Jinja tricks. But it's fragile. We'd also need to limit to a set of pages that we know use all the DOM things.

Another option is to use something like leeoniya/dropcss which doesn't use puppeteer or an AST parser. It's fast and can be done dynamically on every generated page. It'd need to be node but can be executed via subprocess or a HTTP server like our current SSR service.

The results (i.e. the critical CSS payload) might not have to be perfect but it definitely adds fragility and complexity.

Conclusion/Estimate: 5 (out of 5)

noreact

Not really a feasible option since we use React for the header & banners (could be replaced with vanilla JS) and all the SPAs like the subscription management page. But it could be optional in that only the SPAs have React (e.g. subscription pages, site search, settings page). So we could ditch the react JS bundle on document pages but keep it on SPAs.

The noreact variable actually excluded the BCD signalling JS bundle which actually isn't React so perhaps the BCD signalling could stay.

We'd first need to rewrite the header & banners in vanilla JS and then do some careful testing that it doesn't break pages.

By the way, if we go down this path we wouldn't need the serialized JSON that is the page state data so the HTML would become smaller which is nice. Although, with gzip it's not a huge win.

Conclusion/Estimate: 4 (out of 5)

noiframe

The assumption is that you don't need or benefit from the interactive examples iframe on mobile devices because they're already really clunky on mobile devices.

But how do you know if the device is a mobile device in a CDN'ed page? You don't. We don't have a mobile static version (e.g. m.developer.mozilla.org) so you'd need to build a solution that only injects the iframe if you're on a non-mobile device based on media queries or something like window.innerHeight. But mostly people are using desktop so they'd have to have more complexity in JavaScript that re-renders the DOM after load which might cause flickering and make the page "jump" since the height of the iframe is bound to be different than what we'd have in the DOM during initial load.

We'd still probably use the preload HTML headers that downloads everything in advance if possible. But that's fragile since if you don't need what was preloaded it can cause errors.

Conclusion/Estimate: 4 (out of 5)

nobcdsignalling

Perhaps the implementation is not to ditch it entirely but to make it lazy-loaded based on an intersection observer. We can put an intersection observer on any BCD table and only inject the S bundle once you start viewing the BCD table.
To do this we should probably also first decouple all its CSS from the main CSS bundle so that the intersection observer would first inject the CSS into the DOM and then inject the JS bundle. It's not easy to guarantee that the CSS is available first.

Adding the extra JS code to do the intersection observer might add a small cost to the "first input delay" but since all BCD tables are almost always below the fold it won't be happening until way past initial load (unless you go to the BCD table with an anchor fragment on the URL.

Whatever we build could be made in such a way that it simply doesn't do anything if the browser doesn't support intersection observers so it'd be progressive enhancement only.

Conclusion/Estimate: 2 (out of 5)

ssrprism

First of all, the win is arguably small as a variant anyway.

Replacing the client-side syntax highlighting and doing it in KumaScript would make the HTML larger but the JS code smaller so you might just shift the costs.

A change like this in KumaScript means significant work in mdn/kumascript which is cumbersome as a developer experience so the effort might be making it harder than it needs to be.

Conclusion/Estimate: 2 (out of 5)

Was this page helpful?
0 / 5 - 0 ratings