Agregando hojas de estilo y librerías javascript a módulos en Drupal 8

In Drupal 8, stylesheets (CSS) and JavaScript (JS) are loaded through the same system for modules (code) and themes, for everything: asset libraries. Asset libraries can contain one or more CSS assets, one or more JS assets and one or more JS settings.

Drupal uses a high-level principle: assets (CSS or JS) are still only loaded if you tell Drupal it should load them. Drupal does not load all assets (CSS/JS) on all pages, because this is bad for front-end performance.

Differences compared to Drupal 7

There is one important difference compared to Drupal 7 for developers:

  1. Only the JavaScript required on a particular page will be added to that page. In particular, by default Drupal doesn't need JavaScript on most pages that anonymous users can see. This means that jQuery is not automatically loaded on all pages anymore.
    So, if your theme requires jQuery or some other JavaScript to be present (which also is defined in an asset library), you need to tell Drupal that this is the case, by declaring a dependency on the needed asset library.

The process

The general steps for loading assets (CSS/JS) are:

  1. Save the CSS or JS to a file.
  2. Define a "library", which can contain both CSS and JS files.
  3. "Attach" the library to a render array in a hook.

But in the case of themes, there is an alternative to step 3: themes can choose to load any number of asset libraries on all pages.

Defining a library

To define one or more (asset) libraries, add a *.libraries.yml file to your module folder. (If your module is named fluffiness, then the file name should be fluffiness.libraries.yml). Each "library" in the file is an entry detailing CSS and JS files (assets), like this:

cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}

This example assumes that the actual JavaScript cuddly-slider.js is located in the subfolder js of your module. You can also have the JS come from an external URL, include CSS files, and there are other possibilities. See https://www.drupal.org/node/2201089 for details.

However, remember that Drupal 8 no longer loads jQuery on all pages by default; Drupal 8 only loads what's necessary. Therefore, we must declare that our module's cuddly-slider library declares a dependency on the library that contains jQuery. It is neither a module nor a theme that provides jQuery, it's Drupal core: core/jquery is the dependency we want to declare. (This is an extension name followed by a slash, followed by the library name, so if some other library wanted to depend on our cuddly-slider library, it'd have to declare a dependency on fluffiness/cuddly-slider, because fluffiness is the name of of our theme.)

So, to ensure jQuery is available for js/cuddly-slider.js, we update the above to:

cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery

As you'd expect, the order the CSS and JS assets are listed is also the order in which they will be loaded.

Attaching a library to page(s)

Depending on which assets you need to have loaded, you'll want to attach the corresponding asset library in a different way. After all, some asset libraries are needed on all pages, others only very rarely, and yet others on most, but not quite all.

But what matters most is that we don't decide whether to attach a library based on which page we're on (i.e. which URL or route), but based on which things are visible on the page: if a page contains a '#type' => 'table', a '#type' => 'dropbutton' and a '#type' => 'foobar', then we'll only load the libraries associated with each of those '#type's.
But we're not limited to '#type' only: perhaps we want to load a certain asset library only for a certain instance of a '#type'. In that case, we just attach it to the render array of that instance.

Of course, very rarely, there is a valid reason to actually load a certain asset on all pages (e.g. some analytics JavaScript that tracks page loads), regardless of the "things" on a page.

The sub-sections here show examples of how to do these things.

Attaching to a certain '#type' (for all instances of it)

To attach a library to a certain existing '#type', for all instances of it, we use hook_element_info_alter():

<?php
function fluffy_element_info_alter(array &$types) {
  if (isset(
$types['table'])) {
   
$types['table']['#attached']['library'][] = 'fluffy/fancy_table';
  }
}
?>

Then clear the cache so that Drupal is aware of the new hook implementation you added.

Attaching to a render array

To attach a library to a render array (and perhaps a specific instance of a certain '#type'), you must have access to that render array. Perhaps you're defining the render array. Perhaps you're modifying it in a hook. In either case, it will look somewhat like this:

<?php
$build
['the_element_that_needs_the_asset_library']['#attached']['library'][] = 'fluffy/fancy_element';
?>

Attaching to a render array of a Block Plugin

To give another example of attaching a library to a render array, If you are building a block plugin in your module, you can attach the libraries to the render array in the build() function of your class extending the BlockBase class (as of Drupal 8 beta 6).

<?php
   
return array(
     
'#theme' => 'your_module_theme_id',
     
'#someVariable' => $some_variable,
     
'#attached' => array(
       
'library' =>  array(
         
'your_module/library_name'
       
),
      ),
    );
?>

Attaching a library to all (or a subset of) pages

In some cases, the asset library is not associated with a certain part of the page, because it is associated with the entire page. For this case, hook_page_attachments() exists. A clear example can be found in the Contextual Links module:

<?php
function contextual_page_attachments_alter(array &$page) {
  if (!\
Drupal::currentUser()->hasPermission('access contextual links')) {
    return;
  }

 
$page['#attached']['library'][] = 'contextual/drupal.contextual-links';
}
?>

Attaching a library in a twig template

You can also attach a library in a twig template by using the attach_library() twig function. So in any *.html.twig:

{{ attach_library('contextual/drupal.contextual-links') }}
<div>Some markup {{ message }}</div>

Attaching configurable JavaScript

In some cases, you may want to add JavaScript to a page that depends on some computed PHP information.

In this case, create a JavaScript file, define and attach a library just like before, but also attach JavaScript settings and have that JavaScript file read those settings, via drupalSettings (the successor to Drupal 7's Drupal.settings). However, to make drupalSettings available to our JavaScript file, we have to do the same work as we had to do to make jQuery available: we have to declare a dependency on it.

So that then becomes:

cuddly-slider:
  version: 1.x
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

and

<?php
$build
['#attached']['library'][] = 'fluffiness/cuddly-slider';
$build['#attached']['drupalSettings']['fluffiness']['cuddlySlider']['foo'] = 'bar';
?>

Where 'bar' is some calculated value.

Then cuddly-slider.js will be able to access drupalSettings.flufiness.cuddlySlider.foo (and it will === 'bar').

Inline JavaScript

Inline JavaScript is discouraged. It's recommended to put the JS you want to use inline in a file instead, because that allows that JavaScript to be cached on the client side. It also allows JavaScript code to be reviewed and linted.

Inline JavaScript that generates markup

Examples of this are ads, social media sharing buttons, social media listing widgets. These do use inline JavaScript. But they are just a special kind of content/markup, since they're not about decorating the site's content or making it interactive, instead they are about pulling in external content through JavaScript.

You want to put these in either a custom block or even directly in a Twig template.

E.g.:

<script type="text/javascript"><!--
ad_client_id = "some identifier"
ad_width = 160;
ad_height = 90;
//--></script>
<script type="text/javascript" src="http://adserver.com/ad.js"></script>
<a class="twitter-timeline" href="https://twitter.com/wimleers" data-widget-id="307116909013368833">Tweets by @wimleers</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Inline JavaScript that affects the entire page

Examples of inline JavaScript that affects the entire page are analytics (e.g. Google Analytics) and hosted font services. Inline JavaScript that affects the entire page can be in either of two categories: front-end/styling, or logical.

In the case of front-end/styling (e.g. hosted font services), it belongs in the theme, and for that, please see “Adding stylesheets (CSS) and JavaScript (JS) to a Drupal 8 theme”.

In the other case, the JS belongs in the module. In the appropriate hook — likely hook_page_attachments() — define attached HTML <HEAD> data by using the 'html_head' key in the #attached property:

<?php
function fluffy_page_attachments(array &$page) {
 
$page['#attached']['html_head'][] = [
   
// The data.
   
[
     
// The HTML tag to add, in this case a <script> tag.
     
'#tag' => 'script',
     
// The value of the HTML tag, here we want to end up with <script>alert("Hello world!");</script>.
     
'#value' => 'alert("Hello world!");',
    ],
   
// A key, to make it possible to recognize this HTML <HEAD> element when altering.
   
'hello-world'
 
];
}
?>

Dynamically generated CSS and JS

In extremely rare and advanced cases, you may have the need to dynamically generate CSS and JS. There are two categories of "dynamicness":

  1. Dynamically built, but used across multiple requests
  2. Dynamically built for each request

If the dynamic CSS/JS is used across multiple requests, then you can use hook_library_info_alter() to modify a library to include your dynamically/automatically generated CSS/JS. An example in Drupal 8 core of this is color_library_info_alter().

If the dynamic CSS/JS is built for each request, then you enter the truly advanced territory. This is hard, and for a good reason: per-request dynamic assets have to be built on every single request and therefore slow Drupal down. We want to make it hard to make Drupal slow down, so this is why we don't offer a nice API for this — since we don't want you to do it.
It is possible though. In the case of dynamic JS: please consider using configurable JavaScript instead, that is almost always the much better choice. Then the logic is stored in a file (and can be reviewed, linted and cached on the client side), and only the settings to configure the logic in that file need to be generated on each request. And in fact, this can also be used for dynamic CSS: attach dynamic CSS as drupalSettings and let some JS file add it to the page.
If using drupalSettings plus a JavaScript file is not an option, then you still have one option left: use hook_page_attachments(), where you add a new value to $page['#attached']['html_heads'], which contains either a <script> tag or a <style> tag, as the “Inline JavaScript that affects the entire page” section above already showed.

Differences with Drupal 7

More information