1 ¿Qué es el Pipeline de Activos?
El pipeline de activos proporciona un marco para manejar la entrega de activos JavaScript y CSS. Esto se hace aprovechando tecnologías como HTTP/2 y técnicas como concatenación y minificación. Finalmente, permite que tu aplicación se combine automáticamente con activos de otras gemas.
El pipeline de activos está implementado por las gemas importmap-rails, sprockets y sprockets-rails, y está habilitado por defecto. Puedes desactivarlo al crear una nueva aplicación pasando la opción --skip-asset-pipeline
.
$ rails new appname --skip-asset-pipeline
NOTA: Esta guía se centra en el pipeline de activos predeterminado utilizando solo sprockets
para CSS y importmap-rails
para el procesamiento de JavaScript. La principal limitación de estos dos es que no hay soporte para transpilar, por lo que no puedes usar cosas como Babel, TypeScript, formato React JSX o Tailwind CSS. Te animamos a leer la sección de Bibliotecas Alternativas si necesitas transpilar tu JavaScript/CSS.
2 Características Principales
La primera característica del pipeline de activos es insertar una huella digital SHA256 en cada nombre de archivo para que el archivo sea cacheado por el navegador web y el CDN. Esta huella digital se actualiza automáticamente cuando cambias el contenido del archivo, lo que invalida el caché.
La segunda característica del pipeline de activos es usar import maps al servir archivos JavaScript. Esto te permite construir aplicaciones modernas utilizando bibliotecas JavaScript hechas para módulos ES (ESM) sin la necesidad de transpilar y empaquetar. A su vez, esto elimina la necesidad de Webpack, yarn, node o cualquier otra parte de la cadena de herramientas JavaScript.
La tercera característica del pipeline de activos es concatenar todos los archivos CSS en un archivo principal .css
, que luego se minifica o comprime. Como aprenderás más adelante en esta guía, puedes personalizar esta estrategia para agrupar archivos de la manera que desees. En producción, Rails inserta una huella digital SHA256 en cada nombre de archivo para que el archivo sea cacheado por el navegador web. Puedes invalidar el caché alterando esta huella digital, lo que ocurre automáticamente cada vez que cambias el contenido del archivo.
La cuarta característica del pipeline de activos es que permite codificar activos a través de un lenguaje de nivel superior para CSS.
2.1 ¿Qué es la Huella Digital y Por Qué Debería Importarme?
La huella digital es una técnica que hace que el nombre de un archivo dependa del contenido del archivo. Cuando el contenido del archivo cambia, el nombre del archivo también cambia. Para contenido estático o que cambia con poca frecuencia, esto proporciona una manera fácil de saber si dos versiones de un archivo son idénticas, incluso a través de diferentes servidores o fechas de implementación.
Cuando un nombre de archivo es único y se basa en su contenido, se pueden establecer encabezados HTTP para alentar a los cachés en todas partes (ya sea en CDNs, ISPs, equipos de red o en navegadores web) a mantener su propia copia del contenido. Cuando el contenido se actualiza, la huella digital cambiará. Esto hará que los clientes remotos soliciten una nueva copia del contenido. Esto se conoce generalmente como cache busting.
La técnica que Sprockets utiliza para la huella digital es insertar un hash del contenido en el nombre, generalmente al final. Por ejemplo, un archivo CSS global.css
global-908e25f4bf641868d8683022a5b62f54.css
Esta es la estrategia adoptada por el pipeline de activos de Rails.
La huella digital está habilitada por defecto tanto para los entornos de desarrollo como de producción. Puedes habilitarla o deshabilitarla en tu configuración a través de la opción config.assets.digest
.
2.2 ¿Qué son los Import Maps y Por Qué Debería Importarme?
Los import maps te permiten importar módulos JavaScript utilizando nombres lógicos que se mapean a archivos versionados/digeridos, directamente desde el navegador. Así puedes construir aplicaciones JavaScript modernas utilizando bibliotecas JavaScript hechas para módulos ES (ESM) sin la necesidad de transpilar o empaquetar.
Con este enfoque, enviarás muchos archivos JavaScript pequeños en lugar de un archivo JavaScript grande. Gracias a HTTP/2, eso ya no conlleva una penalización de rendimiento material durante el transporte inicial, y de hecho ofrece beneficios sustanciales a largo plazo debido a mejores dinámicas de caché.
3 Cómo usar Import Maps como Pipeline de Activos de JavaScript
Los Import Maps son el procesador JavaScript predeterminado, la lógica de generación de import maps es manejada por la gema importmap-rails
.
ADVERTENCIA: Los import maps se utilizan solo para archivos JavaScript y no se pueden usar para la entrega de CSS. Consulta la sección de Sprockets para aprender sobre CSS.
Puedes encontrar instrucciones detalladas de uso en la página de inicio de la gema, pero es importante entender los conceptos básicos de importmap-rails
.
3.1 Cómo funciona
Los import maps son esencialmente una sustitución de cadenas para lo que se conoce como "especificadores de módulo descalzos". Te permiten estandarizar los nombres de las importaciones de módulos JavaScript.
Toma por ejemplo tal definición de importación, no funcionará sin un import map:
import React from "react"
Tendrías que definirlo así para que funcione:
import React from "https://ga.jspm.io/npm:react@17.0.2/index.js"
Aquí es donde entra el import map, definimos el nombre react
para que esté fijado en la dirección https://ga.jspm.io/npm:react@17.0.2/index.js
. Con tal información, nuestro navegador acepta la definición simplificada import React from "react"
. Piensa en el import map como un alias para la dirección fuente de la biblioteca.
3.2 Uso
Con importmap-rails
creas el archivo de configuración del import map fijando la ruta de la biblioteca a un nombre:
# config/importmap.rb
pin "application"
pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
Todos los import maps configurados deben adjuntarse en el elemento <head>
de tu aplicación agregando <%= javascript_importmap_tags %>
. El javascript_importmap_tags
genera un montón de scripts en el elemento head
:
- JSON con todos los import maps configurados:
<script type="importmap">
{
"imports": {
"application": "/assets/application-39f16dc3f3....js"
"react": "https://ga.jspm.io/npm:react@17.0.2/index.js"
}
}
</script>
- Punto de entrada para cargar JavaScript desde
app/javascript/application.js
:
<script type="module">import "application"</script>
NOTA: Antes de la versión 2.0.0, importmap-rails
incluía Es-module-shims
en la salida de javascript_importmap_tags
como un polyfill para garantizar el soporte de import maps en navegadores antiguos. Sin embargo, con el soporte nativo para import maps en todos los navegadores principales, la versión 2.0.0 ha eliminado el shim incluido. Si deseas soportar navegadores heredados que carecen de soporte para import maps, inserta manualmente Es-module-shims
antes de javascript_importmap_tags
. Para más información, consulta el README de importmap-rails.
3.3 Usar paquetes npm a través de CDNs de JavaScript
Puedes usar el comando bin/importmap
que se agrega como parte de la instalación de importmap-rails
para fijar, desfijar o actualizar paquetes npm en tu import map. El binstub utiliza JSPM.org
.
Funciona así:
$ bin/importmap pin react react-dom
Pinning "react" to https://ga.jspm.io/npm:react@17.0.2/index.js
Pinning "react-dom" to https://ga.jspm.io/npm:react-dom@17.0.2/index.js
Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
Pinning "scheduler" to https://ga.jspm.io/npm:scheduler@0.20.2/index.js
bin/importmap json
{
"imports": {
"application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js",
"react": "https://ga.jspm.io/npm:react@17.0.2/index.js",
"react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js",
"object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js",
"scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/index.js"
}
}
Como puedes ver, los dos paquetes react y react-dom se resuelven en un total de cuatro dependencias, cuando se resuelven a través del jspm por defecto.
Ahora puedes usar estos en tu punto de entrada application.js
como lo harías con cualquier otro módulo:
import React from "react"
import ReactDOM from "react-dom"
También puedes designar una versión específica para fijar:
$ bin/importmap pin react@17.0.1
Pinning "react" to https://ga.jspm.io/npm:react@17.0.1/index.js
Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
O incluso eliminar fijaciones:
$ bin/importmap unpin react
Unpinning "react"
Unpinning "object-assign"
Puedes controlar el entorno del paquete para paquetes con compilaciones separadas de "producción" (el valor predeterminado) y "desarrollo":
$ bin/importmap pin react --env development
Pinning "react" to https://ga.jspm.io/npm:react@17.0.2/dev.index.js
Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
También puedes elegir un proveedor de CDN alternativo y compatible al fijar, como unpkg
o jsdelivr
(jspm
es el predeterminado):
$ bin/importmap pin react --from jsdelivr
Pinning "react" to https://cdn.jsdelivr.net/npm/react@17.0.2/index.js
Recuerda, sin embargo, que si cambias una fijación de un proveedor a otro, es posible que debas limpiar las dependencias agregadas por el primer proveedor que no se utilizan por el segundo proveedor.
Ejecuta bin/importmap
para ver todas las opciones.
Ten en cuenta que este comando es simplemente un envoltorio de conveniencia para resolver nombres de paquetes lógicos a URL de CDN. También puedes buscar las URL de CDN tú mismo y luego fijarlas. Por ejemplo, si quisieras usar Skypack para React, podrías simplemente agregar lo siguiente a config/importmap.rb
:
pin "react", to: "https://cdn.skypack.dev/react"
3.4 Pre-carga de módulos fijados
Para evitar el efecto cascada donde el navegador tiene que cargar un archivo tras otro antes de poder llegar a la importación más anidada, importmap-rails soporta enlaces modulepreload. Los módulos fijados pueden ser precargados añadiendo preload: true
a la fijación.
Es una buena idea precargar bibliotecas o frameworks que se utilizan en toda tu aplicación, ya que esto le dirá al navegador que los descargue antes.
Ejemplo:
# config/importmap.rb
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js", preload: true
pin "md5", to: "https://cdn.jsdelivr.net/npm/md5@2.3.0/md5.js"
<%# app/views/layouts/application.html.erb %>
<%= javascript_importmap_tags %>
<%# incluirá el siguiente enlace antes de que se configure el importmap: %>
<link rel="modulepreload" href="https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js">
...
NOTA: Consulta el repositorio de importmap-rails
para la documentación más actualizada.
4 Cómo Usar Sprockets
El enfoque ingenuo para exponer los activos de tu aplicación a la web sería almacenarlos en subdirectorios de la carpeta public
como images
y stylesheets
. Hacerlo manualmente sería difícil, ya que la mayoría de las aplicaciones web modernas requieren que los activos se procesen de una manera específica, por ejemplo, comprimiendo y agregando huellas digitales a los activos.
Sprockets está diseñado para preprocesar automáticamente tus activos almacenados en los directorios configurados y, después del procesamiento, exponerlos en la carpeta public/assets
con huellas digitales, compresión, generación de mapas de fuentes y otras características configurables.
Los activos aún pueden colocarse en la jerarquía public
. Cualquier activo bajo public
será servido como archivos estáticos por la aplicación o el servidor web cuando config.public_file_server.enabled
esté configurado en true. Debes definir directivas manifest.js
para archivos que deben someterse a algún preprocesamiento antes de ser servidos.
En producción, Rails precompila estos archivos en public/assets
por defecto. Las copias precompiladas son luego servidas como activos estáticos por el servidor web. Los archivos en app/assets
nunca se sirven directamente en producción.
4.1 Archivos de Manifiesto y Directivas
Al compilar activos con Sprockets, Sprockets necesita decidir qué objetivos de nivel superior compilar, generalmente application.css
e imágenes. Los objetivos de nivel superior se definen en el archivo manifest.js
de Sprockets, por defecto se ve así:
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
Contiene directivas - instrucciones que le dicen a Sprockets qué archivos requerir para construir un solo archivo CSS o JavaScript.
Esto está destinado a incluir el contenido de todos los archivos encontrados en el directorio ./app/assets/images
o cualquier subdirectorio, así como cualquier archivo reconocido como JS directamente en ./app/javascript
o ./vendor/javascript
.
Cargará cualquier CSS del directorio ./app/assets/stylesheets
(sin incluir subdirectorios). Suponiendo que tengas archivos application.css
y marketing.css
en la carpeta ./app/assets/stylesheets
, te permitirá cargar esas hojas de estilo con <%= stylesheet_link_tag "application" %>
o <%= stylesheet_link_tag "marketing" %>
desde tus vistas.
Podrías notar que nuestros archivos JavaScript no se cargan desde el directorio assets
por defecto, es porque ./app/javascript
es el punto de entrada predeterminado para la gema importmap-rails
y la carpeta vendor
es el lugar donde se almacenarían los paquetes JS descargados.
En el manifest.js
también podrías especificar la directiva link
para cargar un archivo específico en lugar de todo el directorio. La directiva link
requiere proporcionar una extensión de archivo explícita.
Sprockets carga los archivos especificados, los procesa si es necesario, los concatena en un solo archivo y luego los comprime (basado en el valor de config.assets.css_compressor
o config.assets.js_compressor
). La compresión reduce el tamaño del archivo, permitiendo que el navegador descargue los archivos más rápido.
4.2 Activos Específicos del Controlador
Cuando generas un scaffold o un controlador, Rails también genera un archivo de hoja de estilo en cascada para ese controlador. Además, al generar un scaffold, Rails genera el archivo scaffolds.css
.
Por ejemplo, si generas un ProjectsController
, Rails también agregará un nuevo archivo en app/assets/stylesheets/projects.css
. Por defecto, estos archivos estarán listos para ser usados por tu aplicación inmediatamente usando la directiva link_directory
en el archivo manifest.js
.
También puedes optar por incluir archivos de hojas de estilo específicos del controlador solo en sus respectivos controladores usando lo siguiente:
<%= stylesheet_link_tag params[:controller] %>
Al hacer esto, asegúrate de no estar usando la directiva require_tree
en tu application.css
, ya que eso podría resultar en que tus activos específicos del controlador se incluyan más de una vez.
4.3 Organización de Activos
Los activos del pipeline pueden colocarse dentro de una aplicación en una de tres ubicaciones: app/assets
, lib/assets
o vendor/assets
.
app/assets
es para activos que son propiedad de la aplicación, como imágenes o hojas de estilo personalizadas.app/javascript
es para tu código JavaScriptvendor/[assets|javascript]
es para activos que son propiedad de entidades externas, como frameworks CSS o bibliotecas JavaScript. Ten en cuenta que el código de terceros con referencias a otros archivos también procesados por el pipeline de activos (imágenes, hojas de estilo, etc.), necesitará ser reescrito para usar ayudantes comoasset_path
.
Otras ubicaciones podrían configurarse en el archivo manifest.js
, consulta la sección de Archivos de Manifiesto y Directivas.
4.3.1 Rutas de Búsqueda
Cuando se hace referencia a un archivo desde un manifiesto o un ayudante, Sprockets busca en todas las ubicaciones especificadas en manifest.js
. Puedes ver la ruta de búsqueda inspeccionando Rails.application.config.assets.paths
en la consola de Rails.
4.3.2 Usar Archivos Índice como Proxies para Carpetas
Sprockets utiliza archivos llamados index
(con las extensiones relevantes) para un propósito especial.
Por ejemplo, si tienes una biblioteca CSS con muchos módulos, que se almacena en lib/assets/stylesheets/library_name
, el archivo lib/assets/stylesheets/library_name/index.css
sirve como el manifiesto para todos los archivos en esta biblioteca. Este archivo podría incluir una lista de todos los archivos requeridos en orden, o una simple directiva require_tree
.
También es algo similar a la forma en que un archivo en public/library_name/index.html
puede ser alcanzado por una solicitud a /library_name
. Esto significa que no puedes usar directamente un archivo índice.
La biblioteca como un todo puede ser accedida en los archivos .css
así:
/* ...
*= require library_name
*/
Esto simplifica el mantenimiento y mantiene las cosas limpias al permitir que el código relacionado se agrupe antes de incluirse en otros lugares.
4.4 Codificación de Enlaces a Activos
Sprockets no agrega ningún método nuevo para acceder a tus activos: todavía usas el conocido stylesheet_link_tag
:
<%= stylesheet_link_tag "application", media: "all" %>
Si usas la gema turbo-rails
, que está incluida por defecto en Rails, entonces incluye la opción data-turbo-track
que hace que Turbo verifique si un activo ha sido actualizado y, si es así, lo carga en la página:
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
En vistas regulares puedes acceder a imágenes en el directorio app/assets/images
así:
<%= image_tag "rails.png" %>
Siempre que el pipeline esté habilitado dentro de tu aplicación (y no deshabilitado en el contexto del entorno actual), este archivo es servido por Sprockets. Si existe un archivo en public/assets/rails.png
, es servido por el servidor web.
Alternativamente, una solicitud para un archivo con un hash SHA256 como public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png
se trata de la misma manera. Cómo se generan estos hashes se cubre en la sección En Producción más adelante en esta guía.
Las imágenes también pueden organizarse en subdirectorios si es necesario, y luego pueden accederse especificando el nombre del directorio en la etiqueta:
<%= image_tag "icons/rails.png" %>
ADVERTENCIA: Si estás precompilando tus activos (ver En Producción a continuación), enlazar a un activo que no existe generará una excepción en la página que llama. Esto incluye enlazar a una cadena en blanco. Por lo tanto, ten cuidado al usar image_tag
y los otros ayudantes con datos proporcionados por el usuario.
4.4.1 CSS y ERB
El pipeline de activos evalúa automáticamente ERB. Esto significa que si agregas una extensión erb
a un activo CSS (por ejemplo, application.css.erb
), entonces los ayudantes como asset_path
están disponibles en tus reglas CSS:
.class { background-image: url(<%= asset_path 'image.png' %>) }
Esto escribe la ruta al activo particular que se está haciendo referencia. En este ejemplo, tendría sentido tener una imagen en una de las rutas de carga de activos, como app/assets/images/image.png
, que se referenciaría aquí. Si esta imagen ya está disponible en public/assets
como un archivo con huella digital, entonces se hace referencia a esa ruta.
Si deseas usar un URI de datos, un método para incrustar los datos de la imagen directamente en el archivo CSS, puedes usar el ayudante asset_data_uri
.
#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
Esto inserta un URI de datos correctamente formateado en la fuente CSS.
Ten en cuenta que la etiqueta de cierre no puede ser del estilo -%>
.
4.5 Generar un Error Cuando un Activo No se Encuentra
Si estás usando sprockets-rails >= 3.2.0, puedes configurar lo que sucede cuando se realiza una búsqueda de activos y no se encuentra nada. Si desactivas el "fallback de activos", se generará un error cuando no se pueda encontrar un activo.
config.assets.unknown_asset_fallback = false
Si el "fallback de activos" está habilitado, entonces cuando no se pueda encontrar un activo, se imprimirá la ruta en su lugar y no se generará ningún error. El comportamiento del fallback de activos está deshabilitado por defecto.
4.6 Desactivar Huellas Digitales
Puedes desactivar las huellas digitales actualizando config/environments/development.rb
para incluir:
config.assets.digest = false
Cuando esta opción es verdadera, se generarán huellas digitales para las URLs de los activos.
4.7 Activar Mapas de Fuentes
Puedes activar los mapas de fuentes actualizando config/environments/development.rb
para incluir:
config.assets.debug = true
Cuando el modo de depuración está activado, Sprockets generará un Mapa de Fuentes para cada activo. Esto te permite depurar cada archivo individualmente en las herramientas de desarrollo de tu navegador.
Los activos se compilan y almacenan en caché en la primera solicitud después de que el servidor se inicia. Sprockets establece un encabezado HTTP must-revalidate
para reducir la sobrecarga de solicitudes en solicitudes posteriores: en estas, el navegador obtiene una respuesta 304 (No Modificado).
Si alguno de los archivos en el manifiesto cambia entre solicitudes, el servidor responde con un nuevo archivo compilado.
5 En Producción
En el entorno de producción, Sprockets utiliza el esquema de huellas digitales descrito anteriormente. Por defecto, Rails asume que los activos han sido precompilados y serán servidos como activos estáticos por tu servidor web.
Durante la fase de precompilación, se genera un SHA256 a partir del contenido de los archivos compilados y se inserta en los nombres de archivo a medida que se escriben en el disco. Estos nombres con huellas digitales son utilizados por los ayudantes de Rails en lugar del nombre del manifiesto.
Por ejemplo, esto:
<%= stylesheet_link_tag "application" %>
genera algo como esto:
<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" rel="stylesheet" />
El comportamiento de las huellas digitales está controlado por la opción de inicialización config.assets.digest
(que por defecto es true
).
NOTA: En circunstancias normales, la opción predeterminada config.assets.digest
no debería cambiarse. Si no hay huellas digitales en los nombres de archivo, y se establecen encabezados de futuro lejano, los clientes remotos nunca sabrán que deben volver a buscar los archivos cuando su contenido cambie.
5.1 Precompilación de Activos
Rails viene con un comando para compilar los manifiestos de activos y otros archivos en el pipeline.
Los activos compilados se escriben en la ubicación especificada en config.assets.prefix
. Por defecto, este es el directorio /assets
.
Puedes llamar a este comando en el servidor durante la implementación para crear versiones compiladas de tus activos directamente en el servidor. Consulta la siguiente sección para obtener información sobre la compilación local.
El comando es:
$ RAILS_ENV=production rails assets:precompile
Esto enlaza la carpeta especificada en config.assets.prefix
a shared/assets
. Si ya usas esta carpeta compartida, necesitarás escribir tu propio comando de implementación.
Es importante que esta carpeta sea compartida entre implementaciones para que las páginas en caché remotamente que hacen referencia a los activos compilados antiguos aún funcionen durante la vida útil de la página en caché.
NOTA: Siempre especifica un nombre de archivo compilado esperado que termine con .js
o .css
.
El comando también genera un .sprockets-manifest-randomhex.json
(donde randomhex
es una cadena hexadecimal aleatoria de 16 bytes) que contiene una lista con todos tus activos y sus respectivas huellas digitales. Esto es utilizado por los métodos de ayuda de Rails para evitar devolver las solicitudes de mapeo a Sprockets. Un archivo de manifiesto típico se ve así:
{"files":{"application-<fingerprint>.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383,
"digest":"<fingerprint>","integrity":"sha256-<random-string>"}},
"assets":{"application.js":"application-<fingerprint>.js"}}
En tu aplicación, habrá más archivos y activos listados en el manifiesto, <fingerprint>
y <random-string>
también se generarán.
La ubicación predeterminada para el manifiesto es la raíz de la ubicación especificada en config.assets.prefix
('/assets' por defecto).
NOTA: Si faltan archivos precompilados en producción, recibirás una excepción Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError
que indica el nombre del archivo(s) faltante(s).
5.1.1 Encabezado Expires de Futuro Lejano
Los activos precompilados existen en el sistema de archivos y son servidos directamente por tu servidor web. No tienen encabezados de futuro lejano por defecto, por lo que para obtener el beneficio de las huellas digitales, tendrás que actualizar la configuración de tu servidor para agregar esos encabezados.
Para Apache:
# Las directivas Expires* requieren que el módulo Apache
# `mod_expires` esté habilitado.
<Location /assets/>
# El uso de ETag no se recomienda cuando Last-Modified está presente
Header unset ETag
FileETag None
# RFC dice que solo se debe almacenar en caché durante 1 año
ExpiresActive On
ExpiresDefault "access plus 1 year"
</Location>
Para NGINX:
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
}
5.2 Precompilación Local
A veces, es posible que no desees o no puedas compilar activos en el servidor de producción. Por ejemplo, es posible que tengas acceso limitado de escritura a tu sistema de archivos de producción, o que planees implementar con frecuencia sin hacer ningún cambio en tus activos.
En tales casos, puedes precompilar activos localmente, es decir, agregar un conjunto final de activos compilados y listos para producción a tu repositorio de código fuente antes de enviarlos a producción. De esta manera, no necesitan ser precompilados por separado en el servidor de producción en cada implementación.
Como se mencionó anteriormente, puedes realizar este paso usando
$ RAILS_ENV=production rails assets:precompile
Ten en cuenta las siguientes advertencias:
Si los activos precompilados están disponibles, se servirán, incluso si ya no coinciden con los activos originales (no compilados), incluso en el servidor de desarrollo.
Para asegurarte de que el servidor de desarrollo siempre compile activos sobre la marcha (y por lo tanto siempre refleje el estado más reciente del código), el entorno de desarrollo debe configurarse para mantener los activos precompilados en una ubicación diferente a la de producción. De lo contrario, cualquier activo precompilado para su uso en producción sobrescribirá las solicitudes de los mismos en desarrollo (es decir, los cambios que realices en los activos no se reflejarán en el navegador).
Puedes hacer esto agregando la siguiente línea a
config/environments/development.rb
:config.assets.prefix = "/dev-assets"
La tarea de precompilación de activos en tu herramienta de implementación (por ejemplo, Capistrano) debe deshabilitarse.
Cualquier compresor o minificador necesario debe estar disponible en tu sistema de desarrollo.
También puedes establecer ENV["SECRET_KEY_BASE_DUMMY"]
para activar el uso de una secret_key_base
generada aleatoriamente que se almacena en un archivo temporal. Esto es útil cuando se precompilan activos para producción como parte de un paso de construcción que de otro modo no necesitaría acceso a los secretos de producción.
$ SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
5.3 Compilación en Vivo
En algunas circunstancias, es posible que desees usar la compilación en vivo. En este modo, todas las solicitudes de activos en el pipeline son manejadas directamente por Sprockets.
Para habilitar esta opción, establece:
config.assets.compile = true
En la primera solicitud, los activos se compilan y almacenan en caché como se describe en Almacenamiento en Caché de Activos, y los nombres de manifiesto utilizados en los ayudantes se alteran para incluir el hash SHA256.
Sprockets también establece el encabezado HTTP Cache-Control
en max-age=31536000
. Esto señala a todos los cachés entre tu servidor y el navegador del cliente que este contenido (el archivo servido) puede almacenarse en caché durante 1 año. El efecto de esto es reducir el número de solicitudes para este activo desde tu servidor; el activo tiene una buena oportunidad de estar en el caché local del navegador o en algún caché intermedio.
Este modo utiliza más memoria, funciona peor que el predeterminado y no se recomienda.
5.4 CDNs
CDN significa Red de Entrega de Contenidos, están diseñadas principalmente para almacenar en caché activos en todo el mundo para que cuando un navegador solicite el activo, haya una copia en caché geográficamente cercana a ese navegador. Si estás sirviendo activos directamente desde tu servidor Rails en producción, la mejor práctica es usar un CDN frente a tu aplicación.
Un patrón común para usar un CDN es establecer tu aplicación de producción como el servidor de "origen". Esto significa que cuando un navegador solicita un activo del CDN y hay un fallo de caché, lo obtendrá de tu servidor sobre la marcha y luego lo almacenará en caché. Por ejemplo, si estás ejecutando una aplicación Rails en example.com
y tienes un CDN configurado en mi-subdominio-cdn.fictional-cdn.com
, entonces cuando se realiza una solicitud a mi-subdominio-cdn.fictional-cdn.com/assets/smile.png
, el CDN consultará tu servidor una vez en example.com/assets/smile.png
y almacenará la solicitud en caché. La próxima solicitud al CDN que llegue a la misma URL utilizará la copia en caché. Cuando el CDN puede servir un activo directamente, la solicitud nunca toca tu servidor Rails. Dado que los activos de un CDN están geográficamente más cerca del navegador, la solicitud es más rápida, y dado que tu servidor no necesita dedicar tiempo a servir activos, puede centrarse en servir el código de la aplicación lo más rápido posible.
5.4.1 Configurar un CDN para Servir Activos Estáticos
Para configurar tu CDN, debes tener tu aplicación ejecutándose en producción en Internet en una URL públicamente disponible, por ejemplo, example.com
. Luego, deberás registrarte en un servicio CDN de un proveedor de alojamiento en la nube. Cuando hagas esto, debes configurar el "origen" del CDN para que apunte de regreso a tu sitio web example.com
. Consulta la documentación de tu proveedor para obtener información sobre cómo configurar el servidor de origen.
El CDN que aprovisionaste debería darte un subdominio personalizado para tu aplicación, como mi-subdominio-cdn.fictional-cdn.com
(ten en cuenta que fictional-cdn.com no es un proveedor de CDN válido en el momento de escribir esto). Ahora que has configurado tu servidor CDN, necesitas decirle a los navegadores que usen tu CDN para obtener activos en lugar de tu servidor Rails directamente. Puedes hacer esto configurando Rails para establecer tu CDN como el host de activos en lugar de usar una ruta relativa. Para establecer tu host de activos en Rails, necesitas configurar config.asset_host
en config/environments/production.rb
:
config.asset_host = 'mi-subdominio-cdn.fictional-cdn.com'
NOTA: Solo necesitas proporcionar el "host", este es el subdominio y el dominio raíz, no necesitas especificar un protocolo o "esquema" como http://
o https://
. Cuando se solicita una página web, el protocolo en el enlace a tu activo que se genera coincidirá con cómo se accede a la página web por defecto.
También puedes establecer este valor a través de una variable de entorno para facilitar la ejecución de una copia de ensayo de tu sitio:
config.asset_host = ENV['CDN_HOST']
NOTA: Necesitarías establecer CDN_HOST
en tu servidor a mi-subdominio-cdn.fictional-cdn.com
para que esto funcione.
Una vez que hayas configurado tu servidor y tu CDN, las rutas de los activos desde los ayudantes como:
<%= asset_path('smile.png') %>
Se representarán como URL completas de CDN como http://mi-subdominio-cdn.fictional-cdn.com/assets/smile.png
(huella digital omitida para mayor claridad).
Si el CDN tiene una copia de smile.png
, la servirá al navegador, y tu servidor ni siquiera sabrá que se solicitó. Si el CDN no tiene una copia, intentará encontrarla en el "origen" example.com/assets/smile.png
, y luego la almacenará para uso futuro.
Si deseas servir solo algunos activos desde tu CDN, puedes usar la opción personalizada :host
en tu ayudante de activos, que sobrescribe el valor establecido en config.action_controller.asset_host
.
<%= asset_path 'image.png', host: 'mi-subdominio-cdn.fictional-cdn.com' %>
5.4.2 Personalizar el Comportamiento de Caché del CDN
Un CDN funciona almacenando contenido en caché. Si el CDN tiene contenido obsoleto o incorrecto, entonces está perjudicando en lugar de ayudar a tu aplicación. El propósito de esta sección es describir el comportamiento general de caché de la mayoría de los CDNs. Tu proveedor específico puede comportarse de manera ligeramente diferente.
5.4.2.1 Caché de Solicitudes del CDN
Mientras que un CDN se describe como bueno para almacenar activos en caché, en realidad almacena en caché toda la solicitud. Esto incluye el cuerpo del activo, así como cualquier encabezado. El más importante es Cache-Control
, que le dice al CDN (y a los navegadores web) cómo almacenar en caché los contenidos. Esto significa que si alguien solicita un activo que no existe, como /assets/i-dont-exist.png
, y tu aplicación Rails devuelve un 404, entonces tu CDN probablemente almacenará en caché la página 404 si hay un encabezado Cache-Control
válido presente.
5.4.2.2 Depuración de Encabezados del CDN
Una forma de comprobar que los encabezados se almacenan correctamente en caché en tu CDN es utilizando curl. Puedes solicitar los encabezados tanto de tu servidor como de tu CDN para verificar que sean los mismos:
$ curl -I http://www.example/assets/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK
Server: Cowboy
Date: Sun, 24 Aug 2014 20:27:50 GMT
Connection: keep-alive
Last-Modified: Thu, 08 May 2014 01:24:14 GMT
Content-Type: text/css
Cache-Control: public, max-age=2592000
Content-Length: 126560
Via: 1.1 vegur
Versus la copia del CDN:
$ curl -I http://mi-subdominio-cdn.fictional-cdn.com/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK Server: Cowboy Last-
Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css
Cache-Control:
public, max-age=2592000
Via: 1.1 vegur
Content-Length: 126560
Accept-Ranges:
bytes
Date: Sun, 24 Aug 2014 20:28:45 GMT
Via: 1.1 varnish
Age: 885814
Connection: keep-alive
X-Served-By: cache-dfw1828-DFW
X-Cache: HIT
X-Cache-Hits:
68
X-Timer: S1408912125.211638212,VS0,VE0
Consulta la documentación de tu CDN para obtener información adicional que puedan proporcionar, como X-Cache
o para cualquier encabezado adicional que puedan agregar.
5.4.2.3 CDNs y el Encabezado Cache-Control
El encabezado Cache-Control
describe cómo se puede almacenar en caché una solicitud. Cuando no se utiliza un CDN, un navegador utilizará esta información para almacenar contenidos en caché. Esto es muy útil para los activos que no se modifican, de modo que un navegador no necesita volver a descargar el CSS o JavaScript de un sitio web en cada solicitud. Generalmente queremos que nuestro servidor Rails le diga a nuestro CDN (y al navegador) que el activo es "public". Eso significa que cualquier caché puede almacenar la solicitud. También queremos comúnmente establecer max-age
, que es cuánto tiempo el caché almacenará el objeto antes de invalidar el caché. El valor de max-age
se establece en segundos con un valor máximo posible de 31536000
, que es un año. Puedes hacer esto en tu aplicación Rails configurando
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=31536000'
}
Ahora, cuando tu aplicación sirva un activo en producción, el CDN almacenará el activo hasta por un año. Dado que la mayoría de los CDNs también almacenan en caché los encabezados de la solicitud, este Cache-Control
se pasará a todos los navegadores futuros que busquen este activo. El navegador luego sabe que puede almacenar este activo por un tiempo muy largo antes de necesitar volver a solicitarlo.
5.4.2.4 CDNs e Invalidación de Caché Basada en URL
La mayoría de los CDNs almacenarán en caché el contenido de un activo basado en la URL completa. Esto significa que una solicitud a
http://mi-subdominio-cdn.fictional-cdn.com/assets/smile-123.png
Será un caché completamente diferente de
http://mi-subdominio-cdn.fictional-cdn.com/assets/smile.png
Si deseas establecer un max-age
de futuro lejano en tu Cache-Control
(y lo deseas), entonces asegúrate de que cuando cambies tus activos, tu caché se invalide. Por ejemplo, al cambiar la cara sonriente en una imagen de amarilla a azul, deseas que todos los visitantes de tu sitio obtengan la nueva cara azul. Cuando usas un CDN con el pipeline de activos de Rails, config.assets.digest
está configurado en true por defecto para que cada activo tenga un nombre de archivo diferente cuando se cambia. De esta manera, nunca tienes que invalidar manualmente ningún elemento en tu caché. Al usar un nombre de activo único diferente, tus usuarios obtienen el último activo.
6 Personalizando el Pipeline
6.1 Compresión de CSS
Una de las opciones para comprimir CSS es YUI. El compresor de CSS de YUI proporciona minificación.
La siguiente línea habilita la compresión de YUI y requiere la gema yui-compressor
.
config.assets.css_compressor = :yui
6.2 Compresión de JavaScript
Las opciones posibles para la compresión de JavaScript son :terser
, :closure
y :yui
. Estas requieren el uso de las gemas terser
, closure-compiler
o yui-compressor
, respectivamente.
Toma la gema terser
, por ejemplo. Esta gema envuelve Terser (escrito para Node.js) en Ruby. Comprime tu código eliminando espacios en blanco y comentarios, acortando nombres de variables locales y realizando otras micro-optimizaciones como cambiar declaraciones if
y else
a operadores ternarios donde sea posible.
La siguiente línea invoca terser
para la compresión de JavaScript.
config.assets.js_compressor = :terser
NOTA: Necesitarás un runtime compatible con ExecJS para usar terser
. Si estás usando macOS o Windows, tienes un runtime JavaScript instalado en tu sistema operativo.
NOTA: La compresión de JavaScript también funcionará para tus archivos JavaScript cuando estés cargando tus activos a través de las gemas importmap-rails
o jsbundling-rails
.
6.3 Comprimir tus activos con GZIP
Por defecto, se generará una versión comprimida con GZIP de los activos compilados, junto con la versión no comprimida de los activos. Los activos comprimidos con GZIP ayudan a reducir la transmisión de datos por la red. Puedes configurar esto estableciendo el indicador gzip
.
config.assets.gzip = false # desactivar la generación de activos comprimidos con GZIP
Consulta la documentación de tu servidor web para obtener instrucciones sobre cómo servir activos comprimidos con GZIP.
6.4 Usar tu Propio Compresor
Las configuraciones de compresor para CSS y JavaScript también aceptan cualquier objeto. Este objeto debe tener un método compress
que tome una cadena como único argumento y debe devolver una cadena.
class Transformer
def compress(string)
do_something_returning_a_string(string)
end
end
Para habilitar esto, pasa un nuevo objeto a la opción de configuración en application.rb
:
config.assets.css_compressor = Transformer.new
6.5 Cambiar la Ruta de assets
La ruta pública que Sprockets utiliza por defecto es /assets
.
Esto puede cambiarse a otra cosa:
config.assets.prefix = "/some_other_path"
Esta es una opción útil si estás actualizando un proyecto antiguo que no usaba el pipeline de activos y ya utiliza esta ruta o deseas usar esta ruta para un nuevo recurso.
6.6 Encabezados X-Sendfile
El encabezado X-Sendfile es una directiva para el servidor web para ignorar la respuesta de la aplicación y en su lugar servir un archivo específico desde el disco. Esta opción está desactivada por defecto, pero puede habilitarse si tu servidor la admite. Cuando está habilitada, esto pasa la responsabilidad de servir el archivo al servidor web, lo cual es más rápido. Echa un vistazo a send_file para saber cómo usar esta función.
Apache y NGINX admiten esta opción, que puede habilitarse en config/environments/production.rb
:
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # para Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # para NGINX
ADVERTENCIA: Si estás actualizando una aplicación existente y planeas usar esta opción, ten cuidado de pegar esta opción de configuración solo en production.rb
y en cualquier otro entorno que definas con comportamiento de producción (no en application.rb
).
CONSEJO: Para obtener más detalles, consulta la documentación de tu servidor web de producción:
7 Almacenamiento en Caché de Activos
Por defecto, Sprockets almacena en caché los activos en tmp/cache/assets
en los entornos de desarrollo y producción. Esto puede cambiarse de la siguiente manera:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
{ size: 32.megabytes })
end
Para desactivar el almacenamiento en caché de activos:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:null_store)
end
8 Agregar Activos a tus Gemas
Los activos también pueden provenir de fuentes externas en forma de gemas.
Un buen ejemplo de esto es la gema jquery-rails
. Esta gema contiene una clase de motor que hereda de Rails::Engine
. Al hacer esto, Rails es informado de que el directorio para esta gema puede contener activos y los directorios app/assets
, lib/assets
y vendor/assets
de este motor se agregan a la ruta de búsqueda de Sprockets.
9 Hacer que tu Biblioteca o Gema sea un Preprocesador
Sprockets utiliza Procesadores, Transformadores, Compresores y Exportadores para extender la funcionalidad de Sprockets. Echa un vistazo a Extending Sprockets para aprender más. Aquí registramos un preprocesador para agregar un comentario al final de los archivos text/css (.css
).
module AddComment
def self.call(input)
{ data: input[:data] + "/* Hola desde mi extensión de sprockets */" }
end
end
Ahora que tienes un módulo que modifica los datos de entrada, es hora de registrarlo como un preprocesador para tu tipo MIME.
Sprockets.register_preprocessor 'text/css', AddComment
10 Bibliotecas Alternativas
A lo largo de los años, ha habido múltiples enfoques predeterminados para manejar los activos. La web evolucionó y comenzamos a ver más y más aplicaciones pesadas en JavaScript. En The Rails Doctrine creemos que The Menu Is Omakase, por lo que nos centramos en la configuración predeterminada: Sprockets con Import Maps.
Somos conscientes de que no hay soluciones de talla única para todos los diversos frameworks/extensiones de JavaScript y CSS disponibles. Hay otras bibliotecas de empaquetado en el ecosistema de Rails que deberían empoderarte en los casos donde la configuración predeterminada no es suficiente.
10.1 jsbundling-rails
jsbundling-rails
es una alternativa dependiente del tiempo de ejecución de JavaScript a la forma de importmap-rails
de empaquetar JS con Bun, esbuild, rollup.js o Webpack.
La gema proporciona una tarea de construcción en package.json
para observar cambios y generar automáticamente la salida en desarrollo. Para producción, engancha automáticamente la tarea javascript:build
en la tarea assets:precompile
para asegurarse de que todas tus dependencias de paquetes se hayan instalado y que el JavaScript se haya construido para todos los puntos de entrada.
¿Cuándo usar en lugar de importmap-rails
? Si tu código JavaScript depende de la transpilation, por lo que si estás usando Babel, TypeScript o el formato React JSX, entonces jsbundling-rails
es la forma correcta de proceder.
10.2 Webpacker/Shakapacker
Webpacker
fue el preprocesador y empaquetador de JavaScript predeterminado para Rails 5 y 6. Ahora ha sido retirado. Un sucesor llamado shakapacker
existe, pero no es mantenido por el equipo o proyecto de Rails.
A diferencia de otras bibliotecas en esta lista, webpacker
/shakapacker
es completamente independiente de Sprockets y podría procesar tanto archivos JavaScript como CSS.
NOTA: Lee el documento Comparison with Webpacker para entender las diferencias entre jsbundling-rails
y webpacker
/shakapacker
.
10.3 cssbundling-rails
cssbundling-rails
permite empaquetar y procesar tu CSS usando Tailwind CSS, Bootstrap, Bulma, PostCSS o Dart Sass, y luego entrega el CSS a través del pipeline de activos.
Funciona de manera similar a jsbundling-rails
, por lo que agrega la dependencia de Node.js a tu aplicación con el proceso yarn build:css --watch
para regenerar tus hojas de estilo en desarrollo y engancha en la tarea assets:precompile
en producción.
¿Cuál es la diferencia con Sprockets? Sprockets por sí solo no puede transpilar el Sass en CSS, se requiere Node.js para generar los archivos .css
a partir de tus archivos .sass
. Una vez que los archivos .css
se generan, entonces Sprockets puede entregarlos a tus clientes.
NOTA: cssbundling-rails
depende de Node para procesar el CSS. Las gemas dartsass-rails
y tailwindcss-rails
usan versiones independientes de Tailwind CSS y Dart Sass, lo que significa que no hay dependencia de Node. Si estás usando importmap-rails
para manejar tus JavaScripts y dartsass-rails
o tailwindcss-rails
para CSS, podrías evitar completamente la dependencia de Node, lo que resulta en una solución menos compleja.
10.4 dartsass-rails
Si deseas usar Sass en tu aplicación, dartsass-rails
viene como un reemplazo para la gema sassc-rails
heredada. dartsass-rails
utiliza la implementación de Dart Sass en favor de LibSass que fue deprecada en 2020 y utilizada por sassc-rails
.
A diferencia de sassc-rails
, la nueva gema no está directamente integrada con Sprockets. Consulta la página de inicio de la gema para obtener instrucciones de instalación/migración.
ADVERTENCIA: La popular gema sassc-rails
no se mantiene desde 2019.
10.5 tailwindcss-rails
tailwindcss-rails
es una gema envolvente para la versión ejecutable independiente del framework Tailwind CSS v3. Se utiliza para nuevas aplicaciones cuando se proporciona --css tailwind
al comando rails new
. Proporciona un proceso watch
para generar automáticamente la salida de Tailwind en desarrollo. Para producción, se engancha en la tarea assets:precompile
.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.