Using Web Components to Build PWAs (Progressive Web App Summit 2016)

Articles, Blog

Using Web Components to Build PWAs (Progressive Web App Summit 2016)

Using Web Components to Build PWAs (Progressive Web App Summit 2016)


[MUSIC PLAYING] Thanks for sticking
around, everybody. We’re going to talk about how
to use Web Components to build Progressive Web
applications and the notion of progressively enhancing your
markup using custom elements. So this is me. It’s at @ebidel on
Twitter and GitHub. I act crazy sometimes. I’m a digital Jedi at Google,
which basically just means I’m a web developer that helps
other web developers outside of Google build a
really awesome web apps. And I’ve built a number of
web apps at my time at Google. So we’ve worked on things
like developer sites for HTML5 Rocks. We built developers.chrome.com,
Polymer’s website, chromestatus.com. Santa Tracker is not a
developer site, obviously, but it’s kind of a fun
project that we work on on Developer
Relations, so you can track Santa around the globe,
see where he delivers presents. We built the CodeLab site,
that you see here today at the show in Polymer. And we’ve also worked on the
Google I/O Progressive Web App the last couple of years. Speaking of the
last couple years, I’ve gotten really excited
about Web Components– all these emerging standards,
several new APIs that allow us web developers
to build applications. Code reuse, reusability,
these are really good things for the web. And it’s important to know it’s
a set of emerging standards. So it’s not just one API. It’s a few. And you can use them
each individually. That’s totally cool. But together, it
becomes a web component. And it becomes very powerful. So they allow us to extend
and create new HTML elements. That’s the core, that’s
the principle here. And over the last
four or five years, since the standards
first started to evolve, we’ve grown the
community quite a bit. So there’s WebComponents.org,
which you can check out. It’s got articles. It’s got people
there contributing. That’s also where the
polyfills are hosted. If you want to actually
write an app in production, you can do that
using the polyfills. So check that out. It’s a great resource. But browser support has
actually been lacking. So I think Chrome 36
was the first browser to land all of the
web component APIs. So you can create a component
without the use of a library. That’s really awesome. But browser support
has been lacking. And so if you take away nothing
today from this presentation, take away this slide. Basically what’s happened
is that Chrome implemented the APIs very early on. So Shadow DOM, Custom Elements
Template, and HTML Imports, this is what the
API calls look like. These are called
now the v0 APIs. What’s happened is that
the browsers recently got together and
said, hey, we want to tweak little
things here and there about each of these specs. And they decided and
came to consensus. And what’s going
to happen now is there’s a new version of
Shadow DOM and Custom Elements. We call these the v1 APIs. So if you’ve learned about
Web Components in the past, all the concepts still apply,
same stuff still applies. It’s basically just
syntax and name changes. In this presentation,
I’m not going to talk about Template,
which is actually supported in all the modern
browsers now, which is great. You can use that
without a polyfill. And I’m not going to
talk about HTML Imports. I’m just going to focus on
the two that have changed. And implementation in the
browser is well under way. So Chrome has started in on
Custom Elements and Shadow DOM. You can play with these
today in Canary with a flag. Over at Firefox, Wilson Page,
who’s an advocate there, tweeted this recently. So they’re actually started
in on Shadow DOM v1. Custom Elements has an open bug. They haven’t started that yet. Safari, last week, in fact,
at WWDC, and now, Safari 10 will ship with Shadow DOM. So that’s two browsers this
year alone will have Shadow DOM. And they’ve also started in on
the new Custom Elements API. We thought that might
land in Safari 10, but it just didn’t
get in on time. And over at Microsoft, Edge
is playing crazy catch-up, implementing all these
things– service worker, push notifications. You see Shadow DOM
and Custom Elements and web payments on there. So very exciting times
for web developers. This stuff is coming in hot. Love it! Browser support really matters. Turns out, native
support really matters. We don’t want to use polyfills
for the rest of our lives, right? We don’t want to drop in
a lot, a needless library, that we don’t have to. And the notion that
polyfills goes away over time is the promise of a polyfill. The other reason
browser support really matters to me is because,
if you look today at all these libraries
and frameworks, they all produce
and allow developers to create components. But there’s sort of no standard
way to build the component. Some use a lot of JavaScript. Some use more HTML
and JavaScript. Some just use a
massive amount of HTML. So the goal of creating
a tab strip is the same. You want to create
a usable tab strip. But the way you
get there is very different across the board. And so that’s what Web
Components is here for. It’s here to have a standard way
to create reusable components. So a Web Component tab strip
would look something like this. It would be declarative. It would be my-tabs. We’ll just call it that. It’s got a selected
attribute that you can use to select the tab
that should be selected. And it’s got div
for its content. It’s got HTML attributes. It’s very readable and very
clear what’s going on here. The other thing that
people often forget about is the fact that it’s
just DOM and HTML that we’re creating means we’re
integrated with the browser. We’re integrated
into the platform. So Rob mentioned all
these great accessibility features that the browser
and the HTML elements already give you. We’re integrated with that. We can take advantage
of what’s already there and don’t have to reinvent
the wheel ourselves. And we’re also integrated with
every web developer’s best friend, which is the DevTools. And I’ll show an
example of this. This is Polymer’s old site. And I’ll just fire
up the DevTools and show you what happens
and how you can kind debug a Web Component. So this is a tab, a
selecting tab strip that we have for our code. You can kind of poke
around and see the code. And then you see the live
output on the right there. So I’m just going to open the
DevTools, open the Inspector. And immediately, it’s
just DOM and HTML, so I can just poke around
with the Inspector, see what’s going on, see how
this custom element is built. You see that there’s
Shadow DOM attached to some of these elements. I can open up the console. And I can start
tweaking properties, DOM properties of this element. So I can change its
selected property. And you can see it
live update in the DOM. Since we’re integrated with
the platform in all ways, we can actually change CSS and
affect how this component looks just by changing the color of
the text of one of its panels. I can discover it’s got a panels
property, and it’s got an API. It’s got methods. So things like updated
panels, we can call that and it does something. Who knows what it does,
but we’re discovering that in the DevTools. And it’s got a bottom property. This one happens to actually
change the way this thing renders. And by changing that, you
can see the live DOM update. So this is really cool. And the fact that
we’re integrated with the tools and
the platform means we can build apps even better. So for the rest of
the presentation, I’m going to show a lot of code. Web Components is
one of those things where it’s a developer feature. It’s for us. It’s not a fancy feature
like WebRTC or WebGL. So it’s not sexy. It’s actually for ergonomics. It’s for you guys to
produce reusable code. So let’s dive in. Let’s talk about
Custom Elements. Custom Elements is sort
of the foundational piece of Web Components. And it allows you
to create new HTML and tell the browser
about that HTML. So let’s build a button. There’s no better
way to actually learn this stuff than to
actually build something. So we’ll start simple. We’ll build us a very simple
component, a button component. Now, you probably know, right,
HTML already has a button. But a lot of people
stopped using the button over the years. It doesn’t look that great. It renders differently
in different browsers. But it’s got a lot of
great built-in features, if you think about it. Rob talked about focusability
and keyboard behavior. It’s got that built in. When you tab into this thing,
it actually highlights itself. And it has this effect,
this gradient that applies. It’s got special properties
to treat it like a button. It’s got a disabled
attribute that you can apply that actually affects
the rendering of this element. And if you put this in
a different context, if you put a button
inside of a form, it actually does
something different. It submits the form
and participates in the form submission. So this is magical
and kind of cool. We get all this
stuff for free just by declaring this on the page. But of course, we want to
create an even better button. We’ll call it a
Fancy Button, that’s got a nice little
ripple animation when the user clicks on it. And it’s got a box shadow if
you apply this raised attribute. And we can make this thing
behave exactly like a button. So I’m going to do what
Rob told you not to do, which is create a div button. Don’t do this. This is just building
up the example. But this is the way you
might start off with this. And you could start
by using button and then remove the default
styles the browser applies. But we’re just going to
have a blank slate here. Create a div. Give it a class, better-button. And essentially, that class is
just going to style this thing to look like a button. So it’s got a box shadow. It’s got some border radius. It handles disabled
state, if you have this disable class on it. So we’re just making a thing
that looks like a button. But of course, if you want this
thing to quack like a button, you have to do some things. So we’ll add a tab index 0
and a role equals button. We’ll actually tell
the screen reader that this thing is
going to be a button. And we’ll allow users to
keyboard into our button. And maybe, we want to
have this ripple effect. That’s the extra functionality
we’re adding to the button. So we’ll rip the
element out of the DOM and attach an event
listener for that. And if the button
isn’t disable, we’ll allow people to click on it. And we’ll draw the ripple
animation– so very simple, not much going on here. But this has a couple problems. The first is that
users have to know how to style this thing themselves. They have to kind of know all
that magic that we gave them, put that on their page. And the second is that
we have to add a click handler for all instances
of the button that are used on the page. It’s not very reusable. I can’t really reuse this thing. Users have to add their
own tab index equals 0 and roles equals button. It’s not very componenty. So of course, we can do better. And the answer is to use
a custom element for this. We can create something
that’s reusable. So the first thing
I’ve done here is just replace div with
the name better-button. I’m creating a custom element. And custom elements have to
have a dash in their name. That’s just part of
the specification. We are calling our
button a better-button. And instead of CSS classes
to style this thing, I’m just going to
use HTML attributes. It’s a little cleaner to read. And it’s going to
map really well to the JavaScript
properties we’re going to define on this button. In my CSS, all I’ve done
is just replace classes with that new tag name. Nothing’s changed about the
appearance of this thing. Of course, we can
do even better. We can progressively enhance
this markup from its self right now to something better. We can give it an API and
use JavaScript to do that. And so the way you create
a custom element, just define an ES6 class. We’ll call it better-button. And it’s going to extend the
browser’s native HTML element. So it’s going to gain
all of the DOM API. And since we’re
creating a button, maybe you want to have
a disable property, just like the normal DOM button has. So we can do that in ES6. Just define a getter and
a setter for disable. And in this case,
what I’m doing here is I’m keeping the
attribute in sync with the JavaScript property. So if somebody changes
the JavaScript property in JavaScript, we’re going to
actually reflect that value back out into HTML and set
the attribute on the HTML tag in the live DOM. And other properties
on the web today do this too– the hidden
property, the ID property. If you set them in
JavaScript, they’ll actually update the markup as well. So we can do that
in custom elements. Something else we can
do is go the other way. So if somebody
tweaks an attribute, you want to get a
notification about that. You want to react to that. And so for that, there’s
two special things you can do in element code. The first is to define an
observe attributes array. This is sort of a
white list to say, any attributes in this array,
I want to get a callback for. And so what the
browser is going to do, if it observes a change to
one of these attributes, is call your attached
attribute change callback. It’s going to provide
the name, the old value, and the new value of the
attribute it’s changed to. And here, what I’ve
done is basically just look at the
disable property when that happens
and set a tab index in an aria-disabled
attribute accordingly. So we’re kind of
mimicking the button again by adding this
functionality ourselves. Something else you can do is
run code every time an instance, or your element is created
and declared on the page. So to do that, we’ll just
define a constructor. And the first thing we need
to do in element constructor is call super. That’s just part of the spec. It needs to set up the
prototype correctly. And then from there, you can
put in whatever you want. So in our case, we’re going
to define a key down listener. We want to actually
have, when people press Enter on our
button, to react to that. And that’s what this code does. And then we’ll add that
click listener that actually adds the ripple animation. So if the button
is not disabled, we’ll allow people to click
it and draw the ripple at the user’s click location. We can also react to when our
element is added to the DOM or removed from the DOM. And so for that, we can
use the connected callback. This is a great time
to do any setup work. In our case, we’re adding
the role for screen readers, and we’re adding tab
index 0 at that time. And disconnect it while we’re
not using it in this component is actually very valuable
for things like clean up. So if you had a bunch of event
listeners in your component or do other kinds of set up,
you can remove it at this time. The browser will
call that method. If you’re curious on how
draw ripple is implemented, it’s pretty simple. This is the way I’ve
chosen to implement it– just a div with a ripple
class that styles this thing to look like a ripple effect. So it’s a div with a border
radius that kind of just grows and fades out. Pro tip, I’m using
CSS containment here. We’re creating a
reusable component that’s self-contained. Might as well tell the browser
to optimize this for us, so it doesn’t do– it knows
to scope layout in Paint. That’s awesome. We can just basically drop
that in our Web Components. And the other thing I want
to point out about this code is I’m basically creating
a declarative style API and providing a little bit of
functionality and flexibility for this component. So if some user that
uses better-button styles the color, the
font text with red, the background ripple
is going to be styled the same as the font color. So this is kind of cool. You can do these things
in your components and document how this
stuff works for users. Now, we’ve got a component. We’ve defined its ES6 class. The last thing we need to do
is actually tell the browser about this new element. And so the way you do that
is to use the custom elements interface and call
the define method. You pass it the tag
that you want to create and the class definition
that you just created. And then from there, the
users can use this element as if it was a div or a span
or any normal HTML element. They can declare
it on their page. They can use the new
operator in JavaScript with the element constructor. Or they can call
document, create element. The same tricks apply that
always apply to the web. So that’s a lot of stuff. If you think about
it, there’s a lot of CSS involved to make
it look like a button. We have a class that mimics
all the behavior of button. And the last thing we do
is have the registration. We actually tell the browser
about our new element. We can do better. We can progressively
enhance what’s already available in the web platform. So again, we have a
button already in HTML. Why are we
reinventing the wheel? Why are we making
everything it does for us? Why don’t we just progressively
enhance the bottom element? Well, sure enough,
we can do that. Custom Elements allows
you to extend HTML that’s already existing in the web. So instead of extending
the HTML element, we’re going to extend
the HTML button element. We’re going to extend
the specialized version of that element. And then what happens is this
is our entire class definition. So all that work we had to
do before with tab indexing and key down
listeners, all of that stuff goes away, because
we get the DOM properties and methods that button
has for us just for free. And then we add the
extra functionality. In this case, it’s
that ripple animation with the click handler. So this is really convenient. This is called a customized
built-in element. And the way someone
declares this on a page is a bit different. So instead of using
better-button, they’re going to actually
declare a button. And it’s going to be
an is better-button. So this button is
a better button. That’s how that reads. So we’ve done a lot. We’ve made a custom
elements, and it’s reusable. It’s a fancy button, little
sexier than the normal HTML button. But it’s got all the benefits
that the original one had in it. I want to take a little bit
aside and talk about this call here, the notion that you
call customElements.define with the tag name. And you kind of
upgrade the element. What’s happening is what is
called an element upgrade. This is part of the
custom element spec. You kind of endow your
markup with an API by defining some
JavaScript that gives it its API and its
methods and properties. So you take an HTML element, and
you make it something better. So the notion here is that
custom elements are really just progressively enhanced markup. You’re taking something that
doesn’t have any functionality, and you’re
progressively enhancing it to have functionality. We’ve taken advantage of this
notion of element upgrades on the developers CodeLab site. We built it in Polymer. And if you dissect
this thing, you can actually see where the
components live on the page. So our Search bar
is a component. Our sorting and filtering
widgets are components. And also this main
section in the center is a card-sorting
element that knows how to rearrange its
children based on the user’s sort and filtering. So that’s how this thing
is kind of structured. I want to show what happens
when we load this page. I’m going to slow it
down to a 3G connection, so you can really
see the effect. So we’ll navigate to the page. And instantaneously, it paints. The browser, it
turns out, is really good at just painting pixels,
HTML, CSS, very small payload. And then eventually happens
is the components fade in. We’ll see that one more time,
because it was kind of fast. So first paint is really fast. And then as the
components upgrade and get their super charged API,
they fade in asynchronously. So it turns out
this is really fast. I clocked this at
about 2.2 seconds for a first paint on a
Nexus 5 3G connection. And of course, this
would get even faster with a service worker,
if we had caching and the user came
back to the site. So the way you do this is to
use another feature of custom elements. It’s the CSS colon defined. So you can basically
pre-style elements before they’re ready, before
they get their JavaScript API and you call
customElements.define. So in our case, I’m styling
that paper tabs, that selecting widget, basically
giving it a little layout, giving it initial height to
replicate the styles that paper tabs defines in it. And I’m also just hiding it. On page load, it’s just hidden. And then when the browser
removes this defined pseudo class, our element
will transition in. So the fact is that
these things kind of just take up place holders,
and you see them just fade in when they’re ready. The rest of the page
is very simple, just a little markup at the top
to describe the website. We have that sorting
and filtering section. That’s got some custom elements
in it, the paper tabs element. And the main bulk
of that intersection is this card-sorting element. It’s just a custom element. It’s on the page, ready to go. Everything is server
side rendered. So all these links just
show up on page load. And they’re styled to
look like a CodeLab card. So this is great. This paints really fast. The content is there,
available to users. They can start
reading it right away. And then eventually, at
some point in the future, we actually registered these
elements in the browser. And at that point, that’s when
the card-sorting element gets its API, gets its filtering
API and its sorting API. That’s not critical
on page load. So it’s a great story for
progressive enhancement. Hansen So let’s think about
what we’ve done so far. In a very short
period of time, we’ve created a reusable component. It’s got Custom Elements API. It’s got reusable styles. If you drill down
into the dev tools, you can actually see
a problem, though. One of the implementation
details is kind of leaking. That’s, this ripple div that
we’re creating dynamically is kind of mucking
with the user’s DOM. It’s taking up space. It’s an implementation detail. They really shouldn’t see that. We can fix that. The answer here is to use
another part of Web Components, which is Shadow DOM. So if you think about
today’s elements, there’s the select element. And you drop a couple of option
tags in it, give one of them the selected attribute. And somehow, the
browser magically knows to render this
thing as a dropdown widget and select the correct element. That’s kind of cool. If you have the multiple
attribute to this HTML element, it completely changes
the way it looks. So instead of a dropdown, you
get this MultiSelect widget. Pretty magical. The video element, couple of
attributes on the single tag. You get an autoplaying video
with video controls for free. And where did these
UI controls come from? An input type=’date’, or any
of the various specialized versions of input
are pretty cool. Instead of a text
box, text input, you get a UI that allows
you to select a date. So this, my friends,
is Shadow DOM. Shadow DOM allows us to do the
same thing the native browser vendors have been
using for a long time. They use Shadow DOM to
create these widgets and hide away markup and CSS. We can do that inside of
our component as well. So I’ve already used Shadow
DOM inside of a custom element. It’s just one or two lines. So inside of the
element constructor, we can call this
dot attachShadow. That’s going to create a
document fragment, a shadow root, that’s going to kind of
attach itself to our element. And we can fill that
document fragment with anything we want,
any markup you want. In our case, we’re bringing
in those styles from the page, so users don’t have to
define them themselves. This is great, because
styles instead of Shadow DOM are scoped to your element. So that means styles and
selectors aren’t going to bleed out into the page. And page styles aren’t going
to bleed into your component. So that’s why you see I’ve
replaced some of the collectors here. I’ve changed better-button
to use colon host. That’s the way you style
the element itself inside of Shadow DOM. And my selector for the
ripple div has gotten simpler. We’re just using dot ripple now. And that’s because, again,
these styles are scoped. We can use common IDs again. We can use common
class names again. They’re not going to
collide with the main page, because we’re inside
of Shadow DOM, which has the scoping for us. And the last thing you see is
I’ve moved in this ripple div. It’s become part of
the implementation detail of my component. Users are not going to see
this inside of their DOM. So let’s take a look at this. When you do this, when you call
this attachShadow root, what happens is that you get
a shadow root attached to your better-button. That’s great. We have Shadow DOM in the works. And you can see that we have the
styles– scope to this element. You can see the actual
style information. This thing looks
like the button, but it’s actually
lost something. It’s lost that fancy
button text that we have and that users have declared. And so the reason for
this is that Shadow DOM is kind of like a party. You have to invite
things into it. You have to invite things
in the outside world to render inside
of your Shadow DOM. For this, you use Composition
with the slot element. Slot is another element
that’s part of Shadow DOM that you can use for
bringing those things into your Shadow DOM. So let’s say I have a Shadow
DOM that looks like this. I have style information
and a slot element. What’s going to happen here
is that, when someone declares a better-button on their
page, fancy button text is going to get
projected into this slot. It’s going to render
at that location inside of my Shadow DOM. And the effect is that
you get the text back. That’s exactly what we want. You can also have
default content. So if someone doesn’t
provide the text themselves when they use our component,
you can render just button as the default text. And you can have an entire
DOM tree in here if you wish. It doesn’t have to just be text. And there’s also a
more advanced form. It’s called name slots. So we can have as many
slots in your component in your Shadow DOM as you want. In this case, we’re
defining a slot with the name equals
icon and a default slot that doesn’t have a name. When someone uses
our button like this, maybe they want to
have a little gear icon next to some text,
what’s going to happen is that the name slot is
going to get projected into that location. It’s going to
render in that slot. And the settings text is going
to render in the other slot, because it’s not using a name. The effect is exactly
what you’d expect, which is that gear icon
and a settings text. So I think of Composition,
I think of a slot element as kind of a declarative
API for your elements, a way to really provide
a level of functionality and customization. And this is exactly
what the select element and those other
native elements are doing, allowing for
you to configure that UI based on attributes. But let’s say somebody wanted
to use our button like this. They want a
ginormous pink button with a lot of padding and
a different ripple color. Well, since we brought the
styles into our component, how do you actually allow for
this type of customization? The answer here is to use
another new web platform feature, which is CSS Custom
properties, essentially CSS variables. So what we can do
inside of our Shadow DOM CSS is basically have
placeholders for users to fill, if they want, if they so choose. Support for this is really
good right now, actually. Everyone but Edge has this
available as a native feature. So when someone uses
your custom element, they define some CSS
for better-button, they can fill in the
values if they so choose. So in this case, they’re
defining the button padding, and they’re defining the size
of that ripple animation we’re going to create. And so in this case,
we’ll use those values. But if they didn’t
provide them, again, we’ll just use our defaults
that we set up ourselves. So CSS Custom properties
is a great way to have like placeholders
and allow people to customize your component and the styling
of your internal elements in your component. So that was fast. Let’s recap what we did. In a very short period of
time, in less than 30 minutes, we created a reusable button. It’s progressively enhanced from
the native HTML button, which means we have all the
benefits that the browser and that element
already gives us. It’s got keyboard behavior,
all the stuff that Rob talked about, baked in. It’s self-contained using DOM
and CSS scoping of Shadow DOM. It has an imperative
API, a JavaScript API, thanks to Custom Elements. It’s got a declarative
API, thanks to Composition and the
slot element in Shadow DOM. And it’s also
configurable, so people can use it in different
ways with HTML, and they can also
configure the CSS. That’s pretty cool. So this is why I’m so
excited about Web Components, because it allows
for this flexibility, allows for the good
reuse on the web again. And this is just a button. You can imagine building an
entire app out of Components. So if you do want to
get started– go back– you can actually check out
and try to use Web Components yourselves. So Polymer has put together
a really awesome set of reusable and very
useful components for different types of things. And there’s also
customelements.io that has a list of Web
Components you can check out. So with that, that’s
all the time I have. Again, this is me on
Twitter, if you want to hit me up with questions. I think now we’re going
to transition to a break. We’re going to 20 minutes,
because those guys went over. But I really do appreciate
you guys sticking around. Thanks for your time. Go componentize the web. [APPLAUSE] [MUSIC PLAYING]

5 thoughts on Using Web Components to Build PWAs (Progressive Web App Summit 2016)

  1. If you get an error about window.customElements.define being undfined or null check here for the polyfill: https://github.com/webcomponents/webcomponentsjs/blob/v1/src/CustomElements/v1/CustomElements.js

  2. Do I have to define all my styles inside the <style> tag in the template? I mean sure, I want to keep those styles inside the shadow dom and not have them leak out but is it possible to do this any other way?

Leave a Reply

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