Skip to content

Commit ca463f9

Browse files
committed
publish first article - vuejs blog, markdown powered
1 parent b6e4e93 commit ca463f9

1 file changed

Lines changed: 284 additions & 0 deletions

File tree

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
---
2+
title: Como construí este blog em VueJS + Markdown puro!
3+
published: true
4+
published_at: 2024-12-18
5+
tags:
6+
- development
7+
- software
8+
- git
9+
- vuejs
10+
- markdown
11+
serie: Um blog feito em casa
12+
category:
13+
- Programação
14+
excerpt: Depois de inúmeras tentativas de construir e manter um blog, decidi reinventar a roda e manter a minha própria aplicação.
15+
---
16+
# 💡Introdução
17+
18+
> 🟨 O texto a seguir é razoavelmente técnico e pressupõe um conhecimento básico de programação. Alguns documentos e links são oferecidos como referência, além de todo o código do [meu repositório GitHub](https://github.com/andrepg/andrepg.github.io).
19+
20+
21+
Começamos pelas razões e objetivos principais: *ter um blog simples, junto ao meu cartão de visitas online*. Muitas tentativas foram feitas: Jekyll, HTML básico, WordPress, VueJS. Por fim, resolvi seguir o caminho do SPA VueJS e Markdown. Algumas circunstâncias foram importantes para esta decisão e, no futuro, estão passíveis de revisão:
22+
23+
1. A hospedagem inicial é feita pelo GitHub Pages e, portanto, não há banco de dados
24+
2. Já existia um SPA com meu cartão de visitas, e o VuePress parecia complexo para adoção em projetos existentes
25+
3. Sem banco de dados é impossível construir algo com WordPress — e são recursos demais para um simples blog.
26+
4. O uso do SPA + GitHub me permitiu ter os arquivos em Markdown (e já os escrevo no Obsidian neste mesmo formato)
27+
28+
# 🛠 Pacotes para instalar
29+
Algumas dependências e pacotes são básicas para o funcionamento do framework; aprofundar neste quesito não é o foco do artigo. Vale a pena mencionar, no entanto. Isso dá contexto a diferentes classes e configurações utilizadas ao longo deste texto.
30+
31+
## O básico para começar
32+
Aqui temos a composição da espinha dorsal do projeto, com suas respectivas páginas de documentação. Minha recomendação é ler com atenção as documentações e instruções de cada uma destas para ambientar-se no projeto.
33+
34+
* [Vite](https://vite.dev/)
35+
* [VueJS](https://vuejs.org/) e [Vue Router](https://router.vuejs.org/)
36+
* [Tailwind CSS](https://tailwindcss.com/)
37+
* [Node](https://nodejs.org/) e [Yarn](https://yarnpkg.com/) (ou NPM se preferir)
38+
39+
## O plugin para ler Markdown
40+
🌐 https://www.npmjs.com/package/vite-plugin-markdown
41+
42+
Com um papel importante no projeto, o plugin Vite Markdown permite a importação de arquivos `.md` e seus atributos de *FrontMatter* ([🔍 pesquise um pouco](https://www.google.com/search?q=atributos+frontmatter)). Estes atributos permitem categorizar, definir títulos, datas e outras informações úteis para a renderização.
43+
44+
Para instalar o plugin, basta um comando: `yarn add vite-plugin-markdown`. As configurações faremos depois, no arquivo específico alguns parágrafos à frente.
45+
46+
## O plugin para formatar código-fonte
47+
🌐 https://prismjs.com/
48+
49+
Existe um conceito chamado **[🔍 Codefence](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks)**: um trecho de código formatado especialmente, conforme a linguagem, para facilitar a leitura do código. Este comportamento envolve muito código para reinventar a roda. Assim, a decisão foi por adotar uma extensão para esta formatação.
50+
51+
O PrismJS inclui os arquivos JavaScript, folhas de estilo, e outras necessidades para correta formatação, gerando o código visto na seção **[[#Configurações do Vite]]**. Algumas personalizações foram necessárias e estão disponíveis no arquivo [📚 settings.jsx](https://github.com/andrepg/andrepg.github.io/blob/main/src/data/settings.jsx) do repositório.
52+
53+
São detalhes relacionados a linguagens suportadas pela extensão, bem como adicionais de formatação, numeração e outros.
54+
55+
## Configurações do Vite
56+
Extensões instaladas, agora as configurações: para `Markdown` e `PrismJS`. O trecho abaixo ativa os modos HTML, <abbr title="Tabela de conteúdo">TOC</abbr> e Vue para leitura de arquivos `.md`.
57+
58+
```javascript
59+
// mais em https://github.com/andrepg/andrepg.github.io/blob/main/vite.config.js
60+
markdown({
61+
mode: [Mode.HTML, Mode.TOC, Mode.VUE]
62+
}),
63+
```
64+
65+
Já este trecho permite configurar o PrismJS com injeção de CSS, tema e linguagens, configurados anteriormente no `settings.jsx`.
66+
67+
```js
68+
prismjsPlugin({
69+
css: true,
70+
theme: 'tomorrow',
71+
languages: PrismJsLanguages,
72+
plugins: PrismJsPlugins
73+
})
74+
```
75+
76+
Com isto é possível integrar tudo que instalamos e avançar para a mão no código.
77+
78+
> 🟡 Fique atento! Nesta fase ainda não é possível ver nenhum tipo de integração funcionando. Apenas instalação e configurações foram feitas.
79+
80+
# 📝 A página inicial
81+
82+
> Esta seção serve apenas para apresentar meu website, e introduzir um pouco da estrutura básica. Se preferir, você pode pular para a seção [[#📝 A estrutura de blog]]
83+
84+
Seguindo os passos de um desenvolvimento natural de website, o ponto de partida é a **página inicial**. Um desenho básico (que pode ser visto [aqui](https://andrepg.github.io)). Esta página está construída em VueJS e Tailwind, utilizando conceitos básicos. Também não entrarei em muitos detalhes, apenas dois: a geração automática de seções:
85+
86+
## Os botões & seções adicionais
87+
Para renderizar alguns botões e redirecionar o visitante (já que esta página serve apenas para isto) existe a estrutura de leitura de objetos simples e renderização de botões. O arquivo [📚 homepageLinks](https://github.com/andrepg/andrepg.github.io/blob/main/src/data/homepageLinks.js) possui configurações para cada um dos botões renderizados. Cada botão tem as seguintes propriedades:
88+
89+
```json
90+
{
91+
label: 'GitHub',
92+
icon: 'hugeicons:github',
93+
target: 'https://github.com/andrepg'
94+
}
95+
```
96+
97+
E para renderizar os botões, utilizamos o seguinte código na [HomeView.vue](https://github.com/andrepg/andrepg.github.io/blob/db998354a4bcd5cc123bd10aaf9c954494f1db97/src/views/HomeView.vue):
98+
99+
```jsx
100+
<PrimaryButton
101+
v-for="button in HomepageLinks"
102+
:key="button.target"
103+
:icon="button.icon"
104+
:target="button.target">
105+
{{ button.label }}
106+
</PrimaryButton>
107+
```
108+
109+
Com isto conseguimos a estrutura de gerar botões e adicionar quantos necessários, sem a que seja preciso modificar o código a cada novo link. Estas modificações são feitas no arquivo `homepageLinks.js`.
110+
111+
---
112+
# 📝 A estrutura de blog,
113+
114+
Uma vez com extensões instaladas, configurações feitas e uma página inicial capaz de ler um conteúdo básico, partimos para a estrutura do blog e outras criações para possibilitar isto.
115+
116+
No início comentei que parecia trabalho demais instalar o VitePress. Isso porque não pude encontrar nenhuma documentação clara suficiente sobre o assunto, apenas sobre instalações em sub-páginas, separadas. Isso traz a obrigação de ter um pipeline a mais, e cria mais complexidade para o projeto.
117+
118+
Copiando estruturas básicas de outros sistemas de blog, a decisão foi feita em prol de uma pasta `blogs`, contendo os artigos agrupados por ano. Assim, seria possível organizar melhor o repositório (já que a frequência de postagens não é, assim, tão grande). A estrutura de pastas ficaria:
119+
120+
```
121+
blogs
122+
|
123+
\_[ 2024 ]
124+
|--- artigo-1.md
125+
|--- artigo-2.md
126+
|--- artigo-3.md
127+
/
128+
\_[ 2023 ]
129+
|--- artigo-1.md
130+
|--- artigo-2.md
131+
|--- artigo-3.md
132+
```
133+
134+
Com esta estrutura em mente, é preciso lembrar que um blog tem certos requisitos básicos para existir: um mapa de links, um índice, uma página de post e uma busca. Os recursos serão construídos ao longo do tempo e fazem parte da evolução natural do projeto.
135+
136+
## Publicações e organizações
137+
138+
Seguindo a premissa de pastas e requisitos, o primeiro passo seria a construção do sitemap, responsável por alimentar tanto o índice quanto robôs leitores de conteúdo. Em dois formatos: JSON e XML. Fazer esse controle manual é humanamente impossível depois de alguns poucos artigos. Automatizar é a chave.
139+
140+
## Sitemap
141+
> 📚 [`Sitemap Generator`](https://github.com/andrepg/andrepg.github.io/blob/main/src/SitemapGenerator.js) e gatilho [`sitemap.js`](https://github.com/andrepg/andrepg.github.io/blob/main/src/sitemap.js)
142+
143+
Para a construção do mapa, a premissa é básica: ler as pastas e arquivos dentro de `blog` e gerar os arquivos necessários. Em partes, o `sitemap.js` tem um ponto de entrada `main()`, responsável pelos processos básicos. Para fins de apresentação, não há comentários e registros de log.
144+
145+
```js
146+
function main() {
147+
let references = getArticlesList()
148+
references.forEach(readArticleProperties)
149+
150+
writeFileSync(__sitemapJson, JSON.stringify(sitemap), { encoding: 'utf-8' })
151+
sitemap = generateSitemap(sitemap)
152+
writeFileSync(__sitemap, sitemap, { encoding: 'utf-8' })
153+
}
154+
```
155+
156+
Informações básicas: `writeFileSync` é uma função específica da plataforma para escrever arquivos no sistema. Estas duas linhas geram os arquivos XML e JSON, baseadas em seus próprios parâmetros. Nesta configuração, os arquivos serão exportados para `/public` no projeto. As funções `generateSitemap` e o combo `getArticlesList`/`readArticleProperties` estão no mesmo arquivo.
157+
158+
Estas três tem funções muito específicas:
159+
* [`getArticlesList`](https://github.com/andrepg/andrepg.github.io/blob/db998354a4bcd5cc123bd10aaf9c954494f1db97/src/sitemap.js#L21) busca os arquivos na pasta de blog, somente arquivos
160+
* [`readArticleProperties`](https://github.com/andrepg/andrepg.github.io/blob/db998354a4bcd5cc123bd10aaf9c954494f1db97/src/sitemap.js#L33-L42) faz a leitura do arquivo `.md` e implementa as propriedades do FrontMatter
161+
* [`generateSitemap`](https://github.com/andrepg/andrepg.github.io/blob/db998354a4bcd5cc123bd10aaf9c954494f1db97/src/sitemap.js#L25-L27) utiliza a classe `SitemapGenerator` para estruturar a versão XML do mapa
162+
163+
Este calhamaço de informação fica armazenado numa variável global `sitemap`, inicializada como um `array` vazio, logo no início do código. Esta variável é a mesma que aparece no corpo da `main()`, exportada pelo `writeFileSync` — note que `__sitemapJson` e `__sitemap` são outras variáveis, contendo o caminho de exportação dos arquivos.
164+
165+
## Post Index
166+
🌐 https://github.com/andrepg/andrepg.github.io/blob/main/src/views/PostIndex.vue
167+
168+
Para o índice de postagens, metade do serviço já foi feito. A construção do mapa em formato JSON permite leitura nesta página, e oferece informação suficiente para montagem da interface de usuário. Para cumprir esta tarefa, apenas uma requisição simples:
169+
170+
```jsx
171+
// Fetch do arquivo Sitemap.json, direto da raiz do site
172+
const fetchPosts = () => fetch('/sitemap.json')
173+
.then(response => processPosts(response))
174+
175+
// Processamento da resposta da requisição e leitura
176+
const processPosts = (response: Response) => response.json()
177+
.then(sitemap => posts.value = sitemap)
178+
```
179+
180+
Note duas funções diferentes: a `fetchPosts` e a `processPosts`. Isso porque `response.json()` retorna um objeto `Promise`, que não permite a leitura imediata. Para evitar um aninhamento de `.then` no código, dividir a responsabilidade parece boa ideia.
181+
182+
Ao final, o valor de `sitemap` atribuído a `posts.value` conterá o mapa completo do site, conforme gerado pela automatização. Este arquivo é público aqui no site, e qualquer um pode [consultá-lo aqui](https://github.com/andrepg/andrepg.github.io/blob/main/src/sitemap.js). Na renderização, qualquer layout é possível, baseando uma iteração sobre cada post obtido.
183+
184+
Assim, uma listagem de artigos é possível com poucas linhas de código e um componente extra recebendo informações da postagem, como o [`PostListElement`](https://github.com/andrepg/andrepg.github.io/blob/main/src/components/PostListElement.vue) e sua aplicação no [índice de artigos](https://github.com/andrepg/andrepg.github.io/blob/db998354a4bcd5cc123bd10aaf9c954494f1db97/src/views/PostIndex.vue#L44-L48).
185+
186+
## Post Single
187+
🌐 https://github.com/andrepg/andrepg.github.io/blob/main/src/views/PostSingle.vue
188+
189+
O `PostSingle` é o componente onde a mágica acontece. Nele ocorre a leitura de cada artigo deste blog e a renderização de todos os componentes necessários. Na verdade, a mágica está toda por baixo do capô. Aqui o trabalho é feito _quase todo_ pelo [[#O plugin para ler Markdown|vite-plugin-markdown]].
190+
191+
```javascript
192+
import { nextTick, onMounted } from 'vue';
193+
import Prism from 'prismjs';
194+
195+
onMounted(async () => {
196+
Prism.manual = true;
197+
198+
content.value = await import(`@blog/${year}/${article}.md`);
199+
200+
nextTick(() => {
201+
Prism.highlightAll()
202+
})
203+
})
204+
```
205+
206+
207+
```javascript
208+
<ul class="flex flex-row flex-wrap gap-2 py-2 px-0">
209+
<BadgeElement v-for="tag in content.attributes.tags" key="tag">
210+
{{ tag }}
211+
</BadgeElement>
212+
</ul>
213+
214+
<h1 class="text-3xl font-semibold flex flex-col max-w-[70w]">
215+
<span class="font-serif"> {{ content.attributes.titulo }}</span>
216+
<small class="w-full font-normal text-base leading-snug flex-1">
217+
{{ content.attributes.serie }}
218+
</small>
219+
</h1>
220+
221+
<ul>{{ content.attributes.categories }}</ul>
222+
```
223+
224+
# 🟡 A limitação do GitHub Pages
225+
226+
Antes de continuar, preciso deixar um aviso: o GitHub Pages tem problema com aplicações SPA e redirecionamentos para rotas. Um pequeno *hack* é necessário para funcionar corretamente. **O que acontece**: caso não exista um arquivo HTML com o mesmo nome (`minha-url-buscada`) o GitHub retorna um erro 404 do servidor.
227+
228+
Para contornar este problema, é preciso criar um arquivo 404.html customizado e verificar se um redirecionamento é necessário. Este passo é **muito** importante. E eu coletei algumas referências sobre o assunto para aprofundar a pesquisa depois:
229+
230+
* 📚 [W3C: Using meta refresh to create an instant client-side redirect](https://www.w3.org/TR/WCAG20-TECHS/H76.html)
231+
* 📚 [What is meta refresh?](https://help.ahrefs.com/en/articles/2433739-what-is-meta-refresh-redirect-and-why-is-it-considered-a-critical-issue)
232+
* 📚 [Fragmento CodePen](https://codepen.io/akashrajendra/pen/JKKRvQ)
233+
* 🌐 [Single page hack with GitHub Pages](https://www.smashingmagazine.com/2016/08/sghpa-single-page-app-hack-github-pages/)
234+
* 🌐 [Modelo de arquivo 404 - 1](https://github.com/csuwildcat/sghpa/blob/master/index.html)
235+
* 🌐 [Modelo de arquivo 404 - 2](https://gist.github.com/leonsilicon/1278d429d7c915a9866bc6ea73453d9a)
236+
237+
## O código que importa
238+
📚 https://github.com/andrepg/andrepg.github.io/blob/main/public/404.html
239+
240+
Para resumir, o código mais importante é o trecho abaixo:
241+
242+
```js
243+
const resource = location.href;
244+
245+
if (resource.includes('blog')) {
246+
sessionStorage.redirect = resource
247+
248+
refresh = document.createElement('meta')
249+
refresh.content = '0;URL=\'/\''
250+
refresh.httpEquiv = 'refresh'
251+
252+
document.head.append(refresh)
253+
}
254+
```
255+
256+
Estas linhas são responsáveis por verificar se a URL visitada contém o fragmento `blog` e executar uma série de ações necessárias:
257+
258+
1. Salva a URL atual na sessão para consulta futura
259+
2. Cria um elemento `meta` para gerenciar o documento
260+
3. Atribui `/` como destino do redirecionamento, com tempo de 0 segundos
261+
4. Atribui `refresh` à propriedade `httpEquiv`, para informar ao navegador a razão desta tag
262+
5. Adiciona ao documento ao cabeçalho da página
263+
264+
Quando esta tag `meta` é adicionada ao cabeçalho o navegador poderá interpretar a ordem de redirecionamento e processar a URL do blog.
265+
266+
# 💼 O projeto final
267+
268+
O projeto em sua forma final é este, no qual o artigo foi publicado. Perdi as contas de quantas vezes tentei começar um blog e não foi adiante. Os problemas eram inúmeros: não gostava do domínio ou da ferramenta (alô WordPress), perdia o banco de dados, a hospedagem vencia, ou preguiça.
269+
270+
Agora tenho meus arquivos em Markdown onde escrevo — no Obsidian — e publico no meu repositório GitHub. Assim, tenho histórico e duplo backup para meus artigos publicados. Algumas considerações existem, pois ainda há tarefas pendentes.
271+
272+
Elas estão listadas abaixo, e serão atualizadas à medida que forem concluídas. Por enquanto, fico por aqui, e agradeço a leitura caso tenha chegado ao final com atenção. 😄
273+
274+
## Minhas considerações
275+
276+
Ainda falta para chegar onde quero. Melhorias para implementar e correções a serem feitas. Minha lista e este artigos serão atualizados com o tempo. Estes problemas serão rastreados através do painel de *issues* do repositório também.
277+
278+
- [ ] O rodapé não aparece na página `404`, tirando-a do padrão do website
279+
- [ ] O redirecionamento `404 meta-refresh` para qualquer outra página causa uma piscadela durante o carregamento (em parte pelo rodapé não existente e a falta de um *loading*)
280+
- [ ] Uma transição entre as páginas seria bem-vinda
281+
- [ ] Um carregamento antecipado da imagem de perfil pode acelerar a página
282+
- [ ] Busca com Algolia ou outro mecanismo Javascript local
283+
- [ ] Postagens mais recentes na página inicial do site
284+
- [ ] Adaptar os cartões da página inicial para modo escuro

0 commit comments

Comments
 (0)