1

Pitch: Slynx: por que joguei o CSS fora e fiz meu próprio sistema de estilos

Esse é o segundo post sobre a Slynx — uma linguagem que estou construindo pra criar aplicações desktop que compilam pra múltiplos targets.

Hoje eu estou mais interessado em falar sobre o sistema de estilos dela. Os motivos pelos quais eu dei um pé na bunda do CSS e escrevi um sistema de estilos eu mesmo.

Por que não CSS?

Quando mexi com webgpu, skia, e amigos, a ideia de estilos estáticos como o CSS simplesmente não existe, visto que um conteúdo definido durante runtime precisa ser passado pra GPU. Simplesmente não existe

.estilo {
    padding: 12px;
    border-radius: 12px;
    border-color: #ff0000;
    background-color: #ffff00;
}

O conteúdo precisa estar na GPU de alguma forma e reescrever os shaders só porque a cor mudou é inviável.
Então a melhor ideia que tive desde o dia que nasci: 'E se for uma função pura?' e essa é a ideia por trás.
Uma das coisas que levou a essa escolha é que a linguagem NÃO é idealizada pra ser um projeto único da e pra web, mas compilável pra qualquer canto, com a web sendo o primeiro target. Então esse modelo fica até mais fácil pra se utilizar caso eu queira compilar pra swift-ui por exemplo.
Outros problemas do CSS que me deixaram com pelo menos 4 pés atrás são:

  1. Sem reuso de código, classes e IDs são péssimos pra escalar
  2. 0 Type safety.
  3. Estilos mais complexos são todos cheios de gambiarra
  4. Não compila pra outros targets. Flutter, iOS, desktop — cada um tem seu próprio modelo.

O que é um stylesheet

Um stylesheet é uma função que gera estilos. Ela é parametrizada, tipada, e passa pelo pipeline completo do compilador — parser, HIR, type checker, IR, codegen.

stylesheet Bg(color: int) uses Fg(color | 0x00ff00) {
    let c = (color & 0xff) << 8; //pega somente o azul da cor e utiliza ele como verde de 'c'
    styles {
        backgroundColor: color,
        foregroundColor: c,
    }
}

stylesheet Fg(color: int) {
    styles {
        foregroundColor: color
    }
}

E você usa assim num componente:

component Pedrinho {
    Div {
        style: Bg(12),
        Text { text: "Pedrinho" }
    }
}

Simples. Sem classes, sem seletores, sem cascade acidental.

uses — herança de estilos

Um stylesheet pode herdar de outros via uses. A precedência é da esquerda pra direita, com o próprio stylesheet tendo prioridade máxima:

stylesheet C() uses A(), B() {
    styles {
        foregroundColor: 0x00ff00
    }
}
// precedência: A < B < C
// se A e B definem foregroundColor, C sobrescreve

Isso facilita criar bibliotecas de estilos. Você define SomeStyle(arg1, arg2) uma vez, e quem usa a sua lib faz:

stylesheet MyStyle() uses SomeStyle(red, 4) {}

A sobrescrita é intencional e explícita — sem surpresa de especificidade como no CSS.


Como funciona por baixo

Cada stylesheet gera 3 constructs na IR:

  1. Uma struct com um campo por propriedade resolvida
  2. Uma função construtora que avalia as expressões e monta a struct
  3. Uma função apply que recebe (componente, struct) e emite @sapply por propriedade

O @sapply é uma UI Operation da IR — ela carrega um código numérico (da STYLES_TABLE) que diz pro backend o que aplicar. O backend decide como fazer isso. A IR só declara a intenção.

@sapply BACKGROUND_COLOR, #t0, 0xFF0000FF;
@sapply FOREGROUND_COLOR, #t0, 0x00FF00FF;

No componente, um @initcall garante que o estilo é aplicado antes do componente aparecer na UI:

component %C() {
    #t0 = specialized Div;
    @initcall ApplyBgStyle, #t0, %BgStyle{12}
}

Estado atual — honestidade acima de tudo

O sistema tá funcional, mas cru. Por enquanto só existem 2 propriedades implementadas:

  • backgroundColor
  • foregroundColor

Ambas representadas como i32 por ora — cor é um placeholder até a stdlib de UI existir.

O que já existe no parser mas ainda não lowers:

  • Estados como hover(0.3s) { ... } e clicked { ... }
  • Transições com duração e curva de easing

Também só é possível aplicar estilos em Div e Text por enquanto — componentes customizados ainda não suportam style:.

O roadmap inclui mais propriedades (padding, margin, opacity, border, shadow), estados funcionais, animações, e estilos em componentes customizados. Mas isso vem com o tempo.


Por que não Tailwind

Acredito que não tenha nem como, uma ideia que penso é em utilizar o operador '|' para composição de estilos, então algo como
Style1() | Style2() | Style3(), mas isso há de ser averiguado.


O repositório tá em github.com/Slynx-Language/slynx. Feedbacks são bem-vindos — especialmente sobre o design do sistema de estilos, que ainda tem muita coisa em aberto.

Carregando publicação patrocinada...