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
- Add a publication date field to news-nodes
- Change the view so that it sorts on publication date field
- We add this new field to the teaser view-mode of news-items
- We make the datestamp look like "2 days ago" instead of a timestamp
3 weeks ago
1 week ago
2 hours ago
- Add a publication date field to news-nodes
- Change the view so that it sorts on publication date field
- We add this new field to the teaser view-mode of news-items
- 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
Workshop: Compony
- 1,491