Slide 1

Slide 1 text

Frontend Architecture for Scalable Design Systems Follow me on Twitter! @salem_ghoweri Download Slides at https://bit.ly/drupalcamp-poland Drupalcamp Poland, June 1st 2019

Slide 2

Slide 2 text

How to build a modern, 
 scalable design system with: • Maintainable Code and Docs: make it easy to keep things up to date • Flexible: let developers choose the components to install and update • Easy to Integrate: share Twig templates in Drupal, Symfony, & Pattern Lab • Framework Agnostic: works with Angular, Vue, React, static sites, etc
 
 … in 45 minutes or less.

Slide 3

Slide 3 text

Dzień dobry
 Wrocław!

Slide 4

Slide 4 text

Salem Ghoweri • Twitter @salem_ghoweri • Lead Frontend Architect at Pegasystems (Cambridge, Massachusetts, United States) • 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 5

Slide 5 text

3 years ago, our front-end code in Drupal wasn’t great…

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Our first design system 
 had a great frontend codebase… Tools Generic Elements Settings Objects Components Themes Utils ITCSS Webpack Atomic Design

Slide 8

Slide 8 text

• Code for our Twig-based components was built & lived in Pattern Lab • Drupal Integration through Twig namespaces + Components module • Documented our components via demos and examples in Pattern Lab • Design system code was maintained in a separate Git repository outside of Drupal (frontend code pulled in via Composer) And followed many of the best practices for Drupal integration

Slide 9

Slide 9 text

So many of our 
 Drupal sites used it!

Slide 10

Slide 10 text

Unfortunately, our first design system started having some problems… …major scalability problems

Slide 11

Slide 11 text

…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? Reorganizing Pattern Lab’s docs completely broke all these Twig templates in Drupal… Component
 Fragmentation Issues Shipping Code 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

Slide 12

Slide 12 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 need to use the latest version…” …our first design system was dead. ⚰

Slide 13

Slide 13 text

Rebuilding a Design System for Scale

Slide 14

Slide 14 text

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 A Scalable Design System Need To Solve Two Major Problems

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

1. Make docs & demos more maintainable / stay up to date. 2. Make components more flexible, more encapsulated, and more cross-platform-friendly. 3. Make the design system’s code installable piece by piece. 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: Write your code to be structured // 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 the 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 systems about this data 
 (Twig templates)

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

• A schema describes the API for your components. • Provides easy to read documentation for humans and computers. • Validates the data being used. This is really helpful for: ◦ Automatically testing for regressions ◦ Enforcing what data is allowed to be passed into a component ◦ Managing your design system’s API + making 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 schemas to other systems,
 like 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 schemas 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

4 browser specifications to help build reusable, native-like components: • 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 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

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

Slide 39

Slide 39 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 40

Slide 40 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 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Monorepos

Slide 46

Slide 46 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 47

Slide 47 text

• More maintainable codebase • Simplifies sharing and reusing code in the design system • Easier to coordinate related updates across components • Unified process for checking code quality, building, testing, deploying, and publishing • One place to report issues / publish release notes • Allows for testing multiple components used together • One place for making any changes to the code, demos, tools, or documentation Monorepo Benefits

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

+ How to Monorepo?

Slide 50

Slide 50 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 Publishing a Monorepo

Slide 51

Slide 51 text

1. Reorganized codebase so components don’t live in Pattern Lab, 
 they are simply demoed in Pattern Lab 2. Learned we had to avoid using certain Pattern Lab “best practices” that Drupal or other systems couldn’t be taught 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. Pattern Lab != Design System

Slide 52

Slide 52 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 53

Slide 53 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 54

Slide 54 text

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

Slide 55

Slide 55 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 56

Slide 56 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 57

Slide 57 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 58

Slide 58 text

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

Slide 59

Slide 59 text

Recap

Slide 60

Slide 60 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 61

Slide 61 text

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

Slide 62

Slide 62 text

Dziękuję
 Thank you! boltdesignsystem.com
 
 bolt-design-system channel on the DrupalTwig Slack