Polymer Performance Patterns (The Polymer Summit 2015)

Polymer Performance Patterns (The Polymer Summit 2015)


All right, let’s talk about
performance of your Polymer apps. Not everyone has a Paul
Irish in their pocket they can take around with them
and debug their apps for them. So we need to learn how
to build apps really fast from the start. Again, here’s my
information if you want to follow me on Twitter. Again, all the demos as well
are going to be on GitHub. Everything I showed today
is going to be up there. But let’s hop right in. So there’s two buckets when
I think of performance. The first is load. How fast does your app
paint pixels to the screen, how fast does your app
load its resources. That’s critical. It’s the first experience
when people come to your app. The next one is
render, what happens when they have loaded your app. It’s not enough
that it loads fast. They actually have to have
a good experience when they’re in your app. No jank if you have animations. Or if they’re interacting
with your app, things need to be
snappy and really fast. And if we have time, we’ll talk
about future API’s that are really exciting, really,
really awesome for performance. So why should you care
about performance? Well, you can actually
just go to the definition. One of the definitions
of performance, which reads, “The extent
to which an investment is profitable, especially in
relation to other investments.” I read this and
I said, hey, what if you replace a couple words. The extent to which an
app is usable, especially in relation to other apps. That’s performance. If people are leaving
your app because you suck at performance,
nobody wants that. We want to be
successful developers. And the reason they leave
our apps for another app should not be because
performance is suffering. Paul Irish showed
you this awesome, kind of unofficial table
of user interactions. And I’m not going to
dive too much into this. But the moral of the story
here is 250 milliseconds is that sweet spot. That’s where users
really, really like it and your app feels fast. If you start to go
above 100 milliseconds, maybe a tap takes too
long, something loads, something janks, you
start to lose people. They do a mental context switch. By 10 seconds, you should
basically just give up. Try something else. Maybe go native, I don’t know. So the good news
is for Polymer 1.0, we’ve already levelled you up. Polymer 1.0 is faster
than Polymer 0.5. And we talked about this
at Google IO this year. It’s three x faster than Chrome. That’s even with the native
web component and API’s. Four x faster in the
polyfilled browsers, like iOS. So this is great. Again, your baseline is already
above where we started in 0.5. But this could be– these
are relative numbers to our old version
of Polymer, right? Maybe it’s three x
faster than dog slow. Who knows, right? I want to put some real
numbers around an app written in Polymer, some
absolute numbers, and show you how to
build an app that’s really fast from the get go. So let’s hop in. Let’s talk about load. What can we do to paint
pixels to the screen as fast as possible? We need to dissect what
we recommend to people. This is the basic scaffold of
an application we typically have in our demos, our sample apps. The first thing
you do is you have to load the web component’s
polyfill, that script tag at the top. Now this is already better
than the old version of the polyfill, because it
doesn’t load the full shadow polyfill. It loads the shady DOM
polyfill, which is much smaller and just tries to mimic
shadow DOM capabilities. The second thing you do
is you load elements.html. And here you’re going to have
all of your element imports and dependencies. Now the Polymer team,
we typically just have one of these files and
all the imports inside of it, which is convenient to do that. But you can have a bunch of
these on the page if you want. And the last thing we have here
is this unresolved attribute, which I’ll talk about. This is the Polymer
feature that makes working with apps really easy. So this is great. This is really easy
to reason about. But I wouldn’t be here
if we can’t do better. And of course we can. So already from the start,
because of that set up you’re hindering yourself as
far as performance is concerned. So let’s jump in
and dissect this. What’s wrong with this app? The first thing is we’re
loading this polyfill. The script tag is going to
block rendering of a page right from the get go. So if we’re on a
slow connection, we’re already degregating the
performance, so that’s no good. We should put a
sync on that tag, and then it won’t
block rendering. It’ll just parse that code
out, which is awesome. The second thing we can do
is work with this import. So by default imports actually
block rendering of the page, as well. Until this elements.html
loads, none of those resources and
none of the mark-up is going to show up. It’s going to block
rendering of the page. And that’s because imports
behave exactly like CSS. So by default, style
sheets block the page, because you don’t want
to have this weird flash of unstyled content come in. And imports do that, too,
because you can actually put style sheets
inside of imports, so the behavior is the same. But the good news is we
can pop in a sync attribute on this element as well. And so by doing this,
we are no longer going to be blocking
the rendering. So nothing in our head
is blocking the page. It’s awesome. This mark-up’s going to
render the user right away. It’s worth spending
a little bit of time on talking about imports. If this is how we
load web components, we use it for dependencies
and loading resources, it’s worth knowing what
they do and how they behave. So you might be asking
yourself, why don’t we have a sink imports by default? Why isn’t that the default
behavior if it’s better? And the reason is because
we want this snippet of code to always work. Custom elements and imports
have been designed together to work really well. Let’s assume inside of
x-foo.html we have the import– sorry, the element definition
of the x-foo element. By the time we run this
script, which comes right after this import,
the expectation is that file is loaded,
it’s registered the element, and you can create an x-foo
and all of its properties and methods are ready to go. That’s the expectation
and variance that we always want true. If this was async by
default, the developer would have to do
a lot more work. You would have to
wait for a load event or figure that out yourself. And you wouldn’t be able to
touch this element’s properties or methods. And so that’s why they’re
synchronous by default. It’s a more sane thing for
developers to grok right away. It’s a lot easier to work with. But you don’t have to
use them synchronous. You can also load imports
dynamically using script. So Polymer has this really
awesome method, import h-ref that allows you
to import– sorry, it allows you to import
an HTML file using our nice convenient
function, import h-ref. And essentially
what that’s doing is creating a
dynamic, HTML import and loading that for you
inside of you document. In this example, I’ve just
wrapped it in a promise. Import page, URL to the
HTML file I want to import, and then I want to do stuff. So it’s really easy to
work with dynamic imports as well in script. The reason we like the
declarative route is A, it’s declarative. It’s a lot easier to use. And B, it’s actually a lot
faster for the browser. The browser’s pre
parser can go off and discover elements.html and
all the resources inside of it. And so it can go and
fetch those resources, preemptively download things,
stick it in its cache, and so by doing this
high in the page, we can actually have
stuff quicker in our app. And so it’s really awesome. This is one of the
reasons I really want all browsers to
implement imports, to take advantage of the
browsers pipeline for loading resources. The last thing we need to deal
with to make this app really fast, this scaffold really
fast, is actually remove this unresolved attribute. So unresolve is really
nice, awesome feature that Polymer has that you
can opt in to if you want. But what it does is it hides the
page until all of the elements are ready to go. So if you’re waiting
on a resource, you’re actually just
showing the user a blank screen for a long time. That’s no good. If you remove that, and you have
async on both these resources here, all of your mark-ups
going to render right away, and it’s going to
get upgraded when those resources come through. And so this is the
fast loading scaffold that I recommend to people. Async the imports,
async the script tag, you’re not blocking
anything, and remove the unresolved attribute palmer. So of course we can do
even better than this. You might be saying,
hey, well, what about this script tag up here. We’re unconditionally
loading that no matter what browser we’re in, right? Even in Chrome where all the
native API’s are available. So instead, as good
web developers, we should be feature detecting
the API’s and loading it conditionally based
on feature detection. So let’s remove
that all together and inside of a new Java Script
file, app.js let’s do that. Let’s conditionally
load the polyfill only when they’re needed. We can do that
really easily today, just by three feature checks. You check for custom elements,
you check for imports, and you check for
the template tag. So this feature detect today
is actually really easy. But as browsers implement these
different features, of course, this is going to get a bit
harder, a bit more challenging. But right now this
is what it is. If the web components native
API’s aren’t supported in the browser,
you can dynamically load the script tag with the
web components polyfills. And so that’ll only
provide this resource to the browsers that need it. All right, so we made a
bunch of improvements. Let’s see what it does
to an actual application. On the left side, you see
the synchronous version with none of the
improvements that I made. That, you can see, loads and
you have this big white screen of death for a number
of milliseconds, right? That’s what I call
the white screen of death, when people are just
waiting for stuff to load. It first paints in about 1.4
seconds, on a 3G connection in the dev tools. On the right side, you can
see the improved version, the asynchronous version. Immediately the page loads,
you see the green bar, you see pixels on the screen,
you see a loading message informing the user,
hey, this thing’s fetching stocks for you. And at 6.2 times faster just
by doing those two or three improvements that
we talked about, 230 second millisecond paint. Awesome. That app uses one
custom element. So it’s not really a
full-fledged app that does something interesting. But let’s take a look
at a real app that uses a bunch of different
custom elements that we have and the material design
elements that we have. So this is the
example mark-up here. It uses paper drawer panel,
with the app drawer panel, a paper toolbar for that
blue toolbar up top, paper menu, a bunch of
different custom elements here. And we’re going to all load
using these two techniques. It’s important to note I
didn’t write any CSS to make this look like this. CSS all comes for free
via these elements. It’s one nice thing of
web components, right? You’re encapsulating your
CSS and logic together. So let’s see what happens. In this example, this is
the asynchronous version. We’ll start to load the
page on a 2G connection. Immediately you see those pixels
of the screen, but what you see is the actual menu items of
that unupgraded app drawer. So you’re seeing that
stuff before it’s actually ready to go. So we’re getting
pixels of the screen and then eventually, boom,
the entire app renders and you have the final layout. But that’s no good,
because we have that flash of unstyled content. Now the synchronous
version, if we just want to compare it to
the old version, what this is going to do is basically
just going to sit for a while. So we’re not using any of the
async attributes on the link or script, we have the
unresolved attribute on the body, so it sits there,
it sits there, it sits there, and then finally,
when the app is ready, Polymer unveils the entire app. Your layout’s ready to
go, but it’s no good because we basically have a
white screen for a long time. So these two problems
we have to deal with. We went async with everything. Now we have to deal with this
flash of unstyled content of that app drawer. And the reason for that,
again, is because we went async with this HTML import. Until elements.html
loads and provides all of the CSS inside
of it, we get nothing. And we remove the
unresolved attribute on the body to say we want to
render as quickly as possible. So it’s kind of cool
in building this slide deck, I came to the realization
that custom elements are really just progressively
enhanced mark-up. And the idea with
this is that you can declare an x-foo or a
paper toolbar on your page, and until you call
document.register and tell the browser about
this custom element, it basically just sits
there as an unknown element to the browser. But it’s cool, because
we can actually style this element using CSS
and other parts of the platform until it’s in this
supercharge mode. And this is the idea
of the app shell. It’s progressively
enhanced HTML. The idea is that you provide
all the critical resources up front. Your basic CSS, basic
layout, and basic markup, have it ready to go on the page. And style it to mimic what the
final layout is going to be. So it’s kind of cool, because
you get pixels to the screen right away, but users just
see things upgrade in place. And since we can style
mark-up before it goes to the supercharge
custom element, we can do this very easily. So custom elements has this
unresolved pseudo selector, this pseudo element that you
can use to style mark-up custom elements before
it gets upgraded. So for this example,
maybe what I want to do for this is app
drawer icon, since it’s not interactive yet, maybe
I just want to hide it. So I’ll actually do that. I can style it
using display none and hide that paper header
panel, based on the fact that it’s in this
unresolved state. It’s not useful, so
we’ll just hide it. The second thing
I can do is I can mimic some of the styling
of the paper toolbar that the paper toolbar
CSS actually provides. I can style that and target
it in its unresolved state and make it look like
it’s a 192 pixel height and with a blue background. So this is mimicking some of the
styles inside of paper toolbar. So let’s take a look and
see what this actually does. This is the same app using
this notion of an app shell. So this is going to take care
of flash fun styled content. So we’re on a 2G connection
in the dev tools. We’ll load the page. And immediately what
you’re going to see is basically the final
shell of the app. And you can imagine data
requests and other things happening here. It’s a very simple app now. But what you saw at the very
end, when everything loaded, is the app drawer
actually come in. It’s been upgraded
to its final state. Now the user can
interact with it. So I’m able to then
style it appropriately. So for this app,
this very simple app, the app shell idea took off 100
milliseconds of first paint, so you can see this stuff
adding up over time. Hundreds of milliseconds as
you make these improvements. But I wanted to do
this in a real app. So I made all these
improvements to PolyMail, which is a version that
mimics the native Gmail app. It’s all written Polymer
1.0, it uses ES6 classes, and async imports, and all the
cool stuff like service worker, does offline caching. It looks great. It uses the gesture library
that Dan talked about before. But all this stuff
together, let’s see what this did on a real app that
touches API’s, and does log in, and all that stuff. So on a cable
connection on desktop, this is what the app looks like. And if you’re
curious, the web page test results are below here. 589 millisecond first paint. So just over half a second. That’s really good for an
app that does a lot of stuff. One second total load,
so that’s everything. That’s data loading, that’s the
user logging in really fast, the coveted one second load. And the speed
index is a 101,144. And speed index
is the measurement of the visually
completeness of your app. So that’s actually really good. On a 3G fast connection,
on a mobile connection, on an actual hindered CPU,
let’s see what the numbers are. 1.66 first paint,
so pretty good. Seven second load, that
went up a little bit. I’ll talk about that. And then we have, of
course, because of the load time went up, the speed
index also went up. So let’s dissect the
waterfall events, see what actually happened
in that seven seconds. It’s interesting, because
most of the Polymer stuff– so your loading elements,
.html, bundle JS, the vulcanized bundle, all that
stuff is finished pretty early on, maybe two seconds,
three seconds. All the other stuff is
actually Google’s API’s. It’s like loading
I frames, and RPC calls, and analytics, and font
API’s, and the list goes on. It’s crazy. So it’s actually showing some
of the slowness of our API’s. But the interesting part, the
Polymer part’s done very soon. I also did the app
shell idea in PolyMail. So on a desktop, you
get the true app shell that we just talked about. It kind of mimics the
final version of the app, styles it ahead of time,
and then data loads in, images load in, the
user’s logged in as well. On mobile, it’s just
a splash screen. I decided to do something
little different for mobile, because I
thought it was interesting. It’s kind of a mobile paradigm. And it was cool to show
two different versions. But same idea, pixels to
the screen right away. So the changes we
made for fast load. Make your imports async, don’t
block rendering of the page, make your polyfills
conditional or make them async, manually load and
prevent [INAUDIBLE] using the unresolved
attribute, remove that. And then the app shell
idea is really critical to get pixels to the screen. So these things together will
get you a really fast load time. But load time is
just part of it. So let’s talk about what happens
when someone’s loaded your app and they’re inside of it. So I’ve been on the Polymer
team now for a couple years. I’ve built a lot of apps in
the old version of Polymer, a lot of apps in the
new version of Polymer. But there’s kind of
commonalities between the two different versions. So here’s some quick tips
for building fast apps when you’re inside of it. First is use native shadow DOM. Native shadow DOM is going
to be faster than shady DOM if the browser has it. And that’s because it
runs C++ a lot faster than your JavaScript. It does recounts
faster, layout faster. It does scoping,
DOM scoping faster. So you have to opt into
this in Polymer 1.0. If you set
Polymer.dom to shadow, that will opt you into shadow
DOM if the browser has it. Otherwise you get the
shady DOM polyfill. But this will actually be faster
than working with shady DOM. So that’s a pro tip. Do that if you can. Dan talked a little bit about
the touch stuff in Polymer. Use touch events, rather
than click events. We still suffer from that 300–
that dreaded 300 millisecond delay on some mobile browsers. Chrome and Firefox
have fixed this if you have the right
metaviewport tag. iOS still suffers from
this in certain cases. But the moral of the
story is use ONTAP. Don’t use on click if you’re
working with gestures and user interaction. That’s going to be
faster and get us closer to that 250 millisecond mark. Next thing is yo, don’t
jank those ripples. Those ripples are
awesome interior design. We have all these cool effects. A really common
thing I see is people put an anchor inside
of a paper button or a paper fab, the
full interaction button. And what happens, by
default what happens is, you can actually exhibit
this in the Polymer site. Let’s see that one more time. When you click the
button, the ripple just stops halfway through, right? And that’s because the
browser is no longer running your JavaScript
in that CSS transition. It’s gone to navigate
to a new page. So an easy way around
that is basically just hijack the click on the anchor
tag and wait for the transition end event, that ripple fires. And then redirect the
user to the new page. If you don’t the
ripple at all, you can also have the no ink
attribute on the paper button. It’s really easy to deal with
the material design ripples. [INAUDIBLE] talked about
this in his presentation, the reflect to attribute. I see a lot of people
using reflect to attribute. Please don’t use this
unless you have to. The reason is because you can
have a huge JavaScript object property or a huge
array property. And every time you make a
change to those properties, it’s going to get
serialized back out from the property
to an HTML string. So that could be a source of
performance issues in your app. The only reason you want
to use reflect to attribute is if you want to style your
element based on the fact that it has this
attribute applied to it. So if I want to put a
border around my element when it’s selected,
I can do that and I need to reflect to
attribute in this case. But only use it for
styling purposes. The next one and a really
important one that we recently discovered is, yo,
don’t create thousands of elements on your page. You wouldn’t do
this in the world outside of web components. Why do it in the world
of web components? Do lazy render elements. So what do I mean by this? I actually did an audit of
some of the interesting apps out there, some of
the more modern apps that I think are kind of
a first class, single page applications, Google
Maps, contacts, Google Inbox, Calendar,
and GitHub and others that aren’t Google properties. When they load the page, they
basically create– most of them create a pretty minimal
amount of DOM nodes. So Maps, for instance, only
create 624 nodes at page load. That’s really good, a
small amount of mark-up. And then as the user interacts
with the page, of course they generate more. But on page load,
they’re doing a good job of kind of minimizing how
much stuff they throw down. I ran into this myself. I ported Chrome status,
which is a great resource if you want to know about
new web platform features in Chrome. It’s basically a
list of features. And people can expand the
cards and kind of drill in, learn more about
each of the features. I ported this to 1.0 and I
saw my performance go down. And I was like, what? That doesn’t make any sense. Polymer 1.0 is supposed
to be faster, right? So it turns out what I did was
that I moved from a font icon to using some of our
iron icon elements. When someone expands one
of these future cards, I have a lot of custom elements
inside of these features. So there’s a custom
element for the link that generates the link, custom
element for these colored icons, custom element for
the logos you see here. So a lot of stuff
is going on inside of each one of these 350
features on the page, way too much stuff on page load. Turns out, moving from
that font icon to iron icon is generating over almost
2000 icon elements. And so each one of those
has to run its JavaScript and do its own thing
to set itself up. So that’s no good. I was not being a
responsible developer. Of course we can do better. The moral of the story is
to generate less up front if you can. Now I had a list of
stuff and there’s no better element for that
than the iron list element. Very awesome, easy
to use element. It gives you a
virtual scrolling list that works great on mobile. And it only generates the DOM
that’s needed at the time. So the current viewport
that the user is viewing. I popped that in and saw
a huge performance gain. The second thing I did was
I used the template dom-if. So I only generated the
mark-up when someone actually expanded the card. There was no reason to
generate everything up front, because 90% of
the stuff would be totally invisible to the user
unless they expanded the card. Please don’t use dom-if
for stuff like this. So do not use it for HR tags. First of all, you
shouldn’t be using HR tags. Use CSS instead. But use it when the user is
interacting with your app and you need to generate
DOM based on the fact. So if you have a lot
of custom elements or generating a lot of custom
elements om DOM repeat, that’s a really good
time to use dom-if. So let’s see what these
two improvements did for Chrome status. About 4,000 elements at 1.2
second load time down to 53 milliseconds on page load and
383 millisecond load time. So it’s about 844
milliseconds faster just by reducing the amount of stuff
I was generating up front. All right, let’s
talk about a couple of really awesome new
features coming down the pipeline since we
have a little bit of time. The first is if you’re going
to dynamically load things, if you’re going to dynamically
load some CSS later on, maybe you’re loading
an import for later with a bunch of custom
elements inside of it, we have the ability very soon
to preload, to tell the browser, hey, put this resource in my
cache and have it ready to go. So this is a high
priority resource. It’s going to go off and
fetch that no matter what. You can declare it as link
rel preload with the resources to a link to your
attribute, your resource. You can set an HTTP header. Or if you want, you can
dynamically load it as well. So you can set the rel
in the preload in script. The other thing that’s really
awesome is HTTP 2.0 Push. This is really cool. You can preemptively when
someone loads your index page, push the resource to
the browser’s cache and have it ready to go. I got so excited about
this, I spent a little time on app engine putting
together a little demo. So the URL is here. And you can see
the web page test results for the different runs. The left side shows the
version of the app that has a bunch of HTML imports on it. It’s a Polymer app. And you can see the waterfall
load this stuff as time goes on. So no HTTP 2.0 Push
in this example. Traditional waterfall loads
in about 516 milliseconds. On the right side, you can
see what happens immediately when server push is enabled. So this is sticking
all of the HTML imports in the browser’s cache, the
CSS, the JavaScript files. And the timeline is
completely different, right? So there’s no green,
there’s no network requests being made because these
resources are already in the browser. And you can see that reduces
the page load time just by doing HTTP 2.0 Push by 60%. That’s insane. That’s awesome. What’s even cooler is that if
you drill into these web page test results, you see
that it’s actually faster to load a bunch of smaller
files and push those, rather than vulcanizing
your entire app. So the browser is actually
better at loading small chunks rather than loading this huge
file with JavaScript, CSS, and HTML in it. So it means we don’t the
catenate our JavaScript anymore. We don’t have to
concatenate our CSS anymore. We don’t have to
have all these crazy build tools [? Addy ?] spent
a lot of time talking about. This is really exciting
and a lot of servers are actually enabling this. This is really
well supported now. If you want to
check out the code, app engine actually
has a special header that supports this today. So you can push
resources on app engine. So lastly, before I leave
you, if you can’t measure it, you can’t improve it. Paul Irish talked about the
Polydev Chrome extension that gives you the atomic
weight of all the custom elements on your page. You can identify
what’s at fault. I’ve also worked on a little
performance bookmarklet, if you want to install that. It gives you some other stuff. It gives you things like what’s
the first paint of your app, how long do the HTML
imports take to load, maybe there’s one that’s
hindering your page load. It also gives you a number
of elements created. And it gives you
things like what properties on these customers
are using that reflect to attribute property. So check that out. That’s actually really useful. Anyways, thanks
for sticking around for the entirety of the show. I know it’s late. You guys have had a
lot of information thrown to you today. Feel free to again hit me
up on Twitter or Google+. I really appreciate your time. Amsterdam has been awesome. Thank you.

9 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *