Dev (Back & Front)ARTIGO

Arquitetando CSS

Existe um
grande problema com o CSS, que é sua falta de “estrutura”. Quando você tem um
projeto de 20 páginas, tudo bem – jogue tudo em um arquivo. Quando você tem
centenas de paginas, em dezenas de agências de design diferentes, as coisas
começam a ficar bastante bagunçadas.

E eu
odeio bagunça.

Um
cliente meu está nessa situação. Por três anos, eles tiverem seis designers
diferentes, cada um com estilos visuais diferentes – mas, o que é mais
importante para mim – cada um com diferentes requisitos e implementações CSS.

O impacto
disso tem me incomodado desde que eu comecei, mas eu sempre tive mais problemas
“arquiteturais” para lidar. No entanto, eu decidi que era o suficiente quando
percebi que nossa carga total paga de CSS era de 1 MB, e nosso maior arquivo CSS
possuía mais de 5 mil linhas.

Arquitetando o CSS

Existem
algumas coisas que quero do meu CSS:

  • Ultimamente, sou em maior parte um
    desenvolvedor C#, então
    eu gosto da coisa orientada a objeto – eu entendo, é organizado e
    reutilizável.
  • A parte de “cascata” do CSS
    é maravilhosa, mas é uma espada de dois gumes. Quando algum designer
    insere uma stylesheet com uma tag como “.main h2”, ela irá inevitavelmente
    afetar porções do site que não estavam envolvidas.
  • Os problemas com o CSS somente
    se manifestam quando você de fato os visualiza – em outras palavras, eles
    são erros de execução. E quando
    você tem centenas de páginas no site, geralmente é o usuário que reporta
    um problema de estilo, não nossos “testadores”.

Perto, mas não perto o suficiente

Infelizmente,
não posso afirmar que sou capaz de consertar nenhum desses problemas. Admiro a
ambição de projetos como o.less (www.dotlesscss.org/), mas há algo neles que não
parece certo para mim. Acho que é porque não existe suporte para mainstream
(suporte para mainstream é muito importante para mim, pois tenho um stream
constante de novos desenvolvedores entrando – padrões são meus amigos).

Eu também
li muitas opiniões de pessoas sobre CSS – como precisamos nos afastar da
semântica, como precisamos nos aproximar da semântica etc… mas, mais uma vez,
preciso de algo que tenha amplo suporte da comunidade na internet, e que meus
novos desenvolvedores serão capazes de acompanhar.

Um compromisso brilhante

Portanto,
no ínterim, enquanto eu procuro pela solução perfeita, encontrei uma solução
muito boa. Ela consiste em um único user control. Você pode chamá-lo de WonderControl,
mas eu simplesmente o chamo de CssContainer.ascx:

ASCX

<asp:PlaceHolder runat="server" ID="PHStyle" />

Code-behind

public partial class CssContainer : BaseUserControl
{

[
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(CssContainer)),
TemplateInstance(TemplateInstance.Single),
]
public ITemplate Style { get; set; }

/// <summary>
/// Init
/// </summary>
/// <param name="e"></param>
protected override void OnInit(EventArgs e)
{
// Instantiate any CSS in our template
this.EnableViewState = false;
base.OnInit(e);
if (Style != null) Style.InstantiateIn(PHStyle);
}

/// <summary>
/// Pre render
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

// If, for some reason, the control is called twice, we ignore
if (!HasRegisteredOnThisRequest())
{
this.Visible = true;
this.RegisterStyles();
}
else
{
this.Visible = false;
}
}

/// <summary>
/// This control is designed to render styles only once per request - duplicate styles are a waste of resources
/// </summary>
/// <returns></returns>
private bool HasRegisteredOnThisRequest()
{

// Has this control been rendered before?
Control parent = this.Parent;
while (true)
{
if (parent == null) break;
if (parent.GetType().IsSubclassOf(typeof(BaseUserControl)) || parent.GetType().IsSubclassOf(typeof(PageBase)))
{
break;
}
parent = parent.Parent;
}
if (parent == null)
{
// I'll throw an exception here, but really you could just render it anyway - it would just result in multiple renderings per page
throw new Exception("CssContainer may only be used on classes inheriting from BaseUserControl or PageBase");
}

// The count is kept in the PageBase so that we may retain it per-request, but share across all instances of this CssContainer control
if (!this.Page.GetType().IsSubclassOf(typeof(PageBase)))
{
// I'll throw an exception here, but really you could just render it anyway - it would just result in multiple renderings per page
throw new Exception("CssContainer may only be used on pages inheriting from PageBase");
}

// This control is probably specified multiple times on any one page. Here, we record that its' been done already or not
if (pageBase.CssContainerCount.ContainsKey(parent.GetType())) return true;
pageBase.CssContainerCount[parent.GetType()] = 1;
return false;
}

/// <summary>
/// Register the script contents, if any, to the page
/// </summary>
private void RegisterStyles()
{
// Pull template string OUT of the template we rendered in initially
var sb = new StringBuilder();
var tw = new StringWriter(sb);
var writer = new HtmlTextWriter(tw);
this.PHStyle.RenderControl(writer);
writer.Close();
tw.Close();
var css = sb.ToString();
if (string.IsNullOrWhiteSpace(css)) return;

// User controls can be in multiple folder paths, so we must normalize any URLs
css = css.Replace("~/", WebHelpers.GetFullUrlForPage(""));

// Compress CSS etc
css = Yahoo.Yui.Compressor.CssCompressor.Compress(css);

// Render to page
this.PHStyle.Visible = false;
this.Controls.Add(new LiteralControl("<style type="text/css">" + css + "</style>"));
}
}

Okay, um
pouco espalhafatoso, mas realmente, muito simples. Eu tentei comentar o código
intuitivamente para que você consiga segui-lo, mas a ideia geral é:

  1. O usuário do controle
    especifica um CSS regular em um template (chamado Style)
  2. Renderizamos esse conteúdo
    dentro do placeholder PHStyle, da mesma maneira que você faria com um template
    normal
  3. Então tiramos o CSS de volta do
    template, e renderizamos para a página entre regular
    <style/> tags

Então o uso final é algo assim:

<wc:Css runat="server">
<Style>
.column-con {
overflow: hidden;
}
.column-con .column {
background-color: #fff;
}
</Style>
</wc:Css>

A
intenção é que você solte esse CSS diretamente dentro da página /usercontrol que
o está usando.

Por que isso é ruim…

Sim,
entendo que os problemas com isso são:

  1. É inline, o que significa
    que as requisições de página são maiores
  2. Não pode ser cached no
    browser
  3. Somente pode ser
    reutilizado se estiver sob a página ou usercontrol que está o utilizando

Mas a
resposta para todos eles é simples: se a dor de quebrar essas regras, para esse
conjunto de estilos, for muito grande, então apenas coloque-as de volta em sua style
sheet regular externa do CSS.

Esse
controle não tem o intuito de substituir
suas sheets externas, mas sim de complementá-las.

Então por que isso é bom…

Existe
somente uma razão: é organizado. Onde temos um user control obscuro ou uma
página cujos estilos se afastam significantemente dos nossos estilos usuais,
essa é uma maneira brilhante de isolar os estilos sem desordenar a style sheet principal.

E como
ela é renderizada em código (em oposição a usar diretamente <style/>
tags), sei que tenho bastante controle sobre o que desejarei fazer com isso no
futuro. Por exemplo, eu poderia:

  1. Falsificar uma referência a
    um “arquivo” externo usando um handler, permitindo assim que o pedido
    resultante fique como cache no browser
  2. Minimizar o CSS (na verdade,
    já estou fazendo isso no exemplo acima)
  3. Executar substituições como
    nomes variáveis – como eles fazem em  www.dotlesscss.org/
  4. Posso facilmente encontrar
    os estilos que se aplicam ao meu HTML, apenas visualizando o topo do
    UserControl – não preciso mais executar ‘Search Entire Project’ para
    descobrir a fonte do CSS (atualização: Resharper 6 tem uma boa implementação do ‘Go To
    Definition’ para CSS)
  5. etc etc etc

Mas até
que eu saiba como lidar com minhas sheets, essa solução me permite contê-las e
gerenciá-las muito melhor do que os arquivos CSS do “lado cliente” e as
tags incorporadas permitem.

Mas e as style sheets externas reutilizáveis?

Anteriormente,
onde isolamos o CSS, também criamos arquivos CSS separados, e então os
colocamos dentro do pedido ‘on-demand’. Tecnicamente, isso funcionou tão
bem quanto minha solução acima – na verdade, foi melhor quando o arquivo
resultante estivesse em cache no browser do cliente e portanto melhoraria a
velocidade da visita de modo geral.

Mas isso
pode resultar em dezenas (ou muito mais) de arquivos CSS se acumulando em seu
projeto, confundindo seus desenvolvedores, atrasando o tempo de carregamento do
seu projeto etc. Além disso, como disse acima, sei que depois, quando eu
estiver menos ocupado, poderei criar referências “externas” usando handlers ASHX.

Mas estilos incorporados são terríveis!!

Em
teoria, sim – mas, na prática, definitivamente não. Uma das piores coisas que um
desenvolvedor pode fazer é ficar preso na “melhor prática”. Desde que você
compreenda as razões por trás das regras, você será capaz de quebrá-las quando
achar que as razões não se aplicam ao seu projeto atual.

Além
disso, faça um ‘view source’ em qualquer uma dessas home pages – você verá
muitos estilos incorporados:

Viu?
Estou em boa companhia.

Conclusão

Este não
é o trabalho técnico mais brilhante que já fiz, mas tenho que dizer que foi um
dos mais excitantes. Eu adoro ser capaz de adicionar estilos
despreocupadamente, exatamente como gosto de pouco cuidado com a estrutura –
conteúdo no conhecimento que o futuro Ben será capaz de organizar tudo
facilmente um dia.

Eu
fortemente encorajo outros desenvolvedores a considerar esse tipo de estrutura
para qualquer projeto médio-grande em que eles estiverem trabalhando.

?

Texto original disponível em http://blog.blackballsoftware.com/2011/08/26/architecting-cascading-style-sheets-css/

é fundador do site BlackBall Software (www.blackballsoftware.com), onde trabalha principalmente em aplicativos web Microsoft .Net. Atualmente, seu projeto favorito é o www.stringsof.me.

Ver perfil