Vários produtos e plataformas, e como manter o CSS em tudo isso?

Melhorando a qualidade do CSS no Facebook

Com milhares de engenheiros trabalhando em uma variedade de produtos dentro do Facebook, enfrentamos alguns desafios únicos quando se trata de qualidade de código.

Não lidamos apenas com uma enorme base de código, como também, elas crescem e mudam rapidamente — novos recursos sendo adicionados, os já existentes sendo melhorados e coisas sendo reorganizadas.

E falando de CSS, isso significa ter milhares de arquivos que estão em contínua mudança.

Nós já tentamos garantir uma qualidade do código CSS em vários níveis — revisão de código, guias de estilo e refatoração — erros não-intencionais também podem ser eliminados com análise estática antes de serem comitados.

Até rencentemente, usávamos um CSS linter criado por nós para pegar os erros básicos e garantir a consistência do estilo de código. Essa essa solução foi suficiente no decorrer dos anos, mas queríamos algo mais robusto.

Expressão regular não é suficiente

Aqui um exemplo de uma antiga expressão regular:

preg_match_all(
// Essa expressão procura pelo padrão “[attr]” sem nenhum seletor
// antes dele
‘/\/\*.*?\*\/|\{[^}]*\}|\s(\[[^\]]+\])/s’,
$data,
$matches,
PREG_SET_ORDER | PREG_OFFSET_CAPTURE
);
foreach ($matches as $match) {
if (isset($match[1])) {
raiseError(...);
}
...

Manter um punhado de regras doidas como essa não é nada legal. Era difícil de alterar e entender. E também tinha problemas de performance. Para cada regra, tínhamos que analisar todo o código novamente, imagine várias regras como essa!… not cool dude, not cool!

AST ou Árvore de Sintaxe Abstrata

Que tipo de problemas?

Aqui um pedaço dos escuros e desconhecidos pedaços da nossa grande base de código:

{
display: none:
background-color: #8B1D3;
padding: 10px,10px,0,0;
opacity: 1.0f;
}

Consegue identificar os problemas? Escrita errada no nome da propriedade, valor hexadecimal incorreto, separadores errados — os navegadores simplesmente ignoram todos esses, o que é de fato, bem longe do que o desenvolvedor quer.

Não demorei muito para ver que o PostCSS seria uma ótima ferramenta para esse trabalho — um analisador baseado nos padrões do CSS com ótima arquitetura modular. Nós então escolhemos o Stylelint como nosso CSS linter. Se utiliza do PostCSS, é flexível e bem suportado!

Parecido com linters e parsers para JavaScript, como o Esprima e ESLint, PostCSS e Stylelint te dá acesso a AST (Árvore de Síntaxe Abstrata). AST torna mais fácil o acesso aos nós em qualquer condição: Estamos usando o nome de classe correto? Estamos incluindo abstrações corretas? Estamos removendo ou não suportando algumas extensões? Existe problemas de internacionalização?

No exemplo a seguir, estamos iterando através de todas as declarações e procurando por “text-transform: uppercase”:

root.walkDecls(node => {
if (node.prop === ‘text-transform’ && node.value === ‘uppercase’){
report({
...
});
}
});

Nós também podemos analisar e desconstruir funções de baixo nível linear-gradient. No próximo exemplo, um pouco mais complexo, estamos encontrando todas as declrações de linear-gradient e verificando seu primeiro argumento:

// desabilitando declarações como 
// “linear-gradient(top, blue, green)”
// com o primeiro argumento incorreto
root.walkDecls(node => {
const parsedValue = styleParser(node.value);
parsedValue.walk(valueNode => {
if (valueNode.type === ‘function’ && valueNode.value === ‘linear-gradient’) {
const firstValueInGradient = styleParser
.stringify(valueNode.nodes[0]);
if (disallowedFirstValuesInGradient
.indexOf(firstValueInGradient) > -1) {
report({
...
});
}
}
});
});

O código é relativamente fácil de entender e atualizar. E toda essa verificação irá funcionar, não importa o formato do CSS, também não importa aonde essas regras foram declaradas (no início do arquivo, dentro de blocos como media queries ou keyframes).

Sabe aqueles problemas que citei? É uma ótima sensação ver o PostCSS e o Stylelint vasculhando nossos arquivos, procurando por erros de escrita, separadores, valores dentro de funções, ou até mesmo ! important (que, na verdade, deveria ser !important), seletores complexos, propriedades não-padrão e vários outros!

Regras customizadas

E, nossas regras customizadas são disponibizadas através de alguns plugins que criamos, Stylelint tem um ótimo mecanismo para isso! Algumas das regras que temos incluem:

  • slow-css-properties: para avisar sobre propriedades sensíveis há performance como opacity ou box-shadow (quase que, para saber que eles estão lá)
  • filters-with-svg-files: para avisar sobre filtros que não são suportados pelo navegador Edge, quando referênciamos arquivos SVG
  • use-variables: para avisar sobre valores que poderiam ser substituídos por variáveis já existentes (nós usamos uma abstração chamada **var(…)**)
  • common-properties-whitelist: para achar potenciais propriedades não existentes (mais para erro de escrita)
  • mobile-flexbox: para avisar sobre declarações Flex que não são suportadas em navegadores mobile antigos
  • text-transform-uppercase: para avisar sobre “text-transform: uppercase” (que não é internacionalmente amigável)

Nós também contribuímos algumas regras e melhorias ao Stylelint e estamos planejando criar um ciclo de contribuições, sejam elas diretamente ao Styelint ou em um repositório próprio.

Substituição automática

Infelizmente, Styelint não tem, por padrão, uma ferramente de auto formatação (e, sem dúvida, não deveria ser de responsabilidade do linter fazer isso), então, nós tivemos que reimplementar algumas das regras existentes do Stylelint e adicionar o suporte para auto correção através da nossa infra-estrutura. Enquanto isso, estamos discutindo potenciais mudanças no Styelint para torna-lo mais simples nessas tarefas no futuro.

Testando tudo, e todos!

Nós usamos o confiável Jest framework (e trabalhamos com a equipe do Stylelint para adicionar suporte para ele) e agora nós temos uma maneira de fácil entendimento e escrita, como:

test.ok(‘div { background-image: linear-gradient( 0deg, blue, green 40%, red ); }’, ‘linear gradient with valid syntax’);test.notOk(‘a { background: linear-gradient(top, blue, green); }’,message, ‘linear-gradient with invalid syntax’);

O que o futuro nos aguarda

O linter já está integrado ao Phabricator, nossa ferramenta de colaboração. Com avisos e dicas sendo mostradas em cada revisão.

Isso torna o processo de linting uma etapa importante no processo de commit e colaboração.

Outra grande vantagem em ter um analisador de CSS decente é a possibilidade de extrair estatísticas precisas sobre o nosso código. Quais são as propriedades/valores menos usadas? Talvez elas devem ser removidas ou substituídas (criando um arquivo menor). Quais sãos os valores mais utilizados para color/font-size e z-index? Talvez eles devam ser separados em componentes reutilizáveis ou variáveis. Quais são os seletores mais complexos? Talvez tenha um problema de performance.

Tudo isso pode ajudar na manutenção e qualidade a longo prazo.

React e estilos inline

Ainda estamos explorando CSS em JavaScript no Facebook, ainda estamos nos primeiros experimentos, e nós ainda temos uma grande quantidade de código CSS para manter. Por isso, o linter funciona do modo que precisamos.

Unidos crescemos

Obrigado a todos da equipe de JS Infra e Webspeed, e todos os outros que ajudaram nessa reescrita; David Clark e Richar Hallows do Stylelint, que foram extremamente comprometidos com as idéias; assim como toda a comunidade que tornou possível uma ferramenta fantástica como o PostCSS.

Essa é a essência do trabalho colaborativo.

Créditos

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store