Artículo Contenido destacado

Compartir configuraciones de Compass entre diferentes proyectos

Publicado 2013-08-12 11:10:29 en Sass, CSS

En el proyecto que estoy actualmente, tenemos un esquema en donde diferentes proyectos conviven en un único repositorio. Cada proyecto posee sus propios CSS pero también pueden tener estilos, variables o mixins compartidos entre todos los proyectos.

A su vez, por cuestiones de trabajo en equipo, cada proyecto necesita tener su propio config.rb para que la persona que este trabajando sólo compile los estilos relativos a un proyecto dado y no los de todos. Sin embargo, para mantener una coherencia en los estilos generados (o por si se hace un refactor, por ejemplo, si se mueve la ubicación de las imágenes, fuentes, etc), todos los config.rb deben tener una misma configuración. Si fueran dos o tres proyectos, el problema no sería serio, pero hablando de más de 20 proyectos, tener que cambiar todas las configuraciones es una pérdida de tiempo.

Por lo tanto, se ideó tener una configuración central y que todos los config.rb se alimenten de allí. Lo que se hizo fue:

1 - Crear un archivo .rb (por ejemplo, shared.rb) con lo que se necesita compartir, por ejemplo, alguna dependencia y la configuración de los estilos, URLs, opciones de Sass, comentarios y decimales:

  #Dependencias compartidas por todos los proyectos
  require 'dependencia'

  #Configuración compartida por todos los config.rb
  module Shared

      #Estilo de los CSS
      @output_style = :expanded

      def self.output_style
          return @output_style
      end

      #Utilizar URLs relativas
      @relative_assets = true

      def self.relative_assets
          return @relative_assets
      end

      #Opciones de Sass
      @sass_options = { :cache => true, :debug_info => false }

      def self.sass_options
          return @sass_options
      end

      #Sin comentarios
      @line_comments = false

      def self.line_comments
          return @line_comments
      end

      #Precisión de decimales
      @precision = 5

      def self.precision
          return @precision
      end

  end

2 - Luego, en el config.rb de un proyecto particular se importa a shared.rb y se utilizan las configuraciones declaradas:

  #Importa la configuración común
  require 'ruta_hacia/shared.rb'

  #Estilo de los CSS
  output_style      = Shared.output_style

  #URLs relativas
  relative_assets   = Shared.relative_assets

  #Opciones de Sass
  sass_options      = Shared.sass_options

  #Comentarios
  line_comments     = Shared.line_comments

  #Presición de decimales
  Sass::Script::Number.precision = Shared.precision

  #Luego se pueden seguir poniendo configuraciones propias del proyecto

De esta manera, si en un futuro, se necesita que todos los estilos generados en todos los proyectos posean, por ejemplo, un estilo diferente de output, con sólo cambiar shared.rb (y volviendo a compilar) se tendrá el nuevo resultado.



Artículo Contenido destacado

Capturando errores JavaScript que suceden en el cliente

Publicado 2013-06-25 10:00:00 en JavaScript

Y U No Catch JS Errors?!

Un post de Paul Irish enlistando servicios/apps que permiten capturar y analizar errores JavaScript me tentó para escribir mi experiencia sobre este tema ya que cuenta con varios puntos complejos...

En primer lugar, el principio de todo, ¿de qué trata el asunto de capturar errores JavaScript? ¿Porqué hacerlo?

Nuestra aplicación no es perfecta, y por más que nuestros tests abarquen todas las situaciones posibles, la computadora del cliente es un mundo aparte y para ellos no hay nada más frustrante que utilizar una aplicación/página y que la misma deje de funcionar sin motivo aparente. Por eso nos interesa saber:

  • qué tipo de errores, cuantas veces e, idealmente, ante que acciones suceden;
  • en que navegador y versión;
  • en que URL, archivo y línea.

Esa es la información que necesitamos para poder reproducir los errores en nuestro ambiente de desarrollo. Dependiendo de cada caso, pueden existir otros datos también de interés, por ejemplo, alguno relativo a nuestra aplicación como la versión/release en que sucede.

¿Cómo capturar los errores y de que forma?

A grandes rasgos, la arquitectura estará compuesta por dos partes...

  • La parte de front-end se dedicará a capturar información sobre errores que suceden en el cliente e inmediatamente enviarlos al servidor.
  • En back-end necesitamos una aplicación que se encargue de recibir los errores y los recopile en algún lugar para posterior análisis. Esta aplicación puede filtrar los errores, agruparlos o guardarlos en un log con algún formato común.

Vamos a hablar un poco sobre la parte de front-end, en nuestro código JavaScript, tenemos dos formas de capturar los errores que sucedan:

Usando try...catch

No tiene mucha ciencia, consiste en utilizar el bloque try...catch en los lugares que nosotros pensamos que puede suceder algún error:

try{
     //Función que devuelve error
     funcionErronea();

} catch(error){
     //Sucedió un error!
}

La información que llega en el catch(error) es un objeto Error que posee las siguientes propiedades:

  • message: Mensaje de error que el intérprete JavaScript del navegador devolvió;
  • fileName: Archivo en donde sucedió el error;
  • lineNumber: Línea en que sucedió el error, en el archivo indicado.

Hasta acá todo bien, tenemos la información que necesitamos, sin embargo el problema de los try...catch es que si se utilizan mal o en lugares no indicandos pueden terminar perjudicando la performance de nuestro código.

Usando window.onerror

Por otro lado, window.onerror es un evento que ejecuta automáticamente una función dada cuando sucede un error JavaScript en cualquier parte de nuestro código. La diferencia es justamente esa, es un evento automático, y al no ser intrusivo no perjudica la performance:

window.onerror = function errorHandler(message, fileName, lineNumber) {
     //Sucedió un error!
}

En el ejemplo, errorHandler sobre-escribe la función predeterminada. Dicha función recibe tres parámetros, que son los mismos que las propiedades del objeto Error que vimos en el try...catch.

Un ejemplo real con window.error

Veamos un ejemplo real de implementación:

window.onerror = function(message, fileName, lineNumber) {

    //Url del servicio en donde se enviará la información del error
    var serviceUrl = "/log-service/";

    //Se rellena un array con la información
    var data = [];
    data.push("message=" + encodeURIComponent(message));
    data.push("file=" + encodeURIComponent(fileName));
    data.push("line=" + encodeURIComponent(lineNumber));

    //Se forma un string
    data = data.join("&");
    data = data.substring(1);

    //Se crea una nueva imagen poniendo como src la URL del servicio 
    //y pasando como parámetros la información del error
    ( new Image() ).src = serviceUrl + "?" + data;

};

El script no tiene mucha ciencia:

  • Se recopila la información, se encodea y luego se crea una nueva imagen poniendo como src todos los datos (URL del servicio y datos).
  • Del lado del backend, cada vez que se hace una petición a la URL del servicio y se pasan los datos, los manipula de alguna forma para luego analizarlos (como comenté más arriba). El servicio debe tener algún tipo de validador, además que debe obtener el HTTP referer para saber en que página sucedió el error.

Los problemas de window.error

Todo parece sencillo, pero en mi experiencia window.error posee dos problemas, los cuales dependen de ciertas situaciones:

  • si el error sucede en un archivo que no cumple la regla de Same origin policy (mismo dominio, URL, puerto, etc) los parámetros serán nulos (en verdad el mensaje será "Script Error" on line 0). Es decir, si nosotros servimos los JS desde, por ejemplo, un CDN, no tendremos la información que necesitamos;
  • si nuestros JS están minimizados y concatenados, el único dato interesante será la del mensaje, ya que fileName será el archivo minimizado (por ser un paquete de archivos concatenados) y la línea la número 1 (por estar todo minimizado), por lo tanto perderemos información de valor.

En mi caso concreto, la aplicación que necesitaba analizar errores se encontraba en la dos situaciones, servía los scripts minimizados, concatenados y desde un CDN. Para manejar la situación se ideó un esquema:

  • Modificamos la aplicación para tener dos configuraciones: una que permita habilitar/deshabilitar cuando los scripts se sirven desde el CDN y otra que inyecte o no el script que envia errores al servicio. Además, en conjunto con los scripts minimizados, se subían una versión sin minimizar.
  • Cada vez que se hacía un nuevo release de la aplicación y se subía a producción, durante un lapso de tiempo (digamos, algunas horas) se desactivaba el CDN, la aplicación apuntaba a los scripts sin minimizar y se inyectaba en los páginas el script que envía los errores. Durante ese lapso de tiempo la aplicación de backend logueaba los errores y los guardaba (la aplicación era una versión modificada de ErrorBoard)

El esquema depende de la situación de cada uno, en nuestra experiencia, con algunas horas bastaba para tener un gran número de errores para analizar (la aplicación recibía varios millones de visitas mensuales, por lo cual era suficiente). Claro que durante esas horas la aplicación cargaba un poco más lenta (ya que los JS no eran minimizados y se servían desde servidores internos). Otra manera también podría ser hacer una especie de test A/B y que sólo algunos usuarios tengan habilitada la configuración y otros no.

Algo interesante de ErrorBoard es que al ser open source permite modificarlo a gusto, y posee características interesantes: Agrupación de errores, posibilidad de marcar los que estén solucionados, filtro por navegador, versión y una interfaz sencilla.

También comentar que existe una solución para poder obtener la información de un error si sucede en un script por fuera de nuestro dominio, la misma radica en habilitar CORS en los servidores y en la aplicación, en teoría esta manera permite que Firefox y Chrome comiencen a leer la información del error, sin embargo la última vez que lo probé no tuve suerte y la información seguía sin aparecer (más información: Catching Cross-Domain JS Errors).

En conclusión, por más que nuestra aplicación sea chica, mediana o grande, capturar errores JavaScript es una buena práctica, como decía arriba, para el cliente no hay nada mas frustrante que una página deje de funcionar sin motivo alguno y quien dice que esa persona termine siendo un cliente frecuente.