Cómo utilizar Assetic para gestionar activos

Assetic combina dos ideas principales: assets y filters. Los activos son archivos tales como CSS, JavaScript y archivos de imagen. Los filtros son cosas que se pueden aplicar a estos archivos antes de servirlos al navegador. Esto te permite una separación entre los archivos de activos almacenados en tu aplicación y los archivos realmente presentados al usuario.

Sin Assetic, sólo sirves los archivos que están almacenados directamente en la aplicación:

  • Twig
    <script src="{{ asset('js/script.js') }}" type="text/javascript" />
    
  • PHP
    <script src="<?php echo $view['assets']->getUrl('js/script.js') ?>" type="text/javascript" />
    

Sin embargo, con Assetic, puedes manipular estos activos como quieras (o cargarlos desde cualquier lugar) antes de servirlos. Esto significa que puedes:

  • Minimizarlos con minify y combinar todos tus archivos CSS y JS
  • Ejecutar todos (o algunos) de tus archivos CSS o JS a través de algún tipo de compilador, como LESS, SASS o CoffeeScript
  • Ejecutar la optimización de imagen en tus imágenes

Activos

Assetic ofrece muchas ventajas sobre los archivos que sirves directamente. Los archivos no se tienen que almacenar dónde son servidos y se pueden obtener de diversas fuentes, tal como desde dentro de un paquete:

Puedes utilizar Assetic para procesar ambos Hojas de estilo CSS y archivos JavaScript. La filosofía detrás de añadir cualquiera básicamente la misma, pero con una sintaxis ligeramente diferente.

Incluyendo archivos JavaScript

Para incluir archivos JavaScript, usa la etiqueta javascript en cualquier plantilla. Este generalmente vive en el bloque javascripts, si estás utilizando los nombres de bloque predefinidos de la Edición estándar de Symfony:

  • Twig
    {% javascripts '@AcmeFooBundle/Resources/public/js/*' %}
        <script type="text/javascript" src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array('@AcmeFooBundle/Resources/public/js/*')
    ) as $url): ?>
        <script type="text/javascript" src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    

Truco

También puedes incluir hojas de estilo CSS: consulta Including CSS Stylesheets.

En este ejemplo, se cargan todos los archivos en el directorio Resources/public/js/ del AcmeFooBundle y se sirven desde un lugar diferente. En realidad la etiqueta reproducida simplemente podría ser:

<script src="/app_dev.php/js/abcd123.js"></script>

Este es un punto clave: once you let Assetic handle your assets, the files are served from a different location. This will cause problems with CSS files that reference images by their relative path. See Fixing CSS Paths with the cssrewrite Filter.

Including CSS Stylesheets

To bring in CSS stylesheets, you can use the same methodologies seen above, except with the stylesheets tag. If you’re using the default block names from the Symfony Standard Distribution, this will usually live inside a stylesheets block:

  • Twig
    {% stylesheets 'bundles/acme_foo/css/*' filter='cssrewrite' %}
        <link rel="stylesheet" href="{{ asset_url }}" />
    {% endstylesheets %}
    
  • PHP
    <?php foreach ($view['assetic']->stylesheets(
        array('bundles/acme_foo/css/*'),
        array('cssrewrite')
    ) as $url): ?>
        <link rel="stylesheet" href="<?php echo $view->escape($url) ?>" />
    <?php endforeach; ?>
    

But because Assetic changes the paths to your assets, this will break any background images (or other paths) that uses relative paths, unless you use the cssrewrite filter.

Nota

Notice that in the original example that included JavaScript files, you referred to the files using a path like @AcmeFooBundle/Resources/public/file.js, but that in this example, you referred to the CSS files using their actual, publicly-accessible path: bundles/acme_foo/css. You can use either, except that there is a known issue that causes the cssrewrite filter to fail when using the @AcmeFooBundle syntax for CSS Stylesheets.

Fixing CSS Paths with the cssrewrite Filter

Since Assetic generates new URLs for your assets, any relative paths inside your CSS files will break. To fix this, make sure to use the cssrewrite filter with your stylesheets tag. This parses your CSS files and corrects the paths internally to reflect the new location.

You can see an example in the previous section.

Prudencia

When using the cssrewrite filter, don’t refer to your CSS files using the @AcmeFooBundle. See the note in the above section for details.

Combinando activos

One feature of Assetic is that it will combine many files into one. This helps to reduce the number of HTTP requests, which is great for front end performance. It also allows you to maintain the files more easily by splitting them into manageable parts. This can help with re-usability as you can easily split project-specific files from those which can be used in other applications, but still serve them as a single file:

  • Twig
    {% javascripts
        '@AcmeFooBundle/Resources/public/js/*'
        '@AcmeBarBundle/Resources/public/js/form.js'
        '@AcmeBarBundle/Resources/public/js/calendar.js' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array(
            '@AcmeFooBundle/Resources/public/js/*',
            '@AcmeBarBundle/Resources/public/js/form.js',
            '@AcmeBarBundle/Resources/public/js/calendar.js',
        )
    ) as $url): ?>
        <script src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    

In the dev environment, each file is still served individually, so that you can debug problems more easily. However, in the prod environment (or more specifically, when the debug flag is false), this will be rendered as a single script tag, which contains the contents of all of the JavaScript files.

Truco

Si eres nuevo en Assetic y tratas de usar la aplicación en el entorno prod (usando el controlador app.php), lo más probable es que se rompan todos tus CSS y JS. ¡No te preocupes! Esto es a propósito. For details on using Assetic in the prod environment, see Volcando archivos de activos.

Y la combinación de archivos no sólo se aplica a tus archivos. También puedes usar Assetic para combinar activos de terceros, como jQuery, con tu propio JavaScript en un solo archivo:

  • Twig
    {% javascripts
        '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js'
        '@AcmeFooBundle/Resources/public/js/*' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array(
            '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js',
            '@AcmeFooBundle/Resources/public/js/*',
        )
    ) as $url): ?>
        <script src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    

Filtros

Una vez que son gestionados por Assetic, puedes aplicar filtros a tus activos antes de servirlos. Esto incluye filtros que comprimen la salida de tus activos a un archivo más pequeño (y mejor optimización en la interfaz de usuario). Otros filtros incluyen la compilación de archivos JavaScript desde archivos CoffeeScript y SASS a CSS. De hecho, Assetic tiene una larga lista de filtros disponibles.

Muchos de los filtros no hacen el trabajo directamente, sino que utilizan otras bibliotecas para hacerlo, a menudo, esta es la razón por la que tienes que instalar esos programas también. Esto significa que a menudo tendrás que instalar una biblioteca de terceros para usar un filtro. La gran ventaja de utilizar Assetic para invocar estas bibliotecas (en lugar de utilizarlas directamente) es que en lugar de tener que ejecutarlo manualmente cuando has trabajado en los archivos, Assetic se hará cargo de esto por ti y elimina por completo este paso de tu proceso de desarrollo y despliegue.

Para usar un filtro debes especificarlo en la configuración de Assetic. Añadir un filtro aquí no quiere decir que se esté utilizando —sólo significa que está disponible para usarlo (vamos a utilizar el filtro en seguida).

Por ejemplo, para utilizar el JavaScript YUI Compressor debes añadir la siguiente configuración:

  • YAML
    # app/config/config.yml
    assetic:
        filters:
            yui_js:
                jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
    
  • XML
    <!-- app/config/config.xml -->
    <assetic:config>
        <assetic:filter
            name="yui_js"
            jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" />
    </assetic:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('assetic', array(
        'filters' => array(
            'yui_js' => array(
                'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar',
            ),
        ),
    ));
    

Ahora, para realmente usar el filtro en un grupo de archivos JavaScript, añade esto a tu plantilla:

  • Twig
    {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array('@AcmeFooBundle/Resources/public/js/*'),
        array('yui_js')
    ) as $url): ?>
        <script src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    

Puedes encontrar una guía más detallada sobre la configuración y uso de filtros Assetic así como detalles del modo de depuración Assetic en Cómo minimizar JavaScript y hojas de estilo con YUI Compressor.

Controlando la URL utilizada

Si quieres, puedes controlar las URL que produce Assetic. Esto se hace desde la plantilla y es relativo a la raíz del documento público:

  • Twig
    {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array('@AcmeFooBundle/Resources/public/js/*'),
        array(),
        array('output' => 'js/compiled/main.js')
    ) as $url): ?>
        <script src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    

Nota

Symfony también contiene un método para caché rota, donde la URL final generada por Assetic en el entorno prod contiene un parámetro de consulta que puedes incrementar por medio de configuración en cada despliegue. Para más información, consulta la opción de configuración assets_version.

Volcando archivos de activos

En el entorno dev, Assetic genera rutas para los archivos CSS y JavaScript que no existen físicamente en el ordenador. Pero, sin embargo, los reproduce porque un controlador interno de Symfony abre y sirve los archivos volcando el contenido (después de ejecutar todos los filtros).

Este tipo de servicio dinámico de procesar los activos es muy bueno porque significa que puedes ver inmediatamente el nuevo estado de los archivos de activos que cambies. Por otro lado es malo, porque puede ser bastante lento. Si estás utilizando una gran cantidad de filtros, puede ser realmente frustrante.

Afortunadamente, Assetic proporciona una manera de volcar tus activos a los archivos reales, en lugar de generarlos dinámicamente.

Volcando archivos de activos en el entorno prod

En entorno prod, cada uno de tus archivos JS y CSS está representado por una sola etiqueta. En otras palabras, en lugar de incluir cada archivo JavaScript en tu código fuente, probablemente acabes viendo algo como esto:

<script src="/app_dev.php/js/abcd123.js"></script>

Por otra parte, ese archivo no existe en realidad, ni es reproducido dinámicamente por Symfony (debido a que los archivos de activos se encuentran en el entorno dev). Esto es a propósito —permitir que Symfony genere estos archivos de forma dinámica en un entorno de producción es demasiado lento.

En cambio, cada vez que utilices tu aplicación en el entorno prod (y por lo tanto, cada vez que la despliegues), debes ejecutar la siguiente tarea:

$ php app/console assetic:dump --env=prod --no-debug

Esto va a generar y escribir físicamente todos los archivos que necesitas (por ejemplo /js/abcd123.js). Si actualizas cualquiera de tus activos, tendrás que ejecutarlo de nuevo para generar el archivo.

Volcando archivos de activos en el entorno dev

Por omisión, Symfony procesa dinámicamente cada ruta de activo generada en el entorno dev. Esto no tiene ninguna desventaja (puedes ver tus cambios inmediatamente), salvo que los activos se pueden cargar notablemente lento. Si sientes que tus activos se cargan demasiado lento, sigue esta guía.

En primer lugar, dile a Symfony que deje de intentar procesar estos archivos de forma dinámica. Haz el siguiente cambio en tu archivo config_dev.yml:

  • YAML
    # app/config/config_dev.yml
    assetic:
        use_controller: false
    
  • XML
    <!-- app/config/config_dev.xml -->
    <assetic:config use-controller="false" />
    
  • PHP
    // app/config/config_dev.php
    $container->loadFromExtension('assetic', array(
        'use_controller' => false,
    ));
    

A continuación, debido a que Symfony ya no genera estos activos para ti, tendrás que deshacerte de ellos manualmente. Para ello, ejecuta lo siguiente:

$ php app/console assetic:dump

Esto escribe físicamente todos los archivos de activos que necesita tu entorno dev. La gran desventaja es que necesitas hacerlo manualmente cada vez que actualizas tus activos. Afortunadamente, pasando la opción --watch, la orden regenerará automáticamente tus activos a medida que cambien:

$ php app/console assetic:dump --watch

Debido a que ejecutas esta orden en el entorno dev puede generar un montón de archivos, por lo general es una buena idea apuntar tus archivos de activos a un directorio aislado (por ejemplo /js/compiled), para mantener las cosas organizadas:

  • Twig
    {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %}
        <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    
  • PHP
    <?php foreach ($view['assetic']->javascripts(
        array('@AcmeFooBundle/Resources/public/js/*'),
        array(),
        array('output' => 'js/compiled/main.js')
    ) as $url): ?>
        <script src="<?php echo $view->escape($url) ?>"></script>
    <?php endforeach; ?>
    
Bifúrcame en GitHub