Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Frontend Architecture for Scalable Design Systems

April 10, 2019

Frontend Architecture for Scalable Design Systems

How do you build and manage a design system’s frontend architecture that is both scalable and maintainable?

The typical tools and techniques for approaching design systems often break down when trying to scale across multiple sites, integrate with dynamically injected content, or keep the system up to date. Many of these technical boobytraps aren't discovered till late in development, but can be avoided through progressively decoupled components.

This session is most appropriate for frontend developers & architects or teams building and maintaining a design system. You will learn:

- Why and how to decouple a design system from Pattern Lab and Drupal
- Preventing component fragmentation in Drupal through the use of web components
- Pattern Lab improvements and techniques to reduce rework and ease Twig-based component integration
- How distributing a design system via NPM (yes, NPM) can help teams overcome technical hurdles with Composer


April 10, 2019

More Decks by Salem

Other Decks in Programming


  1. Building a progressively decoupled, 
 external design system…. • More

    maintainable documentation • Individually installable components • Drupal and Pattern Lab friendly + automated Twig integration • Works with other systems (Vue, React, static, etc)
 … in 30 minutes or less.
  2. Salem Ghoweri • Twitter @salem_ghoweri • Lead Frontend Architect at

    Pegasystems • Building design systems, pattern libraries for over 4+ years • Pattern Lab PHP and Node Lead
  3. • Code for Twig-based components built & lived in Pattern

    Lab • Drupal integration w/ Twig namespaces + Components module • Component docs = Pattern Lab demos + mostly static markdown • Design system lived as a standalone Git repo outside of Drupal (pulled in via Composer) And Followed Best Practices for Drupal + Pattern Lab Integration
  4. …major scalability problems How do I use this component? 

    What are the different config options? What changed with this most recent release? How do we pull in just this one component’s updates? Slightly reorganizing Pattern Lab’s docs completely broke all these Twig templates in Drupal… Component
 Fragmentation Problems Shipping via Composer Fragile
 Integrations Why are these component docs out of date?! We had to recreate this component’s Twig template… forgot to tell you… and now your CSS updates broke Drupal. WHY IS DRUPAL HAVING TO INSTALL PATTERN LAB?! We couldn’t configure this in Drupal so we had to hack something together with some inline styles… Our 3rd party service injects hard coded design system components onto the page… We didn’t know that Twig template required that option… Oh we copied over / forked those custom Twig extensions months ago… We need to build and ship a new component ASAP that looks like the design system… Poor Documentation
  5. The fragmentation got so bad… Half the pages on this

    Drupal site need to use the old version of the design system (from a year ago)…
 …and the other half need to use the latest version… …our first design system was basically dead. ⚰
  6. 1. Maintainability: how do you systematically maintain, support, and improve

    the design system over time? ^ bug fixes, docs, testing coverage, refactors, enhancements, etc
 2. Fragmentation: how do you systematically prevent the code that’s shipped from falling apart over time? ^ easy to upgrade + hard to break + related code stays in sync A Scalable Design System Need To Solve Two Big Problems
  7. 1. More maintainable docs & demos that stay up to

    date. 2. More flexible, more encapsulated, and more cross- platform-friendly components. 3. Make the design system’s code installable à la carte. 4. Make integrations as automated as possible. Bolt Design System Goals:
  8. Step 1: Structure your code to be export-friendly // packages/core/styles/…/_settings-colors.scss

    @import ' ../ ../02-tools/tools-data/tools-data.scss'; $bolt-brand-colors: ( indigo: ( xdark: hsl(233, 71%, 8%), dark: hsl(233, 47%, 16%), base: hsl(233, 47%, 23%), light: hsl(233, 33%, 49%), xlight: hsl(233, 73%, 81%), ), ); @include bolt-export-data('colors/brand.bolt.json', $bolt-brand-colors); { "indigo": { "xdark": "rgb(6, 9, 35)", "dark": "rgb(22, 26, 60)", "base": "rgb(31, 38, 86)", "light": "rgb(84, 93, 166)", "xlight": "rgb(171, 179, 242)" } } Step 2: Export data about your code
  9. // BoltCore.php — teach Twig about data in the design

    system function initRuntime(\Twig_Environment $env) { try { $fullManifestPath = TwigTools\Utils ::resolveTwigPath($env, '@bolt-data/full-manifest.bolt.json'); $dataDir = dirname($fullManifestPath); $this ->data = self ::buildBoltData($dataDir); } catch (\Exception $e) {} } /** * @param $dataDir {string} - Path to data directory * @return {array} - Json files parsed as a single array */ function buildBoltData($dataDir) { ...} // Expose data globally via Twig’s getGlobals API public function getGlobals() { return [ 'bolt' => [ 'data' => $this ->data, ], 'enable_json_schema_validation' => true, ]; } Step 3: Teach your design system to other systems (like Twig)
  10. {% for colorName, palette in bolt.data.colors.brand %} {% include "_color-swatch.twig"

    with { color_swatch: { colorName: colorName, palette: palette } } %} {% endfor %} Step 4: Use It!
  11. Solution #2 
 Component Schemas $schema: 'http: //json-schema.org/draft-04/schema#' title: 'Bolt

    Button' description: 'Buttons are the core of our action components.' type: object required: - text properties: attributes: type: object description: A Drupal-style attributes object for adding extra HTML attributes. text: title: 'Button Text' description: 'The text displayed inside a button' type: string icon: type: object description: Nested icon component. Accepts an extra 'position' prop for placement. ref: '@bolt-components-icon/icon.schema.yml' properties:
  12. • Describes your existing data format(s). • Provides clear human—and

    machine—readable documentation. • Validates data which is super helpful for: ◦ Automated testing ◦ Enforcing rules about the data passed into components ◦ Managing API changes over time What’s a schema? https://json-schema.org/
  13. Step 1: Write schemas # button.schema.yml $schema: 'http: //json-schema.org/draft-04/schema#' title:

    'Bolt Button' description: 'Buttons are the core of our action components.' type: object required: - text properties: attributes: type: object description: Drupal attributes object text: // package.json "schema": "button.schema.yml"
  14. // bolt-button/package.json "schema": "button.schema.yml" // www/build/data/full-manifest.bolt.json { "name": "@bolt/components-button", “basicName":

    "bolt-components-button", "dir": "/Users/ghows/sites/bolt-release/packages/components/bolt-button", "assets": { "style": "/Users/ghows/sites/bolt-release/packages/components/bolt-button/index.scss", "main": "/Users/ghows/sites/bolt-release/packages/components/bolt-button/index.js" }, "deps": ["@bolt/core"], "twigNamespace": “@bolt-components-button", "schema": { "$schema": "http: //json-schema.org/draft-04/schema#", "title": "Bolt Button", “description": "Buttons are the core of our action components.", "type": "object", "properties": { "attributes": { Step 2: Aggregate the data // bolt-link/package.json "schema": “link.schema.yml"
  15. <?php namespace Bolt\TwigExtensions; use BasaltInc\TwigTools; class BoltCore extends \Twig_Extension implements

    \Twig_Extension_InitRuntimeInterface { public function getFunctions() { return [ TwigTools\TwigFunctions ::get_data(), TwigTools\TwigFunctions ::validate_data_schema(), ], } } // composer.json "require": { "basaltinc/twig-tools": “^1.4.0", } Step 3: Teach other systems about these schemas (like Twig)! • Globally provide schema data • Import schema files directly • Validate data being passed in • Automatically add default data
  16. Step 3: Teach other systems about these schemas (or JavaScript)!

    import schema from ‘ ../link.schema.yml'; @define class BoltLink extends BoltAction { static is = 'bolt-link'; static props = { display: props.string, valign: props.string, url: props.string, target: props.string, isHeadline: props.boolean, }; constructor(self) { self = super(self); self.schema = schema; return self; } render() { // Validate the original prop data passed along // Returns back the validated data w/ defaults adde const { display, valign, url, target, isHeadline } this.validateProps( this.props, ); } • Globally provide schema data • Import schema files directly • Validate data being passed in • Automatically add default data
  17. {% set schema = bolt.data.components['@bolt-components-button'].schema %} {% for size in

    schema.properties.size.enum %} <p>Button Size Variation: {{ size }} </p> {% include "@bolt-components-button/button.twig" with { "size": size, "text": "Example " ~ size ~" Button" } %} {% endfor %} Pattern Lab demos Step 4: Use it to power everything!
  18. import { render } from '@bolt/twig-renderer'; const { readYamlFileSync }

    = require('@bolt/build-tools/utils/yaml'); const { join } = require('path'); const schema = readYamlFileSync(join( __dirname, ' ../button.schema.yml')); const { tag, size } = schema.properties; // test server-side rendering via Twig + rendering service size.enum.forEach(async sizeOption => { test(`button size: ${sizeOption}`, async () => { const renderedTwigTemplate = await render('@bolt-components-button/button.twig', { text: `${sizeOption} Button`, size: sizeOption, }); expect(renderedTwigTemplate.ok).toBe(true); expect(renderedTwigTemplate.html).toMatchSnapshot(); }); }); Schema-powered Jest tests
  19. Schema-powered component validation (CLI) $schema: 'http: //json-schema.org/draft-04/ schema#' title: 'Bolt

    Button' required: ✖ Failed to recompile Pattern Lab! Error with code 1 after running: php: “/Users/ghows/sites/bolt/packages/components/bolt-button/src/button.twig” had schema validation errors: `text` The property text is required {% include "@bolt-components-button/button.twig" with { iconOnly: true, icon: { name: "close" }
  20. $schema: 'http: //json-schema.org/draft-04/schema#' title: 'Bolt Button' description: 'Buttons are the

    core of our action components.' type: object required: - text properties: attributes: type: object description: A Drupal-style attributes object for adding extra HTML attributes. text: title: 'Button Text' description: 'The text displayed inside a button' One Schema To Rule Them All! Component 
 Documentation Configuration 
 Defaults ✅ CLI + In-Browser 
 Validation Component Tests Component 
 Explorer Label Label Pattern Lab 
  21. 4 core technologies to build reusable, native-like components: • Custom

    Elements: defines a custom HTML tag your component uses • Shadow DOM: encapsulated markup and styling inside a component • HTML Templates: reusable HTML templates • ES6 Modules: import & use components in other components Tons of great options available for using web components: • Libraries & Tools: Lit-HTML, SkateJS, Lit-Element, StencilJS • Frameworks: Polymer 3, Ionic Framework, Angular, Preact, React, Vue.js, Svelte… Web Components: Build Once, Ship Everywhere
  22. Native browser support? 
 Quite good + polyfills for older

    browsers. https://www.webcomponents.org/
  23. {% include "@bolt-components-button/button.twig" with { text: "View The Components", url:

    "/pattern-lab/index.html", style: "primary" } only %} <bolt-button> via Twig <bolt-button url=“/pattern-lab/index.html"> View The Components </bolt-button> <bolt-button> via HTML import '@bolt/components-button'; render(){ return html` <bolt-button url=“/pattern-lab/index.html"> View The Components </bolt-button> `; } <bolt-button> via JS True
 Component Interoperability
  24. Twig Pre-rendered Web Components {% include "@bolt-components-button/button.twig" with { text:

    "View The Components", url: "/pattern-lab/index.html", width: "full", style: "primary" } only %} button.twig <!— Pre-rendered via Twig —> <bolt-button url=“/pattern-lab/index.html”> <a href="/pattern-lab/index.html" class="c-bolt-button c-bolt-button --medium c-bolt-button --full c-bolt-button --border-radius- regular c-bolt-button --primary c-bolt-button --center" is="shadow-root"> <replace-with-children class="c-bolt-button __item">View The Components </replace-with-children> </a> </bolt-button> Rendered HTML (No JavaScript) <!— post-rendered after JavaScript kicks in —> <bolt-button url=“/pattern-lab/index.html”> View The Components </bolt-button> Rendered HTML (With JavaScript)
  25. <style>… </style> <a href="/pattern-lab/index.html" class="c-bolt-button c-bolt-button --center c- bolt-button --primary

    c-bolt-button --medium c- bolt-button --full c-bolt-button—border-radius- regular"> <span class="c-bolt-button __icon is-empty"> <slot name=“before"> </slot> </span> <span class=“c-bolt-button __item"> <slot> </slot> </span> <span class="c-bolt-button __icon is-empty"> <slot name="after"> </slot> </span> </a> No Shadow DOM? No Problem! <bolt-button url="/pattern-lab/index.html"> <a href="/pattern-lab/index.html" class="c-bolt-button c-bolt-button --center c-bolt-button --primary c-bolt-button --medium c- bolt-button --full c-bolt-button—border-radius- regular"> <span class="c-bolt-button __icon is-empty"> <slot name=“before"> </slot> </span> <span class=“c-bolt-button __item"> View The Components </span> <span class="c-bolt-button __icon is-empty"> <slot name="after"> </slot> </span> </a> </bolt-button> <bolt-button url=“/pattern-lab/index.html”> ▸#shadow-root (open) View The Components </bolt-button>
  26. • A repository that contains multiple packages or projects. These

    projects can be related (but don’t have to be). • Many of the most popular JavaScript projects have moved to a monorepo: Babel, Gatsby, Webpack, Vue CLI, Storybook, Jest... • Some of the most popular PHP frameworks like Symfony and Laravel are also monorepos! What is a Monorepo? https://gomonorepo.org/
  27. • More maintainable codebase • Simplified, more easily shared, de-duplicated

    dependencies • Easier to coordinate updates across multiple components • Unified tools / build process for linting code, compiling, testing, deploying, and publishing • Single place to report issues / announce releases • Tests across modules are run together → finds bugs that touch multiple modules easier • One single source of truth for making any changes to the code, demos, or documentation Why Go With A Monorepo?
  28. Design system contributors: 
 more unified + maintainable code Design

    system consumers: 
 only pull in the parts needed
  29. # publish packages w/ changes since last publishing $ lerna

    publish { "lerna": "3.13.1", "npmClient": "yarn", "useWorkspaces": true, "version": “2.3.0", } lerna.json "dependencies": { "lerna": "^3.13.1", }, "workspaces": { "packages": [ "packages /*", "packages/config-presets /*", "docs-site" ] } package.json How to Monorepo?
  30. “Downgrading” Pattern Lab 1. Reorganized codebase so components don’t live

    in Pattern Lab, 
 they are simply demoed in Pattern Lab 2. Avoid using Pattern Labisms in the code being shipped 3. This allows Pattern Lab, our Twig-based static site generator (used for the docs site), Drupal, etc to all integrate the exact same way: Twig.
  31. Reorganizing Your 
 Design System Codebase └── packages ├── components

    │ ├── bolt-button │ │ ├── __tests __ │ │ ├── src │ │ │ ├── button.scss │ │ │ ├── button.twig │ │ │ └── button.js │ │ ├── index.js │ │ ├── index.scss │ │ ├── button.schema.yml │ │ └── package.json │ └── bolt-icon/ ├── core ├── core-php ├── config ├── global └── build-tools ├── website │ └── src │ ├── pattern-lab │ │ └── _patterns │ │ ├── 01-visual-styles │ │ ├── 02-components │ │ └── ├── button │ │ └── icon │ └── docs/ │ ├── getting-started.md │ └── coding-standards.md ├── example-integrations/ │ ├── drupal-lab/ │ └── vue/ ├── internal-scripts/ Code that’s published
 (components, build tools, config, shared styles, cross browser polyfills, Twig extensions, etc) “Everything Else” (support code)
 (docs, website code, pattern lab demos, example integrations, deploy scripts, etc)
  32. But wait, what about PHP dependencies?! • When publishing, git

    subtree split to sync PHP deps to READ-ONLY git repos • Web hook updates Packagist
  33. Design System A La Carte 1. NPM install only the

    components you need + build tools for compiling • These Webpack-based build tools also generate the data that automatically wires up Twig namespaces & extensions to Drupal 2. Configure the build tools based on how you want things setup in Drupal • Also solves the problem of “installed components” vs “enabled components” 3. Composer require a small Drupal module that teaches Drupal about your components + Twig extensions (bolt_connect)
  34. { "name": "pega www_theme", "version": "1.0.0", "scripts": { "build": "bolt

    build --prod", "start": "bolt start", "watch": "bolt watch" }, "dependencies": { "@bolt/build-tools": "2.3.0", "@bolt/components-action-blocks": "2.3.0", "@bolt/components-background": "2.3.0", "@bolt/components-background-shapes": "2.3.0", "@bolt/components-band": "2.3.0", "@bolt/components-block-list": "2.3.0", "@bolt/components-blockquote": "2.3.0", "@bolt/components-breadcrumb": "2.3.0", • NPM installed components live in the node_modules folder • Even works w/ component dependencies!
  35. module.exports = { env: 'drupal', buildDir: './dist/', wwwDir: ' ../

    ../ ../', verbosity: 1, components: { global: [ '@bolt/global', '@bolt/components-action-blocks', '@bolt/components-background', '@bolt/components-background-shapes', '@bolt/components-band', '@bolt/components-blockquote', .boltrc.js {% set hero %} {% grid "o-bolt-grid --flex o-bolt-grid --matrix o-bolt-grid --middle" %} {% cell "u-bolt-width-12/12 u-bolt-width-5/12@medium" %} {{ featured_image }} {% endcell %} {% cell "u-bolt-width-12/12 u-bolt-width-7/12@medium" %} {% include "@bolt-components-headline/headline.twig" with { text: article_title, size: "xxxlarge", tag: "h1", } only %} {% include "@bolt-components-list/list.twig" with { display: "inline@small", tag: "div", node --blog.html.twig { "name": "pega www_theme", "version": "1.0.0", "scripts": { "build": "bolt build --prod", "start": "bolt start", "watch": "bolt watch" }, "dependencies": { "@bolt/build-tools": "2.3.0", "@bolt/components-action-blocks": "2.3.0", "@bolt/components-background": "2.3.0", "@bolt/components-background-shapes": "2.3.0", package.json
  36. 1. Schemas can provide a powerful way to keep your

    design system up to date, validated, and well documented. 2. Twig-integrated Web Components allow for more resilient, more flexible, more future-proof, and more cross-platform interoperability FTW. 3. Progressively decoupling your design system from Drupal — and Pattern Lab can help better organize, maintain & scale your system 4. Publishing / installing your design system a la carte via NPM allows integrators to pick and choose just the parts they need. 5. Automating Twig namespaces and Twig extensions can further reduce frictions with Drupal and with Pattern Lab + address fragility issues with manual approach Key Takeaways
  37. Design System + Pattern Lab BoF April 10th (Today), 2:30

    - 3pm
 BoF Room 1 | Exhibit Hall | Level 4 Thanks! Web Components BoF April 11th (Tomorrow), 9 to 9:30am
 BoF Room 1 | Exhibit Hall | Level 4 boltdesignsystem.com