@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.
Files changed (41) hide show
  1. package/README.md +294 -0
  2. package/build.js +80 -0
  3. package/cli.js +137 -0
  4. package/dev.js +90 -0
  5. package/engine/core.js +51 -0
  6. package/engine/hotreload.js +7 -0
  7. package/engine/renderer.js +23 -0
  8. package/engine/seo.js +13 -0
  9. package/package.json +24 -0
  10. package/preview.js +45 -0
  11. package/templates/default/assets/css/components.css +88 -0
  12. package/templates/default/assets/css/lume.css +75 -0
  13. package/templates/default/assets/css/theme.css +12 -0
  14. package/templates/default/assets/css/utilities.css +28 -0
  15. package/templates/default/components/about.html +14 -0
  16. package/templates/default/components/contact.html +11 -0
  17. package/templates/default/components/features.html +20 -0
  18. package/templates/default/components/footer.html +11 -0
  19. package/templates/default/components/gallery.html +20 -0
  20. package/templates/default/components/header.html +8 -0
  21. package/templates/default/components/hero.html +15 -0
  22. package/templates/default/components/navbar.html +20 -0
  23. package/templates/default/components/portfolio.html +35 -0
  24. package/templates/default/components/products.html +51 -0
  25. package/templates/default/components/testimonials.html +30 -0
  26. package/templates/default/components/whatsapp.html +13 -0
  27. package/templates/default/data/site.json +16 -0
  28. package/templates/default/pages/about.html +22 -0
  29. package/templates/default/pages/index.html +35 -0
  30. package/templates/landing/assets/css/components.css +88 -0
  31. package/templates/landing/assets/css/lume.css +75 -0
  32. package/templates/landing/assets/css/theme.css +12 -0
  33. package/templates/landing/assets/css/utilities.css +28 -0
  34. package/templates/landing/components/benefits.html +20 -0
  35. package/templates/landing/components/cta.html +10 -0
  36. package/templates/landing/components/footer.html +4 -0
  37. package/templates/landing/components/hero.html +15 -0
  38. package/templates/landing/components/testimonials.html +52 -0
  39. package/templates/landing/data/site.json +20 -0
  40. package/templates/landing/pages/index.html +32 -0
  41. package/templates/package.json +7 -0
package/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # 🚀 LumePress
2
+
3
+ ![npm](https://img.shields.io/npm/v/lumepress)
4
+ ![license](https://img.shields.io/npm/l/lumepress)
5
+ ![node](https://img.shields.io/node/v/lumepress)
6
+ ![status](https://img.shields.io/badge/status-active-success)
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,7 @@
1
+ const socket = new WebSocket(`ws://${location.host}`);
2
+
3
+ socket.addEventListener("message", (event) => {
4
+ if (event.data === "reload") {
5
+ location.reload();
6
+ }
7
+ });
@@ -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
+ }