Slide 1

Slide 1 text

Frontend Architecture for Scalable Design Systems Tweet me questions! @salem_ghoweri Slides: bit.ly/boltdesignsystem-d4d Design4Drupal 2019

Slide 2

Slide 2 text

How to build a modern, 
 scalable design system? • Maintainable: make it easy to keep code + docs up to date • Flexible: lets developers pick & choose the components to install + update • Easy to Integrate: share Twig templates in Drupal, Symfony, & Pattern Lab • Framework Agnostic: works with Angular, Vue, React, static sites, etc
 
 … in 50 minutes or less.

Slide 3

Slide 3 text

Salem Ghoweri • Twitter @salem_ghoweri • Lead Frontend Architect at Pegasystems (Cambridge, MA) • Building design systems, pattern libraries for over 4 years • Creator and lead developer of the Bolt Design System • Core contributor on the open source Pattern Lab projects (PHP and Node)

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

3 years ago, the front-end code in our Drupal sites was a bit of a mess…

Slide 6

Slide 6 text

…so we used tools like Pattern Lab to build our first Design System!

Slide 7

Slide 7 text

We used all the best front-end tools & techniques… Tools Generic Elements Settings Objects Components Themes Utils ITCSS Webpack Atomic Design

Slide 8

Slide 8 text

• Built Drupal-friendly Twig-based components in Pattern Lab • Documented our components via markdown + demos in Pattern Lab • Twig namespaces + Components module for tight Drupal integration • “Bleeding edge” approach of having the design system codebase as a separate repository outside of Drupal (pulled in via Composer) …and embraced the best practices for Drupal + PL integration

Slide 9

Slide 9 text

Many of our Drupal sites used our first design system!

Slide 10

Slide 10 text

And everything was perfect. The End.

Slide 11

Slide 11 text

Actually… our first design system started having some problems. …major scalability problems

Slide 12

Slide 12 text

…major scalability problems How do I use this component? 
 What are the different config options? What changed with this most recent release? Reorganizing Pattern Lab’s docs completely broke all these Twig templates in Drupal… Component
 Fragmentation Issues Shipping Code via Composer Fragile
 Integrations 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… We didn’t know that Twig template required that option… We need to build and ship a new component ASAP that looks like the design system… Poor Documentation

Slide 13

Slide 13 text

The fragmentation got so bad… “We need half the pages in Drupal to use the old version of the design system (from a year ago)…
 
 …and the other half needs to use the latest version…” …our first design system was basically dead. ⚰

Slide 14

Slide 14 text

A Scalable Design System Needs To Solve 2 Major Challenges 1. Maintainability: how do you maintain, support, and improve the design system over time? ^ bug fixes, docs, testing coverage, refactors, enhancements, etc
 
 2. Fragmentation: how do you prevent the code that’s been shipped from falling apart over time? ^ easy to upgrade + hard to break + related code stays in sync

Slide 15

Slide 15 text

So we rebuilt.

Slide 16

Slide 16 text

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:

Slide 17

Slide 17 text

Solution #1: 
 API-powered Docs

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

// 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)

Slide 20

Slide 20 text

{% for colorName, palette in bolt.data.colors.brand %} {% include "_color-swatch.twig" with { color_swatch: { colorName: colorName, palette: palette } } %} {% endfor %} Step 4: Use It!

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

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'

Slide 23

Slide 23 text

• 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/

Slide 24

Slide 24 text

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"

Slide 25

Slide 25 text

// 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"

Slide 26

Slide 26 text

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

{% set schema = bolt.data.components['@bolt-components-button'].schema %} {% for size in schema.properties.size.enum %}

Button Size Variation: {{ size }}

{% include "@bolt-components-button/button.twig" with { "size": size, "text": "Example " ~ size ~" Button" } %} {% endfor %} Pattern Lab demos Step 4: Use it to power everything!

Slide 29

Slide 29 text

Component Documentation

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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" }

Slide 32

Slide 32 text

Schema-powered component validation (in-browser)

Slide 33

Slide 33 text

Schema-powered Component Explorer

Slide 34

Slide 34 text

$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 
 Demos

Slide 35

Slide 35 text

Solution #3 
 Twig + Web Components View The Components ✨

Slide 36

Slide 36 text

❖ Web Components are a collection of low level APIs: • Custom Elements: defines the custom HTML tag used by the component (ex. ) • Shadow DOM: encapsulates the HTML and CSS inside a component • HTML Templates: reusable HTML templates • ES6 Modules: import & use components in other components ✤ Web Components bring native components to the web platform. ❖ Many great web component libraries & tools available: • Libraries & Tools: Lit-HTML, SkateJS, Lit-Element, StencilJS • Frameworks: Polymer 3, Ionic Framework, Angular, Preact, React, Vue.js, Svelte… Web Components:
 Components That Work Everywhere!

Slide 37

Slide 37 text

Cross-browser support?
 It’s quite good + polyfills exist for older browsers! https://www.webcomponents.org/

Slide 38

Slide 38 text

import { define, BoltBase, html } from ‘@bolt/core’; import classNames from 'classnames/bind'; import styles from ‘./button.scss'; const cx = classNames.bind(styles); class BoltButton extends BoltBase { static props = { url: props.string, size: props.string, color: props.string, }; render() { const classes = cx('c-bolt-button', { 'c-bolt-button --medium': !this.props.size, [`c-bolt-button --${this.props.size}`]: this.props.size, 'c-bolt-button --primary': !this.props.color, [`c-bolt-button --${this.props.color}`]: this.props.color, }); return html` ${this.addStyles([styles])} ${this.slot(‘default')} `; } customElements.define('bolt-button', BoltButton); View The Docs

Slide 39

Slide 39 text

{% include "@bolt-components-button/button.twig" with { text: "View The Components", url: "/pattern-lab/index.html", style: "primary" } only %} via Twig View The Components via HTML import '@bolt/components-button'; render(){ return html` View The Components `; } via JS Same component 
 in Twig, Javascript, and plain HTML

Slide 40

Slide 40 text

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 View The Components Rendered HTML (No JavaScript) View The Components Rendered HTML (With JavaScript)

Slide 41

Slide 41 text

Browser doesn’t support Shadow DOM? No problem! View The Components ▸#shadow-root (open) View The Components With Shadow DOM Without Shadow DOM

Slide 42

Slide 42 text

Using Bolt’s 
 Web Components in Vue.js https://github.com/bolt-design-system/bolt/tree/ master/example-integrations/vue

Slide 43

Slide 43 text

Using Bolt’s 
 Web Components in Angular https://github.com/bolt-design-system/bolt/tree/ master/example-integrations/angular

Slide 44

Slide 44 text

Using Bolt’s 
 Web Components in Static HTML https://github.com/bolt-design-system/bolt/tree/ master/example-integrations/static-html

Slide 45

Slide 45 text

Solution #4 
 Progressively Decoupling + 
 Publishing to NPM npm install @bolt/components-button

Slide 46

Slide 46 text

Monorepos

Slide 47

Slide 47 text

• 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/

Slide 48

Slide 48 text

• 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?

Slide 49

Slide 49 text

Design system contributors: 
 more unified + maintainable code Design system consumers: 
 only pull in the parts needed

Slide 50

Slide 50 text

+ How to Monorepo?

Slide 51

Slide 51 text

# 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?

Slide 52

Slide 52 text

“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.

Slide 53

Slide 53 text

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)

Slide 54

Slide 54 text

But wait, what about PHP dependencies?! • When publishing, git subtree split to sync PHP deps to READ-ONLY git repos • Web hook updates Packagist

Slide 55

Slide 55 text

Solution #5 
 Integrations composer require 
 bolt-design-system/bolt_connect

Slide 56

Slide 56 text

Design System “A La Carte”:
 Install and use just what you need 1. NPM install only the components you need + build tools for compiling • The Webpack-based build tools in Bolt 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 the bolt_connect Drupal module to automatically teach Drupal about Twig namespaces and custom Twig extensions

Slide 57

Slide 57 text

{ "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!

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

https://github.com/bolt-design-system/bolt/tree/master/example-integrations/drupal-lab

Slide 60

Slide 60 text

Recap

Slide 61

Slide 61 text

1. Component schemas can provide a powerful way to keep your design system up to date, validated, and well documented. 2. Building 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 using a monorepo can help better organize, maintain & scale your system. 4. Publishing your design system to NPM allows developers to install and use to use just the components that 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

Slide 62

Slide 62 text

https://github.com/bolt-design-system/bolt

Slide 63

Slide 63 text

Thanks! boltdesignsystem.com bolt-design-system channel on the DrupalTwig Slack