lumpiajs 1.0.11 → 1.0.13
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/lib/commands/build.js +60 -64
- package/lib/commands/create.js +6 -1
- package/lib/commands/serve.js +36 -35
- package/package.json +1 -1
package/lib/commands/build.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// ... (KAMUS
|
|
3
|
-
// Saya akan fokus update browserCore dan indexHtml
|
|
4
|
-
|
|
2
|
+
// ... (KAMUS same as before) ...
|
|
5
3
|
const KAMUS = [
|
|
6
4
|
{ from: /paten\s/g, to: 'const ' },
|
|
7
5
|
{ from: /ono\s/g, to: 'let ' },
|
|
@@ -26,12 +24,12 @@ import { loadConfig } from '../core/Config.js';
|
|
|
26
24
|
|
|
27
25
|
function transpileSemarangan(content) {
|
|
28
26
|
let code = content;
|
|
27
|
+
// 1. Keep .lmp import relative for now, or assume .js
|
|
29
28
|
code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// Jika import '/...' -> bahaya kalau di subfolder.
|
|
29
|
+
|
|
30
|
+
// 2. DO NOT TOUCH 'lumpiajs' IMPORT.
|
|
31
|
+
// We will use Import Map in index.html to resolve 'lumpiajs' to './core/lumpia.js'
|
|
32
|
+
// This solves the relative path hell once and for all.
|
|
35
33
|
|
|
36
34
|
code = code.split('\n').map(line => {
|
|
37
35
|
let l = line;
|
|
@@ -52,21 +50,6 @@ function processDirectory(source, dest, rootDepth = 0) {
|
|
|
52
50
|
} else {
|
|
53
51
|
if (item.endsWith('.lmp') || item.endsWith('.js')) {
|
|
54
52
|
let content = fs.readFileSync(srcPath, 'utf8');
|
|
55
|
-
|
|
56
|
-
// ADJUST IMPORT PATHS FOR BROWSER (CRITICAL!)
|
|
57
|
-
// Controller/Route ada di kedalaman tertentu. Core ada di /dist/core.
|
|
58
|
-
// Kita harus rewrite "from 'lumpiajs'" menjadi path relative yang benar ke core.
|
|
59
|
-
// Misal dari /app/controllers (depth 2) -> '../../core/lumpia.js'
|
|
60
|
-
|
|
61
|
-
// Hitung relative backstep
|
|
62
|
-
let backSteps = '../'.repeat(rootDepth);
|
|
63
|
-
// Karena structure dist:
|
|
64
|
-
// dist/routes/web.js (depth 1) -> butuh '../core/lumpia.js'
|
|
65
|
-
// dist/app/controllers/Home.js (depth 2) -> butuh '../../core/lumpia.js'
|
|
66
|
-
// Jadi logicnya benar.
|
|
67
|
-
|
|
68
|
-
content = content.replace(/from\s+['"]lumpiajs['"]/g, `from '${backSteps}../core/lumpia.js'`);
|
|
69
|
-
|
|
70
53
|
content = transpileSemarangan(content);
|
|
71
54
|
const finalDest = destPath.replace('.lmp', '.js');
|
|
72
55
|
fs.writeFileSync(finalDest, content);
|
|
@@ -82,15 +65,24 @@ export class Controller {
|
|
|
82
65
|
constructor() { this.params={}; }
|
|
83
66
|
async tampil(viewName, data={}) {
|
|
84
67
|
try {
|
|
85
|
-
// Fetch view relative to root
|
|
86
|
-
//
|
|
87
|
-
//
|
|
68
|
+
// Fetch view must be relative to root of dist.
|
|
69
|
+
// Since we use Import Map, we are modernized.
|
|
70
|
+
// Better to assume deployment at known base or use explicit relative if possible.
|
|
71
|
+
// But fetch('views/...') is relative to current page URL.
|
|
72
|
+
// If current page is /2025/lumpiajs/produk, fetch('views/home.lmp') -> /2025/lumpiajs/views/home.lmp (Works!)
|
|
73
|
+
// Wait, if /produk is virtual path, browser thinks we are in folder /lumpiajs/.
|
|
74
|
+
// If /produk/detail/1 -> browser thinks folder is /lumpiajs/produk/detail/.
|
|
75
|
+
// Fetch 'views/...' will fail (404).
|
|
88
76
|
|
|
89
|
-
|
|
77
|
+
// SOLUSI: Selalu fetch dari ROOT relative (pakai Window Base yang kita set di index.html)
|
|
78
|
+
const basePath = window.LUMPIA_BASE || '';
|
|
79
|
+
// Ensure slash
|
|
80
|
+
const url = basePath + '/views/' + viewName + '.lmp';
|
|
81
|
+
|
|
82
|
+
const res = await fetch(url.replace('//', '/')); // simple cleanup
|
|
90
83
|
if(!res.ok) throw new Error('View 404: ' + viewName);
|
|
91
84
|
let html = await res.text();
|
|
92
85
|
|
|
93
|
-
// ... (Parsing logic same as before) ...
|
|
94
86
|
const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
|
|
95
87
|
const matchIsi = html.match(/<isi>([\\s\\S]*?)<\\/isi>/);
|
|
96
88
|
const matchKlambi = html.match(/<klambi>([\\s\\S]*?)<\\/klambi>/);
|
|
@@ -130,40 +122,44 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
130
122
|
<meta charset="UTF-8">
|
|
131
123
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
132
124
|
<title>LumpiaJS</title>
|
|
133
|
-
<!--
|
|
134
|
-
<script>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
<!-- IMPORT MAP: KUNCI SUKSES LOAD MODULE DI SUBFOLDER -->
|
|
126
|
+
<script type="importmap">
|
|
127
|
+
{
|
|
128
|
+
"imports": {
|
|
129
|
+
"lumpiajs": "./core/lumpia.js"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
139
132
|
</script>
|
|
140
133
|
<link rel="stylesheet" href="public/css/style.css">
|
|
134
|
+
<script>
|
|
135
|
+
// DETEKSI BASE PATH DENGAN LEBIH AMAN (Via current script location fallback)
|
|
136
|
+
// Kita ambil lokasi index.html ini sebagai 'Source of Truth' root aplikasi.
|
|
137
|
+
window.LUMPIA_BASE = window.location.pathname
|
|
138
|
+
.replace(new RegExp('/index\\\\.html$'), '')
|
|
139
|
+
.replace(new RegExp('/$'), '');
|
|
140
|
+
// console.log('Base:', window.LUMPIA_BASE);
|
|
141
|
+
</script>
|
|
141
142
|
</head>
|
|
142
143
|
<body>
|
|
143
144
|
<div id="app">Loading...</div>
|
|
144
145
|
|
|
145
146
|
<script type="module">
|
|
146
|
-
// Import
|
|
147
|
+
// Import Map makes this work:
|
|
147
148
|
import { Jalan } from './routes/web.js';
|
|
148
149
|
|
|
149
150
|
async function navigate() {
|
|
150
|
-
|
|
151
|
-
// Simple approach: Match pathname against routes.
|
|
152
|
-
// If app is in /my-app/, and route is '/', browser pathname is '/my-app/'.
|
|
153
|
-
// WE NEED TO STRIP THE BASE PATH.
|
|
151
|
+
let currentPath = window.location.pathname;
|
|
154
152
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (basePath && currentPath.startsWith(basePath)) {
|
|
159
|
-
currentPath = currentPath.substring(basePath.length);
|
|
153
|
+
// Normalize: Strip Base Path
|
|
154
|
+
if (window.LUMPIA_BASE && currentPath.startsWith(window.LUMPIA_BASE)) {
|
|
155
|
+
currentPath = currentPath.substring(window.LUMPIA_BASE.length);
|
|
160
156
|
}
|
|
161
|
-
if (!currentPath) currentPath = '/';
|
|
157
|
+
if (!currentPath || currentPath === '/index.html') currentPath = '/';
|
|
162
158
|
|
|
163
159
|
let m = null, args = {};
|
|
164
160
|
for(let r of Jalan.routes) {
|
|
165
|
-
|
|
166
|
-
let reg = new RegExp(
|
|
161
|
+
let regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
|
|
162
|
+
let reg = new RegExp(regexStr);
|
|
167
163
|
let res = currentPath.match(reg);
|
|
168
164
|
if(res){ m=r; args=res.slice(1); break; }
|
|
169
165
|
}
|
|
@@ -171,28 +167,35 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
171
167
|
if(m) {
|
|
172
168
|
const [cName, fName] = m.a.split('@');
|
|
173
169
|
try {
|
|
174
|
-
// Import Relative to index.html
|
|
170
|
+
// Import Relative to index.html (selalu root dist)
|
|
175
171
|
const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
|
|
176
172
|
const C = mod.default; const i = new C(); i.params=args;
|
|
177
173
|
await i[fName](...args);
|
|
178
174
|
} catch(e) {
|
|
179
175
|
console.error(e);
|
|
180
|
-
document.getElementById('app').innerHTML='<h1>Error
|
|
176
|
+
document.getElementById('app').innerHTML='<h1>Error</h1><pre>'+e.message+'</pre>';
|
|
181
177
|
}
|
|
182
178
|
} else {
|
|
183
|
-
document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>
|
|
179
|
+
document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Route: '+currentPath+'</p>';
|
|
184
180
|
}
|
|
185
181
|
}
|
|
186
182
|
|
|
187
183
|
window.addEventListener('popstate', navigate);
|
|
184
|
+
|
|
185
|
+
// SPA LINK INTERCEPTOR (SAT SET MODE)
|
|
188
186
|
document.body.addEventListener('click', e => {
|
|
189
|
-
|
|
187
|
+
// Cari elemen <a> (bisa jadi klik di span dalam a)
|
|
188
|
+
const link = e.target.closest('a');
|
|
189
|
+
if (link && link.href.startsWith(window.location.origin)) {
|
|
190
|
+
// Ignore if target blank or control click
|
|
191
|
+
if (link.target === '_blank' || e.ctrlKey) return;
|
|
192
|
+
|
|
190
193
|
e.preventDefault();
|
|
191
|
-
|
|
192
|
-
history.pushState(null,'',e.target.href);
|
|
194
|
+
history.pushState(null, '', link.href);
|
|
193
195
|
navigate();
|
|
194
196
|
}
|
|
195
197
|
});
|
|
198
|
+
|
|
196
199
|
navigate();
|
|
197
200
|
</script>
|
|
198
201
|
</body>
|
|
@@ -203,7 +206,7 @@ export function buildProject() {
|
|
|
203
206
|
const dist = path.join(root, 'dist');
|
|
204
207
|
const config = loadConfig(root);
|
|
205
208
|
|
|
206
|
-
console.log('🍳 Goreng Project (Mode
|
|
209
|
+
console.log('🍳 Goreng Project (Mode Import Map + Sat Set SPA)...');
|
|
207
210
|
if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
|
|
208
211
|
fs.mkdirSync(dist);
|
|
209
212
|
|
|
@@ -212,10 +215,8 @@ export function buildProject() {
|
|
|
212
215
|
spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
|
|
213
216
|
}
|
|
214
217
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
processDirectory(path.join(root, 'app'), path.join(dist, 'app'), 2); // app/controllers/X -> depth 2 from dist
|
|
218
|
-
processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'), 1); // routes/X -> depth 1 from dist
|
|
218
|
+
processDirectory(path.join(root, 'app'), path.join(dist, 'app'), 2);
|
|
219
|
+
processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'), 1);
|
|
219
220
|
|
|
220
221
|
fs.cpSync(path.join(root, 'views'), path.join(dist, 'views'), { recursive: true });
|
|
221
222
|
if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
|
|
@@ -224,12 +225,7 @@ export function buildProject() {
|
|
|
224
225
|
fs.writeFileSync(path.join(dist, 'core', 'lumpia.js'), browserCore);
|
|
225
226
|
fs.writeFileSync(path.join(dist, 'index.html'), indexHtml);
|
|
226
227
|
|
|
227
|
-
// .htaccess for "Subfolder Friendly" SPA?
|
|
228
|
-
// RewriteRule ^index\.html$ - [L]
|
|
229
|
-
// RewriteCond %{REQUEST_FILENAME} !-f
|
|
230
|
-
// RewriteRule . index.html [L]
|
|
231
|
-
// Note: Removed leading slash in redirect destination to be relative
|
|
232
228
|
fs.writeFileSync(path.join(dist, '.htaccess'), `<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteRule ^index\\.html$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . index.html [L]\n</IfModule>`);
|
|
233
229
|
|
|
234
|
-
console.log('✅ Mateng!
|
|
230
|
+
console.log('✅ Mateng! Siap dihidangkan di folder manapun.');
|
|
235
231
|
}
|
package/lib/commands/create.js
CHANGED
|
@@ -119,5 +119,10 @@ export async function createProject(parameter) {
|
|
|
119
119
|
fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), '/* CSS */');
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
console.log(`✅ Project "${projectName}"
|
|
122
|
+
console.log(`✅ Project "${projectName}" Siap!`);
|
|
123
|
+
console.log('-------------------------------------------');
|
|
124
|
+
console.log(`👉 cd ${projectName}`);
|
|
125
|
+
console.log(`👉 npm install <-- OJO LALI IKI! (Wajib)`);
|
|
126
|
+
console.log(`👉 lumpia kukus`);
|
|
127
|
+
console.log('-------------------------------------------');
|
|
123
128
|
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import http from 'http';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
|
-
import { loadEnv } from '../core/Env.js';
|
|
6
|
-
import { loadConfig } from '../core/Config.js';
|
|
7
|
-
|
|
1
|
+
// ... (Same KAMUS & Imports) ...
|
|
8
2
|
const KAMUS = [
|
|
9
3
|
{ from: /paten\s/g, to: 'const ' },
|
|
10
4
|
{ from: /ono\s/g, to: 'let ' },
|
|
@@ -22,6 +16,13 @@ const KAMUS = [
|
|
|
22
16
|
{ from: /->/g, to: '.' }
|
|
23
17
|
];
|
|
24
18
|
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import http from 'http';
|
|
22
|
+
import { spawn } from 'child_process';
|
|
23
|
+
import { loadEnv } from '../core/Env.js';
|
|
24
|
+
import { loadConfig } from '../core/Config.js';
|
|
25
|
+
|
|
25
26
|
function matchRoute(definedRoute, method, pathname) {
|
|
26
27
|
if (definedRoute.method !== method) return null;
|
|
27
28
|
if (definedRoute.path === pathname) return { params: {} };
|
|
@@ -57,18 +58,8 @@ async function loadLumpiaModule(filePath) {
|
|
|
57
58
|
const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
|
|
58
59
|
const destPath = path.join(cacheDir, flatName);
|
|
59
60
|
|
|
60
|
-
// INI DEV SERVER: Path ke Core Library bisa tetap absolute (package) atau relative
|
|
61
|
-
// Karena ini dijalankan di Node.js, 'lumpiajs' masih resolve ke node_modules dengan benar.
|
|
62
|
-
// TAPI content user (seperti `from 'lumpiajs'`) harus dijaga tetap valid buat Node.
|
|
63
|
-
// Jika user nulis `from 'lumpiajs'`, biarkan.
|
|
64
|
-
// Hanya transpile Semarangan -> JS.
|
|
65
|
-
|
|
66
61
|
let transcoded = originalContent;
|
|
67
|
-
|
|
68
|
-
// DEV MODE: Keep .lmp imports as .js imports
|
|
69
62
|
transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
70
|
-
// DEV MODE: Do NOT rewrite 'lumpiajs' import because Node handles it via package.json resolution
|
|
71
|
-
|
|
72
63
|
transcoded = transcoded.split('\n').map(line => {
|
|
73
64
|
let l = line;
|
|
74
65
|
if (l.trim().startsWith('//')) return l;
|
|
@@ -77,14 +68,33 @@ async function loadLumpiaModule(filePath) {
|
|
|
77
68
|
}).join('\n');
|
|
78
69
|
|
|
79
70
|
fs.writeFileSync(destPath, transcoded);
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
|
|
72
|
+
// Safety check for user forgetting npm install
|
|
73
|
+
try {
|
|
74
|
+
const module = await import('file://' + destPath + '?t=' + Date.now());
|
|
75
|
+
return module;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND' && e.message.includes('lumpiajs')) {
|
|
78
|
+
console.error('\n❌ ERROR: Gagal load "lumpiajs".');
|
|
79
|
+
console.error('💡 Sampeyan wis njalanke "npm install" durung?');
|
|
80
|
+
console.error(' Ketik: npm install\n');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
export async function serveProject() {
|
|
85
88
|
const root = process.cwd();
|
|
86
89
|
const routesFile = path.join(root, 'routes', 'web.lmp');
|
|
87
|
-
if (!fs.existsSync(routesFile)) return console.log("❌ Missing routes/web.lmp");
|
|
90
|
+
if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project (Missing routes/web.lmp)");
|
|
91
|
+
|
|
92
|
+
// Pre-check node_modules
|
|
93
|
+
if (!fs.existsSync(path.join(root, 'node_modules'))) {
|
|
94
|
+
console.log("⚠️ Waduh, folder 'node_modules' ra ono!");
|
|
95
|
+
console.log(" Tolong jalanne 'npm install' sek ya, Lur.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
88
98
|
|
|
89
99
|
const env = loadEnv(root);
|
|
90
100
|
const config = loadConfig(root);
|
|
@@ -94,18 +104,8 @@ export async function serveProject() {
|
|
|
94
104
|
await loadLumpiaModule(routesFile);
|
|
95
105
|
const activeRoutes = global.LumpiaRouter || [];
|
|
96
106
|
console.log(`🛣️ Routes registered: ${activeRoutes.length}`);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Di Dev Server (Node), kita merender HTML string langsung (SSR).
|
|
100
|
-
// Jadi tidak ada masalah "loading..." dari sisi client-side fetch.
|
|
101
|
-
// Jika dev server loading terus, itu karena res.end() tidak terpanggil.
|
|
102
|
-
|
|
103
|
-
// Periksa controller user: apakah memanggil `balek` (return)?
|
|
104
|
-
// Jika user lupa return, function return undefined.
|
|
105
|
-
// Logic di bawah menangani undefined result -> `res.end(String(result))` -> `undefined`.
|
|
106
|
-
// Browser akan menampilkan "undefined". Bukan loading terus.
|
|
107
|
-
// Loading terus biasanya hang.
|
|
108
|
-
|
|
107
|
+
console.log(`✨ Mode: Semarangan (paten, ono, fungsi, aku)`);
|
|
108
|
+
|
|
109
109
|
const server = http.createServer(async (req, res) => {
|
|
110
110
|
const method = req.method;
|
|
111
111
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
@@ -149,7 +149,6 @@ export async function serveProject() {
|
|
|
149
149
|
res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
|
|
150
150
|
res.end(result.content);
|
|
151
151
|
} else {
|
|
152
|
-
// FIX: Jika result null/undefined (lupa return balek), kirim response kosong atau error
|
|
153
152
|
if(result === undefined) res.end('');
|
|
154
153
|
else res.end(String(result));
|
|
155
154
|
}
|
|
@@ -161,6 +160,8 @@ export async function serveProject() {
|
|
|
161
160
|
}
|
|
162
161
|
});
|
|
163
162
|
const port = 3000;
|
|
164
|
-
server.listen(port, () => console.log(`🚀 Server
|
|
165
|
-
} catch (e) {
|
|
163
|
+
server.listen(port, () => console.log(`🚀 Server: http://localhost:3000`));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
if(e.code !== 'ERR_MODULE_NOT_FOUND') console.error(e);
|
|
166
|
+
}
|
|
166
167
|
}
|