Agregar hojas de estilo (CSS) y JavaScript (JS) en un tema de Drupal 8
Esta documentación es para temas. Para información sobre módulos, consulte la sección Agregar hojas de estilo (CSS) y JavaScript (JS) en un módulo 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 para todo: las bibliotecas de recursos.
Para mayor claridad, estas instrucciones están destinadas SOLO para trabajar en temas y no se aplican en módulos.
Drupal utiliza un principio de alto nivel: los recursos (CSS o JS) se cargan solo si se indica explícitamente a Drupal que deben cargarse. Drupal no carga todos los recursos en cada página, ya que esto reduce el rendimiento de la interfaz.
Diferencias con Drupal 7
Hay seis diferencias importantes respecto a Drupal 7 para entusiastas:
- El archivo THEME.info.yml reemplazó al archivo THEME.info (con los mismos datos).
- La propiedad stylesheets (para agregar CSS) en THEME.info fue eliminada y reemplazada por *.libraries.yml, donde `*` es el nombre del tema o módulo.
- La propiedad scripts (para agregar JS) en THEME.info fue eliminada y también reemplazada por *.libraries.yml, donde `*` es el nombre del tema o módulo.
- Solo se cargan los CSS y JS necesarios en la página. Por ejemplo, jQuery ya no se carga automáticamente a menos que se declare explícitamente en *.libraries.yml. Si tu tema requiere jQuery u otros recursos que quieras cargar en todas las páginas, agrégalos en *.libraries.yml y luego incluye la biblioteca en THEME.info.yml.
- En Drupal 7 las bibliotecas debían definirse con hook_library_info(). Esto fue reemplazado por archivos *.libraries.yml.
- En Drupal 8, drupal_add_css(), drupal_add_js() y drupal_add_library() fueron eliminados en favor de #attached
Proceso
Para cargar recursos CSS o JS:
- Guarda CSS o JS en un archivo siguiendo las convenciones de nomenclatura y estructura de archivos apropiadas.
- Define una "biblioteca" que registre esos archivos CSS / JS en tu tema.
- "Adjunta" la biblioteca a todas las páginas, a ciertos templates Twig o dirígete a páginas específicas usando un elemento de renderización en una función preprocess.
Definición de biblioteca
Define todas tus bibliotecas de recursos en un archivo *.libraries.yml dentro de la carpeta de tu tema. Si tu tema se llama fluffiness, el archivo debe llamarse fluffiness.libraries.yml. Cada "biblioteca" dentro del archivo es una entrada que detalla los archivos CSS y JS (recursos), por ejemplo:
# fluffiness.libraries.yml cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {}
En este ejemplo, el JavaScript cuddly-slider.js y el CSS cuddly-slider.css se encuentran en los directorios correspondientes js y css de tu directorio.
Nota: aunque este ejemplo muestra la inclusión de un archivo CSS y JS + jQuery, hay muchas más opciones disponibles al definir bibliotecas. Consulta la sección "Definición de bibliotecas: opciones y detalles" para más información.
Incluir jQuery en tu biblioteca
Recuerda que Drupal 8 ya no carga jQuery en todas las páginas por defecto, por lo que si cuddly-slider necesita jQuery, debes declarar la dependencia de la biblioteca base que contiene jQuery (Drupal core provee jQuery, no un módulo o tema). Declara la dependencia con el nombre de extensión seguido por una barra y el nombre de la biblioteca, en este caso core/jquery. Si otra biblioteca depende de cuddly-slider, declarará: fluffiness/cuddly-slider (nombre del tema seguido por el nombre de la biblioteca). No puedes declarar un archivo individual como dependencia, solo bibliotecas.
Así que para hacer jQuery disponible para cuddly-slider, actualizamos el ejemplo anterior:
# fluffiness.libraries.yml cuddly-slider: version: 1.x css: theme: css/cuddly-slider.css: {} js: js/cuddly-slider.js: {} dependencies: - core/jquery
Declaración de dependencias
Para declarar una dependencia, la biblioteca requerida se declara en forma recurso/biblioteca. Para las bibliotecas principales, el recurso es core, para otras es el nombre del módulo o tema. Por lo tanto, si new_library depende de jQuery de core, my_library declarada en my_theme, y my_library declarada en my_module, debes declarar las dependencias así:
# fluffiness.libraries.yml new_library: js: js/new_libary.js: {} dependencies: - core/jquery - my_module/my_library - my_theme/my_library
Los nombres de módulos y temas proporcionan un espacio de nombres para bibliotecas con nombres similares.
Adjuntar biblioteca a todas las páginas
La mayoría de los temas usan una biblioteca de recursos llamada global-styling para CSS que debe cargarse en cada página donde el tema está activo. También es posible hacer esto para JS usando la biblioteca global-scripts.
# fluffiness.libraries.yml (se pueden agregar múltiples bibliotecas en un archivo libraries.yml, estas aparecerán debajo de la biblioteca cuddly-slider añadida anteriormente) global-styling: version: 1.x css: theme: css/layout.css: {} css/style.css: {} css/colors.css: {} global-scripts: version: 1.x js: js/navmenu.js: {}
Para que estén disponibles globalmente en el tema, las bibliotecas global-styling/global-scripts deben agregarse en el info.yml de tu tema (en este caso fluffiness.info.yml):
#fluffiness.info.yml name: Fluffiness type: theme description: 'Un tema adorable que ofrece extra suavidad.' core: 8.x # al agregar global-styling y global-scripts aquí, los archivos css/js de la biblioteca # estarán disponibles en cada página que presente el tema libraries: - fluffiness/global-styling - fluffiness/global-scripts base theme: classy regions: header: Header content: Content sidebar_first: 'Sidebar first' footer: Footer
Adjuntar biblioteca desde una plantilla Twig
Puedes adjuntar una biblioteca de recursos a una plantilla Twig usando la función attach_library() en cualquier archivo *.html.twig, por ejemplo:
{{ attach_library('fluffiness/cuddly-slider') }} <div>Algún marcado suave {{ message }}</div>
Adjuntar biblioteca a un subconjunto de páginas
En algunos casos, no necesitas que tu biblioteca esté activa en todas las páginas, sino solo en un subconjunto. Por ejemplo, puedes querer que tu biblioteca esté activa solo cuando se muestra un bloque específico o un tipo específico de nodo.
El tema puede hacerlo implementando una función THEME_preprocess_HOOK() en el archivo .theme, reemplazando "THEME" con el nombre de máquina de tu tema y "HOOK" con el nombre de máquina del hook del tema.
Por ejemplo, para adjuntar JavaScript a la página de mantenimiento, la parte "HOOK" es "maintenance_page", y tu función sería así:
function fluffiness_preprocess_maintenance_page(&$variables) { $variables['#attached']['library'][] = 'fluffiness/cuddly-slider'; }
Puedes hacer algo similar para otros hooks del tema, y por supuesto tu función puede contener lógica, por ejemplo para determinar qué bloque se está preprocesando en el hook "block", qué tipo de nodo en el hook "node", etc.
¡Nota importante! En este caso, debes proporcionar metadatos de caché que correspondan a tu estado actual. El ejemplo anterior funciona incondicionalmente, por lo que no necesita metadatos de caché. El uso más común es adjuntar alguna biblioteca de recursos basada en la ruta actual:
function fluffiness_preprocess_page(&$variables) { $variables['page']['#cache']['contexts'][] = 'route'; $route = "entity.node.preview"; if (\Drupal::routeMatch()->getRouteName() === $route) { $variables['#attached']['library'][] = 'fluffiness/node-preview'; } }
Definición de bibliotecas: opciones y detalles
Agregar propiedades a los CSS/JS adjuntos
Las propiedades se agregan en llaves {} tras cada archivo añadido en el archivo THEMENAME.libraries.yml de tu tema.
Propiedades CSS
Las siguientes propiedades son opcionales y aplican para cada activo CSS.
attributes | Atributos opcionales. Un caso de uso conocido es Bootstrap CDN. |
{ attributes: { crossorigin: anonymous } } |
browsers | Carga condicional basada en navegador. Este método usa comentarios condicionales no soportados en IE10 y superiores. |
{ browsers: { IE: 'lte IE 9', '!IE': false } } |
group | Los activos se agregan por grupos. Por defecto: el grupo SMACSS donde el activo está ubicado. |
Raramente usado |
media | Tipo de medio. |
{ media: print } |
minified | Indica si el activo ya está minificado. Por defecto: false |
{ type: external, minified: true } |
preprocess | Si los activos deben ser agregados. Por defecto: true |
{ preprocess: false } |
type | Origen del activo. Por defecto: archivo |
{ type: external, minified: true } |
weight | Ajusta el orden relativo a otros activos (dentro del mismo grupo SMACSS). Por defecto: 0. Usa valores numéricos entre -50 y +50. |
{ weight: 1 } |
Propiedades JS
Las siguientes propiedades son opcionales y aplican para cada activo JS.
attributes | Atributos adicionales para el script. |
{ type: external, attributes: { async: true } } |
browsers | Carga condicional basada en navegador. Usa comentarios condicionales no soportados en IE10 y superiores. |
{ browsers: { IE: 'lte IE 9', '!IE': false } } |
preprocess | Si los activos deben ser agregados. Por defecto: true |
{ preprocess: false } |
type | Origen del activo. Por defecto: archivo |
{ type: external, minified: true } |
weight | No se recomienda usar dependencias para esto. Ajusta el orden relativo a otros activos. Debe ser negativo. |
{ weight: -1 } |
Sobrescribir y extender bibliotecas
Debes ir a *.info.yml para sobrescribir bibliotecas definidas en *.libraries.yml. Estas pueden ser sobrescritas o extendidas mediante overrides o extensiones de bibliotecas. Las sobrescrituras que añadas en *.info.yml serán heredadas por subtemas.
La propiedad stylesheets-remove usada en archivos *.info.yml está obsoleta y será eliminada en Drupal 9.0.x. La propiedad stylesheets-override ya fue eliminada.
libraries-override
La lógica para crear sobrescrituras es:
- Usar el espacio de nombres original del módulo (o core) para el nombre de la biblioteca.
- Usar la ruta del último override como clave.
- La ruta debe ser la ruta completa al archivo.
Ejemplo:
libraries-override: contextual/drupal.contextual-links: css: component: /core/themes/stable/css/contextual/contextual.module.css: false
Aquí contextual/drupal.contextual-links es el espacio de nombres de la biblioteca base y /core/themes/stable/css/contextual/contextual.module.css es la ruta completa del último override de esta biblioteca. En este caso, el archivo fue eliminado con false.
Nota que solo la última parte es la ruta real del sistema de archivos, las otras partes son espacios de nombres. Las líneas css: y component: reflejan la estructura de la biblioteca sobrescrita.
Ten en cuenta que depender de la ruta absoluta puede romperse si la estructura de archivos cambia. Por eso existe la cuestión de eliminar esta dependencia usando empaquetadores.
A continuación, algunos otros ejemplos de uso de libraries-override para eliminar o reemplazar recursos CSS o JS o bibliotecas enteras heredadas por tu tema desde módulos o temas.
libraries-override: # Reemplazar una biblioteca entera. core/drupal.collapse: mytheme/collapse # Reemplazar un activo con otro. subtheme/library: css: theme: css/layout.css: css/my-layout.css # Reemplazar un activo override de stable. contextual/drupal.contextual-toolbar: css: component: core/themes/stable/css/contextual/contextual.toolbar.css: css/contextual.toolbar.css # Reemplazar un activo JavaScript de un módulo core. toolbar/toolbar: js: js/views/BodyVisualView.js: js/views/BodyVisualView.js # Eliminar un activo. drupal/dialog: css: theme: dialog.theme.css: false # Eliminar una biblioteca entera. core/modernizr: false # Reemplazar activos muy específicos de la biblioteca de un módulo contribuido. # Nota: Las bibliotecas disponibles para sobrescribir se encuentran en el archivo *.libraries.yml del módulo. En este ejemplo, el archivo libraries.yml estaría en: /modules/contrib/webform/webform.libraries.yml webform/webform.element.location.places: css: component: css/webform.element.location.places.css: css/my-themes-replacement-file.css js: js/webform.element.location.places.js: js/my-themes-replacement-file.js
libraries-extend
libraries-extend permite a los temas modificar recursos de una biblioteca agregando recursos dependientes adicionales en cada inclusión de la biblioteca.
libraries-extend se definen extendiendo una biblioteca con otras bibliotecas.
Esto es ideal para estilizar algunos componentes de forma diferente en tu tema sin cargar CSS global. Es decir, ajustar la apariencia del componente sin cargar CSS en todas las páginas.
# Extender drupal.user: añadir activos de las bibliotecas user de classy. libraries-extend: core/drupal.user: - classy/user1 - classy/user2
Configuración adicional de Javascript
Orden de carga de activos
Como era de esperar, el orden en que los archivos están listados es el orden de carga. Por defecto, todos los recursos JS ahora se cargan en el pie de página. JS para elementos críticos de UI, que no pueden mostrarse sin su JS correspondiente, puede cargarse en el encabezado si es necesario, así:
js-header: header: true js: header.js: {} js-footer: js: footer.js: {}
Establece header en true para indicar que los recursos JS en esta biblioteca están en el "camino crítico" y deben cargarse desde el encabezado. Cualquier dependencia directa o indirecta de bibliotecas declaradas así también se cargará automáticamente desde el encabezado, no es necesario declararlas por separado. Esto es el significado de "camino crítico": cuando un recurso se declara en el encabezado, es "crítico" que este recurso y sus dependencias se carguen primero.
Adjuntar JavaScript personalizado:
En algunos casos, puedes querer agregar JavaScript a una página que depende de información calculada en PHP.
En ese caso, crea un archivo JavaScript, define y adjunta la biblioteca como antes, pero también adjunta configuraciones JS y haz que ese archivo JavaScript lea esas configuraciones vía drupalSettings (sucesor de Drupal 7's Drupal.settings). Para que drupalSettings esté disponible en tu JS, debes declarar dependencia de core/drupalSettings.
Así que se convierte en:
cuddly-slider: version: 1.x js: js/cuddly-slider.js: {} dependencies: - core/jquery - core/drupalSettings
y
function fluffiness_page_attachments_alter(&$page) { $page['#attached']['library'][] = 'fluffiness/cuddly-slider'; $page['#attached']['drupalSettings']['fluffiness']['cuddlySlider']['foo'] = 'bar'; }
donde 'bar' es un valor calculado. (Recuerda que los metadatos de caché también son necesarios aquí.)
Entonces cuddly-slider.js podrá acceder a settings.fluffiness.cuddlySlider.foo (que será === 'bar'):
(function ($, Drupal, drupalSettings) { 'use strict'; Drupal.behaviors.mybehavior = { attach: function (context, settings) { console.log(settings.fluffiness.cuddlySlider.foo); } }; })(jQuery, Drupal, drupalSettings);
Agregar atributos a etiquetas script
Si deseas agregar atributos a la etiqueta <script>, debes añadir una clave attributes en JSON tras la URL del script. Dentro del objeto bajo attributes, agrega el nombre del atributo como clave y su valor como valor. Si el valor es true, el atributo se mostrará solo (sin valor explícito).
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 generará 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>
JavaScript inline
El JavaScript inline es muy desaconsejado. Se recomienda colocarlo en un archivo JS separado para permitir el caché en el cliente. Esto también permite que el código JS sea legible y mantenible.
JavaScript inline que genera marcado
No se recomienda y generalmente no es necesario. Coloca el JS en un archivo. Ejemplos son publicidad, botones para compartir en redes sociales, widgets sociales. Usan JS inline pero son un tipo especial de contenido/markup, no destinados a decorar o interactuar con el sitio, sino para incluir contenido externo vía JS.
Querrás ponerlos en un bloque personalizado o directamente en una 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 inline que afecta toda la página
El uso de JavaScript inline es muy desaconsejado. Ejemplos incluyen analíticas (como Google Analytics) y servicios de fuentes alojadas. El JavaScript inline que afecta toda la página puede ser de dos tipos: front-end/estilos o lógico.
En el caso de front-end/estilos (como fuentes alojadas), el JS pertenece al tema. Pon el JS directamente en tu archivo html.html.twig. Para fuentes esto ayuda a colocarlo donde el usuario final tiene la mejor (y más rápida) experiencia, evitando FOUT (Flash Of Unstyled Text), mientras la fuente aún carga (las fuentes cargadas vía JS deben ir en el HTML <HEAD> antes que el CSS).
(Para más información, revisa el excelente artículo "Async Typekit & Micro-FOUT".)
En el otro caso, pertenece a un módulo; para eso consulta "Agregar hojas de estilo (CSS) y JavaScript (JS) a un módulo Drupal 8".
JavaScript inline en un módulo de integración
El uso de JavaScript inline está muy desaconsejado. Considera usar los ejemplos anteriores antes de intentarlo.
Dos cosas a tener en cuenta al proporcionar un campo que acepta JavaScript inline proporcionado por un usuario:
1. El campo, formulario o página que acepte este JavaScript inline debe tener permiso adjunto.
Ejemplo: MODULE.routing.yml
MODULE.settings: path: /admin/config/services/MODULE defaults: _title: 'Configuraciones de MODULE' _form: \Drupal\MODULE\Form\MODULESettings requirements: _permission: 'administer site configuration'
2. Si el valor se guarda en un objeto de configuración, debe informar al sistema de renderizado sobre su CacheableMetadata, para que cuando cambie, el caché de renderizado se limpie o expire.
Ejemplo: MODULES.module
<?php /** * @file * Integra MODULE en un sitio Drupal. */ use Drupal\Core\Render\Markup; /** * Implementa hook_page_bottom(). */ function MODULE_page_bottom(array &$page_bottom) { $settings = \Drupal::config('MODULE.settings'); $user = \Drupal::currentUser(); $page_bottom['MODULE'] = [ '#markup' => Markup::create($settings->get('js_code')), '#cache' => [ 'contexts' => ['user'], 'tags' => ['user:' . $user->id()], ], ]; // Agrega metadata de cacheabilidad de configuración. /** @var Drupal\Core\Render\Renderer $renderer */ $renderer = \Drupal::service('renderer'); $renderer->addCacheableDependency($page_bottom['MODULE'], $settings); }
CDN / bibliotecas externas
Quizás desees usar JavaScript alojado en un CDN (Content Delivery Network), por ejemplo, las fuentes web normalmente solo están disponibles mediante URL externas. Esto se hace declarando la biblioteca como externa (type: external). También es útil incluir información sobre la biblioteca externa en la definición.
(Ten en cuenta que generalmente no es una buena idea cargar bibliotecas desde CDN; para evitarlo, si es posible, pues introduce más puntos de fallo, afecta rendimiento y estabilidad, requiere más conexiones TCP/IP y usualmente no está cacheado por el navegador. Sin embargo, las bibliotecas externas no deben alojarse en Drupal.org como parte de tu repositorio — revisa la política de bibliotecas externas en Drupal.org para detalles.)
angular.angularjs: remote: https://github.com/angular 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 }
Si deseas que el archivo externo se solicite usando el mismo protocolo que la página, usa una URL relativa al protocolo:
js: //ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
O si deseas agregar CSS, aquí tienes un ejemplo integrando Font Awesome:
font-awesome: remote: https://fortawesome.github.io/Font-Awesome/ version: 4.5.0 license: name: MIT url: https://fortawesome.github.io/Font-Awesome/license/ gpl-compatible: true css: theme: https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css: { type: external, minified: true }
Ejemplo para Bootstrap CDN CSS con atributos personalizados.
bootstrap-cdn: remote: getbootstrap.com version: 4.0 license: name: MIT url: https://github.com/twbs/bootstrap/blob/master/LICENSE css: theme: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css': type: external minified: true attributes: crossorigin: anonymous integrity: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
Más información
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.