Добавление таблиц стилей (CSS) и JavaScript (JS) в модуль Drupal 8
Эта документация для модулей. Информацию о темах смотрите в разделе Добавление таблиц стилей (CSS) и JavaScript (JS) в тему Drupal 8.
В Drupal 8 таблицы стилей (CSS) и JavaScript (JS) загружаются через одну и ту же систему для модулей (кода) и тем: библиотеку ресурсов. Библиотеки активов могут содержать один или несколько ресурсов CSS, один или несколько ресурсов JS и одну или несколько настроек JS.
Drupal использует принцип высокого уровня: ресурсы (CSS или JS) по-прежнему загружаются только в том случае, если вы сообщаете Drupal, что они должны их загружать. Drupal не загружает все ресурсы (CSS/JS) на всех страницах, потому что это плохо сказывается на производительности интерфейса.
Отличия от Drupal 7
Есть два важных отличия от Drupal 7 для разработчиков:
1. Только JavaScript, необходимый для конкретной страницы, будет добавлен на эту страницу. В частности, по умолчанию Drupal не нуждается в JavaScript на большинстве страниц, которые могут видеть анонимные пользователи. Это означает, что jQuery больше не загружается автоматически на все страницы.
Итак, если вашей теме требуется jQuery или какой-либо другой JavaScript (который также определен в библиотеке ресурсов), вам нужно сообщить Drupal, что это так, объявив зависимость от необходимой библиотеки активов.
2. Javascript-объект Drupal.settings заменяется на drupalSettings.
Процесс
Основные этапы загрузки ресурсов (CSS / JS):
1. Сохраните CSS или JS в файл.
2. Определите «библиотеку», которая может содержать файлы CSS и JS.
3. «Присоединить» библиотеку к массиву визуализации в хуке.
Но в случае тем есть альтернатива шагу 3: темы могут загружать любое количество библиотек ресурсов на всех страницах.
Определение библиотеки
Чтобы определить одну или несколько библиотек (ресурсов), добавьте файл *.libraries.yml в корень папки вашего модуля (вместе с файлом .info.yml). (Если ваш модуль называется fluffiness, тогда имя файла должно быть fluffiness.libraries.yml). Каждая «библиотека» в файле - это запись, детализирующая файлы CSS и JS (ресурсы), например:
Особое примечание для французских людей: будьте осторожны, чтобы правильно написать .libraries.yml! Не .librairies.yml !! (библиотека - это книжный магазин на французском языке ...) или вы можете долго искать, что не так, поскольку Drupal не выдаст ошибку при попытке присоединить несуществующую библиотеку ...;)
cuddly-slider: version: 1.x css: layout: css/cuddly-slider-layout.css: {} theme: css/cuddly-slider-theme.css: {} js: js/cuddly-slider.js: {}
Вы можете заметить клавиши 'layout' и 'theme' для css, которых нет для js. Это указывает на тип стиля, к которому относится файл CSS.
Вы можете установить вес CSS с 5 различными уровнями стиля:
- base: CSS сброс / нормализация плюс стилизация HTML-элементов. Ключ назначает вес CSS_BASE = -200
- layout: макропорядок веб-страницы, включая любые сеточные системы. Ключ назначает вес CSS_LAYOUT = -100
- component: дискретные, многократно используемые элементы пользовательского интерфейса. Ключ назначает вес CSS_COMPONENT = 0
- state: стили, связанные с изменениями компонентов на стороне клиента. Ключ назначает вес CSS_STATE = 100
- theme: чисто визуальный стиль («наглядность») для компонента. Ключ назначает вес CSS_THEME = 200
Это определяется стандартом SMACSS. Поэтому, если вы укажете theme, это означает, что CSS-файл содержит стили, связанные с темой, которые являются чистым внешним видом. Больше информации здесь. Вы не можете использовать другие ключи, так как они приведут к строгим предупреждениям.
В этом примере предполагается, что фактический JavaScript cuddly-slider.js находится в подпапке js вашего модуля. Вы также можете сделать так, чтобы JS исходил из внешнего URL-адреса, включал файлы CSS, и есть другие возможности. См. CDN / внешние библиотеки для деталей.
Однако помните, что Drupal 8 больше не загружает jQuery на все страницы по умолчанию; Drupal 8 загружает только то, что нужно. Поэтому мы должны объявить, что библиотека cuddly-slider нашего модуля объявляет зависимость от библиотеки, содержащей jQuery. Это не модуль и не тема, которая предоставляет jQuery, это ядро Drupal: core/jquery - это зависимость, которую мы хотим объявить. (Это имя расширения, за которым следует косая черта, за которым следует имя библиотеки, поэтому, если какая-то другая библиотека хочет зависеть от нашей библиотеки cuddly-slider, она должна будет объявить зависимость от fluffiness/cuddly-slider, поскольку fluffiness название нашего модуля.)
Таким образом, чтобы обеспечить доступность jQuery для js/cuddly-slider.js, мы обновим приведенное выше:
cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {} dependencies: - core/jquery
Как и следовало ожидать, порядок перечисления ресурсов CSS и JS также является порядком их загрузки.
По умолчанию Drupal прикрепляет ресурсы JS внизу страницы, чтобы избежать некоторых частых проблем, таких как: блокировка загрузки содержимого DOM, доступ к не готовому элементу DOM из кода jquery и т. д. Если по какой-то причине необходимо подключить активы JS в разделе <head> возможно использование опции header, таким образом:
cuddly-slider: version: 1.x header: true js: js/cuddly-slider.js: {}
Итак, теперь js/cuddly-slider.js будет прикреплен вверху страницы.
Присоединение библиотеки к страницам
В зависимости от того, какие активы вам нужно загрузить, вы можете присоединить соответствующую библиотеку активов по-разному. В конце концов, некоторые библиотеки ресурсов нужны на всех страницах, другие - очень редко, а другие - на большинстве, но не на всех.
Но что важнее всего, так это то, что мы не решаем, подключать ли библиотеку, основываясь на том, на какой странице мы находимся (т.е. какой URL или маршрут), но на основе того, какие вещи видны на странице: если страница содержит '# type '=>' table ',' #type '=>' dropbutton 'и' #type '=>' foobar ', тогда мы будем загружать только библиотеки, связанные с каждым из этих типов #.
Но мы не ограничены только «#type»: возможно, мы хотим загрузить определенную библиотеку ресурсов только для определенного экземпляра «#type». В этом случае мы просто присоединяем его к массиву рендеринга этого экземпляра.
Конечно, очень редко есть веская причина для фактической загрузки определенного ресурса на все страницы (например, некоторый аналитический JavaScript, который отслеживает загрузку страниц), независимо от «вещей» на странице.
Подразделы здесь показывают примеры того, как сделать эти вещи.
Присоединение к определенному «#type» (для всех его экземпляров)
Чтобы присоединить библиотеку к определенному существующему «#type», для всех его экземпляров мы используем hook_element_info_alter():
function yourmodule_element_info_alter(array &$types) { if (isset($types['table'])) { $types['table']['#attached']['library'][] = 'your_module/library_name'; } }
Затем очистите кеш, чтобы Drupal знал о новой реализации хука, которую вы добавили.
Присоединение к массиву рендеринга
Чтобы присоединить библиотеку к массиву визуализации (и, возможно, к конкретному экземпляру определенного '#type'), вы должны иметь доступ к этому массиву визуализации. Возможно, вы определяете массив визуализации. Возможно, вы модифицируете это в хуке. В любом случае это будет выглядеть примерно так:
$build['the_element_that_needs_the_asset_library']['#attached']['library'][] = 'your_module/library_name';
Всегда используйте цифровые клавиши!
Возможно, вы захотите помочь Drupal и не создавать дубликаты библиотек, используя нечисловые ключи:
$build['the_element_that_needs_the_asset_library']['#attached']['library']['your_module/library_name'] = 'your_module/library_name';
Не делайте этого. Способ слияния массивов в Drupal приведет к недопустимому вложенному массиву, и обратите внимание на следующее (googlefood):
Warning: explode() expects parameter 2 to be string, array given in Drupal\Core\Asset\LibraryDependencyResolver->doGetDependencies() Notice: Array to string conversion in system_js_settings_alter() Notice: Array to string conversion in Drupal\Core\Asset\AttachedAssets->setLibraries()
Присоединение к массиву рендеринга блочного плагина
Чтобы привести еще один пример присоединения библиотеки к массиву рендеринга. Если вы собираете блочный плагин в своем модуле, вы можете присоединить библиотеки к массиву рендеринга в функции build() вашего класса, расширяющей класс BlockBase (начиная с Drupal 8 бета 6).
return [ '#theme' => 'your_module_theme_id', '#someVariable' => $some_variable, '#attached' => [ 'library' => [ 'your_module/library_name', ], ], ];
Присоединение библиотеки к форме
Поскольку формы представляют собой просто массивы рендеринга, присоединение библиотеки работает точно так же:
/** * Implements hook_form_alter(). */ function yourmodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { /* @var Drupal\Core\Entity\FieldableEntityInterface $entity */ $formObject = $form_state->getFormObject(); if ($formObject instanceof \Drupal\Core\Entity\EntityFormInterface) { $entity = $formObject->getEntity(); if ( $entity->getEntityTypeId() === 'node' && in_array($entity->bundle(), ['organisation', 'location', 'event', 'article']) ) { $form['#attached']['library'][] = 'yourmodule/yourlibrary'; } } }
Присоединение библиотеки ко всем (или подмножеству) страниц
В некоторых случаях библиотека ресурсов не связана с определенной частью страницы, поскольку она связана со всей страницей. Для этого случая hook_page_attachments() существует. Яркий пример можно найти в модуле «Контекстные ссылки»:
// From core/modules/contextual/contextual.module. function contextual_page_attachments(array &$page) { if (!\Drupal::currentUser()->hasPermission('access contextual links')) { return; } $page['#attached']['library'][] = 'contextual/drupal.contextual-links'; }
Присоединение библиотеки в функцию предварительной обработки
Вы можете присоединить библиотеку в функции предварительной обработки, используя специальный ключ '#attached':
function yourmodule_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'your_module/library_name'; }
Прикрепление библиотеки в шаблон веточки
Вы также можете прикрепить библиотеку в шаблон веточки, используя функцию ветки attach_library(). Так в любом *.html.twig:
{{ attach_library('your_module/library_name') }} <div>Some markup {{ message }}</div>
Присоединение библиотеки во время замены токена
Вы также можете прикрепить библиотеку, если ваш собственный токен присутствует в отфильтрованном тексте, добавив библиотеку к объекту BubbleableMetadata во время замены в hook_tokens():
/** * Implements hook_tokens(). */ function your_module_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) { $replacements = []; if ($type == 'your_module') { foreach ($tokens as $name => $original) { switch ($name) { case 'your-token': $your_render_array = your_module_build_your_renderable_thing(); $replacements[$original] = \Drupal::service('renderer')->render($your_render_array); // LOOK HERE! WE CAN ADD LIBRARIES TOO! $bubbleable_metadata->addAttachments(['library' => ['your_module/library_name'] ]); break; } } } return $replacements; }
Обратите внимание, что в этом примере показано только, как сделать вложение библиотеки во время замены - чтобы полностью реализовать пользовательский токен, вы также должны реализовать hook_token_info().
Присоединение библиотеки в плагине фильтра
Если модуль предоставляет текстовый фильтр, он может использовать метод setAttachments() или addAttachments() класса FilterProcessResult. Например, фильтр filter_caption делает это:
if (...) { ... $result->setProcessedText(Html::serialize($dom)) ->addAttachments([ 'library' => [ 'filter/caption', ], ]); } return $result;
Присоединение настраиваемого JavaScript
В некоторых случаях вы можете захотеть добавить JavaScript на страницу, которая зависит от некоторой вычисленной информации PHP. Вы можете сделать это с drupalSettings (преемником Drupal 7's Drupal.settings), массивом настроек, определенных в вашем PHP-скрипте, к которому можно обращаться как к объекту настроек в вашем JavaScript.
Для использования drupalSettings в библиотеке сначала необходимо объявить зависимость от core / drupalSettings в определении библиотеки.
Таким образом, определение библиотеки нашего предыдущего примера:
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {} dependencies: - core/jquery - core/drupalSettings
В наших PHP-файлах теперь мы можем передавать требуемые drupalSettings вместе с нашей библиотекой. По соглашению мы используем имя модуля lowerCamelCase в качестве ключа для настроек и добавляем имя библиотеки lowerCamelCase в качестве дополнительного ключа.
Если бы мы хотели передать вычисленные значения 'foo' и 'baz' из PHP в JavaScript нашего примера, мы могли бы сделать:
$computed_settings = [ 'foo' => 'bar', 'baz' => 'qux', ]; $build['#attached']['library'][] = 'your_module/library_name'; $build['#attached']['drupalSettings']['fluffiness']['cuddlySlider'] = $computed_settings;
Тогда cuddly-slider.js сможет получить доступ к drupalSettings.fluffiness.cuddlySlider.foo и drupalSettings.fluffiness.cuddlySlider.baz, которые будут иметь значения «bar» и «qux» соответственно.
Массивы рендеринга кэшируются. В зависимости от характера ваших вычисленных значений и компонента, к которому вы присоединяете drupalSettings, вам, возможно, придется соответствующим образом изменить метаданные кэшируемости.
Добавление атрибутов в элементы скрипта
Если вы хотите добавить атрибуты в тег скрипта, вам нужно добавить ключ атрибутов в JSON после URL скрипта. Внутри объекта, следующего за ключом атрибутов, добавьте имя атрибута, которое вы хотите отобразить в сценарии в качестве нового ключа. Значение для этого ключа будет значением атрибута. Если для этого значения установлено значение true, атрибут будет отображаться сам по себе без значения элемента.
Например:
https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap: {type: external, attributes: { defer: true, async: true, data-test: map-link } }
Это приведет к следующей разметке:
<script src="https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap" async defer data-test="map-link"></script>
Отключение агрегации
По умолчанию несколько локальных файлов будут объединены, где это возможно. Чтобы отключить это для файла, установите его флаг 'preprocess' в false.
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {preprocess: false} dependencies: - core/jquery - core/drupalSettings
CDN / внешние библиотеки
Возможно, вы захотите использовать JavaScript, который находится на CDN (Content Delivery Network), для повышения скорости загрузки страницы. Это можно сделать, объявив библиотеку «внешней». Также полезно включить в определение некоторую информацию о внешней библиотеке.
angular.angularjs: remote: https://github.com/angular/angular.js version: 1.4.4 license: name: MIT url: https://github.com/angular/angular.js/blob/master/LICENSE gpl-compatible: true js: https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
Встроенный JavaScript
Встроенный JavaScript крайне не рекомендуется. Рекомендуется поместить в файл JS, который вы хотите использовать inline, потому что это позволяет кэшировать JavaScript на стороне клиента. Это также позволяет коду JavaScript быть просмотренным и написанным. Inline JS также конфликтует с политикой безопасности контента многих сайтов и делает ваш модуль непригодным для использования ими.
Встроенный JavaScript, который генерирует разметку
Это не рекомендуется. Поместите JavaScript в файл вместо. Примерами этого являются реклама, кнопки общего доступа к социальным сетям, виджеты списков социальных сетей. Они используют встроенный JavaScript. Но это просто особый вид контента/разметки, поскольку они не предназначены для украшения контента сайта или его интерактивности, а для извлечения внешнего контента через JavaScript.
Вы хотите поместить их в пользовательский блок или даже непосредственно в шаблон Twig.
Например.:
<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>
Встроенный JavaScript, который влияет на всю страницу
Встроенный JavaScript крайне не рекомендуется. Примерами встроенного JavaScript, который влияет на всю страницу, являются аналитика (например, Google Analytics) и службы размещенных шрифтов. Встроенный JavaScript, влияющий на всю страницу, может относиться к одной из двух категорий: front-end / styleling или логический. Большинство из этих случаев могут быть удовлетворены с помощью фиксированного JavaScript в файле плюс дополнительные настройки.
В случае внешнего интерфейса / стиля (например, размещенные службы шрифтов) он относится к теме, и для этого, пожалуйста, смотрите «Добавление таблиц стилей (CSS) и JavaScript (JS) в тему Drupal 8».
В другом случае JS принадлежит модулю. В соответствующем хуке - скорее всего, hook_page_attachments() - определите вложенные данные HTML <HEAD>, используя ключ 'html_head' в свойстве #attached:
function fluffiness_page_attachments(array &$attachments) { $attachments['#attached']['html_head'][] = [ // The data. [ '#type' => 'html_tag', // The HTML tag to add, in this case a tag. '#tag' => 'script', // The value of the HTML tag, here we want to end up with // alert("Hello world!");. '#value' => 'alert("Hello world!");', // Set attributes like src to load a file. '#attributes' => array('src' => ''), ], // A key, to make it possible to recognize this HTML element when altering. 'hello-world', ]; }
Динамически генерируемые CSS и JS
В крайне редких и сложных случаях вам может понадобиться динамически генерировать CSS и JS. Существует две категории «динамичности»:
1. Динамически построен, но используется для нескольких запросов
2. Динамически построен для каждого запроса
Если динамический CSS / JS используется в нескольких запросах, то вы можете использовать hook_library_info_alter(), чтобы изменить библиотеку для включения вашего динамически/автоматически сгенерированного CSS / JS. Примером в ядре Drupal 8 является color_library_info_alter(). Поймите, что просто использование hook_library_info_build() или hook_library_info_alter() для добавления библиотеки не приведет к автоматическому появлению библиотеки на странице. Вы все еще должны определить его как вложение (либо для страницы, либо для определенного элемента), используя любой из методов, описанных выше.
Если динамический CSS / JS создается для каждого запроса, вы входите в действительно продвинутую территорию. Это сложно, и на то есть веская причина: динамические активы для каждого запроса должны быть построены на каждом отдельном запросе, что замедляет работу Drupal. Мы хотим усложнить замедление работы Drupal, поэтому мы не предлагаем хороший API для этого, поскольку мы не хотим, чтобы вы это делали.
Это возможно, хотя. В случае динамического JS: пожалуйста, рассмотрите возможность использования настраиваемого JavaScript вместо этого, это почти всегда намного лучший выбор. Затем логика сохраняется в файле (и может просматриваться, распечатываться и кэшироваться на стороне клиента), и при каждом запросе необходимо создавать только параметры для настройки логики в этом файле. И на самом деле, это также может быть использовано для динамического CSS: присоедините динамический CSS как drupalSettings и позвольте некоторому файлу JS добавить его на страницу.
Если использование drupalSettings плюс файл JavaScript не является опцией, то у вас все еще остается одна опция: используйте hook_page_attachments(), где вы добавляете новое значение в $page ['# attach'] ['html_head'], которая содержит либо тег <script> или тег <style>, как уже было показано в разделе «Встроенный JavaScript, который влияет на всю страницу» выше.
Добавлена hook_library_info_build() для определений динамических библиотек
Для некоторых расширенных случаев использования - таких как обнаружение сторонних библиотек, которые необходимо загрузить вручную, и затем представление их как библиотеки ресурсов Drupal (например, модуль API библиотек) - вы хотите иметь возможность использовать код PHP для регистрации библиотек с использованием некоторой дополнительной логики, Вот почему была добавлена hook_library_info_build()
Обратите внимание, что «динамический» не означает «время выполнения» (т. Е. Для каждого запроса) - это было бы ужасно для производительности. Динамически добавленные библиотеки все еще кэшируются, как библиотеки, определенные в файлах YML. Это означает, что вам все еще нужно прикрепить библиотеку к странице или элементу, используя любой из вышеперечисленных методов. Это «динамично», потому что вы можете использовать логику для управления подключением библиотек.
Отличия от Drupal 7
- В Drupal 7 библиотеки должны были быть определены с помощью hook_library_info(). Это было заменено файлом *.libraries.yml.
- В Drupal 8 drupal_add_css(), drupal_add_js() и drupal_add_library() были удалены в пользу #attached
- В Drupal 8 настройки добавляются на страницу, только если необходимая библиотека зависит от библиотеки core/drupalSettings.
Больше информации
Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.