Getting your hands dirty and feet wet with Open Web Component Recommendations...sort of.
This a cross-post of a Feb 26, 2019 article from Medium that takes advantage of my recent decision to use Grammarly in my writing (so, small edits have been made here and there), thanks for looking again if you saw it there 🙇🏽‍♂️ and if this is your first time reading, welcome!
Welcome to “Not Another To-Do App”, an overly lengthy review of making one of the smallest applications every developer ends up writing at some point or another. If you’re here to read up on a specific technique to writing apps or have made your way from a previous installation, then likely you are in the right place and should read on! If not, it’s possible you want to start from the beginning so you too can know all of our characters’ backstories...
If you’ve made it this far, why quit now?
Separate Things Early, Often, and Only as Needed
Sometimes a little distance is a good thing.
Thanks to ES modules, it’s easy to put a little extra distance between things in your codebase, so easy that it’s also easy to put a little too much distance into your codebase. When you have too much distance in your code, it’s hard to comprehend how, where, or why something is happening, but when you have just enough, you can be unbothered by concerns that are of little importance to the task at hand. This story is best told in my To-Do app code through the two main approaches I’ve taken to including styles in components.
For context, let’s make sure everyone’s up-to-date on the static styles
property of LitElement
. Here we’re able to deliver an array of CSSResults
to our element that will be prepared as Constructible Stylesheet Object to be adopted by our custom elements when available, or to be delivered as <style/>
elements inside of the shadow DOM in contexts where this API is not available, or even via the ShadyCSSS polyfill as needed. This means that rather than each instance of your custom element receiving its own copy of its CSS rules and then relying on the browser to try and decide when to deduplicate them internally, you are explicitly telling the browser that this set of rules will be applied across multiple instances; skipping the heuristics and getting right to the benefits of deduplication even faster. These benefits include a faster boot up time for your end users, and not just because there’s less CSS on the page to parse, an easier debugging process for your fellow developers, as editing the styles of one element with dev tools will effects all instances of it, and more. Many benefits are still being actively researched as Constructable Stylesheets is a fairly new API.
To take advantage of these capabilities, and to focus the code included directly in the classes for my custom elements, I often start straight away by separating my styles from my functional/template code. This would look something like:
// custom-element-styles.js
import { css } from 'lit-element';
export const styles = css`
:host {
/* Styles for my custom element... */
}
/* Styles for the remaining content of its Shadow DOM... */
`;
// custom-element.js
import { styles } from './custom-element-styles.js';
// ...
static get styles() {
return [
styles,
];
}
The generated code for open-wc’s Starter App did not do this. By default it’s generated static styles
getter outlined styles directly, a la:
static get styles() {
return [
css`
:host {
/* Styles for my custom element... */
}
/* Styles for the remaining content of its Shadow DOM */
`,
];
}
This did mean that when I had a good amount of styles in a component that I was doing some manual work to separate the styles as outlined above. However, it also meant that in cases where there were only a few lines of CSS, I had a smaller file footprint by component. (No added files to ensure :host { display: block; }
made it into an element.) What's more in some cases where I might not have thought all the way through the compositional possibilities of my styles I was given an extra touchpoint to do so. This happened in src/to-do.js
who’s static styles
getter looks like the following:
import { toDoButton } from './shared-styles.js';
// ...
static get styles() {
return [
css`
:host {
display: block;
}
div {
border-top: 1px solid;
padding-left: 5px;
white-space: pre-wrap;
}
`,
toDoButton,
];
}
Yes, the toDoButton
import is a stylesheet that is shared between multiple components. I ended up choosing not to separate the to-do
element-specific styles into their own file, but having the toDoButton
styles separated as such allows them to be shared between the to-do
and to-do-write
elements, the effect of which you can see below:
This same concept can be taken one step further to share not just whole sheets, but lists of specific rules for individual elements. Included in the src/shared-styles.js
list of exports are formElementFocus
and formElementHover
:
export const formElementFocus = css`
outline: none;
background-image:
linear-gradient(
to top,
#0077FF 0px,
#0077FF 2px,
transparent 2px,
transparent 100%
);
`;
export const formElementHover = css`
background-image:
linear-gradient(
to top,
currentColor 0px,
currentColor 2px,
transparent 2px,
transparent 100%
);
`;
These styles are mixed both into the button
rules included in toDoButton
but also are applied to the textarea
in to-do-write
. LitElement
and lit-html
open the door to these sorts of techniques and many more yet to be fully explored. Drop a comment below with ways are you separating, sharing, and composing functionality/styles/etc. to the benefit of your applications. Judiciously leveraging opportunities like the ones above and those shared in the comments below to separate your code (or not) can give you a broad pallet of options when it comes to composing features to new ends.
PRO TIP: while using LitElement
's static styles
getter you will be relying on the adoptedStyleSheets
API in browsers (like Chrome) that support it. In Chrome, there is a bug whereby styles that are added to your custom elements in this way will not be directly editable in the Chrome DevTools. While this might be seen as an added benefit (on top of the general performance benefit of Constructible Stylesheets generally) in production, it can be a pain during development. If you'd like to get the ability to edit those styles at any part of your development life cycle, the following snippet can help!
<script>
// @TODO: Remove this after user agent stylesheet bug is fixed
// https://bugs.chromium.org/p/chromium/issues/detail?id=946975
delete Document.prototype.adoptedStyleSheets;
</script>
The Short Game
As voted on by a plurality of people with opinions on such topics that are both forced to see my tweets in their Twitter feed and had a free minute this last week, a 9000+ word article is a no, no.
So, it is with the deepest reverence to you my dear reader that I’ve broken the upcoming conversations into a measly ten sections. Congratulations, you’re nearing the end of the first! If you’ve enjoyed yourself so far, or are one of those people that give a new sitcom a couple of episodes to hit its stride, here’s a list of the others for you to put on your Netflix queue:
- Not Another To-Do App
- Getting Started
- Test Early, Test Often
- Measure Twice, Lint Once
- Make it a Component
- Make it a Reusable Part
- Does Your Component Really Need to Know That?
- Separate Things Early, Often, and Only as Needed (you are here)
- Some Abstractions Aren’t (Just) For Your App
- Reusable and Scaleable Data Management/And, in the end... (You can start to see the end now...can’t you?) (Coming Soon to dev.to)
- See the app in action
Special thanks to the team at Open Web Components for the great set of tools and recommendations that they’ve been putting together to support the ever-growing community of engineers and companies bringing high-quality web components into the industry. Visit them on GitHub and create an issue, submit a PR, or fork a repo to get in on the action!
Top comments (0)