Pressione enter para ver os resultados ou esc para cancelar.

Como reaproveitar estilos usando CSS Modules

Repositório com exemplo completo (branch continuation)

O CSS Modules representa uma verdadeira quebra de paradigma na escrita de estilos para a web. A possibilidade de finalmente ter noção de escopo e segurança quanto aos side-effects tira um grande peso das costas dos desenvolvedores. Porém, ainda é preciso entender que essas mudanças não vêm sozinhas: nossa visão sobre o que os estilos representam numa aplicação também deve ser adaptada.

O que, então, precisamos saber para aplicar com segurança o CSS Modules em um projeto? Bom, além de saber como ele funciona, trago algumas dicas que ajudam a responder algumas perguntas frequentes sobre manutenibilidade e escalabilidade.

Reaproveitamento

Esse é um assunto bastante delicado e com pouca referência. E existem alguns motivos para isso. Considerando que a principal aplicação do CSS Modules é para sistemas componentizados, a forma como se pensam os componentes pode influenciar nesse aspecto. Afinal, uma vez que consideremos os estilos como parte integrante de um componente, não é preciso reescrever os mesmos estilos em nenhum outro lugar, basta que o componente seja reaproveitado inteiramente, incluindo o CSS.

Seguindo a partir do ponto onde paramos no artigo anterior, vamos criar o arquivo src/link.js:

import styles from './link.css'

export const link = `
  <a class='${styles.link}' href='#'>
    Try out now! >>
  </a>
`

É um componente bastante simples, chega até a ser bobo, mas vai servir para o que pretendo explicar. Repare que agora a variável do componente está sendo exportada, e não impressa imediatamente. Isso vai dar mais controle de quando e onde ela vai ser usada no arquivo em que for importada.

Vamos então usar esse componente e imprimir o link nele criado nos arquivo src/list.js e src/otherList.js:

import { link } from './link.js'
import styles from './list.css'

let list = `
  <ul class='${styles.myList}'>
    <li>I love CSS ${link}</li>
    <li>You love CSS ${link}</li>
    <li>We love CSS ${link}</li>
  </ul>
`
document.write(list)
import { link } from './link.js'
import styles from './otherList.css'

let otherList = `
  <ul class='${styles.myList}'>
    <li>I still love CSS ${link}</li>
    <li>You still love CSS ${link}</li>
    <li>We still love CSS ${link}</li>
  </ul>
`

document.write(otherList)

Alguns estilos no arquivo src/link.css para efeitos de demonstração:

.link {
  font-size: 0.8rem;
  color: white;
  background-color: orange;
  padding: 0.5rem;
  border-radius: 0 0.5rem;
}

Agora é só rodar o comando webpack, abrir o index.html no navegador e notar que o link que acabamos de criar está sendo renderizado em todos os lugares onde foi colocado e com os mesmos estilos para todos os casos. Mas o que isso quer dizer em termos de reaproveitamento de estilos?

Ao invés de escrever o link várias vezes em cada arquivo (list e otherList), foi criado um novo arquivo, que exporta o componente de link de forma que possa ser usado livremente por outros componentes. Esse componente já tem seus estilos carregados com ele, portanto onde ele foi impresso os estilos foram junto. Nós nem começamos a falar de como reaproveitar estilos e eles já foram reaproveitados simplesmente por uma decisão de arquitetura.

É por isso que é vantajoso considerar os estilos como parte integrante da aplicação, e não somente um adereço decorativo.

Impressionante, não é mesmo?

Ah, então tá tudo resolvido 😀

Nem tudo. Uma grande e importante parte é resolvida como descrito acima, mas falando em CSS ainda podemos ter que nos repetir muito durante a escrita. Se essa for a única estratégia que usarmos para reaproveitar estilos, vamos nos pegar reescrevendo o mesmo font-size ou background-color uma irritante porção de vezes. E também não dá pra sair criando componente pra cada propriedade de estilo que a gente repetir. É disso que vamos tratar a seguir.

Módulos de styleguide

No nosso exemplo, já estamos utilizando duas ferramentas que sugerem fortemente a micromodularização: NPM e o próprio CSS Modules. O que aconteceria se uníssemos as vantagens que as duas nos trazem?

E se eu te dissesse que isso é possível?

Acontece que o recurso “compose” do CSS Modules funciona não somente com arquivos de caminhos relativos, mas também consegue carregar estilos de módulos instalados via NPM. Vamos criar um módulo que vai servir de styleguide para os componentes que já criamos:

mkdir local_modules/my-custom-styleguide

Todo módulo NPM precisa de um package.json, e o nosso pode ficar assim:

{
  "name": "my-custom-styleguide",
  "version": "1.0.0",
  "description": "My custom styleguide package.",
  "main": "main.css",
  "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},
  "author": ""
}

Repare na propriedade main, que recebeu o arquivo main.css como valor. Ela está dizendo que aquele é o arquivo principal desse módulo, então quando algo for importado dele, será a partir desse arquivo que o NPM vai procurar.
Nesse arquivo vão alguns estilos para serem usados no exemplo:

.roundBox {
  padding: 1rem;
  border-radius: 1rem;
}

Legal, mas como usa?

Agora é possível compor essa classe usando o nome do módulo como caminho. Vejamos isso no nosso arquivo src/list.css:

.myList {
  composes: roundBox from 'my-custom-styleguide';
  color: white;
  background-color: blue;
}

Isso ainda não vai funcionar. Perceba que não instalamos o módulo que recém criamos. Existem algumas formas de se fazer isso: é possível baixar direto do github, criar um módulo público ou privado no repositório do NPM, ou mesmo instalar a partir de uma pasta local, que é o que vamos fazer agora.

Vai ser preciso adicionar uma seção “dependencies” no package.json com a declaração do módulo:

{
  "name": "css-modules-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "my-custom-styleguide": "file:local_modules/my-custom-styleguide"
  },
  "devDependencies": {
    "babel-core": "^6.10.4",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "webpack": "^1.13.1"
  }
}

Rodando os comandos npm install e webpack os resultados já devem aparecer.

Nada mau, não é mesmo?

Mas aí você me pergunta: não era só criar uma pasta dentro do meu próprio projeto e consumir a partir de lá? Era, mas desse jeito você já sabe fazer, porque é lógico que já leu o primeiro artigo da série.

Mas pra que essa complexidade toda então?

Tecnicamente ainda não existe muito consenso quanto à melhor prática de se reaproveitar estilos nesse contexto. Pode ser que um único arquivo agnóstico da aplicação seja o suficiente para consumir as propriedades que precisam ser compartilhadas por todo o sistema, mas também pode ser que a complexidade aumente e que tenhamos que pensar em maneiras mais robustas de cumprir esse objetivo.

Não cabe a mim evangelizar como fazer ou não, quero apenas ressaltar algumas vantagens de se criar estilos reaproveitáveis como módulos de NPM, principalmente em projetos grandes:

  • Reaproveitamento entre projetos: vai ser possível usar o mesmo módulo em projetos diferentes com maior facilidade. Isso seria bastante útil no caso de um cliente com vários projetos que seguem um styleguide semelhante.
  • Dependências declaradas: o módulo vai ficar explicitamente declarado como dependência do projeto, o que fará com que seja fácil identificar de onde vêm os estilos reaproveitados.
  • Construção de bibliotecas: para o open-source isso é fantástico! Uma vez que tenhamos um styleguide tão genérico que não conseguiremos mais viver sem, ele já estará pronto para virar uma contribuição à comunidade 😀

Não acaba por aqui

Essa série veio pra ficar! Deixe-nos seu feedback, conte-nos como você está usando o CSS Modules no seu trabalho e quais dúvidas vêm surgindo. Esse é um universo que tem bastante coisa para escrever, contribuir e explorar, e sua contribuição pode ajudar no próximo artigo (: