Workshop Compony

By Mathieu Spillebeen

Mathieu Spillebeen

@MathieuSpil

mathieu.spillebeen@gmail.com

Frontend United.org

Compony.io

Freelance Drupal Frontend developer

Workshoppy

  • Setting up Compony:
    • Single theme
    • multi theme
  • ​Component naming convention
  • CSS-generating & non-CSS-generating Sass
  • Migrating a theme to Compony

Overview

  • When to abstract components
  • Re-usable vs project-specific components
  • Anti patterns:
    • Cross-component-fragility with images
    • Spriting & url-encoding
    • aggregation
    • global breakpoints
    • CDN's
  • How to update components?
  • Drupal's different caching contexts
  • How to deploy components
  • Hook system for dummies
    • HOOK_preprocess_hook
    • HOOK_theme_suggestions_hook_alter
    • HOOK_library_info_alter__hook
    • HOOK_theme__hook
  • Drupal Field UI vs Twig hardcoding
  • Business vs presentational logic
  • Integrating libraries as components

Setting up Compony: a single-theme

DO YOURSELF

New theme structure

Compony.io

Download theme-structure + tools

_sass-essentials

compony

components

compony.info.yml

compony.libraries.yml

compony.theme

gulpfile.js

.nvmrc

package.json

package-lock.json

yarn.lock

Enable the theme

Tooling workflow

_sass-essentials

compony

components

compony.info.yml

compony.libraries.yml

compony.theme

gulpfile.js

core

gulpfile.js

config.js

index.js

local.config.js

project.config.js

Configurable

  • project-specific
  • environment-specific

 

module.exports = {
  gulpthemes: [
    {
      path: 'web/themes/compony',
      with_styleguide: false
    },
  ],
  features: {
    autoprefixer: {
      enable: true,
      options: {
        browsers: ['last 2 versions', 'ie 9', '> 0.2%'],
        cascade: false
      },
    },
    browserify: {
      enable: true,
      debug_mode: false,
    },
    clean_css: {
      enable: false,
    },
    css_mapping: {
      enable: false,
    },
    image_optimise: {
      enable: true,
      gifsicle: {
        interlaced: true,
        optimizationLevel: 3
      },
      optipng: {
        optimizationLevel: 5
      },
      jpegtran: {
        progressive: true
      },
      svgo: {
        plugins: [
          {
            removeViewBox: false
          },
          {
            removeDimensions: true
          }
        ]
      }
    },
    sass_includes: {
      bourbon: false,
      bourbonNeat: false,
      breakpoint: false
    },
    // Deprecated
    kss: {
      enable: false,
    },
  },
};

project.config.js

module.exports = {
  features: {
    auto_rebuild_drupal_cache: {
      enable: false,
      cache_rebuild_command: 'drush cr'
    },
    browsersync: {
      enable: false,
      localhost_url: "https://local.dev/"
    },
    validate_yml: {
      enable: true,
    },
    lint_php: {
      enable: true,
    },
    lint_html: {
      enable: false,
    }
  },
  notifications: {
    html: {
      linting_errors: false,
    },
    css: {
      sass_errors: true
    },
    javascript: {
      browserify_errors: true,
      uglify_errors: true,
    },
    yml: {
      validation_errors: true,
    },
    php: {
      linting_errors: true,
    },
    internal: {
      cache_rebuilding_status: true,
      cache_rebuild_error: true,
    }
  }
};

local.config.js

$ cd path/to/theme


$ nvm use


$ npm install


$ gulp

Command line

Setting up Compony: a multi-theme

DO YOURSELF

_sass-essentials

my-theme

components

my-theme.info.yml

my-theme.libraries.yml

my-theme.theme

gulpfile.js

.nvmrc

package.json

package-lock.json

yarn.lock

module.exports = {
  options: {
    ...
    gulpthemes: [
      {
        path: 'themes/custom/my-first-theme',
      },
      {
        path: 'themes/custom/my-second-theme',
      },
    ]
  },
};

project.config.js

Compony.io

Download theme-structure + tools

+ rename the theme!

Component naming conventions

slideshow

components

toggle-thingy

blogs

node--article--teaser

components

taxonomy-term--tag

user--full

node

_global

node.html.twig

node.css

libraries.yml

node--article

node--article.html.twig

components

node

_global

node.html.twig

node.css

libraries.yml

node--article

node--article.html.twig

components

node

node

components

node

node--article

node--article-full

node--article--teaser

node--blog

node--blog--full

node--blog--teaser

Multiple Twig files inside a component?

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'menu__anonymous_menu' -->
<!-- FILE NAME SUGGESTIONS:
   * menu--anonymous-menu.html.twig
   x menu.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/compony/components/menu/menu/menu.html.twig' -->
<ul class="menu-main__container">
  <!-- THEME DEBUG -->
  <!-- THEME HOOK: 'menu-item' -->
  <!-- FILE NAME SUGGESTIONS:
     x menu-item.html.twig
  -->
  <!-- BEGIN OUTPUT from 'themes/compony/components/menu/menu-item.html.twig' -->
  <li>
    <a href="/user/login" class="">Login</a>
  </li>
  <!-- END OUTPUT from 'themes/compony/components/menu/menu-item.html.twig' -->
</ul>
<!-- END OUTPUT from 'themes/compony/components/menu/menu/menu.html.twig' -->

menu

libraries.yml

menu.html.twig

menu-item.html.twig

menu.js

menu.scss

No Twig files?

Avoid inventing a name

CSS-generating and non-CSS-generating Sass

my-component

dist

my-component.css

my-component.scss

$c-grey: #333;

@mixin button {
  color: red;
}
// Empy file

my-component

dist

my-component.css

my-component.scss

.my-component {
  color: $c-grey;
}
.my-component {
  color: #CCC;
}

_sass-essentials

compony

components

my-theme.info.yml

my-theme.libraries.yml

my-theme.theme

non-CSS-generating Sass

CSS-generating Sass

my-component

my-component.scss

@include "sass-essentials";
.my-component {
  color: $c-grey;
}

dist

sass-essentials

_colors.scss

sass-essentials.scss

components

Migrate an existing theme to a Compony based theme. 

DO YOURSELF 

Splitting up your kickstart-theme

_sass-essentials

compony

components

my-theme.info.yml

my-theme.libraries.yml

my-theme.theme

CSS-generating Sass

_global

non-CSS-generating Sass

_sass-partials

templates

style.scss

Split up your Sass in to different files

When to abstract components

When it makes sense

Too much: abstracting up until field level

Reusable components vs project-specific components

Not reusable

  • Layout-components
  • content-specific components                                     

Reusable

  • Smallest parts that contain most complexity
  • Built upon contrib templates
  • self-independaent functionality
  • small widgets
  • 1-template-specific

Create a component

DO YOURSELF 

Anti-patterns: images

node--article

dist

libraries.yml

smile.svg

node--article.html.twig

smile.svg

node--article.css

node--article.scss

.node--article {
  background: url('smile.svg');
}

node--article

libraries.yml

smile.svg

node--article.html.twig

node--article.scss

node--blog

libraries.yml

smile.svg

node--blog.html.twig

node--blog.scss

/some-page

node--article

libraries.yml

smile.svg

node--article.html.twig

node--article.scss

node--blog

libraries.yml

node--blog.html.twig

node--blog.scss

node

node.html.twig

.node--blog {
  //background-image: url('smile.svg);
  background-image: url('../../node/dist/smile.svg');
}
.node--article {
  //background-image: url('smile.svg);
  background-image: url('../../node/dist/smile.svg');
}

/some-page

node--article

libraries.yml

smile.svg

node--article.html.twig

node--article.scss

node--blog

libraries.yml

node--blog.html.twig

node--blog.scss

node

node.html.twig

.node--blog {
  //background-image: url('smile.svg);
  background-image: url('../../node/dist/smile.svg');
}
.node--article {
  //background-image: url('smile.svg);
  background-image: url('../../node/dist/smile.svg');
}

/some-page

Cross-Component Fragility

smile.svg

node

node.html.twig

still in use? ¯\_(ツ)_/¯

node--article

libraries.yml

smile.svg

node--article.html.twig

node--article.scss

node

node.html.twig

node--article

smile.svg

node

node.html.twig

node--article--full

libraries.yml

node--article--full.html.twig

node--article--full.scss

node--article--blog

.node--article {
  background: url('../../node/dist/smile.svg');
}
.node--article {
  background: url('¯\_(ツ)_/¯');
}

node--article

smile.svg

node--article.html.twig

_global

styles.scss

.smiley--happy {
  background: url('smile.svg');
}
<div class="smiley--happy">...</div>

Re-usability:  (╯°□°)╯︵ ┻━┻

Class-based theming 

No CCF
+ class-based theming
+ reusability?

Components!

node--article

smile.svg

node--article.html.twig

smileys

smileys.scss

.smiley--happy {
  background: url('smile.svg');
}
{% extends 'node.html.twig' %}

{% block before %}
  {{ attach_library("compony/node--article") }}
{% endblock %}

{% block content %}
  {{ attach_library("compony/smileys") }}
  <div class="my-messages smiley--happy">...</div>
{% endblock %}

Re-usable

Class-based theming 

libraries.yml

CSS-only component 

node--article

smile.svg

node--article.html.twig

smileys

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 368 368"><path d="M261.336 226.04c-3.296-2.952-8.36-2.664-11.296.624C233.352 245.312 209.288 256 184 256c-25.28 0-49.352-10.688-66.04-29.336-2.952-3.288-8-3.576-11.296-.624-3.296 2.944-3.568 8-.624 11.296C125.76 259.368 154.176 272 184 272c29.832 0 58.248-12.64 77.96-34.664 2.944-3.296 2.664-8.352-.624-11.296z"/><path d="M184 0C82.544 0 0 82.544 0 184s82.544 184 184 184 184-82.544 184-184S285.456 0 184 0zm0 352c-92.64 0-168-75.36-168-168S91.36 16 184 16s168 75.36 168 168-75.36 168-168 168z"/><path d="M248 128c-22.056 0-40 17.944-40 40 0 4.416 3.584 8 8 8s8-3.584 8-8c0-13.232 10.768-24 24-24s24 10.768 24 24c0 4.416 3.584 8 8 8s8-3.584 8-8c0-22.056-17.944-40-40-40zm-104 40c0 4.416 3.584 8 8 8s8-3.584 8-8c0-22.056-17.944-40-40-40s-40 17.944-40 40c0 4.416 3.584 8 8 8s8-3.584 8-8c0-13.232 10.768-24 24-24s24 10.768 24 24z"/></svg>
{% extends 'node.html.twig' %}

{% block content %}
  <div class="my-messages smileys smileys--smile">
    {% include smile.html.twig %}
  </div>
{% endblock %}

Re-usable

Inlining SVG

HTML-only component 

smile.html.twig

Anti-patterns: spriting

Spriting was for HTTP1

developer-ease

!==

good practice

Anti-patterns:
url-encoding

node--article

libraries.yml

smile.svg

node--article.html.twig

node--article.scss

.node--article {
  background: url('data: ... ');
  fill: blue;
}

100 bytes

130 bytes

won't work

Image is not accessible

Image won't be cached by browser, but CSS-file will

page

libraries.yml

page.html.twig

page.scss

.home h1 {
  content: 'Welcome to the homepage');
}

 (╯°□°)╯︵ ┻━┻

Content

Styling

Icon fonts suffer the same issues

Anti-patterns:
global breakpoints

sass-essentials

_breakpoints.scss

menu--main

menu--main.scss

views-view--news

$bp-m: 30rem;
$bp-l: 50rem;
.menu {
  margin: 0 auto;
  @media(min-width: $bp-m) {
    margin: 0;
  }  
}

sass-essentials

_breakpoints.scss

menu--main

views-view--news

views-view--news.scss

$bp-m: 30rem;
$bp-l: 50rem;
$bp-m: 35rem;
.views-view--news {
  display: block;
  @media(min-width: $bp-m) {
    display: flex;
  }  
}

Secure components

if(google.isOffline()) {
  die();
}

 (╯°□°)╯︵ ┻━┻

Self-host your assets

my-component

libraries.yml

my-component:
  css:
    component:
      https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css: { type: external, minified: true}

my-component

libraries.yml

bootstrap.min.css

reliable

fragile

DX doesn't justify

  • CDN breach
  • CDN outage
  • Network negotiation
    • extra DNS lookups
    • extra TCP handshakes
  • Third-party determined cache policy
  • Assuming cross-domain caching
  • Wealthy White Web                                                                   

Compony.io 

Secured by Compony

Solve a security issue

DO YOURSELF 

Updatability of components

NPM.js needed a Henry Ford

Updatability never meant blindly updating

Drupal core & contrib is stuck

status-messages

libraries.yml

status-messages.html.twig

status-messages.scss

.component.yml

component:  
  name: 'Status messages'
  folder_name: status-messages
  details: 'https://www.compony.io/component/status-messages'
  downloaded_on: 07-26-2019
  built_upon:
    project: Drupal
    version: '8'
    specific:
      type: core
      link: false
  repository:
    details: 'https://gitlab.com/componies/flat-design/Core/status-messages'
    gitlab_project_id: '7511021'
    version: 'https://gitlab.com/componies/flat-design/Core/status-messages/commit/ae7c929d9e3a8ca2122facc2170ad0b0b4dd54de'
    version_sha: ae7c929d9e3a8ca2122facc2170ad0b0b4dd54de
    version_sha_short: ae7c929d
    diff_with_latest_version: 'https://gitlab.com/componies/flat-design/Core/status-messages/compare/ae7c929d9e3a8ca2122facc2170ad0b0b4dd54de...master'
    clone_this_version: 'git clone https://gitlab.com/componies/flat-design/Core/status-messages.git 
  status-messages && cd status-messages && git checkout ae7c929d9e3a8ca2122facc2170ad0b0b4dd54de'
  contributors:
    1:
      name: Compony
      details: 'https://www.compony.io/user/1'

collection:
  name: 'Bare essentials'
  details: 'https://www.compony.io/collection/bare-essentials'
  contributor:
    1:
      name: Compony
      details: 'https://www.compony.io/user/1'

Drupal Field UI

vs

Twig Hardcoding

 

node--article

node--article.html.twig

{% block content %}
  {{ content.field_c }}
  {{ content.field_a }}
  {{ content.field_b }}
  {{ content.field_d }}
{% endblock %}

Use the field UI, and hardcode the parts you don't want someone to change

node--article

node--article.html.twig

{% block content %}
  {{ content.field_c }}
  {{ content.field_a }}

  {{ content|without('field_c', 'field_a') }}
{% endblock %}

How to deploy components?

my-old-theme

dist

smile.svg

global.css

@charset "UTF-8";@import url(https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400,700|Roboto:100,300,400);@font-face{font-family:Dupla-Bold;src:url(../fonts/dupla/dupla_bold-webfont.eot);src:url(../fonts/dupla/dupla_bold-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/dupla/dupla_bold-webfont.woff2) format("woff2"),url(../fonts/dupla/dupla_bold-webfont.woff) format("woff"),url(../fonts/dupla/dupla_bold-webfont.ttf) format("truetype")}@font-face{font-family:DuplaDemibold...

-  @charset "UTF-8";@import url(https://fonts.googleapis.com/css
+ @charset "UTF-8";@import url(https://fonts.googleapis.com/css

 ¯\_(ツ)_/¯

Stop minifying CSS?

Aggregates!

But keep uglifying JS!

Commit the dist folder

  • Only intentional merge conflicts when you are working in the same component with multiple developers
  • Avoid NPM on server-level
  • Your backend-colleague shouldn't have to run Gulp
  • More resilience in deployment, as the needed frontend files don't depend on a gulp-task running successfully.
  • More secure, no third party unchecked scripts on server

Ignore the dist folder

  • Everyone needs all the same tools, the frontend dev has
  • Gulp needs to run on each environment
  • less secure, as you install unchecked NPM dependencies on a server
  • Harder to debug in case something goes wrong
  • PRO: you don't need to commit the CSS in Git.

Drupal’s hook system explained for dummies

Drupal castle:

Node/7

Template files

{# page.html.twig #}
<div class="page-content clearfix">
  {{ page.header }}
  <main id="main-content" role="main">
    {{ page.help }}

    {{ page.content }}
  </main>

  {{ page.footer }}
</div>

Re-ordering

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--page--full.html.twig
   * node--page.html.twig
   * node--full.html.twig
   x node.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/compony/components/node/node.html.twig' -->
  <article>...</article>
<!-- END OUTPUT from 'themes/compony/components/node/node.html.twig' -->

You can hook in to any step to build this castle, but you can't change the order of the different steps

HOOK_preprocess_hook

The only 4 hooks you need: 

1

Theme-name

Lego-name

HOOK_preprocess_hook

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--page--full.html.twig
   * node--page.html.twig
   * node--full.html.twig
   x node.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/compony/components/node/node.html.twig' -->
  <article>...</article>
<!-- END OUTPUT from 'themes/compony/components/node/node.html.twig' -->

components

node

node.html.twig

node.theme

function compony_preprocess_node(&$variables) {
   // DO stuff here...
}
<article (( attributes ))>
  <h2><a href="{{ url }}" rel="bookmark">{{ label }}</a></h2>

  {{ content }}
</article>

components

node

node.html.twig

node.theme

function compony_preprocess_node(&$variables) {
   $variables['party'] = 'jipie';
}
<article {{ attributes }}>
  <h2><a href="{{ url }}" rel="bookmark">{{ label }}</a></h2>

  {{ content }}
  {{ party }}
</article>

components

node

node.html.twig

node.theme

function compony_preprocess_node(&$variables) {
  $variables['attributes']['class'][] = 'new-class';
}
<article {{ attributes.addClass('new-class') }}>
  <h2><a href="{{ url }}" rel="bookmark">{{ label }}</a></h2>

  {{ content }}
</article>

components

node

node.html.twig

node.theme

function compony_preprocess_node(&$variables) {
  $variables['#attached']['library'][] = 'compony/node';
}
{{ attach_library("compony/node") }}
<article {{ attributes }}>
  <h2><a href="{{ url }}" rel="bookmark">{{ label }}</a></h2>

  {{ content }}
</article>

Tryout your first preprocess!

DO YOURSELF 

HOOK_theme_suggestions_hook_alter

The only 4 hooks you need: 

2

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--blog--full.html.twig
   * node--blog.html.twig
   * node--full.html.twig
   x node.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/compony/components/node/node.html.twig' -->
  <article>...</article>
<!-- END OUTPUT from 'themes/compony/components/node/node.html.twig' -->

This one!

components

node

node.html.twig

node.theme

function compony_theme_suggestions_node_alter(array &$suggestions, array $variables) {
  $suggestions[] = 'node__party_pooper';
}
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--party-pooper.html.twig
   * node--blog--full.html.twig
   * node--blog.html.twig
   * node--full.html.twig
   x node.html.twig
-->

New suggestion

components

node

node.html.twig

node.theme

function compony_theme_suggestions_node_alter(array &$suggestions, array $variables) {
  $suggestions[] = 'node__party_pooper';
}

node--party-pooper

node--party-pooper.html.twig

_     -

Only add suggestions with logic 

Instead of

form

form.html.twig

<form{{ attributes.addClass(classes) }}>
  {% if element['#form_id'] == 'register_form' %}
    Some custom header  
  {% endif %}
  
  {{ children }}

  {% if element['#form_id'] == 'contact_form' %}
    Reach us on 00468 638295
  {% endif %}
</form>

form

form.html.twig

form.theme

function compony_theme_suggestions_form_alter(&$variables) {
  $suggestions[] = 'form__'.$variables['element']['#form_id'];
}

form--register-form

form--contact-form

form--register-form.html.twig

form--contact-form.html.twig

Tryout your first theme suggestions alter!

DO YOURSELF 

HOOK_library_info_alter
__hook

The only 4 hooks you need: 

3

Only use this to undo attach_library

function compony_library_info_alter__table(&$libraries, $extension) {
  // Remove all the weird table functionality no-one wants.
  unset($libraries['drupal.tabbingmanager']);
  unset($libraries['drupal.tabledrag']);
  unset($libraries['drupal.tableresponsive']);
  unset($libraries['drupal.tableselect']);
}

Find out where libraries come from 

Tryout your first libraries info alter!

DO YOURSELF 

HOOK_theme__hook

The only 4 hooks you need: 

4

Invent new Lego

text-dropdown

text-dropdown.theme

function compony_theme__text_dropdown($existing, $type, $theme, $path) {
  return [
    'template' => 'text-dropdown',
    'variables' => [
      'toggle' => NULL,
      'content' => NULL,
      'classes' => NULL,
    ],
  ];
}

text-dropdown

text-dropdown.theme

{{ attach_library('compony/text-dropdown') }}

<div class='text-dropdown'>
  <button type="button" class="text-dropdown__toggle">
    <span>{{ toggle }}</span>
  </button>

  <div class="text-dropdown__content">
    <p>{{ content }}</p>
  </div>
</div>

text-dropdown.html.twig

text-dropdown

text-dropdown.theme

text-dropdown:
  version: VERSION
  css:
    component:
      dist/text-dropdown.css: {}

text-dropdown.html.twig

dist

text-dropdown.scss

libraries.yml

text-dropdown

text-dropdown.theme

text-dropdown.html.twig

text-dropdown.scss

libraries.yml

function compony_theme__text_dropdown($existing, $type, $theme, $path) {

function compony_preprocess_text_dropdown(&$variables) {...}

function compony_library_info_alter__text_dropdown(&$libraries, $extension) {...}

function compony_theme_suggestions_text_dropdown_alter(&$variables) {

some-module

some-module.module

$build['my_dropdown'] = [
  '#theme' => 'text_dropdown',
  '#toggle' => 'Text for the toggle',
  '#classes' => ['my-extra-class'], // optional
  '#content' => [
    '#markup' => 'The text that should be shown when clicked',
  ],
];

Text

module code, don't write this yourself

Tryout your first libraries info alter!

DO YOURSELF 

Business logic
vs
presentational logic

/news

The news

Newsitem 2

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

Newsitem 3

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

Newsitem 1

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

Client

Help! The news view is not

sorting the items on publication

date!

 

And visitors can't see the date

of each news item!

 

/news

The news

Newsitem 3

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

Newsitem 2

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

Newsitem 1

Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her Dolor lorem ipsum her

  1. Add a publication date field to news-nodes
  2. Change the view so that it sorts on publication date field
  3. We add this new field to the teaser view-mode of news-items
  4. We make the datestamp look like "2 days ago" instead of a timestamp

3 weeks ago

1 week ago

2 hours ago

  1. Add a publication date field to news-nodes
  2. Change the view so that it sorts on publication date field

     
  3. We add this new field to the teaser view-mode of news-items
  4. We make the datestamp look like "2 days ago" instead of a timestamp

Business logic

Presentational logic

Backend

Frontend

my-component.theme

 Integrate libraries in to your projects

DO YOURSELF 

CSS-library: Bootstrap 4

bootstrap

libraries.yml

bootstrap.min.css

bootstrap:
  version: VERSION
  css:
    component:
      dist/bootstrap.min.css: {}

_sass-essentials

compony

components

my-theme.info.yml

my-theme.libraries.yml

my-theme.theme

name: Compony
core: 8.x
libraries:
  - compony/global
  - compony/bootstrap

node--article

dist

libraries.yml

node--article.html.twig

node--article.css

node--article.scss

node--article:
  css:
    component:
      dist/node--article.css: {}
  dependencies:
    - compony/bootstrap

JS-library:

ReactJS

Photoswipe

photoswipe

dist

libraries.yml

main.css

photoswipe.min.js

node--article:
  css:
    component:
      dist/main.css: {}
  js:
    photoswipe.min.js: {}

main.scss

node--article

libraries.yml

node--article.js

node--article:
  css:
    component:
      dist/node--article.css: {}
  js:
    dist/node--article.js: {}

node--article.scss

photoswipe

node--article.html.twig

Google Maps

Slick slider

Contribute what you just made

DO YOURSELF 

Drupal's different caching contexts

A) Every-page CSS
B) Page-specific CSS

Broken by Paragraphs, Views

<html>
  <head>
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_Ew7mHsG94pEUKReBZ_SsV4PxDlHo5Q6R284l2o0Pdew.css?pyte6e">
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_zHBfDe5PZOuUeNbGmUprwXTvq--9nJnmYm9ZhSIOGPI.css?pyte6e">
  </head>

  <body>...</body>
</html>
<html>
  <head>
    <link rel="stylesheet" href="global.css">
  </head>

  <body>...</body>
</html>

contains 90% too much CSS for any given page

<html>
  <head>
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_Ew7mHsG94pEUKReBZ_SsV4PxDlHo5Q6R284l2o0Pdew.css?pyte6e">
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_zHBfDe5PZOuUeNbGmUprwXTvq--9nJnmYm9ZhSIOGPI.css?pyte6e">
  </head>

  <body>...</body>
</html>

contains only the CSS needed!

<html>
  <head>
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_Ew7mHsG94pEUKReBZ_SsV4PxDlHo5Q6R284l2o0Pdew.css?pyte6e">
    <link rel="stylesheet" media="all" href="/sites/default/files/css/css_zHBfDe5PZOuUeNbGmUprwXTvq--9nJnmYm9ZhSIOGPI.css?pyte6e">
  </head>

  <body>...</body>
</html>

contains only the CSS needed!

40% of the CSS is 100% render blocking

1 change in component, will cache-bust the entire CSS

Critical CSS

<html>
  <head>
    <link rel="stylesheet" href="critical.css" >
    <link rel="stylesheet" href="non-critical.css">
  </head>

  <body>...</body>
</html>
<html>
  <head>
    <link rel="stylesheet" href="critical.css">
    <script>
      // make a stylesheet link
      var myCSS = document.createElement( "link" );
      myCSS.rel = "stylesheet";
      myCSS.href = "non-critical.css";
      
      // insert it at the end of the head in a legacy-friendly manner
      document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
    </script>
  </head>
  <body>...</body>
</html>
<html>
  <head>
    <link href="critical.css" rel="stylesheet">
    <link href="non-critical.css" rel="stylesheet">
  </head>

  <body>...</body>
</html>
<html>
  <head>
    <link rel="stylesheet" href="critical.css" >
    <link rel="alternate stylesheet" href="non-critical.css" onload="this.rel='stylesheet'">
  </head>
  <body>...</body>
</html>

40% of the CSS is now render blocking instead of 100%

1 change in component, will cache-bust the entire CSS

Global CSS

Menu CSS

Banner CSS

node--teaser CSS

fonts!

Only desktop

Search!

Status messages

Region CSS

Block CSS

Maintainability:  (╯°□°)╯︵ ┻━┻

<html>
  <head>
    <link href="critical.css" rel="stylesheet">
    <link href="non-critical.css" rel="stylesheet">
  </head>

  <body>...</body>
</html>
<html>
  <head>
    <link rel="stylesheet" href="core.css" />
    <link rel="stylesheet" href="site-header.css" />
    <link rel="stylesheet" href="site-nav.css" />
    <link rel="stylesheet" href="content.css" />
    <link rel="stylesheet" href="content-primary.css" />
    <link rel="stylesheet" href="date-picker.css" />
    <link rel="stylesheet" href="content-secondary.css" />
    <link rel="stylesheet" href="ads.css" />
    <link rel="stylesheet" href="site-footer.css" />
  </head>
  <body>...</body>
</html>

a change in
a component CSS,
will cache-bust the component CSS

Non-critical CSS:
async loading!

Still complex differentiation

<html>
<head>
  <link rel="stylesheet" href="core.css" />
</head>
  
<body>
  <link rel="stylesheet" href="site-header.css" />
  <header class="site-header">
    <link rel="stylesheet" href="site-nav.css" />
    <nav class="site-nav">...</nav>
  </header>

  <link rel="stylesheet" href="content.css" />
  <main class="content">
    <link rel="stylesheet" href="content-primary.css" />
    <section class="content-primary">
      <h1>...</h1>

      <link rel="stylesheet" href="date-picker.css" />
      <div class="date-picker">...</div>
    </section>

    <link rel="stylesheet" href="content-secondary.css" />
    <aside class="content-secondary">
      <link rel="stylesheet" href="ads.css" />
      <div class="ads">...</div>
    </aside>
  </main>

  <link rel="stylesheet" href="site-footer.css" />
  <footer class="site-footer"></footer>
</body>

status-messages

libraries.yml

status-messages.html.twig

status-messages.scss

{{ attach_library("compony/status-messages") }}

<div class="status-messages">
  {{ message }}
</div>

dist

<link rel="stylesheet" href="{{ _self|split('/', -1)|join('/') }}/dist/status-messages.css" />

<div class="status-messages">
  {{ message }}
</div>

Drupal’s JavaScript behaviours

How to extend the hook system?

Twig’s capabilities.

Mathieu Spillebeen

@MathieuSpil

mathieu.spillebeen@gmail.com

Freelance Drupal Frontend developer

Workshop: Compony

By Mathieu Spillebeen