# Stack de frontend

A configuração padrão com Tailwind CSS, DaisyUI e Alpine.js, e React, Vue ou qualquer outro conjunto de ferramentas.

O Nimbly gera HTML no servidor e deixa o lado do navegador com você. Um projeto já vem com Tailwind CSS, DaisyUI e Alpine.js configurados, e o admin é construído com eles. Suas próprias rotas podem usá-los, acrescentar a eles ou usar um conjunto de ferramentas totalmente diferente.

## A stack padrão

| Ferramenta | Usada para |
|---|---|
| Tailwind CSS 4 | Classes utilitárias nos templates |
| DaisyUI 5 | Classes de componentes e temas |
| Alpine.js | Interação, direto no markup |

O `./nimbly build` procura classes nos templates de `core/` e `ext/` e gera `ext/static/app.css` e `ext/static/app.js`. O `./nimbly watch` os mantém atualizados enquanto você edita.

- **Tema.** Cores e temas do DaisyUI ficam em `ext/tailwind.theme.js`. O admin e a barra do Nimbly usam os mesmos tokens, então as ferramentas de edição seguem a marca.
- **Estilos.** `ext/theme.css` contém o CSS próprio do projeto, importado depois dos estilos do framework para prevalecer.
- **Classes em variáveis.** O Tailwind encontra classes lendo o código-fonte. Coloque entre aspas uma lista de classes guardada numa variável, `[#set menu-text="text-stone-500"#]`, para que as classes apareçam como texto no template.

### Markup do servidor com Alpine

O Alpine acrescenta comportamento a um markup que o servidor já gerou. Mantenha o conteúdo em HTML quando o navegador só o mostra, esconde, ordena ou filtra, e passe ao Alpine os poucos valores de que ele precisa:

```html
<article data-sector="[#item.sector#]" x-show="sector === 'all' || sector === $el.dataset.sector">
  [#get-html item.description#]
</article>
```

Quando o navegador monta a interface a partir de dados, passe os registros como JSON num bloco de script, o que é seguro para qualquer texto dos registros:

```html
<script type="application/json" id="projects-data">[#fmt var=data.projects empty={} type=json#]</script>
<div x-data="{ projects: Object.values(JSON.parse(document.getElementById('projects-data').textContent)) }">
  <template x-for="project in projects" :key="project.uuid">
    <h3 x-text="project.title"></h3>
  </template>
</div>
```

Para conjuntos de dados grandes ou restritos, deixe a página pedir à API uma página de cada vez, em vez de embutir tudo.

## Uma biblioteca numa página

Carregue uma biblioteca na rota que precisa dela. O exemplo de projetos coloca seu mapa numa página com Leaflet:

```html
[#set head="<link rel='stylesheet' href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'>"#]
[#html#]
```

e carrega o script com o `index.js` próprio da rota, ou com um `<script>` no `main.tpl` dela. As outras páginas nunca o carregam.

## React, Vue ou outro framework

Uma rota pode hospedar uma aplicação escrita com qualquer framework. O padrão é o mesmo para todos:

1. **Faça o build** da aplicação com a toolchain dela, por exemplo Vite, numa pasta dentro de `ext/static/`. Os arquivos em `ext/static/` são servidos a partir da raiz do site, então `ext/static/planner/main.js` é `/planner/main.js`.
2. **Monte-a** a partir de uma rota: gere o elemento em que ela é montada e carregue o build.
3. **Passe os dados** na página, ou deixe a aplicação chamar a API.

Uma rota que mantém a shell padrão, `ext/uri/planner/index.tpl`:

```html
[#set page-title="Planner"#]
[#set head="<link rel='stylesheet' href='[#base-url#]/planner/main.css'>"#]
[#html#]
```

e o `main.tpl` dela:

```html
[#data projects filter=published:1#]
<div id="planner"></div>
<script type="application/json" id="planner-data">[#fmt var=data.projects empty={} type=json#]</script>
<script type="module" src="[#base-url#]/planner/main.js"></script>
```

A aplicação lê seus dados iniciais do bloco de script:

```js
import { createRoot } from 'react-dom/client';
import Planner from './Planner';

const projects = Object.values(JSON.parse(document.getElementById('planner-data').textContent));
createRoot(document.getElementById('planner')).render(<Planner projects={projects} />);
```

No navegador, a aplicação pode ler e gravar pela API REST com a sessão do usuário autenticado, com as mesmas permissões do admin:

```js
const response = await fetch(`${window.nb.base_url}/api/v1/projects?limit=20`, { credentials: 'same-origin' });
const { projects, total } = await response.json();
```

`window.nb.base_url` é definido pela shell. Numa rota com documento próprio, escreva o endereço base na página com `[#base-url#]`.

Mantenha o código-fonte e a toolchain da aplicação numa pasta própria dentro de `ext/`, como `ext/planner/` com seu próprio `package.json`, e faça commit ou deploy do resultado do build do mesmo jeito que o `app.css`.

## Uma stack diferente para o site inteiro

Para usar outro framework de CSS, ou nenhum, em todas as páginas públicas, dê a cada rota um documento próprio, ou substitua partes da shell da página em `ext/tpl/html/`. O admin usa a mesma shell e precisa de `app.css` e `app.js`, então acrescente seus assets ao lado deles em vez de removê-los. Veja [Seu próprio HTML](/pt/docs/guides/html-shell/).

## Leia a seguir

- [Seu próprio HTML](/pt/docs/guides/html-shell/)
- [API REST](/pt/docs/reference/api/)
- [Build](/pt/docs/operate/build/)
