CSS scoped

E ae pessoas e demais!

Normalmente quando começamos um projeo, sempre pensamos no mais simples, sem complicar muito. Nos projetos JavaScript não é diferente. Gosto de tudo bem organizado em diretórios e arquivos.

Mas as vezes não percebemos que alguns arquivos podem se tornar gigantes em um time com muitas pessoas trabalhando e inserindo classes e mais classes para alterar um mesmo componente mas em uma tela diferente.

Recentemente estou trabalhando em um projeto que utiliza o Framework Aurelia. É um ótimo framework, o aurelia-cli facilita muitas coisas.

No início não vimos necessidade de usar um pré processador de CSS como SASS ou LESS. Então foi na unha mesmo que criamos as primeiras classes. Mas a cada novo componente criado no Aurelia, mais classes iam sendo inseridas em um único arquivo e quando nos demos conta, o arquivo tinha mais de 1000 linhas!

Então veio a dúvida. Refatorar tudo? Iniciar um novo processo? O projeto ainda estava longe de acabar e aquele arquivo ainda ia aumentar bastante. Puxei pra mim o desafio de buscar uma outra abordagem para futuras classes CSS a serem criadas.

Então comecei pesquisando na documentação do Aurelia mesmo.

Tentativa 1:

Usando <require> do aurelia. Dessa forma ele insere o conteúdo do arquivo em tags <style> no <head> do HTML.

<template>
  <require from="component-style.css"></require>
</template>

Parecia ser uma ótima solução, mas uma vez que a tag <style> é inserida, ela não é removida quando navegamos para outra página onde o template do container nem está presente. Isso acabava sobrecrevendo estilos de outro componente. Ou seja, não serviu.

 

Tentativa 2:

Na documentação do <require> dizia que além do atributo from,  existia o atributo as e que poderia receber o valor scoped. Então poderia funcionar como uma tag <style scoped>.

<template>
  <require from="component-style.css" as="scoped"></require>
</template>

Agora parecia que ia funcionar. Pra explicar como funciona o scoped, cito aqui a documentação da Mozilla: "Se este atributo está presente, então o estilo aplica-se apenas ao seu elemento pai. Se está ausente, o estilo aplica-se ao documento inteiro." Leia aqui

Mas não funcionou. O comportamento continuava igual a tentativa 1

 

Solução:.

Então pensei em criar uma marcação customizada e pré-processar essa marcação. Eis a marcação:

<istaile src="component-style.css"></istaile>

Na task que pré-processa o HTML eu incluí no pipe um replace que lê o src, resolve o caminho até esse arquivo, lê o conteúdo e depois no lugar da marcação customizada coloca a tag <style> com o conteúdo do arquivo. Ficando assim:

<style scoped>
.component-title {
  color: blue;
}
</style>

Detalhe que quando o replace é executado, inserimos o atributo scoped para não haver problemas em que o estilo de outro compomente sobreponha esse.

Calma, não pense que chegamos até aqui sem mostrar como ficou a task no gulp. Antes de mostrar, saliento que faz parte da task de build do Aurelia mas nada impede de usar em qualquer outro projeto com gulp.

...
gulp.src(project.markupProcessor.source)
  .pipe(replace(/<xtyle[^>]+src="?([^"\s]+)"?\s*\/{0,1}>(?:<\/xtyle>){0,1}/g, function (s, filename) {
    let fileToReplace = this.file.dirname + path.sep + path.normalize(filename);
    let style = fs.readFileSync(fileToReplace, 'utf8');
    return '<style scoped>\n' + style + '\n</style>';
}))
...

Uma das etapas de build do Aurelia é o processMarkup então bastou incluir esse pipe que a nossa marcação customizada funcionou. 

Agora cada componente do Aurélia pode ter um CSS separado e inserido sempre que o compoente estiver presente na página.

Então é isso pessoal! Opiniões, críticas ou dúvidas são bem vindas!