sexta-feira, 17 de maio de 2013

Pré-Processadores CSS: um passo em frente, dois atrás?

Oi,

Para os aficionados do Rails, o advento dos pré-processadores não é mais uma novidade. Tema aflorado ao de leve e terrívelmente programado em versões pré-3 do Rails através dos infâmes templates RJS, ganharam uma importância renovada assim que o Coffeescript e o SCSS foram adoptados como escolhas pré-definidas do esqueleto de uma aplicação nova a partir do Rails 3 (alguns de vocês devem ter notado as linhas adicionadas ao vosso Gemfile inicial) e o HAML foi adoptado como o formato preferido da comunidade para escrever os seus templates. Gosto de avaliar imparcialmente todas estas adopções da comunidade em termos do benefício para a minha produtividade, adequação às novas técnicas, "efeitos secundários" (por exemplo, se é compatível com HTML5, se o meu IDE de eleição reconhece a linguagem e existe suporte "ir-para"), entre outros factores mais contextuais.

Preferindo me referir a conteúdo (HTML) e comportamento (Javascript) em uma outra ocasião, gostava de me concentrar na parte da apresentação (CSS), aquela em que reconheço mais benefícios do pré-processamento para a vida de quem mantem o código (ou a especificação, se quisermos ser puristas e lembrar que CSS não é realmente código).

Para quem é do tempo que se escrevia CSS "à pata", sabe do que eu falo quando eu digo que esta foi provavelmente a tarefa menos programática, mais inconsequente e mais predisposta a erro humano que já colocaram nas mãos de um profissional na área da informática. Isso deve-se a várias razões, a maior delas, que será importante para a minha conclusão, sendo a de que apresentação não é tarefa para engenheiro, cientista ou simples programador informático.

O CSS foi desenvolvido como uma especificação de apresentação para generalizar regras de renderização para os elementos HTML pertencentes a um documento segundo uma estrutura ramificada. A primeira especificação foi completada em 1996, o que em anos Internet é equivalente ao tempo do Antigo Egipto. À medida que as necessidades do mercado se impuseram, as aplicações se tornaram cada vez mais polidas e ambiciosas visualmente e a adopção da especificação dos navegadores se fragmentou, começando a surgir os primeiros problemas.

A minha primeira experiência com CSS coincidiu com o meu primeiro emprego e com a minha introdução à ferramenta Ruby on Rails, por volta de 2008. 4/5 desenvolvedores mantinham os ficheiros CSS de duas aplicações, cada uma delas com significativa envergadura, e logo, diferentes requisitos visuais. Todas as regras CSS estavam espalhadas por 3 ficheiros, pois na altura não existiam pré-processadores e os desenvolvedores mais experimentados diziam que 3 ficheiros era o máximo que os navegadores podiam carregar concorrentemente sem perdas de performance para o usuário (ou o seu navegador). Existiam algumas pré-directivas para decidir em que ficheiro entrava determinada regra, tentávamos usar comentários para estruturar as áreas a que as regras se referiam, tentávamos optimizar os espaços brancos, mas no geral, à medida que novas funcionalidades e características eram introduzidas separadamente, o esforço redundava em fracasso. Outra curiosidade era que puros desenvolvedores, gente que coda, é que tratava de escrever as especificações de elementos visuais para os quais não haviam regras matemáticas definidas, somente um esboço enviado pelo designer de serviço. A situação proporcionava a tarefa diária de falar directamente com o designer para se informar do código hexadecimal de certa cor, ou qual a distância entre elementos, ou informar que determinada imagem ainda não tinha sido entregue, algo que achava extremamente ineficiente e até tristemente cómico. CSS tornou-se o meu pesadelo de desenvolvimento, com o qual lidei nos meus projectos seguintes.

Alguns dos problemas que experienciei nos tempos pré-pré-processamento podem ser assim mais sucintamente e claramente descritos:

- CSS não é linguagem de programação, mas é geralmente visto pelos decisores como tal, e quem geralmente mantém a especificação acaba por ser gente que programa.
- Requisitos visuais é algo que profissionais da informática não compreendem.
- Não havendo ferramenta de minificação, todo o esforço de compactar artificialmente a especificação resulta em menos legibilidade posterior.
- Designers, decisores, accionistas em geral, não testam em modo de desenvolvimento, somente em produção ou numa fase intermédia. Todas as listas de correcções resultantes só aumentam a confusão na priorização de tarefas do desenvolvedor.
- Designer sabe e escreve HTML e CSS. Que eles não participem no processo de desenvolvimento é de uma redundância extrema, já que quem desenha delega esta função para uma entidade que não "fala a mesma língua". Aqui posso também culpar a forma como as ferramentas (em que incluo o Rails) são arquitecturadas, não proporcionando uma integração fácil para um não-programador.
- Várias dificuldades na fragmentação do suporte de funcionalidades para diversas versões de diversos navegadores (estou a falar de um tempo em que nem o IE8 nem o Chrome existiam, e os prefixos ainda não tinham sido adoptados pelos desenvolvedores; desde aí a confusão só aumentou).

Os pré-processadores colmatam todas estas falhas? É difícil dizer... Penso que todos eles identificam falhas de design do CSS, pontos de melhoria, dificuldades de manutenção, e conseguem resultados bastante impressionantes, gerando outros problemas no caminho. Em alguns casos, surgem da frustração de programadores em terem que trabalhar com uma especificação que desconsideram. Noutros casos, são optimizações na forma de colaboração.

Sintaxe

Quem já tem alguma experiência tanto com SASS como com LESS consegue fazer a correspondência entre o formato prévio ao processamento e o CSS que é gerado:

/* SASS */ 

.botoes
  width: 230px
  a
    display: inline

/* CSS */
.botoes {
  width: 230px;
}
.botoes a {
  display: inline;
}

Adoptam-se aqui conceitos de linguagens de programação orientadas a objectos na forma como as regras são agrupadas e estruturadas mais relacionalmente, ganha-se clareza na leitura das regras e não existe um corte abrupto entre sintaxes (quem conhece CSS compreende facilmente tanto SASS, como LESS, como SCSS). O reverso da moeda aqui é que, só se concentrando na relação entre elementos, perde-se noção do CSS que é gerado, resultando em falhas de optimização nas regras de CSS que é no fundo o que é entregue aos navegadores. Um pequeno exemplo:


/* SASS */
.botoes
  width: 230px
  a
    padding: 10px 
    display: inline
  span
    padding: 10px 
    display: block

/* CSS */
.botoes {
  width: 230px;
}

.botoes a {
  padding: 10px; 
  display: inline;
}

.botoes a {
  padding: 10px;
  display: block;
}

em vez de:

/* CSS */ 
.botoes a, botoes b {
  padding: 10px;
} 

.botoes a {
  display: inline;}

.botoes a {
  display: block;}

A utilização do novo sintaxe implica uma maior atenção para identificar possíveis problemas de optimização. Mas há aqui um claro benefício na redução do número de linhas de código que se têm que manter em relação ao CSS correspondente.

Variáveis

Tanto SASS como LESS permitem a utilização de variáveis para identificar propriedades que de outro modo teriam que ser repetidas em vários pontos da especificação, tais como fontes ou cores. As aplicações web utilizam em geral o mesmo esquema de cores, com algumas variantes na tonalidade. Por exemplo, a cor dos links é a mesma de determinado ícone ou plano de fundo, entre outros. Em CSS, o código hexadecimal correspondente teria que ser copiado em várias regras. Caso algum dia a decisão fosse revertida em favor de outra cor, esse código teria que ser substituído em todos esses sítios, e provavelmente de algumas regras que não fariam parte do escopo dessa mudança. As variáveis não resolvem todas as falhas, mas facilitam a manutenção de tais propriedades.

/* SASS */

$blackbetty: #000

.botoes
  background-color: $blackbetty
.imagens
  background-color: $blackbetty 



/* CSS */
.botoes {
  background-color: #000;
}

.imagens {
  background-color: #000;
}

Mudar de cor, neste exemplo, implica reescrever uma linha de código, ao invés de duas.


Propriedades exclusivas dos navegadores

Ás sintaxes acrescentam-se outras ferramentas de pré-processamento destinadas a resolver outro tipo de problemas. Um deles é o pesadelo dos prefixos vendor. Muitas vezes os navegadores implementam propriedades específicas aos mesmos, que mais tarde são adoptadas por outros navegadores e posteriormente estandardizadas (um pouco virado do avesso, este processo). O processo de estandardização é geralmente longo, e os fornecedores não querem esperar para mostrar as funcionalidades aos clientes, quando só a especificação oficial é que falta. Na indústria dos navegadores foram então adoptados os prefixos vendor, que se destinam a identificar implementações de propriedades em um navegador (ou motor de renderização) que ou são específicas a esse navegador ou são uma primeira versão de uma propriedade que ainda se encontra em fase de avaliação pelo W3C (exemplos no CSS3). Tomemos como exemplo a propriedade border-radius. Todos os navegadores suportam a propriedade, providenciam uma especificação proprietária, mas nem todos suportam a especificação oficial. A forma mais segura que quem escreve CSS pode utilizar a propriedade bem-sucedidamente redunda no seguinte exemplo:

.arredondado {
  -moz-border-radius: 10px;
  -khtml-border-radius: 10px;
  -webkit-border-radius: 10px;
  border-radius: 10px;
} 

Esta regra tem as vantagens de ser compreendida pelos motores de renderização mais utilizados no mercado, ser eficiente (os motores de renderização começam pela última regra, logo privilegiam a forma estandardizada e ignoram a proprietária caso a identifiquem), mas é altamente desvantajoso do ponto de vista de manutenção por representar 4 linhas de especificação para manter. E esta propriedade é até relativamente simples de manter no formato crú em comparação com outras em que a ordem dos argumentos varia de prefixo para prefixo. Existem duas (que eu conheça) bibliotecas/ferramentas em ruby que nos facilitam a vida, com sintaxe semelhante para estes casos, o bourbon e o compass. Ambas estabelecem um conjunto de funções SASS mapeadas a cada propriedade ainda não estandardizada que no acto do pré-processamento são substituídas pelas versões proprietárias nas regras onde são declaradas. A regra acima descrita seria então escrita do seguinte modo:


/* Bourbon / Compass */
@include border-radius(10px);

Uma linha de código para manter.

Sprites

Uma das optimizações mais importantes quando se constrói uma aplicação web é a redução do número de pedidos HTTP que um documento dispara. Uma típica aplicação moderna geralmente utiliza muitas imagens na sua experiência de utilização, o que tem influência no tempo de carregamento total de uma página web. Uma típica forma de optimizar esse processo no CSS é colocar todas as imagens utilizadas em uma aplicação no mesmo ficheiro e fazer uso da propriedade background-position para renderizar cada componente incluída.

a {
   background-image: ("button.png");
}

a:hover {
  background-image: ("button-pressed.png");
}

a:active {
  background-image: ("button-active.png");
}

O exemplo acima descrito (em CSS puro) representa 3 diferentes estados de um elemento e apresenta uma diferente imagem de fundo para cada um, cada uma delas num ficheiro diferente. O exemplo acima descrito implica 3 pedidos HTTP diferentes ao servidor web pelas imagens. Todos estes pedidos, ou a latência por eles provocado, aumentará o tempo de carregamento de uma página e o atraso na renderização dos elementos visuais para o usuário, o que em casos extremos pode dar-lhe tempo para preparar um café enquanto espera ou simplesmente fechar a página e voltar ao Facebook. Comparemos agora com o exemplo seguinte, também em CSS puro.

a {
   background: url("images/buttons.png") no-repeat;
   background-position: 0 0;
}

a:hover {
  background: url("images/buttons.png") no-repeat;
  background-position: 0 -20px; 
}

a:active {
  background: url("images/buttons.png") no-repeat;
  background-position: 0 -40px,
}

Neste caso só existe um ficheiro (chamado na gíria sprite) contendo as imagens dos diversos estados do botão a ser baixado do servidor web. Um pedido HTTP,  uma clara melhoria de performance no carregamento da página. Já a manutenção dificultou. Quem estiver responsável pela introdução das imagens na página tem que saber agora de antemão a posição da imagem desejada no ficheiro e escrevê-la explicítamente. Não só isso, se dois desenvolvedores diferentes necessitarem de introduzir uma nova imagem correspondente a um novo estado no mesmo ficheiro, e contando que trabalham em dois terminais diferentes e utilizam um sistema de versionamento do código, não podem reposicionar as imagens que já existem, têm que contar que o colega não insira a imagem deles exactamente na mesma posição em que eles inserem, e no final, nada disso vai ajudar, porque muito provavelmente o sistema de versionamento não consegue fundir ficheiros em formato binário, só textual, o que vai implicar posteriormente e conjuntamente reposicionar as novas imagens, recalcular as posições e reescrever as regras. Dizem que este processo de desenvolvimento é responsável pela maior parte dos casos de violência doméstica a nível mundial.
Existem duas formas de tentar contornar este problema no universo Rails. A primeira é recorrer a funções SASS. Desta forma, poder-se-ia descrever programáticamente a posição de uma imagem no sprite. Da minha experiência, acaba-se por deixar a nova programatização definar a visualização, privilegiar a generalização, e acaba por se perder controlo na optimização do CSS resultante de novo. A outra forma é usar a ferramenta Compass. Esta tem uma propriedade muito interessante: deixa-nos ter as imagens em ficheiros diferentes, permitindo-nos declará-las separadamente como no primeiro exemplo, e o pré-processamento vai fundir essas imagens num sprite, calcular o seu posicionamento e escrever o CSS correspondente.
 

/* SASS/Compass */
@import "buttons/*.png";
@include all-buttons-sprites;

a
  @include buttons-sprite(button);
  &:hover
    @include buttons-sprite(button-pressed);
  &:active
    @include buttons-sprite(button-active);

/* CSS */
.buttons-sprite, a, a:hover, a:active {
   background: url("/images/buttons-2323r3r32s21swed.png") no-repeat;
} 

a {
   background-position: 0 0;
}

a:hover {
  background-position: 0 -20px; 
}

a:active {
  background-position: 0 -40px,
}

Voltemos ao nosso cenário dos dois desenvolvedores suicidas. Cada um deles vai introduzir um ficheiro diferente no directório correspondente e declará-lo na regra SASS respectiva. Ao submeter as mudanças para o controlo de versionamento, este vai identificar dois ficheiros binários diferentes em vez de um binário alterado separadamente, não vomitando conflitos. Com o tempo ganho os dois desenvolvedores decidem beber uma cerveja juntos.
Para mim só existem vantagens no processo em si. As desvantagens advém do suporte da ferramenta para determinados casos de uso: ao que sei ainda não existe suporte para geração de sprites em formato SVG (é no entanto algo que está planeado) e a geração do sprite final está optimizada para 24-bits, algo que pode causar problemas em navegadores mais antigos como o famigerado IE6 ou mesmo para transporte por redes 3G para dispositivos móveis. Não é no entanto um estado definitivo.

Concatenação

Outra das grandes desvantagens dos tempos anciãos era ter que escrever todas as regras no mínimo de ficheiros, não se podendo separar contextualmente. O SASS permite importar regras de ficheiros diferentes, através do @import. Deste modo podemos, por exemplo, colocar todas as regras para botões num ficheiro SASS diferente, e importá-lo no ficheiro principal declarado no documento.

/* SASS */
/* buttons.sass */
a
  color: green

/* application.sass */

@import "buttons"

.content 
  width: 200px

/* CSS */
/* application.css */
.content {
  widht: 200px; 
}

a {
  color: green;
}

A vantagem neste caso é podermos separar as regras contextualmente e termos desta forma o código fonte mais manutenível. Mas esta abordagem tem outra desvantagem: limita-nos o debugging em modo de desenvolvimento. Se utilizarmos uma ferramenta como o Firebug ou a consola do Chrome para inspeccionarmos as regras CSS acima descritas, a regra para o elemento 'a' vai estar identificada como pertencendo ao ficheiro application.css na linha 5, o que não corresponde ao nosso código fonte SASS. Num projecto com diversos ficheiros .sass pode dificultar-nos a identificação do ficheiro onde determinada regra inspeccionada está descrita.
Esta desvantagem pode ser mitigada recorrendo ao sprockets, uma biblioteca Rails que pretende centralizar as estratégias de pré-processamento, compilação e minificação tanto para apresentação (suporta SASS, LESS e SCSS) como para comportamento (Coffeescript). Em modo de desenvolvimento, por exemplo, os ficheiros .css entregues ao navegador são distribuídos separadamente quando declarados num ficheiro css que actua como manifest.

/* SASS */
/* buttons.sass */
a
  color: green

/* application.css */
//= require buttons

.content 
  width: 200px


/* CSS modo dev */

/* application.css */
.content {
  widht: 200px; 
}

/* buttons.css */
a {
  color: green;
}

Mesmo que o número da linha não seja o correspondente à sua versão SASS (isto é um problema de debugging do SASS em geral), pelo menos conseguiremos identificar o ficheiro onde a regra se encontra.

Pré-Processamento e Minificação

O ambiente de desenvolvimento Rails possui várias ferramentas que auxiliam a minificação de ficheiros .css há já bastante tempo. A biblioteca Sprockets simplesmente centralizou o processo. O deenvolvedor somente tem que declarar qual a biblioteca a utilizar (closure, uglifier, yui) e o sprockets insere o passo da minificação no processo de geração do ficheiro CSS. O que nos leva ao passo final da pilha. Depois de ter facilitado todo o processo de desenvolvimento com sintaxe, separação de ficheiros, etc..., o Sprockets centraliza em um passo (um comando) todo o processo de concatenação de todas as regras SASS em um único ficheiro .css minificado, entre outras tarefas relacionadas com outras tecnologias. Na verdade, pode ser gerado mais que um ficheiro .css final, sendo que para isso é necessário então mais que um ficheiro manifest. Uma ferramenta deveras flexível, que também dá acesso a funções auxiliares para gerar os caminhos absolutos (URI) das imagens utilizadas, cujo timestamp pode ter sido actualizado.


Conclusão

"Poh, tanta vantagem, pra quê esse título tão pessimista"? É, o título é um pouco exagerado. Na verdade, todas essas ferramentas (e outras que provavelmente me esqueci de referir), cada uma à sua maneira, conseguiu resolver cada um dos problemas que fui tendo desde que comecei a trabalhar em aplicações web. Mas resolveu exclusivamente problemas de desenvolvedor. Os outros foram esquecidos.

O HTML e as suas extensões, como o XHTML, foram publicitados na sua incepção como ferramentas para designer. O objectivo aqui seria provavelmente introduzir o designer em tarefas de templatização do lado do cliente e libertar os desenvolvedores para tarefas funcionais do lado do servidor. O CSS foi visto como a extensão apresentacional do HTML, logo foi sempre visto como uma ferramenta de designer, tanto que estes são formados nas duas especificações. No entanto, as várias ferramentas desenvolvidas para acelerar o processo de desenvolvimento web seguiram a direcção de ligar todas as componentes, sem estabelecer uma ponte invisível entre os dois contextos (o que se vê/ o que faz). As velhinhas JSP (Java Server Pages) foi das primeiras ferramentas populares que embebia código Java nos templates HTML. O PHP vive de ser embebido em templates HTML (e é muito popular entre designers). E a biblioteca por defeito usada pelo Rails na geração de HTML é o ERB, que permite embeber código Ruby dentro de templates HTML (o HAML aplica uma metodologia semelhante). Todos eles contribuíram para, em projectos de larga escala, afastar cada vez mais o designer do HTML ou forçá-lo a aprender a programar. Os projectos Rails estão pejados de gente que faz a ponte entre os dois mundos, gente que programa e desenha, e que os torna em "donos do projecto" e relativamente ineficientes nas duas tarefas que têm a cumprir (50% do tempo, 50% da produtividade). Já no caso em que eles se afastam, caso queiramos reverter o processo e trazê-los de novo para O HTML e o CSS, tenho as minhas reservas quanto aos pré-processadores. O problema em geral das ferramentas que nomeei é que são mais paradigmáticas para gente de informática. Para o designer trabalhar com SASS, tem que aprender não só a sintaxe, mas a integrar a tecnologia. Ora, ele já sabe CSS, e este é ubíquo nos navegadores. Qual é o valor profissional que o SASS tem para ele? Vale a pena a longo prazo ou é para um projecto de 8 meses? O próprio Compass é uma ferramenta mais usada na comunidade Rails e muito dirigida pela linha de comandos (o que é um vade retro para designers), ainda não existindo uma integração convincente para ambientes de desenvolvimento front end.

Reconheço as vantagens para mim. Mas para a indústria em geral? Só o tempo o dirá.

Sem comentários:

Enviar um comentário