Frontend Architecture for Scalable Design Systems
This past week at DrupalCon Seattle, I presented my 30 minute talk on “Frontend Architecture for Scalable Design Systems”!
In this jam-packed session, I outlined 5 of the front-end architectural approaches we’ve been using on the Bolt Design System to address the maintainability and fragmentation problems that ultimately killed off our first design system attempt.
Definitely go check out the session and the slides for all the nuts and bolts but here’s the quick TLDR version 😉!
1. APIs
Sufficiently documenting your design system is hard, but doable.
Indefinitely maintaining and improving that documentation for developers, designers, and content authors to always stay up to date and relevant? Borderline insane.
One big way we’ve been tackling this whole “maintainable docs” problem is via APIs.
Basically, we make sure certain types of things in the system system’s codebase automatically stay up to date— especially things related to specific colors, sizes, breakpoints, config options, etc.
4 Step Process
- Make your code is “export friendly” (ex. via Sass Maps)
- Automatically export data about your code (ex. auto-convert Sass Maps to JSON via custom Sass function)
- Automatically-wire up this data to other systems (ex. globally make sure Twig templates have access to all global design system-specific data)
- Use it! For example, we use this always-up-to-date data to loop through our color palette options and generate our swatch demos in Pattern Lab.
This approach has helped us better manage the dozens of little moving parts that make up the system’s codebase AND has allowed us to build off of this approach to produce some really neat stuff.
2. Schemas
Schemas are the “human and machine-readable” way to define the rules about how things like Components can be configured, customized, and used.
For example, here’s a portion of the schema file we have for our button component:
I’ll skip over the implementation details but suffice to say, just one schema file can power TONS of different things!
For example, we currently use schemas to:
- Generate many of the component demos in Pattern Lab
- Enforce component validation via our CLI and browser console messages
- Setup default / available component config options
- Auto-generate docs in Pattern Lab to display available component options
- Setup Jest tests used for for visual and functional regression testing
- Power our in-browser component preview (my personal favorite)
3. Twig + Web Components
Oh boy. This one probably deserves it’s own dedicated Medium post given the (controversial?) buzz on Twitter around this!
Until I can get around to writing a deeper dive on this, here’s just a couple key takeaways from this section:
- Web Components are 3 (or 4 if you count ES Modules) official browser standards / APIs to allow devs to build super powerful solutions. Think of them like the “native” version of components seen in JS frameworks like React, Vue, Angular, etc.
- The latest versions of Chrome, Safari and Firefox all fully support Web Components. Edge support is half-way there (working on it) and both Edge and IE 11 can use Web Components via polyfills like webcomponents.js.
- Tons of lightweight framework-agnostic libraries are out there like SkateJS (no JavaScript framework required) however nearly all of the big frameworks out there have perfect support if you want to use Web Components with a framework like, Preact / React, Angular, or Vue.
- We’ve been moving all of our components over to Web Components for more universal cross-framework / cross-platform support so our design system can solve even more problems in many different areas and use cases.
- Polyfilling Shadow DOM in IE 11 is a performance nightmare SO instead we treat Shadow DOM as a progressive enhancement based on browser support, component support, and component-specific use case support — all without giving up super powerful features like <slots>s!
- We pre-render many of our web components via PHP-rendered Twig templates (a little more on this below) 😱
Twig Pre-rendered Web Components
TLDR: our Drupal-friendly Twig templates setup the minimal amount of markup / HTML logic needed for our Web Components before our JavaScript loads up and takes over.
Yes, that means our button.twig
template has some duplicate template logic with our button component’s lit-html template HOWEVER both of these solve different problems in different environments (client-side vs server-side).
For us, the benefits for us far outweigh the minimal overhead:
- Progressive Enhancement: most of our components visually and functionally work if JavaScript is manually turned off, is slow to load, or stops working if an error occurs. Ex. our button component still looks good and is a semantic clickable
<button>
or<a href>
, etc. - Customization: this approach provides us with a way to customize how a web component’s markup gets setup / used (ex. adding the ability to add data attributes to some HTML element that client-side JavaScript retains when hydrating)
- Backend Integration: this Twig layer also provides us with a rich, extendable templating language that backend devs can use to prep data passed along to these templates
- Perceived Performance: no flash of unstyled web component HTML FTW
- Component Defaults: server-side schema validation and config defaults
- Server-side performance: I’ve been working on a pure Javascript-powered server-side rendering solution using JSDOM however things are still a bit too slow (like, 0.4 to 1.5 seconds to render every single component, slow) to ship as-is. Caching might help but until we have a more performant solution, PHP-based Twig templates are still more performant!
There’s a ton more to chat about on this evolving area but needless to say, Twig still is a super valuable tool for us and has significantly helped with design system adoption, better end-use experiences, and more compliant component usage.
4. Publishing to NPM
TLDR: we switched to using a monorepo + Lerna to publish nearly everything in the design system as standalone package on NPM.
This also means that Pattern Lab has been “downgraded” from being where components live (single source of truth) to instead be “just another consumer” of the design system’s bits and pieces — no different than Drupal or any other system wired up to use the design system.
It’s almost as if our progressively decoupled design system has been progressively decoupling itself…? 🤯
5. Automating Twig Integrations
Finally, last but not least, we decided to ditch the “best practice” approach that nearly every single Drupal-integrated design system has been using — using the Drupal Components module and writing out each Twig namespace path manually — and instead switched to something that’s a bit more automated and less fragile (see the reoccurring trend here?)
In the Drupal theme:
- Components are NPM installed just like any other front-end dependency,
- They are added to a simple
.boltrc
config to add them to the Webpack build process (without needing to know Webpack) - Via the tiny bolt_connect Drupal Module we built, we automatically teach Drupal where to find these components (added via Twig namespaces internally) + add any shared Twig extensions.
It’s the same overall idea as the Drupal Components module BUT unlike before, renaming a component folder, etc won’t cause the Drupal integration to break because we’re teaching telling the Twig environment inside of Drupal where to find a component’s Twig template, etc automatically.
Phew — it’s been a long but super exciting week. Can’t wait to share more about the work we’ve been doing to make design systems a heckuva lot more scalable and maintainable!
-Salem
Be sure to check out our docs site or take a peek at our code on Github!