There’s a fun metaphor to describe the benefits of a component based front-end development ecosystem that works great whether you’re talking about native web components or librarygeneratedcomponents: “they’re the LEGO® blocks of the internet.” It is a moving sentiment for someone who grew up with LEGOs and easily evokes both the emotion of the brand new set that explicitly comes together as the Millennium Falcon™ and the bucket years later full of time tested remains that come together in new and different ways each time they’re poured across the table. This concept can feel especially promising to someone who wants to build something as specific as Bugatti Chiron knowing that it’s been done before and can be done again. However, while being able to build a Saturn V from parts might get you excited for your own BFR, it doesn’t explicitly prepare you to do so “out of the box”. For this you’ve got to go all the way to the factory and start pressing your own components.
A simple beginning
Creating your own web components is pretty straight forward. Your first custom element can be as simple as:
customElements.define(
'custom-element',
classextendsHTMLElement{
constructor() {
super();
this.innerHTML = '<p>This is a custom element.</p>';
}
}
);
Making the upgrade to Shadow DOM is just one extra line of code:
Getting the baseline application of Custom Elements, Shadow DOM, and the <template/> element into your web component requires far less than the capabilities that become available from there would imply. However, from there the techniques available to you and expected of you are many and varied. Here are some particularly worth abstracting out of the way of your day-to-day:
The responsibilities are enough for any sane developer to wonder if they’re in the wrong business. Even deciding what to do on all these points is a daunting task, let alone working out how to manage them all together in one project.
They’re also a subset of developers would take on the masochistic task of actually managing all of these things by hand. However, another subset of developers think it is silly to do these types of things over and over again. So much so that they’ve created tools to do the work for us.
Taking as many of these things into account as possible, and building on the shoulders of those who came before, as well as a numberofprojects I’ve been a part of in the past, I’d like to introduce you to the Easy Web Component Generator. In it I’ve stuffed the best of tools that I’ve borrowed from others or iterated over in a number of generations of my own generators that really make development fun and easy, and moved to the newest, fastest, smartest versions available of each.
There are a number of great features that are outlined below, and before we get into that I wanted to touch on some of the structural differences you’ll see from previous work in this area.
The polymer-cli is alive and well, so much so that components built with this generator rely heavily on its capabilities, however I wanted to move away from a generation process that relied on it. Users new to the web components ecosystem should not feel like they are required to understand a multitude of tools and the intricacies therein just to get started making their first elements, and conceptual freedom from the CLI allows the generator to move between it and other tools as they may arise. To make both of these true, I chose not to make this generator compatible with the polymer init command. Skipping this also makes the naming convention of generator-easy-wc so much more digestible that what other options would have been. Along these same lines, while at current this generator creates a web component from the lit-element base class, particularly because of the way it packages together solutions for “Efficient, Declarative Rendering” and “Property and Attribute Management”, it just didn’t feel right making this a lit-element generator. Newer, better answers to the problems we face when developing for the web will come, and the hope is decisions made now will make switching to those, possibly even in the context of this generator, easier than ever.
Before I get into the things this generator can do, just a quick list of things it doesn’t do. You can certainly make an application out of a web component, and I have/do. However, this generator does not take into account many concepts that would make doing so easier: it doesn’t touch on E2E or integration testing, it doesn’t manage deployments, it doesn’t manage packages via a mono-repo, it doesn’t create a service worker or manage cache in anyway, it doesn’t make pie, it doesn’t clean your room, etc. It’s pretty opinionated as to what makes a good solution to a problem, so it doesn’t surface too many options when generating a new element. And, finally, it doesn’t do much in the way of telling you how to grow from the structure it generates; the world is, as they say, your oyster. However, if you really think that one of these things or something else cool should be included, you issues and PRs are greatly appreciated, so please submit away.
Not only is it difficult just getting your components set up with proper accessibility, it is super easy to regress in this area, or loose track of important changes in the field at large. Not worrying about it enough is costing companies money. To make sure you stay on the right side of the law, as well as the ~15% of the population that might benefit from enhanced accessibility trust axe-core no longer just extending your browser, but making itself known in your unit testing suite as seen below:
describe('your element', () => {
let element;
beforeEach(function() {
element = fixture('your-element-tester');
});
it('a11y', function() {
return axeReport(element);
});
});
Baking this directly into the generator means you can be sure that with every build that you’re putting your best foot forward when it comes to meeting the needs of users of all kinds. For more information on how axe-report works and what it’s going to ensure for you, check out this great talk from Google’s I/O 2018.
Continuous Integration
There are many and varied tools to work with when it comes to continuous integration. Easy WC gets you started in this are with CircleCi and defaults to two commands to ensure you’re delivering your best work:
- run: yarn nsp
- run: yarn test
nsp will check to make sure that nothing in your dependency tree is compromised, and test runs your unit tests which you’ll find out more about below. I’m looking forward to spending some extra time with this part of the generator as it can open up opportunities to go beyond the day to day best practices applied via other decisions and help support the longer development cycle of working with a project over time.
Efficient, Declarative Rendering
With the advent of the template literal, JS finally had a native templating language all it’s own. Using a template like `I ${canHaz ? 'can' : 'can\'t'} haz template.` gets you well on the road to more maintainable string based code, but when you tag that template html`I ${canHaz ? 'can' : 'can\'t'} haz template.` things really shift into high gear. Now you’re actually passing the template into a method where the template is turned into an array of static parts of the template as string, in this case ['I ', ' haz template.'] and a series of individual arguments for each expression in the template. Now, imagine that the string you are templating actually spells out DOM. This makes it pretty straight forward to manage the static parts of your DOM separate from the dynamic expressions therein, meaning testing for what changes are required when new properties are pushed into a template can rely on the a contained number of tests (O(expressions)) making those updates super efficient. With a goal of doing just that, while maintaining a continuously shrinking code size footprint (thanks to a close relationship to the proposed Template Instantiation spec) lit-html was born. Its happy medium between a fully virtualized DOM that is required to test all of your template (O(nodes) whether they’re static or not) on each update and a more focused templating library that might exchange test count (O(changes)) for a higher level of processing at both boot and change time. With these benefits in mind, lit-html by way of the Polymer Project’s lit-element is the basis of templating in easy-wc.
Getting started doesn’t take much more than the example above applied to the _render method supplied by the generator, and because it’s pure javascript you have first class access to all the capabilities therein, with none of the compilation:
_render() {
return html`${this._renderStyle()}${this.options.map(option => this.optionTemplate(option))}
`;
}
optionTemplate(option) {
return html`You have access to the ${option} option.`;
}
Disclaimer: lit-html is currently gearing up for a 1.0 release that will update some of the APIs and syntax for writing your templates. More on that soon!
Styling
You’ll notice the inclusion of this._renderStyle() in the template above. This is added by default when generating your element. This allows your element to rely on external files for styling, a la:
While offering a clean extension point _renderStyle() where you can easily add share style templates or style customization when extending a base element to create another. As you’ll find in the *-styles.js file, all styles can be defined with the html`` template tag as either a complete <style/> element html`<style>/* ... your styles here ... */</style>` or you can define them as a mixin of sorts that can be injected into one, a la:
You write perfectly clean code, every semi-colon, every line length, every attribute, every CSS custom property, perfection. We all know this to be true. However, at some point you’ll probably want other people to work along side you on your new element, and the best way to make sure their code is also spotless is to manage coding style via linting. On every commit, and on demand via yarn lint you get powerfully customizable linting from ESlint for your javascript, stylelint for your CSS, and HTMLHint for your HTML. Each is configured with great default settings for a new custom element, but from there you can customize to your heart’s desire via their documentation below.
By default the custom elements specification gives you the attributeChangedCallback(name, oldValue, newValue) that allows you to respond to changes to your properties and attributes. This is an exceptionally powerful way to manage the external API of your component, but as we all know by now “with great power comes great responsibility” and managing that responsibility can be a lot of work. In acknowledgement of that reality and to make sure none of us are just dangling out their by a thread, your generated component comes with the Polymer Project’s PropertiesMixin built in by way of lit-element so that changes to your attributes and properties feed directly into the rendering pipeline of your element.
This means that once you’ve described your properties and their types via a static getter forproperties they will be monitored immutably for injection into your element’s template.
The PropertiesMixin also gives you access to a _propertiesChanged(props, changedProps, oldProps) callback that will allow you to respond directly to changes to the element’s properties with things like DOM events, or other non-rendering side effects. This tied with the getter/setter pattern to surface computed properties sets your element up with a most flexible and powerful internal data management API.
Disclaimer: lit-element is currently gearing up for a 1.0 release that will update some of the APIs and syntax for managing your properties and their relationship to your templates. More on that soon!
Release Management
Once your new element is running like a champion, getting it released so you can safely depend on it is super important. Whether you’re only deploying to your own GitHub via tags or to NPM one quick call to yarn release has got you covered. With the full power of the release-it library at your disposal, making sure all your package files and you various repos and registries are up-to-date is a synch.
Beyond these basics, there is a whole litany of features available to your through release-it, I highly recommend perusing their documentation for ways that you can make it a more customized part of your develop/test/deploy workflow.
Unit Testing
web-component-tester is hands down the best tool when writing unit tests for code working with the combined realities of custom elements and shadow DOM. The way it brings together the power of spies, stubs and mocks by sinon.js with assertions by Chai in the Mocha.js test framework makes testing as easy as I’ve had the opportunity to apply it.
web-component-tester also connects with Sauce Labs for you to do remote cross browser passes on your unit tests. By default you’re set up to run your tests locally on the version Chrome available in your build environment, and remotely on the latest version of Edge and Firefox 60 for Windows 10, as well as Safari 11 for OS X 10.13 (10.12 has a fun issue where it requires compiled code due to an over zealous understanding of async as a reserved word, see #1 where this might be addressed). As capabilities of browsers change and new versions are released, this is something that will be important to keep an eye on. Particularly, the pending release of Firefox 62 and its native support for web components will offer a wealth of testing options not previously afforded. To get started with Sauce Labs all you need to do is update the following properties of the wct.config.json to include your own account information and to no skip that step in the test series:
Once you tests are running, and hopefully passing, you’ll also want to manage test coverage. The wct-istanbub plugin which uses a web component specific modification to the popular Istanbul test coverage tools is pre-installed and set to a strict 100% of coverage. I’ve found that the best way to continue to meet that level is to test early and often. That’s not to say I’m a fan of test driven development (though I’ve been having some good luck with a style of ping pong pair development powered by test driven development as of late), but documenting your code with tests as close to the time it was written is gonna save everyone trouble later down the line.
The Next Generation
Already, as I revisited many of the decisions that went into making this generator possible for documentation in this post, I’ve found places to continue to build on in order to empower even better web components than it already does. Some of these things include the following, GitHub issues are linked in case you’re excited to be a part of the solutions here:
adding nomodule support options to the demo pages (when will IE11 finally leave us alone for good?) (#1)
scripting for making expanding your demo and tests suites less copy pasta (#7)
…and, we’re only just getting started. However, the good news is you can make solid, maintainable element projects today with a powerful suite of tools for making the management of that code much easier than it could be.