Debug Attached Libraries

When we load a Drupal page, we can see the css and javascript that got loaded in the browser dev tools, but we don't know which libraries brought them in.

This is an attempt to find out which libraries got loaded, and learn about asset rendering along the way.

 

Comment showing attached libraries on the page

 

How does Drupal know which libraries are on the page?

The theme defines global libraries in THEME.info.yml.

Libraries define css/js, and additional library dependencies.

Render arrays define attached libraries.

Preprocess and alter hooks can modify render arrays.

Templates can attach libraries.

 

html.html.twig

Looking at the HTML template, the css and js are placeholder tokens. That means rendering is delayed on the first pass and updated after other items are parsed so that they can aggregate all the attached libraries.

  <head>
    <css-placeholder token="{{ placeholder_token }}">
    <js-placeholder token="{{ placeholder_token }}">
  </head>
  <body{{ attributes.addClass(body_classes) }}>
    <js-bottom-placeholder token="{{ placeholder_token }}">
  </body>

 

theme.inc: template_preprocess_html()

Generate a random placeholder token for each type and add it to ['#attached']['html_response_attachment_placeholders'].

// Create placeholder strings for these keys.
// @see \Drupal\Core\Render\HtmlResponseSubscriber
$types = [
  'styles' => 'css',
  'scripts' => 'js',
  'scripts_bottom' => 'js-bottom',
  'head' => 'head',
];
$variables['placeholder_token'] = Crypt::randomBytesBase64(55);
foreach ($types as $type => $placeholder_name) {
  $placeholder = '<' . $placeholder_name . '-placeholder token="' . $variables['placeholder_token'] . '">';
  $variables['#attached']['html_response_attachment_placeholders'][$type] = $placeholder;
}

 

HtmlResponseSubscriber.php

Respond to KernelEvents::RESPONSE, which is fired after the page response is prepared, allowing modifications before it is sent.

Checks for instanceof HtmlResponse (don't process redirects or other types).

public function onRespond(ResponseEvent $event) {
  $response = $event->getResponse();
  if (!$response instanceof HtmlResponse) {
    return;
  }
  $event->setResponse($this->htmlResponseAttachmentsProcessor->processAttachments($response));
}

 

HtmlResopnseAttachmentsProcessor.php

Assets include libraries and drupalSettings, this is where the magic happens.

This is stripped down to show the relevant parts.

public function processAttachments(AttachmentsInterface $response) {
  // Get a reference to the attachments.
  $attached = $response->getAttachments();
  
  // Get the placeholders from attached and then remove them.
  $attachment_placeholders = $attached['html_response_attachment_placeholders'];
  unset($attached['html_response_attachment_placeholders']);
  
  $assets = AttachedAssets::createFromRenderArray(['#attached' => $attached]);
  $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); 
  $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
  $variables = $this->processAssetLibraries($assets, $attachment_placeholders);
  
  // $variables now contains the markup to load the asset libraries. Update
  // $attached with the final list of libraries and JavaScript settings, so
  // that $response can be updated with those. Then the response object will
  // list the final, processed attachments.
  $attached['library'] = $assets->getLibraries();
  $attached['drupalSettings'] = $assets->getSettings();
  
  // Now replace the attachment placeholders.
  $this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
  
  // AttachmentsResponseProcessorInterface mandates that the response it
  // processes contains the final attachment values.
  $response->setAttachments($attached);
  
  return $response;
}

See processAssetLibraries() for more details on how assets are converted to variables. 

 

At this point, the $response object has the placeholders rendered, with links to css and js, and the final attachments (libraries and drupalSettings) are set. The page is ready to send to the user.

 

ThemeStyleGuideEventSubscriber.php

Create an event subscriber that can access $response and get the libraries.

class AttachedLibrariesSubscriber implements EventSubscriberInterface {
  use StringTranslationTrait;
  
  public function __construct(MessengerInterface $messenger) {
    $this->messenger = $messenger;
  }
  
  public function onKernelResponse(ResponseEvent $event) {
    $response = $event->getResponse();
    if (!$response instanceof HtmlResponse) {
      return;
    }
    $output = new FormattableMarkup('<pre>@libraries</pre>', ['@libraries' => var_export($response->getAttachments()['library'], true)]);
    $this->messenger->addStatus($output);
  }
}

This does the same check for instanceof HtmlResponse.

Use the FormattableMarkup utility to generate a valid html for the message. Otherwise, the message content will strip line breaks and whitespace.

TODO

The major caveat at the moment is that the messages are sent after the response is created, so they are not shown until after the next page reload.

Next steps are to create a block that also uses placeholders to render the output, or find a way to insert the message into the response.

Also, this doesn't show the nested dependencies. A UI to check those out would be nice, with links to the files or preview in the browser. If we had pages we could link to, we could add those to theme debugging to find out more about rendered components.