@marcos_aurelio/lumepress 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -0
- package/build.js +80 -0
- package/cli.js +137 -0
- package/dev.js +90 -0
- package/engine/core.js +51 -0
- package/engine/hotreload.js +7 -0
- package/engine/renderer.js +23 -0
- package/engine/seo.js +13 -0
- package/package.json +24 -0
- package/preview.js +45 -0
- package/templates/default/assets/css/components.css +88 -0
- package/templates/default/assets/css/lume.css +75 -0
- package/templates/default/assets/css/theme.css +12 -0
- package/templates/default/assets/css/utilities.css +28 -0
- package/templates/default/components/about.html +14 -0
- package/templates/default/components/contact.html +11 -0
- package/templates/default/components/features.html +20 -0
- package/templates/default/components/footer.html +11 -0
- package/templates/default/components/gallery.html +20 -0
- package/templates/default/components/header.html +8 -0
- package/templates/default/components/hero.html +15 -0
- package/templates/default/components/navbar.html +20 -0
- package/templates/default/components/portfolio.html +35 -0
- package/templates/default/components/products.html +51 -0
- package/templates/default/components/testimonials.html +30 -0
- package/templates/default/components/whatsapp.html +13 -0
- package/templates/default/data/site.json +16 -0
- package/templates/default/pages/about.html +22 -0
- package/templates/default/pages/index.html +35 -0
- package/templates/landing/assets/css/components.css +88 -0
- package/templates/landing/assets/css/lume.css +75 -0
- package/templates/landing/assets/css/theme.css +12 -0
- package/templates/landing/assets/css/utilities.css +28 -0
- package/templates/landing/components/benefits.html +20 -0
- package/templates/landing/components/cta.html +10 -0
- package/templates/landing/components/footer.html +4 -0
- package/templates/landing/components/hero.html +15 -0
- package/templates/landing/components/testimonials.html +52 -0
- package/templates/landing/data/site.json +20 -0
- package/templates/landing/pages/index.html +32 -0
- package/templates/package.json +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# 🚀 LumePress
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
**LumePress** é um framework leve para criação de sites estáticos com HTML, componentes reutilizáveis e dados dinâmicos via JSON.
|
|
9
|
+
|
|
10
|
+
Inspirado em ferramentas modernas como Astro e Next.js (modo estático), o LumePress foca em simplicidade e produtividade.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ✨ Features
|
|
15
|
+
|
|
16
|
+
* ⚡ Zero configuração
|
|
17
|
+
* 🧩 Componentes HTML (`<lume-*>`)
|
|
18
|
+
* 📄 Sistema de páginas automático
|
|
19
|
+
* 🔥 Hot Reload
|
|
20
|
+
* 📦 Build estático (SSG)
|
|
21
|
+
* 🎯 Estrutura simples e organizada
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# 📦 Instalação
|
|
26
|
+
|
|
27
|
+
## Global (recomendado)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g lumepress
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Desenvolvimento local
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone <seu-repo>
|
|
37
|
+
cd lume
|
|
38
|
+
npm link
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# 🚀 Criando um Projeto
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
lumepress create meu-site
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Com template:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
lumepress create meu-site --template landing
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# ▶️ Rodando o Projeto
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd meu-site
|
|
61
|
+
lumepress dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Acesse:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
http://localhost:3000
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
# 🔥 Hot Reload
|
|
73
|
+
|
|
74
|
+
O projeto recarrega automaticamente ao salvar:
|
|
75
|
+
|
|
76
|
+
* pages/
|
|
77
|
+
* components/
|
|
78
|
+
* data/
|
|
79
|
+
* assets/
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
# 🏗️ Estrutura do Projeto
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
meu-site/
|
|
87
|
+
├── pages/
|
|
88
|
+
│ └── index.html
|
|
89
|
+
├── components/
|
|
90
|
+
│ ├── navbar.html
|
|
91
|
+
│ ├── hero.html
|
|
92
|
+
│ └── footer.html
|
|
93
|
+
├── data/
|
|
94
|
+
│ └── site.json
|
|
95
|
+
├── assets/
|
|
96
|
+
│ └── css/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# 📄 Sistema de Páginas
|
|
102
|
+
|
|
103
|
+
Cada arquivo em `pages/` vira uma rota:
|
|
104
|
+
|
|
105
|
+
| Arquivo | URL |
|
|
106
|
+
| --------------- | ------ |
|
|
107
|
+
| index.html | / |
|
|
108
|
+
| sobre.html | /sobre |
|
|
109
|
+
| blog/index.html | /blog |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
# 🧩 Componentes
|
|
114
|
+
|
|
115
|
+
Crie componentes em:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
components/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Exemplo:
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<!-- components/hero.html -->
|
|
125
|
+
<section>
|
|
126
|
+
<h1>{{hero.title}}</h1>
|
|
127
|
+
</section>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Uso:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<lume-hero></lume-hero>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# 📊 Dados Dinâmicos
|
|
139
|
+
|
|
140
|
+
Arquivo:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
data/site.json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Exemplo:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"site": {
|
|
151
|
+
"name": "Minha Empresa",
|
|
152
|
+
"footer": "Todos os direitos reservados"
|
|
153
|
+
},
|
|
154
|
+
"hero": {
|
|
155
|
+
"title": "Bem-vindo",
|
|
156
|
+
"subtitle": "Meu site com LumePress",
|
|
157
|
+
"cta": "Começar"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Uso:
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<h1>{{hero.title}}</h1>
|
|
166
|
+
<p>{{hero.subtitle}}</p>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# ⚙️ Como Funciona
|
|
172
|
+
|
|
173
|
+
No modo **dev**:
|
|
174
|
+
|
|
175
|
+
* HTML é servido
|
|
176
|
+
* Componentes são carregados via JS
|
|
177
|
+
* Dados são aplicados em runtime
|
|
178
|
+
|
|
179
|
+
No **build**:
|
|
180
|
+
|
|
181
|
+
* HTML final é gerado
|
|
182
|
+
* Sem dependência de JS
|
|
183
|
+
* Pronto para produção
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# 📦 Build para Produção
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
lumepress build
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Gera:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
dist/
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
# 🌐 Preview
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
lumepress preview
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Acesse:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
http://localhost:4173
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
# 🧠 Dev vs Build
|
|
216
|
+
|
|
217
|
+
| Ambiente | Comportamento |
|
|
218
|
+
| -------- | --------------------- |
|
|
219
|
+
| dev | renderização dinâmica |
|
|
220
|
+
| build | HTML estático final |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
# 🎨 Templates
|
|
225
|
+
|
|
226
|
+
Templates disponíveis:
|
|
227
|
+
|
|
228
|
+
* default
|
|
229
|
+
* landing
|
|
230
|
+
|
|
231
|
+
Criar com template:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
lumepress create site --template landing
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
# 🛠️ Comandos
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
lumepress create <nome>
|
|
243
|
+
lumepress dev <pasta>
|
|
244
|
+
lumepress build <pasta>
|
|
245
|
+
lumepress preview <pasta>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
# ⚠️ Boas práticas
|
|
251
|
+
|
|
252
|
+
✔ Sempre manter `data/site.json`
|
|
253
|
+
✔ Usar paths diretos:
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
{{hero.title}}
|
|
257
|
+
{{site.name}}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
❌ Evitar:
|
|
261
|
+
|
|
262
|
+
```html
|
|
263
|
+
{{content.hero.title}}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
# 🚀 Roadmap
|
|
269
|
+
|
|
270
|
+
* Layouts globais
|
|
271
|
+
* Props em componentes
|
|
272
|
+
* Plugins
|
|
273
|
+
* CLI avançado
|
|
274
|
+
* Marketplace de templates
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
# 🤝 Contribuição
|
|
279
|
+
|
|
280
|
+
Pull requests são bem-vindos.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
# 📄 Licença
|
|
285
|
+
|
|
286
|
+
MIT
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
# 👨💻 Autor
|
|
291
|
+
|
|
292
|
+
Marcos Aurelio 🚀
|
|
293
|
+
|
|
294
|
+
---
|
package/build.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
function resolvePath(obj, pathStr) {
|
|
5
|
+
return pathStr.split(".").reduce((o, k) => o?.[k], obj);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function renderData(html, data) {
|
|
9
|
+
return html.replace(/\{\{(.*?)\}\}/g, (_, key) => {
|
|
10
|
+
const value = resolvePath(data, key.trim());
|
|
11
|
+
return value ?? "";
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function renderComponents(html, componentsPath) {
|
|
16
|
+
const regex = /<lume-(.*?)><\/lume-\1>/g;
|
|
17
|
+
|
|
18
|
+
let match;
|
|
19
|
+
|
|
20
|
+
while ((match = regex.exec(html)) !== null) {
|
|
21
|
+
const componentName = match[1];
|
|
22
|
+
const componentFile = path.join(componentsPath, `${componentName}.html`);
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(componentFile)) {
|
|
25
|
+
const componentHtml = fs.readFileSync(componentFile, "utf-8");
|
|
26
|
+
html = html.replace(match[0], componentHtml);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return html;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function removeDevScripts(html) {
|
|
34
|
+
html = html.replace(/\n?\s*<!--\s*Engine do Framework\s*-->\s*\n?\s*<script[^>]*src="\/engine\/core\.js"[^>]*><\/script>\s*\n?/g, "\n")
|
|
35
|
+
html = html.replace(/\n?\s*<!--\s*Hot reload\s*-->\s*\n?\s*<script[^>]*src="\/engine\/hotreload\.js"[^>]*><\/script>\s*\n?/g, "\n")
|
|
36
|
+
|
|
37
|
+
return html;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function build(sitePath) {
|
|
41
|
+
const pagesPath = path.join(sitePath, "pages");
|
|
42
|
+
const componentsPath = path.join(sitePath, "components");
|
|
43
|
+
const dataPath = path.join(sitePath, "data", "site.json");
|
|
44
|
+
const distPath = path.join(sitePath, "dist");
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(distPath)) {
|
|
47
|
+
fs.rmSync(distPath, {
|
|
48
|
+
recursive: true,
|
|
49
|
+
force: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fs.mkdirSync(distPath);
|
|
54
|
+
|
|
55
|
+
const data = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
|
|
56
|
+
const pages = fs.readdirSync(pagesPath);
|
|
57
|
+
|
|
58
|
+
for (const page of pages) {
|
|
59
|
+
const pagePath = path.join(pagesPath, page);
|
|
60
|
+
|
|
61
|
+
if (!page.endsWith(".html")) continue;
|
|
62
|
+
|
|
63
|
+
let html = fs.readFileSync(pagePath, "utf-8");
|
|
64
|
+
|
|
65
|
+
html = await renderComponents(html, componentsPath);
|
|
66
|
+
html = renderData(html, data);
|
|
67
|
+
html = removeDevScripts(html);
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(path.join(distPath, page), html);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const assetsSrc = path.join(sitePath, "assets");
|
|
73
|
+
const assetsDest = path.join(distPath, "assets");
|
|
74
|
+
|
|
75
|
+
if (fs.existsSync(assetsSrc)) {
|
|
76
|
+
fs.cpSync(assetsSrc, assetsDest, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log("Build concluído!");
|
|
80
|
+
}
|
package/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import readline from "readline";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { startPreview } from "./preview.js";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const command = process.argv[2];
|
|
12
|
+
const projectName = process.argv[3];
|
|
13
|
+
|
|
14
|
+
let templateName = "default";
|
|
15
|
+
|
|
16
|
+
const templateIndex = process.argv.indexOf("--template");
|
|
17
|
+
if (templateIndex !== -1) {
|
|
18
|
+
templateName = process.argv[templateIndex + 1];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ================= CREATE =================
|
|
22
|
+
function createProject() {
|
|
23
|
+
if (!projectName) {
|
|
24
|
+
console.log("Uso: lumepress create <nome-do-projeto>");
|
|
25
|
+
process.exit();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
rl.question("Nome da Empresa: ", (companyName) => {
|
|
34
|
+
|
|
35
|
+
const templatePath = path.join(__dirname, "templates", templateName);
|
|
36
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(templatePath)) {
|
|
39
|
+
console.log(`Template "${templateName}" não encontrado.`);
|
|
40
|
+
process.exit();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(projectPath)) {
|
|
44
|
+
console.log("A pasta já existe.");
|
|
45
|
+
process.exit();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fs.cpSync(templatePath, projectPath, { recursive: true });
|
|
49
|
+
|
|
50
|
+
const dataPath = path.join(projectPath, "data", "site.json");
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(dataPath)) {
|
|
53
|
+
const data = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
|
|
54
|
+
|
|
55
|
+
data.seo = data.seo || {};
|
|
56
|
+
data.site = data.site || {};
|
|
57
|
+
data.hero = data.hero || {};
|
|
58
|
+
|
|
59
|
+
data.seo.title = companyName;
|
|
60
|
+
data.site.name = companyName;
|
|
61
|
+
data.hero.title = `Bem vindo à ${companyName}`;
|
|
62
|
+
|
|
63
|
+
fs.writeFileSync(dataPath, JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`Projeto ${projectName} criado com sucesso 🚀`);
|
|
67
|
+
|
|
68
|
+
rl.close();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ================= DEV =================
|
|
73
|
+
async function startDev() {
|
|
74
|
+
const sitePath = projectName
|
|
75
|
+
? path.resolve(process.cwd(), projectName)
|
|
76
|
+
: process.cwd();
|
|
77
|
+
|
|
78
|
+
const dev = await import("./dev.js");
|
|
79
|
+
|
|
80
|
+
dev.start(sitePath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ================= BUILD =================
|
|
84
|
+
async function buildProject() {
|
|
85
|
+
const sitePath = projectName
|
|
86
|
+
? path.resolve(process.cwd(), projectName)
|
|
87
|
+
: process.cwd();
|
|
88
|
+
|
|
89
|
+
const builder = await import("./build.js");
|
|
90
|
+
|
|
91
|
+
await builder.build(sitePath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ================= PREVIEW =================
|
|
95
|
+
async function previewProject() {
|
|
96
|
+
const sitePath = projectName
|
|
97
|
+
? path.resolve(process.cwd(), projectName)
|
|
98
|
+
: process.cwd();
|
|
99
|
+
|
|
100
|
+
const builder = await import("./build.js");
|
|
101
|
+
|
|
102
|
+
await builder.build(sitePath);
|
|
103
|
+
|
|
104
|
+
const distPath = path.join(sitePath, "dist");
|
|
105
|
+
|
|
106
|
+
startPreview(distPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ================= ROUTER =================
|
|
110
|
+
if (command === "create") {
|
|
111
|
+
createProject();
|
|
112
|
+
}
|
|
113
|
+
else if (command === "dev") {
|
|
114
|
+
startDev();
|
|
115
|
+
}
|
|
116
|
+
else if (command === "build") {
|
|
117
|
+
buildProject();
|
|
118
|
+
}
|
|
119
|
+
else if (command === "preview") {
|
|
120
|
+
previewProject();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`
|
|
124
|
+
LumePress CLI
|
|
125
|
+
|
|
126
|
+
Comandos:
|
|
127
|
+
|
|
128
|
+
lumepress create <nome>
|
|
129
|
+
lumepress dev <pasta>
|
|
130
|
+
lumepress build <pasta>
|
|
131
|
+
lumepress preview <pasta>
|
|
132
|
+
|
|
133
|
+
Ex:
|
|
134
|
+
lumepress create meu-site
|
|
135
|
+
lumepress dev meu-site
|
|
136
|
+
`);
|
|
137
|
+
}
|
package/dev.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { WebSocketServer } from "ws";
|
|
3
|
+
import chokidar from "chokidar";
|
|
4
|
+
import http from "http";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
export function start(sitePath) {
|
|
13
|
+
|
|
14
|
+
const app = express();
|
|
15
|
+
const server = http.createServer(app);
|
|
16
|
+
|
|
17
|
+
let PORT = 3000;
|
|
18
|
+
|
|
19
|
+
const pagesPath = path.join(sitePath, "pages");
|
|
20
|
+
|
|
21
|
+
const enginePath = path.join(__dirname, "engine");
|
|
22
|
+
|
|
23
|
+
app.use("/assets", express.static(path.join(sitePath, "assets")));
|
|
24
|
+
app.use("/components", express.static(path.join(sitePath, "components")));
|
|
25
|
+
app.use("/data", express.static(path.join(sitePath, "data")));
|
|
26
|
+
|
|
27
|
+
app.use("/engine", express.static(enginePath));
|
|
28
|
+
|
|
29
|
+
// ================= ROUTING =================
|
|
30
|
+
app.get("*", (req, res) => {
|
|
31
|
+
|
|
32
|
+
let route = req.path;
|
|
33
|
+
|
|
34
|
+
if (route === "/") {
|
|
35
|
+
route = "/index";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 1️⃣ /pages/sobre.html
|
|
39
|
+
let filePath = path.join(pagesPath, `${route}.html`);
|
|
40
|
+
|
|
41
|
+
// 2️⃣ /pages/sobre/index.html
|
|
42
|
+
if (!fs.existsSync(filePath)) {
|
|
43
|
+
filePath = path.join(pagesPath, route, "index.html");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3️⃣ fallback index
|
|
47
|
+
if (!fs.existsSync(filePath)) {
|
|
48
|
+
const fallback = path.join(pagesPath, "index.html");
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(fallback)) {
|
|
51
|
+
return res.sendFile(fallback);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return res.status(404).send("Página não encontrada");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
res.sendFile(filePath);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ================= HOT RELOAD =================
|
|
61
|
+
const wss = new WebSocketServer({ server });
|
|
62
|
+
|
|
63
|
+
const watcher = chokidar.watch(sitePath, {
|
|
64
|
+
ignoreInitial: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
watcher.on("change", () => {
|
|
68
|
+
console.log("🔥 Reload");
|
|
69
|
+
|
|
70
|
+
wss.clients.forEach(client => {
|
|
71
|
+
client.send("reload");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ================= START =================
|
|
76
|
+
function startServer(port) {
|
|
77
|
+
server.listen(port, () => {
|
|
78
|
+
console.log("🚀 LumePress Dev Server");
|
|
79
|
+
console.log(`👉 http://localhost:${port}`);
|
|
80
|
+
})
|
|
81
|
+
.on("error", (err) => {
|
|
82
|
+
if (err.code === "EADDRINUSE") {
|
|
83
|
+
console.log(`Porta ${port} ocupada, tentando ${port + 1}`);
|
|
84
|
+
startServer(port + 1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
startServer(PORT);
|
|
90
|
+
}
|
package/engine/core.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
async function loadComponents() {
|
|
2
|
+
const elements = document.querySelectorAll("*");
|
|
3
|
+
|
|
4
|
+
for (const element of elements) {
|
|
5
|
+
const tag = element.tagName.toLocaleLowerCase();
|
|
6
|
+
|
|
7
|
+
if (tag.startsWith("lume-")) {
|
|
8
|
+
const component = tag.replace("lume-", "");
|
|
9
|
+
const response = await fetch(`/components/${component}.html`);
|
|
10
|
+
|
|
11
|
+
if (!response.ok) continue;
|
|
12
|
+
|
|
13
|
+
const html = await response.text();
|
|
14
|
+
|
|
15
|
+
element.innerHTML = html;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function loadData() {
|
|
21
|
+
const response = await fetch("/data/site.json");
|
|
22
|
+
return await response.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolvePath(obj, path) {
|
|
26
|
+
return path.split(".").reduce((o, k) => o?.[k], obj);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderData(data) {
|
|
30
|
+
const walker = document.createTreeWalker(
|
|
31
|
+
document.documentElement,
|
|
32
|
+
NodeFilter.SHOW_TEXT
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
let node;
|
|
36
|
+
|
|
37
|
+
while (node = walker.nextNode()) {
|
|
38
|
+
node.textContent = node.textContent.replace(/\{\{(.*?)\}\}/g, (match, key) => {
|
|
39
|
+
const value = resolvePath(data, key.trim());
|
|
40
|
+
return value ?? "";
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function init() {
|
|
46
|
+
await loadComponents();
|
|
47
|
+
const data = await loadData();
|
|
48
|
+
renderData(data);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function renderContent(data) {
|
|
2
|
+
let html = document.body.innerHTML;
|
|
3
|
+
const matches = html.match(/{{(.*?)}}/g);
|
|
4
|
+
|
|
5
|
+
if (!matches) return;
|
|
6
|
+
|
|
7
|
+
matches.forEach(tag => {
|
|
8
|
+
const path = tag.replace("{{", "").replace("}}", "").trim();
|
|
9
|
+
const value = getValueFromPath(data, path);
|
|
10
|
+
|
|
11
|
+
if (value) {
|
|
12
|
+
html = html.replaceAll(tag, value);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
document.body.innerHTML = html;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getValueFromPath(obj, path) {
|
|
20
|
+
return path.split(".").reduce((acc, part) => {
|
|
21
|
+
return acc ? acc[part] : undefined;
|
|
22
|
+
}, obj);
|
|
23
|
+
}
|
package/engine/seo.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function applySEO(data) {
|
|
2
|
+
if (!data.seo) return;
|
|
3
|
+
|
|
4
|
+
if (data.seo.title) {
|
|
5
|
+
document.title = data.seo.title;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const description = document.querySelector('meta[name="description"]');
|
|
9
|
+
|
|
10
|
+
if (description && data.seo.description) {
|
|
11
|
+
description.setAttribute("content", data.seo.description);
|
|
12
|
+
}
|
|
13
|
+
}
|