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

Experiment: load gridicons from external SVG file. #32172

Closed
wants to merge 1 commit into from

Conversation

sgomes
Copy link
Contributor

@sgomes sgomes commented Apr 10, 2019

Gridicons are currently being wrapped in JSX and included as a separate JS bundle. This is suboptimal for a number of reasons, among which:

  • JS is byte-for-byte much more CPU-demanding than SVG, a concern on mobile devices. The fewer JS bytes the browser needs to handle, the better.
  • Encoding image data as JS invalidates any optimizations the browser may otherwise be able to perform, such as download prioritization.

This PR experiments with an alternate approach: SVG external content.

SVG data is kept in a static file, with each icon being assigned an ID.
We then have a small component which simply references the correct ID in this external file (using <use>, a native SVG feature), leaving it to the browser to fetch the data at an appropriate time during bootup.

Since the SVG data is all kept in a single file, we strike a good balance between cacheability and number of requests, we avoid having large amounts of procedurally-generated JSX, and we delegate all image fetching to the browser, which can apply the usual optimizations as it sees fit.

Potential caveats that were tested and appear to be handled correctly in this PR:

  • IE11 and older versions of modern browsers work correctly, through the svg4everybody polyfill (~1KB). The polyfill is explicitly excluded from the evergreen bundle, as it's not needed.
  • Icons are aligned correctly by copying existing alignment logic. Some minor changes to existing CSS rules were needed.
  • A blank icon is shown when the icon name is unknown.

Note: this PR is not intended to be merged as-is. I'm looking to validate the approach before moving forward. If it's approved, the work will likely need to be split between this repo and the gridicons repo, with updates to the gridicons NPM package.

Changes proposed in this Pull Request

  • Remove AsyncGridicons component
  • Add ExternalGridicons component
  • Copy alignment data from gridicons repo and use in ExternalGridicons
  • Add tiled SVG file to static (formerly public), copied from the gridicons repo.
  • Modify gridicon CSS rules to be compatible with this approach
  • Add svg4everybody polyfill to support SVG external content in old browsers

Testing instructions

  • Open the calypso.live branch of this PR in one browser tab.
  • Open production in another browser tab.
  • Alternate between both tabs, comparing as many different Calypso pages as you see fit. They should all be identical, pixel for pixel.

@sgomes sgomes added [Type] Enhancement [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. [Type] Performance DO NOT MERGE labels Apr 10, 2019
@sgomes sgomes requested review from blowery, simison, ockham, sirreal and a team April 10, 2019 11:30
@matticbot
Copy link
Contributor

@sgomes sgomes requested a review from jsnajdr April 10, 2019 11:31
@blowery
Copy link
Contributor

blowery commented Apr 10, 2019

Works in IE11. :)

onClick={ onClick }
{ ...otherProps }
>
<use xlinkHref={ `/calypso/images/gridicons/sprite.svg#${ iconName }` } />
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to cachebust this somehow?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe use file-loader to get the url, which can bake the hash in?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we'll need to have both a mechanism for keeping gridicons up-to-date from the gridicons repo, and a mechanism to keep users' copies up-to-date (with some form of cache-busting, yes).

I intentionally omitted any attempt at either of those in this PR, since I'm just looking for validation on the general approach, for now.

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 the approach is good. How do you want to proceed with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the approach is good. How do you want to proceed with this?

Here are my initial thoughts:

Work in gridicons repo:

  • Modify build to include spritemap SVG as part of the gridicons NPM package.
  • Modify build to include offset configuration as part of the gridicons NPM package (see the icons-offset.js file in this PR)
  • For both of the above changes, we could keep the entry points as they are, so that existing consumers aren't affected. These would be extra files on the same package that would have to be required explicitly.
  • Deploy new NPM package.

Work in Calypso repo:

  • Increment the gridicons NPM package version to match the new release.
  • Include the copying of the spritemap as part of the build process. Cache-bust spritemap based on the NPM package version, if that's somehow possible.
  • Implement the rest of the approach as per this PR.

How does that sound?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. I think we have an earlier candidate for svg4everybody over in #32236. @griffbrad Do you need to coordinate that work at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@griffbrad 's approach for the Material icons looks quite different, in that he's wrapping each icon in a React component, created at build-time through SVGR. That's the approach previously used in gridicons (SVG-in-JS), albeit with a simplified pipeline. It's a good approach if you're using an icon or two, but if you're using a large number of them (like we do in gridicons), the cost of wrapping SVG in JS starts taking a toll.

svg4everybody isn't needed in @griffbrad 's case, since as far as I can tell he's not using SVG <use> pointing to external resources, nor actually loading anything from SVG files at runtime.

Copy link
Contributor

Choose a reason for hiding this comment

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

svg4everybody isn't needed in @griffbrad 's case, since as far as I can tell he's not using SVG pointing to external resources, nor actually loading anything from SVG files at runtime.

Right, I'd like to consolidate down to one method of loading external SVGs rather than both of these and I'd rather avoid transpiling svg to react.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha. Feel free to add me as a reviewer in any SVG-loading PRs, then, and please feel free to reuse any of the code in this PR! I'll see if I can get to work on the gridicons side of things before continuing here in Calypso.

@sgomes sgomes force-pushed the try/gridicons-from-external-svg branch from 044df83 to 6df9a90 Compare April 15, 2019 11:01
Gridicons are currently being wrapped in JSX and included as a separate
JS bundle. This is suboptimal for a number of reasons, among which:
- JS is byte-for-byte much more CPU-demanding than SVG, a concern on
  mobile devices. The fewer JS bytes the browser needs to handle, the
  better.
- Encoding image data as JS invalidates any optimizations the browser
  may be able to otherwise perform, such as download prioritization.

This PR experiments with an alternate approach: SVG external content.

SVG data is kept in a static file, with each icon being assigned an ID.
We then have a small component which simply references the correct
ID in this external file, leaving it to the browser to fetch the data at
an appropriate time during bootup.

Since the SVG data is all kept in a single file, we strike a good
balance between cacheability and number of requests, we avoid having
large amounts of procedurally-generated JSX, and we delegate all image
fetching to the browser, which can apply the usual optimizations as it
sees fit.

Potential caveats that appear to be handled correctly in this PR:
- IE11 and older versions of modern browsers work correctly, through
  the svg4everybody polyfill (~1KB). The polyfill is not included in
  the evergreen bundle.
- Icons are aligned correctly by copyign existing alignment logic.
  Some minor changes to existing CSS rules were needed.
- A blank icon is shown when the icon name is unknown.
@sgomes sgomes force-pushed the try/gridicons-from-external-svg branch from 6df9a90 to 1cd2c0d Compare April 15, 2019 13:27
@matticbot
Copy link
Contributor

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints
Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.

name            parsed_size           gzip_size
build              -22870 B  (-1.2%)    -2671 B  (-0.5%)
domainsLanding     -22832 B  (-3.0%)    -2653 B  (-1.3%)

Legacy SCSS Stylesheet
The monolithic CSS stylesheet that is downloaded on every app load.
This PR increases the size of the stylesheet, which is a bad news. Please consider migrating the CSS styles you modified to webpack imports.

name       parsed_size           gzip_size
style.css         +8 B  (+0.0%)       +3 B  (+0.0%)

Sections
Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

name                      parsed_size             gzip_size
gridicons                   -228362 B  (deleted)   -22417 B  (deleted)
post-editor                     +68 B    (+0.0%)      +20 B    (+0.0%)
signup                          +58 B    (+0.0%)       +7 B    (+0.0%)
stats                           +52 B    (+0.0%)       -2 B    (-0.0%)
woocommerce                     +46 B    (+0.0%)       -1 B    (-0.0%)
purchases                       +40 B    (+0.0%)       +3 B    (+0.0%)
plugins                         +40 B    (+0.0%)       -7 B    (-0.0%)
domains                         +40 B    (+0.0%)       +4 B    (+0.0%)
reader                          +38 B    (+0.0%)       +1 B    (+0.0%)
settings-writing                +36 B    (+0.0%)       -2 B    (-0.0%)
help                            +36 B    (+0.0%)       -4 B    (-0.0%)
posts-pages                     +34 B    (+0.0%)       +2 B    (+0.0%)
plans                           +34 B    (+0.0%)       -3 B    (-0.0%)
settings                        +32 B    (+0.0%)      +12 B    (+0.0%)
security                        +32 B    (+0.0%)       +4 B    (+0.0%)
jetpack-connect                 +32 B    (+0.0%)       +2 B    (+0.0%)
gutenberg-editor                +32 B    (+0.0%)       +4 B    (+0.0%)
posts-custom                    +30 B    (+0.0%)       +2 B    (+0.0%)
checkout                        +30 B    (+0.0%)      -11 B    (-0.0%)
activity                        +28 B    (+0.0%)       +0 B
notification-settings           +26 B    (+0.0%)       -8 B    (-0.0%)
media                           +26 B    (+0.0%)       +2 B    (+0.0%)
email                           +26 B    (+0.0%)       -4 B    (-0.0%)
account                         +26 B    (+0.0%)       -8 B    (-0.0%)
settings-security               +24 B    (+0.0%)       -5 B    (-0.0%)
themes                          +22 B    (+0.0%)       -6 B    (-0.0%)
people                          +22 B    (+0.0%)       +3 B    (+0.0%)
comments                        +22 B    (+0.0%)       +1 B    (+0.0%)
theme                           +20 B    (+0.0%)       +1 B    (+0.0%)
account-close                   +20 B    (+0.0%)       -6 B    (-0.0%)
sharing                         +18 B    (+0.0%)       -1 B    (-0.0%)
earn                            +18 B    (+0.0%)       -2 B    (-0.0%)
site-blocks                     +16 B    (+0.0%)       -7 B    (-0.0%)
settings-traffic                +16 B    (+0.0%)       -8 B    (-0.0%)
privacy                         +16 B    (+0.0%)       -7 B    (-0.0%)
me                              +16 B    (+0.0%)       -7 B    (-0.0%)
happychat                       +16 B    (+0.0%)       -7 B    (-0.0%)
google-my-business              +16 B    (+0.0%)       -3 B    (-0.0%)
wp-super-cache                  +14 B    (+0.0%)       -7 B    (-0.0%)
settings-performance            +12 B    (+0.0%)       -2 B    (-0.0%)
settings-discussion             +12 B    (+0.0%)       -2 B    (-0.0%)
zoninator                       +10 B    (+0.0%)       -3 B    (-0.0%)
login                            +8 B    (+0.0%)       +4 B    (+0.0%)
jetpack-onboarding               +6 B    (+0.0%)       +3 B    (+0.0%)
feature-upsell                   +6 B    (+0.0%)       +3 B    (+0.0%)
concierge                        +6 B    (+0.0%)       -7 B    (-0.0%)
accept-invite                    +6 B    (+0.0%)       +1 B    (+0.0%)
devdocs                          +4 B    (+0.0%)       +2 B    (+0.0%)
preview                          +2 B    (+0.0%)       -2 B    (-0.1%)
mailing-lists                    +2 B    (+0.0%)       -6 B    (-0.3%)
domain-connect-authorize         +2 B    (+0.0%)       +1 B    (+0.0%)
customize                        +2 B    (+0.0%)       -2 B    (-0.0%)
checklist                        +2 B    (+0.0%)       +3 B    (+0.0%)

Async-loaded Components
React components that are loaded lazily, when a certain part of UI is displayed for the first time.

name                                                         parsed_size           gzip_size
async-load-design-blocks                                          +142 B  (+0.0%)      +10 B  (+0.0%)
async-load-design                                                  +86 B  (+0.0%)      -21 B  (-0.0%)
async-load-design-playground                                       +78 B  (+0.0%)      -17 B  (-0.0%)
async-load-components-web-preview-component                        +30 B  (+0.0%)       +2 B  (+0.0%)
async-load-post-editor-media-modal                                 +18 B  (+0.0%)       +3 B  (+0.0%)
async-load-blocks-inline-help-popover                              +18 B  (+0.0%)       +8 B  (+0.0%)
async-load-reader-following-manage                                 +10 B  (+0.0%)       +2 B  (+0.0%)
async-load-extensions-woocommerce-app-store-stats-referrers        +10 B  (+0.0%)       +3 B  (+0.0%)
async-load-extensions-woocommerce-app-store-stats                  +10 B  (+0.0%)       +2 B  (+0.0%)
async-load-reader-site-stream                                       +8 B  (+0.0%)       +4 B  (+0.0%)
async-load-reader-feed-stream                                       +8 B  (+0.0%)       +4 B  (+0.1%)
async-load-quick-language-switcher                                  +8 B  (+0.0%)       +3 B  (+0.0%)
async-load-my-sites-checklist-wpcom-checklist-component-jsx         +8 B  (+0.0%)       -3 B  (-0.0%)
async-load-my-sites-site-settings-section-import                    +6 B  (+0.0%)       -3 B  (-0.0%)
async-load-extensions-woocommerce-app-store-stats-listview          +6 B  (+0.0%)       +4 B  (+0.1%)
async-load-reader-tag-stream-main                                   +4 B  (+0.0%)       +4 B  (+0.1%)
async-load-reader-search-stream                                     +4 B  (+0.0%)       +2 B  (+0.0%)
async-load-my-sites-site-settings-section-export                    +4 B  (+0.0%)       +3 B  (+0.0%)
async-load-my-sites-current-site-domain-warnings                    +4 B  (+0.0%)       +2 B  (+0.0%)
async-load-blocks-reader-full-post                                  +4 B  (+0.0%)       -2 B  (-0.0%)
async-load-blocks-inline-help                                       +4 B  (+0.0%)       +2 B  (+0.0%)
async-load-reader-sidebar                                           +2 B  (+0.0%)       +3 B  (+0.0%)
async-load-reader-list-stream                                       +2 B  (+0.0%)       +0 B
async-load-reader-conversations-stream                              +2 B  (+0.0%)       +4 B  (+0.2%)
async-load-post-editor-editor-sharing-accordion                     +2 B  (+0.0%)       +1 B  (+0.0%)
async-load-post-editor-editor-post-formats-accordion                +2 B  (+0.0%)       +6 B  (+0.3%)
async-load-post-editor-editor-location                              +2 B  (+0.0%)       +6 B  (+0.2%)
async-load-post-editor-editor-author                                +2 B  (+0.0%)       +3 B  (+0.1%)
async-load-my-sites-guided-transfer                                 +2 B  (+0.0%)       +0 B
async-load-my-sites-current-site-notice                             +2 B  (+0.0%)       +5 B  (+0.1%)
async-load-layout-nps-survey-notice                                 +2 B  (+0.0%)       +4 B  (+0.1%)
async-load-layout-masterbar-drafts-popover                          +2 B  (+0.0%)       +3 B  (+0.1%)
async-load-layout-guided-tours-component                            +2 B  (+0.0%)       -4 B  (-0.0%)
async-load-layout-community-translator-launcher                     +2 B  (+0.0%)       +5 B  (+0.1%)
async-load-featured-image                                           +2 B  (+0.1%)       +2 B  (+0.2%)
async-load-components-jetpack-header                                +2 B  (+0.1%)       +4 B  (+0.4%)
async-load-components-happychat                                     +2 B  (+0.0%)       +0 B
async-load-components-community-translator                          +2 B  (+0.0%)       +2 B  (+0.1%)
async-load-blocks-calendar-popover                                  +2 B  (+0.0%)       +4 B  (+0.0%)

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@sgomes
Copy link
Contributor Author

sgomes commented Apr 24, 2019

Not rebasing, as this PR is intended to be closed and replaced with a new PR after Automattic/gridicons#307 gets merged and a new NPM package for gridicons is deployed.

@sgomes
Copy link
Contributor Author

sgomes commented May 2, 2019

This PR is superseded by #32763.

@sgomes sgomes closed this May 2, 2019
@sgomes sgomes deleted the try/gridicons-from-external-svg branch May 2, 2019 15:49
@matticbot matticbot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants