Agregar hojas de estilo (CSS) y JavaScript (JS) en un módulo de Drupal 8
Esta documentación es para módulos. Para temas, consulte la sección Agregar hojas de estilo (CSS) y JavaScript (JS) en un tema de Drupal 8.
En Drupal 8, las hojas de estilo (CSS) y JavaScript (JS) se cargan mediante el mismo sistema para módulos (código) y temas: la biblioteca de activos. Las bibliotecas de activos pueden contener uno o varios recursos CSS, uno o varios recursos JS y una o varias configuraciones JS.
Drupal utiliza un principio a alto nivel: los recursos (CSS o JS) solo se cargan si usted le indica a Drupal que debe cargarlos. Drupal no carga todos los recursos (CSS/JS) en todas las páginas, ya que eso perjudica el rendimiento de la interfaz.
Diferencias con Drupal 7
Hay dos diferencias importantes respecto a Drupal 7 para desarrolladores:
1. Solo se añadirá JavaScript necesario para una página específica. En particular, por defecto Drupal no necesita JavaScript en la mayoría de las páginas vistas por usuarios anónimos. Esto significa que jQuery ya no se carga automáticamente en todas las páginas.
Por tanto, si su tema necesita jQuery u otro JavaScript (definido también en una biblioteca de activos), debe indicarle a Drupal que es así, declarando la dependencia de la biblioteca necesaria.
2. El objeto JavaScript Drupal.settings se reemplaza por drupalSettings.
Proceso
Etapas principales para cargar recursos (CSS / JS):
1. Guarde el CSS o JS en un archivo.
2. Defina una "biblioteca" que pueda contener archivos CSS y JS.
3. "Adjunte" la biblioteca al array de renderizado en un hook.
Pero para los temas hay una alternativa al paso 3: los temas pueden cargar cualquier cantidad de bibliotecas de activos en todas las páginas.
Definir una biblioteca
Para definir una o varias bibliotecas (de recursos), añada un archivo *.libraries.yml en la raíz de la carpeta de su módulo (junto con el archivo .info.yml). (Si su módulo se llama fluffiness, entonces el archivo debe llamarse fluffiness.libraries.yml). Cada "biblioteca" en el archivo es una entrada que detalla los archivos CSS y JS (recursos), por ejemplo:
Nota especial para los francófonos: tenga cuidado de escribir correctamente .libraries.yml! No .librairies.yml !! (library en francés significa librería...) o podría buscar mucho tiempo qué está mal, porque Drupal no dará error al intentar adjuntar una biblioteca inexistente ... ;)
cuddly-slider: version: 1.x css: layout: css/cuddly-slider-layout.css: {} theme: css/cuddly-slider-theme.css: {} js: js/cuddly-slider.js: {}
Puede notar las claves 'layout' y 'theme' para css, que no existen para js. Esto indica el tipo de estilo al que pertenece el archivo CSS.
Puede establecer el peso del CSS con 5 niveles diferentes de estilo:
- base: CSS de reinicio/normalización más estilización de elementos HTML. La clave asigna el peso CSS_BASE = -200
- layout: macroestructura de la página web, incluyendo cualquier sistema de cuadrícula. La clave asigna el peso CSS_LAYOUT = -100
- component: elementos discretos y reutilizables de la interfaz. La clave asigna el peso CSS_COMPONENT = 0
- state: estilos relacionados con cambios en el componente en el cliente. La clave asigna el peso CSS_STATE = 100
- theme: estilo puramente visual ("look and feel") para el componente. La clave asigna el peso CSS_THEME = 200
Esto está definido por el estándar SMACSS. Por eso, si indica theme, significa que el archivo CSS contiene estilos relacionados con el tema que son puramente visuales. Más información aquí. No puede usar otras claves ya que causarán advertencias estrictas.
En este ejemplo se asume que el JavaScript real cuddly-slider.js está en la subcarpeta js de su módulo. También puede hacer que el JS venga de una URL externa, incluir archivos CSS, y hay otras opciones. Vea CDN / bibliotecas externas para detalles.
Sin embargo, recuerde que Drupal 8 ya no carga jQuery en todas las páginas por defecto; solo carga lo necesario. Por ello, debemos declarar que la biblioteca cuddly-slider de nuestro módulo declara dependencia de la biblioteca que contiene jQuery. No es un módulo ni un tema lo que provee jQuery, es el núcleo de Drupal: core/jquery es la dependencia que queremos declarar. (Este es el nombre de la extensión, seguido por una barra y luego el nombre de la biblioteca, por lo que si otra biblioteca quiere depender de nuestra cuddly-slider, tendrá que declarar dependencia de fluffiness/cuddly-slider, porque fluffiness es el nombre de nuestro módulo.)
Así, para asegurar que jQuery esté disponible para js/cuddly-slider.js, actualizamos el ejemplo anterior:
cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {} dependencies: - core/jquery
Como es lógico, el orden en que enumera los recursos CSS y JS también es el orden en que se cargarán.
Por defecto, Drupal adjunta recursos JS al final de la página para evitar problemas frecuentes como bloqueo de carga del DOM, acceso a elementos DOM no preparados desde código jquery, etc. Si por alguna razón necesita cargar los activos JS en la sección <head>, puede usar la opción header, así:
cuddly-slider: version: 1.x header: true js: js/cuddly-slider.js: {}
Así, js/cuddly-slider.js se adjuntará en la parte superior de la página.
Adjuntar biblioteca a páginas
Dependiendo de qué activos necesite cargar, puede adjuntar la biblioteca correspondiente de varias formas. Al fin y al cabo, algunas bibliotecas se necesitan en todas las páginas, otras muy raramente, y otras en la mayoría pero no en todas.
Pero lo más importante es que no decidimos cargar la biblioteca basándonos en qué página estamos (es decir, la URL o ruta), sino en qué cosas están visibles en la página: si la página contiene '#type' => 'table', '#type' => 'dropbutton' y '#type' => 'foobar', entonces cargaremos solo las bibliotecas relacionadas con cada uno de esos tipos #.
Pero no estamos limitados a solo "#type": también podemos querer cargar una biblioteca de activos específica solo para cierta instancia de "#type". En ese caso simplemente la adjuntamos al array de renderizado de esa instancia.
Por supuesto, raramente hay razón para cargar un recurso en todas las páginas (por ejemplo, cierto JavaScript analítico que monitorea cargas de página) sin importar qué haya en la página.
Las subsecciones siguientes muestran ejemplos de cómo hacer estas cosas.
Adjuntar a un "#type" específico (para todas sus instancias)
Para adjuntar una biblioteca a un "#type" existente para todas sus instancias, usamos hook_element_info_alter():
function yourmodule_element_info_alter(array &$types) { if (isset($types['table'])) { $types['table']['#attached']['library'][] = 'your_module/library_name'; } }
Luego limpie la caché para que Drupal reconozca la nueva implementación del hook que agregó.
Adjuntar a un array de renderizado
Para adjuntar una biblioteca a un array de renderizado (y posiblemente a una instancia específica de cierto '#type'), debe tener acceso a ese array de renderizado. Quizás esté definiendo ese array, o modificándolo en un hook. En cualquier caso, se verá algo así:
$build['the_element_that_needs_the_asset_library']['#attached']['library'][] = 'your_module/library_name';
¡Siempre use claves numéricas!
Quizás quiera ayudar a Drupal y evitar duplicados usando claves no numéricas:
$build['the_element_that_needs_the_asset_library']['#attached']['library']['your_module/library_name'] = 'your_module/library_name';
No haga eso. La forma en que Drupal fusiona arrays causará un array anidado inválido, y aparecerán errores como estos (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()
Adjuntar biblioteca en el array de renderizado de un plugin de bloque
Para dar otro ejemplo de adjuntar una biblioteca a un array de renderizado: si construye un plugin de bloque en su módulo, puede adjuntar bibliotecas al array de renderizado en el método build() de su clase que extiende BlockBase (desde Drupal 8 beta 6).
return [ '#theme' => 'your_module_theme_id', '#someVariable' => $some_variable, '#attached' => [ 'library' => [ 'your_module/library_name', ], ], ];
Adjuntar biblioteca a un formulario
Como los formularios son simplemente arrays de renderizado, adjuntar una biblioteca funciona igual:
/** * 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'; } } }
Adjuntar biblioteca a todas (o un subconjunto) de páginas
En algunos casos, una biblioteca de activos no está relacionada con una parte específica de la página, sino con toda la página. Para ese caso existe hook_page_attachments(). Un buen ejemplo está en el módulo Contextual Links:
// 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'; }
Adjuntar biblioteca en una función preprocess
Puede adjuntar una biblioteca en una función preprocess usando la clave especial '#attached':
function yourmodule_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'your_module/library_name'; }
Adjuntar biblioteca en un template twig
También puede adjuntar una biblioteca en un template twig usando la función attach_library(). Así, en cualquier archivo *.html.twig:
{{ attach_library('your_module/library_name') }}Some markup {{ message }}
Adjuntar biblioteca durante la sustitución de tokens
También puede adjuntar una biblioteca si su token personalizado aparece en texto filtrado, agregando la biblioteca al objeto BubbleableMetadata durante la sustitución en 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); // ¡MIRE AQUÍ! ¡TAMBIÉN PODEMOS AGREGAR BIBLIOTECAS! $bubbleable_metadata->addAttachments(['library' => ['your_module/library_name'] ]); break; } } } return $replacements; }
Note que en este ejemplo solo se muestra cómo adjuntar la biblioteca durante la sustitución — para implementar un token personalizado completo, también debe implementar hook_token_info().
Adjuntar biblioteca en un plugin de filtro
Si un módulo provee un filtro de texto, puede usar los métodos setAttachments() o addAttachments() de la clase FilterProcessResult. Por ejemplo, el filtro filter_caption lo hace:
if (...) { ... $result->setProcessedText(Html::serialize($dom)) ->addAttachments([ 'library' => [ 'filter/caption', ], ]); } return $result;
Adjuntar JavaScript personalizado
En algunos casos puede querer añadir JavaScript a una página que dependa de cierta información calculada en PHP. Puede hacer esto con drupalSettings (sucesor de Drupal 7 Drupal.settings), un array de configuraciones definidas en su script PHP, accesibles como objeto de configuración en su JavaScript.
Para usar drupalSettings en una biblioteca primero debe declarar dependencia de core/drupalSettings en la definición de la biblioteca.
Así, la definición de la biblioteca de nuestro ejemplo anterior:
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {} dependencies: - core/jquery - core/drupalSettings
En nuestros archivos PHP ahora podemos pasar los drupalSettings requeridos junto con nuestra biblioteca. Por convención usamos el nombre del módulo en lowerCamelCase como clave para la configuración y el nombre de la biblioteca en lowerCamelCase como clave secundaria.
Si quisiéramos pasar los valores calculados 'foo' y 'baz' desde PHP a JavaScript en nuestro ejemplo, podríamos hacer:
$computed_settings = [ 'foo' => 'bar', 'baz' => 'qux', ]; $build['#attached']['library'][] = 'your_module/library_name'; $build['#attached']['drupalSettings']['fluffiness']['cuddlySlider'] = $computed_settings;
Entonces cuddly-slider.js podrá acceder a drupalSettings.fluffiness.cuddlySlider.foo y drupalSettings.fluffiness.cuddlySlider.baz, que tendrán los valores "bar" y "qux" respectivamente.
Los arrays de renderizado se almacenan en caché. Dependiendo de la naturaleza de sus valores calculados y del componente al que adjunte drupalSettings, puede que necesite modificar adecuadamente los metadatos de caché.
Agregar atributos a los elementos script
Si desea añadir atributos a la etiqueta script, debe añadir la clave 'attributes' en JSON después de la URL del script. Dentro del objeto que sigue a la clave 'attributes', añada el nombre del atributo que desea que aparezca en el script como una nueva clave. El valor para esa clave será el valor del atributo. Si se establece en true, el atributo se mostrará por sí mismo sin un valor.
Por ejemplo:
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 } }
Esto producirá el siguiente marcado:
<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>
Desactivar agregación
Por defecto varios archivos locales se combinan cuando es posible. Para desactivar esto para un archivo, establezca su bandera 'preprocess' en false.
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {preprocess: false} dependencies: - core/jquery - core/drupalSettings
CDN / bibliotecas externas
Es posible que desee usar JavaScript alojado en un CDN (Content Delivery Network) para mejorar la velocidad de carga de página. Esto se puede hacer declarando una biblioteca "externa". También es útil incluir en la definición información sobre la biblioteca externa.
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 en línea
JavaScript en línea no es recomendado. Se recomienda ponerlo en un archivo JS que quiera usar en línea, porque permite cachear el JavaScript en el cliente. También permite que el código JavaScript sea visible y mantenible. JS en línea también choca con las políticas de seguridad de contenido de muchos sitios y hace que su módulo no sea compatible con ellos.
JavaScript en línea que genera marcado
No se recomienda esto. Ponga JavaScript en un archivo en su lugar. Ejemplos son publicidad, botones para compartir en redes sociales, widgets de listas sociales. Usan JavaScript en línea, pero son un tipo especial de contenido/marcado, porque no están destinados a adornar el contenido o interactividad del sitio, sino a obtener contenido externo mediante JavaScript.
Debe ponerlos en un bloque personalizado o directamente en la plantilla Twig.
Por ejemplo:
<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 en línea que afecta a toda la página
JavaScript en línea no es recomendado. Ejemplos son analíticas (Google Analytics) y servicios de fuentes alojadas. El JavaScript en línea que afecta toda la página puede ser de dos tipos: front-end/estilo o lógico. La mayoría de los casos pueden cubrirse con JS fijo en archivo más configuraciones adicionales.
En el caso de front-end/estilo (como servicios de fuentes alojadas) pertenece a un tema; para eso vea “Agregar hojas de estilo (CSS) y JavaScript (JS) en un tema de Drupal 8”.
En otro caso, el JS pertenece a un módulo. En el hook correspondiente — probablemente hook_page_attachments() — defina datos HTML anidados para <HEAD> usando la clave 'html_head' en la propiedad #attached:
function fluffiness_page_attachments(array &$attachments) { $attachments['#attached']['html_head'][] = [ // Los datos. [ '#type' => 'html_tag', // La etiqueta HTML a añadir, en este caso una etiqueta
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.