domingo, 13 de octubre de 2013

Carga condicional de recursos con yepnope en ASP.NET MVC

En la empresa estamos desarrollando una aplicación web en ASP.NET MVC para la que queremos dar cierto soporte a dispositivos móviles. Digo lo de “cierto” porque nuestra intención es que el mismo front-end sirva para todos los dispositivos por igual (ya sean desktop o mobile). Sin embargo, hay ocasiones donde en función del tipo de dispositivo nos gustaría cargar uno u otro recurso (ya sea un .js o un .css).

La solución por la que hemos optado ha sido la carga condicional de recursos a través de yepnope. En este blog ya se escribió sobre yepnope y la verdad es que resulta muy sencillo de utilizar.

Por otro lado, todos usamos Modernizr para detectar características del navegador. La idea es detectar si el navegador es táctil o no y en función de esto cargar una u otra hoja de estilos.

El propio Modernizr suministra un cargador de recursos basado en yepnope a través del método Moderniz.load. Por defecto, el paquete de Nuget de Moderniz que se incluye en las plantillas de los proyectos de MVC no incluye esta característica. Un simple vistazo a la cabecera del fichero .js de Modernizr nos da la clave del asunto:

*
* Modernizr has an optional (not included) conditional resource loader
* called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
* To get a build that includes Modernizr.load(), as well as choosing
* which tests to include, go to www.modernizr.com/download/
*

Por otro lado, sí que hay un paquete de Nuget que incluye el cargador de recursos, el paquete se llama Modernizr.full. Entiendo que este paquete sería lo mismo que descargar Modernizr desde la web e incluir todas opciones disponibles (el método Modernizr.load está marcado como predeterminado en la sección “Extra”).

En cualquier caso, nuestra decisión ha sido utilizar yepnope directamente sin pasar por Modernizr. Me gustaría pensar que puedo actualizar tanto Modernizr como yeopnope sin tener que preocuparme de si uno afecta a otro o viceversa.

En esta situación, la carga condicional del fichero .css sería como sigue:

<script>
    yepnope({
        test: Modernizr.touch,
        yep: '@Url.Content("~/Content/mobile.css")',
        nope: '@Url.Content("~/Content/desktop.css")'
    });
</script>


Esto ya funciona y no hay ningún problema. Sin embargo no estamos utilizando bundles y para mí son casi obligados simplemente por el hecho de no tener que preocuparme de si está o no cacheado en cliente el recurso ya que cualquier cambio en el servidor provocará que el cliente refresque el recurso automáticamente.


Siendo así, mismo ejemplo pero ahora con bundles:



<script>
    yepnope({
        test: Modernizr.touch,
        yep: '@Styles.Url("~/Content/mobile")',
        nope: '@Styles.Url("~/Content/desktop")'
    });
</script>


Simplemente hemos cambiado Url.Content por Styles.Url, pero obtenemos el siguiente error de javascript:


image


El problema está en que yepnope trata a nuestro fichero .css descargado como si fuera un fichero .js. Esto lo hace porque el recurso no acaba con la extension .css y entonces no sabe que lo que estamos cargando es una hoja de estilos en vez de un fichero de script. Fíjate que el código javascript que estamos cargando finalmente en cliente es el siguiente:


<script>
    yepnope({
        test: Modernizr.touch,
        yep: '/Content/mobile?v=lZic6x4eHbbXGYLQzArfcY-IMpq4H2ZVRQXn0B1-3Bc1',
        nope: '/Content/desktop?v=BvoYWkJHqnyVrPiZcSn963sWrvuVmBvTQ4y7_w9WabE1'
    });
</script>


¿Cómo decirle entonces a yepnope que trate el recurso como css en vez de como js? Pues a través del prefijo !css.Según la documentación de yepnope es justo para casos como en el que estamos:


The css! prefix is for those people who need to load css files without a `.css` extension. Since yepnope 1.5 - yepnope can detect the presence of query parameters without the help of the css prefix, so this is usually for cases when the css files have a php ending or something similar.


Este prefijo no viene de serie con yepnope y hay que descargarlo desde aquí https://github.com/SlexAxton/yepnope.js/blob/master/prefixes/yepnope.css-prefix.js


Una vez descargado, lo utilizaremos y ahora sí funcionará yepnope junto a nuestros bundles:


<script>
    yepnope({
        test: Modernizr.touch,
        yep: 'css!@Styles.Url("~/Content/mobile")',
        nope: 'css!@Styles.Url("~/Content/desktop")'
    });
</script>


Cierto es que se podría haber evitado la carga condicional de recursos en este caso concreto utilizando las las clases de Modernizr .touch y .no-touch que agrega automáticamente al elemento html. Es decir, lo siguiente es válido y no requiere nada de código:


<style>
    .touch body {
        color: red;
    }
    .no-touch body {
        color: yellow;
    }
</style>


Sin embargo sigo pensando que depende de en que escenarios prefiero la carga condicional de recursos.


Un saludo!

No hay comentarios:

Publicar un comentario