Most web pages share pieces that are reused throughout the entire site. While I’ve heard them called different things, I prefer to call them components. Some examples include the header, footer, main navigation, logo, slideshow and so on. Most of these can be broken down into smaller components as well. For example, maybe the header is a specific layout that includes the main navigation and the logo. For more information about component based approaches to web pages check out our blog series on components as well as Brad Frost's Atomic Web Design.
In most cases, each of these components needs CSS, HTML and possibly JavaScript that’s unique to them. How can we best organize our files so that it’s easy to find the relevant file we need to update?
Here are two different examples of how I’ve seen CSS, JavaScript, and HTML (Twig) organized.
Legacy Approach
legacy_theme/||-css/||-styles.css||-js/||-scripts.js||-sass/||-styles.scss||-templates/||-html.html.twig||-page.html.twig||…
In this approach files are organized by type and finding anything requires a “grep” or a “find in folder”.
Page Based Approach
page_based_theme/||-css/||-styles.css||-js/||-scripts.js||-sass/||-_home.scss||-_contact.scss||-_story.scss||-style.scss||-templates/||-html.html.twig||-page.html.twig||…
This is a similar approach to the legacy approach but here we’ve split our styles necessary for different pages out to their own Sass partials. While this helps to some degree, what do we do about styles that are shared across pages? What about that hunking scripts.js file? What can we do differently to better organize our components?
Ian Feather has a fantastic article where he advocates grouping files together by component instead of file type. I also really like this React Boilerplate application structure conversation which discusses a similar approach. In Drupal 7, something similar wasn’t really possible but in Drupal 8, twig and Drupal libraries can get us closer. There are still some gaps *cough forms* but in general centralizing components is much easier.
Component Based Approach
component_based_theme/||-dist/||-css/|||-menu.css|||-header.css||-js/|||-menu.js|||- header.js||-src/||-global/|||-global.scss|||…|||||-components/|||-header/||||-header.scss||||-header.es6.js||||-header.twig||||||-menu/||||-menu.scss||||-menu.es6.js||||-menu.twig|||…|||||-templates/|||-html.html.twig|||-page.html.twig|||…|||||-component_based_theme.info.yml||-component_based_theme.libraries.yml||-component_based_theme.theme||...
Whoa. A lot just changed. Let’s step through each modification.
Moving files into dist and src directories.
In our theme, we’re using build tools to compile Sass and convert ES6 JavaScript. This means we’ll have a “source” or “src” file and a file that’s compiled from that source, “dist” or “distributed.” The src file is the file we create and modify and it’s used to generate the dist file that will be loaded by the browser.Separating the compiled files from source helps a new front end developer to easily know which files they should modify and where they should add new stuff.
Within our src directory we now have our main theme files:
component_based_theme.info.yml
component_based_theme.libraries.yml
component_based_theme.themeWe also have our template directory which holds all our Drupal twig templates, the same as the previous approaches. Drupal will not find any twig files unless they are within this directory.
Lastly, we have the meat of our src directory, the component folder. Here you find each component within its own folder and any relevant Sass, JS and markup (twig). What’s not shown here is that each component name should be reflected with the CSS classes used to style the component. I.E. if the component is named “Super Slider” the CSS class used should be “.super-slider.” This makes it super easy to find and modify any component. For example: find any component you want to modify within the browser. Right click, inspect element to bring up the web inspector. Check the class name and find that name in the components directory. Everything you need to modify should be there.
Wait didn’t you just say “Drupal will not find find any twig files unless they are within the template directory”? Yes, yes I did. To bridge the gap between the markup within the component and the markup that Drupal is looking for we include one from the other.
Example: src/templates/node--teaser-full.html.twig
{% include '@component_based_theme/teaser/teaser.twig' with { url: url, modifier_class: 'teaser--sbs', title: label, desc: content.field_teaser.0['#text'], img: content.field_teaser_image, btn_text: 'Read More'|t } only %}
This includes the component markup at src/components/teaser/teaser.twig with the variables from node--teaser-full.html.twig. This approach allows us to keep the markup with its respective component and provides a single source of truth for that markup.
Bonus: Check out https://www.drupal.org/project/components if you want to use twig namespaces like “@component_based_theme”. This keeps you from having to type the entire path.
What’s not shown in the above example is how we’re leveraging Drupal libraries to let each component load its own assets. Whenever we add a new component, we add a new library for that component within component_based_theme.libraries.yml. Inside the component’s twig file we use attach_library() to make sure the library is attached whenever the twig file is included. This means by default only the JS and CSS needed to render the loaded page should be loaded. Goodbye all.js and all.css.
Wait, I hear you say, what about caching the requests? Good question, your approach may vary depending on use case. I prefer to trim down the initial page load as much as Drupally possible. Drupal also splits the CSS aggregates into “all” and “dynamic” so at least the “all aggregate” is the same for all pages.
One of my favorite parts of Drupal libraries is being able to declare dependencies on other libraries and letting Drupal sort the loading out. For example, if we have one component that depends on other smaller components we can do something like this:
header: css: component: dist/css/header.css: {} dependencies: - component_based_theme/site-logo - component_based_theme/main-menu
This would ensure the CSS and JS load order is:
1. site-logo
2. main-menu
3. header
Component organization in Drupal is still in its early days. To read more about the conversation around components in Drupal core check out this issue. That said, while there are still some loose ends we need to tie up to have a true component based approach, Drupal 8 gets us farther than before.
Additional Resources
Drupal 8 Theming Without Panels | Blog
Integrating Components with Drupal 8 - Part 1, Part 2, and Part 3 | Blog
Leveling Up CSS With SMACSS and BEM | Blog