1 ¿Qué es el Pipeline de Activos?
El pipeline de activos proporciona un marco para manejar la entrega de activos de JavaScript y CSS. Esto se logra 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 de forma predeterminada. Puedes deshabilitarlo 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 que utiliza solo sprockets
para CSS y importmap-rails
para el procesamiento de JavaScript. La principal limitación de estos dos es que no admiten la transpilación, por lo que no se pueden utilizar cosas como Babel
, Typescript
, Sass
, React JSX format
o TailwindCSS
. Te recomendamos que leas la sección Bibliotecas Alternativas si necesitas transpilación para 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 almacenado en caché por el navegador web y la CDN. Esta huella digital se actualiza automáticamente cuando cambias el contenido del archivo, lo que invalida la caché.
La segunda característica del pipeline de activos es utilizar import maps al servir archivos de JavaScript. Esto te permite construir aplicaciones modernas utilizando bibliotecas de JavaScript hechas para módulos ES (ESM) sin necesidad de transpilación y empaquetado. A su vez, esto elimina la necesidad de Webpack, yarn, node o cualquier otra parte de la cadena de herramientas de 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 los 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 almacenado en caché por el navegador web. Puedes invalidar la caché modificando esta huella digital, lo cual ocurre automáticamente cada vez que cambias el contenido del archivo.
La cuarta característica del pipeline de activos es que permite codificar activos mediante 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, también cambia el nombre de archivo. Para contenido estático o que cambia con poca frecuencia, esto proporciona una forma sencilla de determinar si dos versiones de un archivo son idénticas, incluso en 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 las cachés en todas partes (ya sea en CDNs, en ISP, en equipos de red o en navegadores web) a mantener su propia copia del contenido. Cuando se actualiza el contenido, la huella digital cambiará. Esto hará que los clientes remotos soliciten una nueva copia del contenido. Esto generalmente se conoce como cache busting.
La técnica que utiliza Sprockets 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 de forma predeterminada tanto para los entornos de desarrollo como de producción. Puedes habilitar 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 de JavaScript utilizando nombres lógicos que se mapean a archivos versionados/digestos, directamente desde el navegador. De esta manera, puedes construir aplicaciones modernas de JavaScript utilizando bibliotecas de JavaScript hechas para módulos ES (ESM) sin necesidad de transpilación o empaquetado.
Con este enfoque, enviarás muchos archivos JavaScript pequeños en lugar de un solo archivo JavaScript grande. Gracias a HTTP/2, esto ya no tiene una penalización de rendimiento significativa durante el transporte inicial, y de hecho ofrece beneficios sustanciales a largo plazo debido a una mejor dinámica de almacenamiento en caché.
3 Cómo utilizar Import Maps como un pipeline de activos de JavaScript
Import Maps es el procesador de JavaScript por defecto, la lógica de generación de los mapas de importación es manejada por la gema importmap-rails
.
ADVERTENCIA: Los mapas de importación se utilizan únicamente para archivos de JavaScript y no se pueden utilizar para la entrega de CSS. Consulta la sección 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 mapas de importación son básicamente una sustitución de cadena para lo que se conoce como "especificadores de módulos sin nombre". Te permiten estandarizar los nombres de las importaciones de módulos de JavaScript.
Por ejemplo, toma esta definición de importación, que no funcionará sin un mapa de importación:
import React from "react"
Tendrías que definirlo de esta manera para que funcione:
import React from "https://ga.jspm.io/npm:[email protected]/index.js"
Aquí es donde entra el mapa de importación, definimos el nombre react
para que se vincule a la dirección https://ga.jspm.io/npm:[email protected]/index.js
. Con esta información, nuestro navegador acepta la definición simplificada import React from "react"
. Piensa en el mapa de importación como un alias para la dirección de origen de la biblioteca.
3.2 Uso
Con importmap-rails
, puedes crear el archivo de configuración del mapa de importación para vincular la ruta de la biblioteca a un nombre:
# config/importmap.rb
pin "application"
pin "react", to: "https://ga.jspm.io/npm:[email protected]/index.js"
Todos los mapas de importación configurados deben ser adjuntados en el elemento <head>
de tu aplicación agregando <%= javascript_importmap_tags %>
. La función javascript_importmap_tags
renderiza una serie de scripts en el elemento head
:
- JSON con todos los mapas de importación configurados:
<script type="importmap">
{
"imports": {
"application": "/assets/application-39f16dc3f3....js"
"react": "https://ga.jspm.io/npm:[email protected]/index.js"
}
}
</script>
Es-module-shims
actúa como un polyfill que garantiza el soporte deimport maps
en navegadores antiguos:
<script src="/assets/es-module-shims.min" async="async" data-turbo-track="reload"></script>
- Punto de entrada para cargar JavaScript desde
app/javascript/application.js
:
<script type="module">import "application"</script>
3.3 Uso de paquetes npm a través de CDNs de JavaScript
Puedes utilizar 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 mapa de importación. El binstub utiliza JSPM.org
.
Funciona de la siguiente manera:
./bin/importmap pin react react-dom
Fijando "react" a https://ga.jspm.io/npm:[email protected]/index.js
Fijando "react-dom" a https://ga.jspm.io/npm:[email protected]/index.js
Fijando "object-assign" a https://ga.jspm.io/npm:[email protected]/index.js
Fijando "scheduler" a https://ga.jspm.io/npm:[email protected]/index.js
./bin/importmap json
{
"imports": {
"application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js",
"react": "https://ga.jspm.io/npm:[email protected]/index.js",
"react-dom": "https://ga.jspm.io/npm:[email protected]/index.js",
"object-assign": "https://ga.jspm.io/npm:[email protected]/index.js",
"scheduler": "https://ga.jspm.io/npm:[email protected]/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 de jspm de forma predeterminada.
Ahora puedes utilizar 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 [email protected]
Fijando "react" a https://ga.jspm.io/npm:[email protected]/index.js
Fijando "object-assign" a https://ga.jspm.io/npm:[email protected]/index.js
Incluso puedes eliminar fijaciones:
./bin/importmap unpin react
Desfijando "react"
Desfijando "object-assign"
Puedes controlar el entorno del paquete para paquetes con compilaciones separadas de "producción" (la predeterminada) y "desarrollo":
./bin/importmap pin react --env development
Fijando "react" a https://ga.jspm.io/npm:[email protected]/dev.index.js
Fijando "object-assign" a https://ga.jspm.io/npm:[email protected]/index.js
También puedes elegir un proveedor de CDN alternativo compatible al fijar, como unpkg
o jsdelivr
(jspm
es el predeterminado):
./bin/importmap pin react --from jsdelivr
Fijando "react" a https://cdn.jsdelivr.net/npm/[email protected]/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 lógicos de paquetes 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, simplemente podrías agregar lo siguiente a config/importmap.rb
:
pin "react", to: "https://cdn.skypack.dev/react"
3.4 Precarga de módulos fijados
Para evitar el efecto de cascada donde el navegador tiene que cargar un archivo tras otro antes de poder llegar a la importación más anidada, importmap-rails admite enlaces de precarga de módulos. Los módulos fijados se pueden precargar agregando 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 indicará al navegador que las descargue antes.
Ejemplo:
# config/importmap.rb
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/[email protected]/dist/index.js", preload: true
pin "md5", to: "https://cdn.jsdelivr.net/npm/[email protected]/md5.js"
# app/views/layouts/application.html.erb
<%= javascript_importmap_tags %>
# incluirá el siguiente enlace antes de configurar el mapa de importación:
<link rel="modulepreload" href="https://ga.jspm.io/npm:@github/[email protected]/dist/index.js">
...
NOTA: Consulta el repositorio importmap-rails
para obtener la documentación más actualizada.
4 Cómo usar Sprockets
El enfoque ingenuo para exponer los activos de tu aplicación en 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, comprimirlos y agregar huellas digitales a los activos.
Sprockets está diseñado para preprocesar automáticamente tus activos almacenados en los directorios configurados y, después de procesarlos, exponerlos en la carpeta public/assets
con huellas digitales, compresión, generación de mapas de origen y otras características configurables.
Los activos aún se pueden colocar en la jerarquía de public
. Cualquier activo bajo public
se servirá como archivos estáticos por la aplicación o el servidor web cuando config.public_file_server.enabled
se establezca en true. Debes definir directivas manifest.js
para los archivos que deben someterse a algún preprocesamiento antes de ser servidos.
En producción, Rails precompila estos archivos en public/assets
de forma predeterminada. Las copias precompiladas luego se sirven como activos estáticos por el servidor web. Los archivos en app/assets
nunca se sirven directamente en producción.
4.1 Archivos y directivas de manifiesto
Al compilar activos con Sprockets, Sprockets necesita decidir qué objetivos principales compilar, generalmente application.css
e imágenes. Los objetivos principales 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 indican 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 en cualquier subdirectorio, así como cualquier archivo reconocido como JS directamente en ./app/javascript
o ./vendor/javascript
.
Cargará cualquier CSS desde el directorio ./app/assets/stylesheets
(sin incluir subdirectorios). Suponiendo que tienes los 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.
Puede que notes que nuestros archivos JavaScript no se cargan desde el directorio assets
de forma predeterminada, esto se debe a que ./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 archivo manifest.js
también puedes 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 (según el valor de config.assets.css_compressor
o config.assets.js_compressor
). La compresión reduce el tamaño del archivo, lo que permite 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 (CSS) 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 usar en tu aplicación de inmediato utilizando 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 utilizando lo siguiente:
<%= stylesheet_link_tag params[:controller] %>
Al hacer esto, asegúrate de no utilizar 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 se pueden colocar 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 JavaScript.vendor/[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.) deberá ser reescrito para usar ayudantes comoasset_path
.
Otras ubicaciones se pueden configurar en el archivo manifest.js
, consulta Archivos y directivas de manifiesto.
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 Uso de archivos de í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 de 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 de 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 se puede acceder a un archivo en public/library_name/index.html
mediante una solicitud a /library_name
. Esto significa que no puedes usar directamente un archivo de índice.
La biblioteca en su conjunto se puede acceder en los archivos .css
de la siguiente manera:
/* ...
*= require library_name
*/
Esto simplifica el mantenimiento y mantiene las cosas limpias al permitir que el código relacionado se agrupe antes de su inclusión en otros lugares.
4.4 Codificación de enlaces a activos
Sprockets no agrega nuevos métodos para acceder a tus activos, aún se utiliza el familiar stylesheet_link_tag
:
<%= stylesheet_link_tag "application", media: "all" %>
Si se utiliza la gema turbo-rails
, que está incluida de forma predeterminada en Rails, entonces se incluye la opción data-turbo-track
, que hace que Turbo verifique si un activo ha sido actualizado y, de ser así, lo carga en la página:
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
En las vistas regulares, se pueden acceder a las imágenes en el directorio app/assets/images
de la siguiente manera:
<%= image_tag "rails.png" %>
Siempre que el pipeline esté habilitado en tu aplicación (y no esté desactivado en el contexto del entorno actual), este archivo será servido por Sprockets. Si existe un archivo en public/assets/rails.png
, será servido por el servidor web.
Alternativamente, una solicitud de 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 explica en la sección En producción más adelante en esta guía.
Las imágenes también se pueden organizar en subdirectorios si es necesario, y luego se pueden acceder 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), vincular a un activo que no existe generará una excepción en la página que lo llama. Esto incluye vincular a una cadena vacía. Por lo tanto, ten cuidado al usar image_tag
y los demás 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 específico al que se hace 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 utilizar una 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 una URI de datos correctamente formateada en la fuente CSS.
Ten en cuenta que la etiqueta de cierre no puede tener el estilo -%>
.
4.5 Generar un error cuando no se encuentra un activo
Si estás utilizando sprockets-rails >= 3.2.0, puedes configurar qué sucede cuando se realiza una búsqueda de activo y no se encuentra nada. Si desactivas "fallback de activo", se generará un error cuando no se encuentre un activo.
config.assets.unknown_asset_fallback = false
Si el "fallback de activo" está habilitado, cuando no se encuentre un activo, se mostrará la ruta y no se generará ningún error. El comportamiento de fallback de activo está desactivado de forma predeterminada.
4.6 Desactivar los hashes
Puedes desactivar los hashes actualizando config/environments/development.rb
para incluir:
config.assets.digest = false
Cuando esta opción está en true, se generarán hashes para las URL de los activos.
4.7 Activar los mapas de origen
Puedes activar los mapas de origen actualizando config/environments/development.rb
para incluir:
config.assets.debug = true
Cuando el modo de depuración está activado, Sprockets generará un mapa de origen 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 se inicia el servidor. Sprockets establece una cabecera HTTP must-revalidate
de Control de caché para reducir la sobrecarga de solicitudes en las solicitudes posteriores; en estas, el navegador recibe una respuesta 304 (No modificado).
Si alguno de los archivos en el manifiesto cambia entre las 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 dactilares descrito anteriormente. Por defecto, Rails asume que los activos han sido precompilados y serán servidos como activos estáticos por su 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 dactilares 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 dactilares es 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 debe ser cambiada. Si no hay huellas dactilares en los nombres de archivo y se establecen encabezados de fecha futura, los clientes remotos nunca sabrán que deben volver a solicitar 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 la canalización.
Los activos compilados se escriben en la ubicación especificada en config.assets.prefix
. Por defecto, esto es el directorio /assets
.
Puede llamar a este comando en el servidor durante la implementación para crear versiones compiladas de sus activos directamente en el servidor. Consulte la siguiente sección para obtener información sobre la compilación local.
El comando es:
$ RAILS_ENV=production rails assets:precompile
Esto vincula la carpeta especificada en config.assets.prefix
a shared/assets
. Si ya utiliza esta carpeta compartida, deberá escribir su propio comando de implementación.
Es importante que esta carpeta se comparta entre las implementaciones para que las páginas en caché remotamente que hacen referencia a los antiguos activos compilados sigan funcionando durante la vida de la página en caché.
NOTA. Siempre especifique un nombre de archivo compilado esperado que termine con .js
o .css
.
El comando también genera un archivo .sprockets-manifest-randomhex.json
(donde randomhex
es una cadena hexadecimal aleatoria de 16 bytes) que contiene una lista con todos sus activos y sus respectivas huellas dactilares. Esto es utilizado por los métodos auxiliares de Rails para evitar devolver las solicitudes de asignación 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 su aplicación, habrá más archivos y activos enumerados en el manifiesto, también se generarán <fingerprint>
y <random-string>
.
La ubicación predeterminada para el manifiesto es la raíz de la ubicación especificada en config.assets.prefix
('/assets' de forma predeterminada).
NOTA: Si faltan archivos precompilados en producción, obtendrá una excepción Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError
que indica el nombre del archivo(s) que falta(n).
5.1.1 Encabezado Expires de fecha futura
Los activos precompilados existen en el sistema de archivos y son servidos directamente por su servidor web. Por defecto, no tienen encabezados de fecha futura, por lo que para obtener el beneficio de las huellas dactilares deberá actualizar la configuración de su 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 se desaconseja cuando Last-Modified está presente
Header unset ETag
FileETag None
# La RFC dice que solo se almacena 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 desee o no pueda compilar los activos en el servidor de producción. Por ejemplo, es posible que tenga acceso limitado de escritura a su sistema de archivos de producción o que planee implementar con frecuencia sin realizar cambios en sus activos.
En estos casos, puede precompilar los activos localmente, es decir, agregar un conjunto finalizado de activos compilados y listos para producción a su repositorio de código fuente antes de implementar en producción. De esta manera, no es necesario compilarlos por separado en el servidor de producción en cada implementación.
Como se mencionó anteriormente, puede realizar este paso utilizando
$ RAILS_ENV=production rails assets:precompile
Tenga 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 asegurarse de que el servidor de desarrollo siempre compile los activos sobre la marcha (y así siempre refleje el estado más reciente del código), el entorno de desarrollo debe estar configurado 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 ellos en desarrollo (es decir, los cambios posteriores que realice 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 estar desactivada.
- Cualquier compresor o minificador necesario debe estar disponible en tu sistema de desarrollo.
También puedes configurar ENV["SECRET_KEY_BASE_DUMMY"]
para activar el uso de un secret_key_base
generado 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 otra manera no necesita 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 utilizar 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, configura:
config.assets.compile = true
En la primera solicitud, los activos se compilan y se almacenan en caché como se describe en Assets Cache Store, y los nombres de los manifiestos utilizados en los helpers se modifican para incluir el hash SHA256.
Sprockets también establece la cabecera HTTP Cache-Control
en max-age=31536000
. Esto indica a todas las cachés entre tu servidor y el navegador del cliente que este contenido (el archivo servido) se puede almacenar en caché durante 1 año. El efecto de esto es reducir el número de solicitudes de este activo desde tu servidor; es muy probable que el activo esté en la caché del navegador local o en alguna caché intermedia.
Este modo utiliza más memoria, tiene un rendimiento inferior al valor predeterminado y no se recomienda.
5.4 CDNs
CDN significa Content Delivery Network (Red de Distribución de Contenido), 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é cerca geográficamente de ese navegador. Si estás sirviendo activos directamente desde tu servidor de Rails en producción, la mejor práctica es utilizar un CDN delante de tu aplicación.
Un patrón común para utilizar un CDN es configurar tu aplicación de producción como el servidor "origen". Esto significa que cuando un navegador solicita un activo desde el CDN y hay un fallo de caché, el CDN obtendrá el archivo de tu servidor sobre la marcha y luego lo almacenará en caché. Por ejemplo, si estás ejecutando una aplicación de Rails en example.com
y tienes un CDN configurado en mycdnsubdomain.fictional-cdn.com
, entonces cuando se realiza una solicitud a mycdnsubdomain.fictional-cdn.com/assets/smile.png
, el CDN consultará tu servidor una vez en example.com/assets/smile.png
y almacenará en caché la solicitud. La siguiente solicitud al CDN que llegue a la misma URL accederá a la copia en caché. Cuando el CDN puede servir un activo directamente, la solicitud nunca llega a tu servidor de 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 pasar tiempo sirviendo 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 en ejecución en producción en Internet en una URL pública disponible, por ejemplo, example.com
. A continuación, debes registrarte en un servicio de CDN de un proveedor de alojamiento en la nube. Cuando lo hagas, debes configurar el "origen" del CDN para que apunte 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 hayas provisionado debería darte un subdominio personalizado para tu aplicación, como mycdnsubdomain.fictional-cdn.com
(nota: fictional-cdn.com no es un proveedor de CDN válido en el momento de escribir esto). Ahora que has configurado tu servidor de CDN, debes indicar a los navegadores que utilicen tu CDN para obtener los activos en lugar de tu servidor de Rails directamente. Puedes hacer esto configurando Rails para que establezca tu CDN como el host de activos en lugar de utilizar una ruta relativa. Para establecer tu host de activos en Rails, debes configurar config.asset_host
en config/environments/production.rb
:
config.asset_host = 'mycdnsubdomain.fictional-cdn.com'
NOTA: Solo necesitas proporcionar el "host", que es el subdominio y el dominio raíz, no necesitas especificar un protocolo o "scheme" como http://
o https://
. Cuando se solicita una página web, el protocolo en el enlace a tu activo que se genera coincidirá con la forma en que se accede a la página web de forma predeterminada.
También puedes configurar este valor a través de una variable de entorno para facilitar la ejecución de una copia de tu sitio en etapa de pruebas:
config.asset_host = ENV['CDN_HOST']
NOTA: Debes configurar CDN_HOST
en tu servidor como mycdnsubdomain.fictional-cdn.com
para que esto funcione.
Una vez que hayas configurado tu servidor y tu CDN, las rutas de los activos desde los helpers, como:
<%= asset_path('smile.png') %>
Se renderizarán como URLs completas del CDN, como http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
(digest omitido por legibilidad).
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 su uso futuro.
Si deseas servir solo algunos activos desde tu CDN, puedes usar la opción personalizada :host
en tu helper de activos, que sobrescribe el valor establecido en config.action_controller.asset_host
.
<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %>
5.4.2 Personalizar el comportamiento de almacenamiento en caché del CDN
Un CDN funciona almacenando en caché el contenido. Si el CDN tiene contenido obsoleto o incorrecto, en realidad está perjudicando en lugar de ayudar a tu aplicación. El propósito de esta sección es describir el comportamiento general de almacenamiento en caché de la mayoría de los CDNs. Tu proveedor específico puede comportarse ligeramente diferente.
5.4.2.1 Almacenamiento en caché de solicitudes del CDN
Si bien se dice que un CDN es bueno para almacenar en caché activos, en realidad almacena en caché toda la solicitud. Esto incluye el cuerpo del activo y cualquier encabezado. El más importante es Cache-Control
, que le indica al CDN (y a los navegadores web) cómo almacenar en caché el contenido. Esto significa que si alguien solicita un activo que no existe, como /assets/i-dont-exist.png
, y tu aplicación de Rails devuelve un error 404, es probable que tu CDN almacene en caché la página de error 404 si hay un encabezado Cache-Control
válido presente.
5.4.2.2 Depuración de encabezados del CDN
Una forma de verificar que los encabezados se almacenen en caché correctamente en tu CDN es utilizando curl. Puedes solicitar los encabezados tanto desde tu servidor como desde tu CDN para verificar que sean iguales:
$ 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://mycdnsubdomain.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 cualquier 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 utiliza esta información para almacenar en caché el contenido. Esto es muy útil para los activos que no se modifican, para que un navegador no tenga que volver a descargar el CSS o JavaScript de un sitio web en cada solicitud. Por lo general, queremos que nuestro servidor de Rails le diga a nuestro CDN (y al navegador) que el activo es "público". Esto significa que cualquier caché puede almacenar la solicitud. También comúnmente queremos establecer max-age
, que es cuánto tiempo la caché almacenará el objeto antes de invalidarla. 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 de Rails configurando:
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=31536000'
}
Ahora, cuando tu aplicación sirve un activo en producción, el CDN almacenará el activo durante hasta 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 transmitirá a todos los navegadores futuros que busquen este activo. El navegador sabrá entonces que puede almacenar este activo durante mucho tiempo antes de tener que volver a solicitarlo.
5.4.2.4 CDNs y la invalidación de caché basada en URL
La mayoría de los CDNs almacenarán el contenido de un activo en caché en función de la URL completa. Esto significa que una solicitud a
http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png
Será una caché completamente diferente a
http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
Si deseas establecer un max-age
en el futuro lejano en tu Cache-Control
(y lo deseas), asegúrate de que cuando cambies tus activos, se invalide tu caché. 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
se establece en true
de forma predeterminada para que cada activo tenga un nombre de archivo diferente cuando se cambia. De esta manera, no tienes que invalidar manualmente ningún elemento en tu caché. Al usar un nombre de activo único diferente, tus usuarios obtienen el activo más reciente.
6 Personalización del 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.
Tomemos como ejemplo la gema terser
.
Esta gema envuelve a Terser (escrito para Node.js) en Ruby. Comprime tu código eliminando espacios en blanco y comentarios, acortando los nombres de las variables locales y realizando otras micro-optimizaciones, como cambiar las declaraciones if
y else
a operadores ternarios cuando sea posible.
La siguiente línea invoca a terser
para la compresión de JavaScript.
config.assets.js_compressor = :terser
NOTA: Necesitarás un tiempo de ejecución compatible con ExecJS para poder utilizar terser
. Si estás utilizando macOS o Windows, tienes un tiempo de ejecución de JavaScript instalado en tu sistema operativo.
NOTA: La compresión de JavaScript también funcionará para tus archivos JavaScript cuando cargues tus activos a través de las gemas importmap-rails
o jsbundling-rails
.
6.3 Compresión GZip de tus activos
De forma predeterminada, se generarán versiones comprimidas en formato GZip de los activos compilados, junto con la versión no comprimida de los activos. Los activos comprimidos en formato GZip ayudan a reducir la transmisión de datos a través de la red. Puedes configurar esto estableciendo la opción gzip
.
config.assets.gzip = false # deshabilitar la generación de activos comprimidos en formato GZip
Consulta la documentación de tu servidor web para obtener instrucciones sobre cómo servir activos comprimidos en formato GZip.
6.4 Uso de tu propio compresor
La configuración del compresor para CSS y JavaScript también acepta cualquier objeto. Este objeto debe tener un método compress
que tome una cadena como único argumento y devuelva una cadena.
class Transformer
def compress(string)
hacer_algo_devolviendo_una_cadena(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 Cambio de la ruta de los assets
La ruta pública que Sprockets utiliza de forma predeterminada es /assets
.
Esto se puede cambiar a otra cosa:
config.assets.prefix = "/otra_ruta"
Esta es una opción útil si estás actualizando un proyecto más antiguo que no utilizaba el pipeline de activos y ya utiliza esta ruta, o si deseas utilizar esta ruta para un nuevo recurso.
6.6 Encabezados X-Sendfile
El encabezado X-Sendfile es una directiva para el servidor web que indica al servidor web que ignore la respuesta de la aplicación y, en su lugar, sirva un archivo especificado desde el disco. Esta opción está desactivada de forma predeterminada, pero se puede habilitar si tu servidor lo admite. Cuando está habilitada, esto pasa la responsabilidad de servir el archivo al servidor web, lo cual es más rápido. Consulta send_file para obtener información sobre cómo utilizar esta función.
Apache y NGINX admiten esta opción, que se puede habilitar 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 tienes la intención de utilizar esta opción, asegúrate 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
De forma predeterminada, Sprockets almacena en caché los activos en tmp/cache/assets
en los entornos de desarrollo y producción. Esto se puede cambiar de la siguiente manera:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
{ size: 32.megabytes })
end
Para deshabilitar el almacenamiento en caché de activos:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:null_store)
end
8 Añadir 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 se informa de que el directorio de 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 ampliar la funcionalidad de Sprockets. Consulta Extending Sprockets para obtener más información. Aquí registramos un preprocesador para agregar un comentario al final de los archivos de texto/css (.css
).
module AddComment
def self.call(input)
{ data: input[:data] + "/* Hello From my sprockets extension */" }
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.
ruby
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 aplicaciones cada vez más cargadas de JavaScript. En The Rails Doctrine creemos que El menú es Omakase, por lo que nos enfocamos en la configuración predeterminada: Sprockets con Import Maps.
Somos conscientes de que no hay soluciones universales para los diversos frameworks/extensiones de JavaScript y CSS disponibles. Hay otras bibliotecas de empaquetado en el ecosistema de Rails que deberían permitirte en los casos en los que la configuración predeterminada no sea suficiente.
10.1 jsbundling-rails
jsbundling-rails
es una alternativa dependiente de Node.js para la forma de empaquetar JavaScript con esbuild, rollup.js o Webpack.
La gema proporciona un proceso yarn build --watch
para generar automáticamente la salida en desarrollo. Para producción, se engancha automáticamente la tarea javascript:build
en la tarea assets:precompile
para asegurarse de que todas las dependencias de tus paquetes se hayan instalado y se haya construido JavaScript para todos los puntos de entrada.
¿Cuándo usar en lugar de importmap-rails
? Si tu código de JavaScript depende de la transpilación, es decir, si estás utilizando Babel, TypeScript o el formato JSX
de React, entonces jsbundling-rails
es la forma correcta de proceder.
10.2 Webpacker/Shakapacker
Webpacker
era el preprocesador y empaquetador de JavaScript predeterminado para Rails 5 y 6. Ahora se ha retirado. Existe un sucesor llamado shakapacker
, pero no es mantenido por el equipo o proyecto de Rails.
A diferencia de otras bibliotecas de esta lista, webpacker
/shakapacker
es completamente independiente de Sprockets y puede procesar tanto archivos JavaScript como CSS. Lee la guía de Webpacker para obtener más información.
NOTA: Lee el documento Comparación con Webpacker para entender las diferencias entre jsbundling-rails
y webpacker
/shakapacker
.
10.3 cssbundling-rails
cssbundling-rails
permite empaquetar y procesar tu CSS utilizando 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 se 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 a CSS, se requiere Node.js para generar los archivos .css
a partir de tus archivos .sass
. Una vez que se generan los archivos .css
, entonces Sprockets
puede entregarlos a tus clientes.
NOTA: cssbundling-rails
depende de Node para procesar el CSS. Las gemas dartsass-rails
y tailwindcss-rails
utilizan versiones independientes de Tailwind CSS y Dart Sass, lo que significa que no hay dependencia de Node. Si estás utilizando importmap-rails
para manejar tus JavaScript y dartsass-rails
o tailwindcss-rails
para CSS, podrías evitar completamente la dependencia de Node, lo que resultaría en una solución menos compleja.
10.4 dartsass-rails
Si deseas utilizar Sass
en tu aplicación, dartsass-rails
es un reemplazo para la gema heredada sassc-rails
. dartsass-rails
utiliza la implementación Dart Sass
en lugar de LibSass
, que fue descontinuada en 2020 y era utilizada por sassc-rails
.
A diferencia de sassc-rails
, la nueva gema no está integrada directamente 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 ha recibido mantenimiento 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. En 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 encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.