lumpiajs 1.0.10 → 1.0.11
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 +130 -68
- package/lib/commands/serve.js +33 -15
- package/package.json +1 -1
package/lib/commands/build.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
// KAMUS
|
|
3
|
-
//
|
|
2
|
+
// ... (KAMUS dan Helper functions sama seperti sebelumnya) ...
|
|
3
|
+
// Saya akan fokus update browserCore dan indexHtml
|
|
4
|
+
|
|
4
5
|
const KAMUS = [
|
|
5
6
|
{ from: /paten\s/g, to: 'const ' },
|
|
6
7
|
{ from: /ono\s/g, to: 'let ' },
|
|
7
|
-
{ from: /fungsi\s/g, to: 'function ' },
|
|
8
|
+
{ from: /fungsi\s/g, to: 'function ' },
|
|
8
9
|
{ from: /nteni\s/g, to: 'await ' },
|
|
9
10
|
{ from: /mengko\s/g, to: 'async ' },
|
|
10
11
|
{ from: /balek\s/g, to: 'return ' },
|
|
@@ -13,56 +14,62 @@ const KAMUS = [
|
|
|
13
14
|
{ from: /jajal\s*\{/g, to: 'try {' },
|
|
14
15
|
{ from: /gagal\s*\(/g, to: 'catch(' },
|
|
15
16
|
{ from: /kandani\(/g, to: 'console.log(' },
|
|
16
|
-
{ from: /aku->/g, to: 'this.' },
|
|
17
|
-
{ from: /aku\./g, to: 'this.' },
|
|
17
|
+
{ from: /aku->/g, to: 'this.' },
|
|
18
|
+
{ from: /aku\./g, to: 'this.' },
|
|
18
19
|
{ from: /->/g, to: '.' }
|
|
19
20
|
];
|
|
20
21
|
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { spawnSync } from 'child_process';
|
|
25
|
+
import { loadConfig } from '../core/Config.js';
|
|
26
|
+
|
|
21
27
|
function transpileSemarangan(content) {
|
|
22
28
|
let code = content;
|
|
23
|
-
|
|
24
|
-
// Import .lmp -> .js
|
|
25
29
|
code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
26
|
-
code = code.replace(/from\s+['"]lumpiajs['"]/g, "from '
|
|
30
|
+
code = code.replace(/from\s+['"]lumpiajs['"]/g, "from './core/lumpia.js'"); // FIX: Relative import to core
|
|
31
|
+
|
|
32
|
+
// Handle imports agar path-nya relatif browser freindly
|
|
33
|
+
// Jika import './...' -> aman.
|
|
34
|
+
// Jika import '/...' -> bahaya kalau di subfolder.
|
|
27
35
|
|
|
28
|
-
// Safe Replace Logic
|
|
29
|
-
// We must handle this carefully to not break valid JS code if mixed.
|
|
30
|
-
// 'aku' is common word, but as keyword usually followed by -> or .
|
|
31
|
-
|
|
32
36
|
code = code.split('\n').map(line => {
|
|
33
37
|
let l = line;
|
|
34
38
|
if (l.trim().startsWith('//')) return l;
|
|
35
|
-
|
|
36
|
-
KAMUS.forEach(rule => {
|
|
37
|
-
l = l.replace(rule.from, rule.to);
|
|
38
|
-
});
|
|
39
|
+
KAMUS.forEach(rule => { l = l.replace(rule.from, rule.to); });
|
|
39
40
|
return l;
|
|
40
41
|
}).join('\n');
|
|
41
|
-
|
|
42
42
|
return code;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
// ... I will copy the previous logic but update browserCore as well.
|
|
47
|
-
|
|
48
|
-
import fs from 'fs';
|
|
49
|
-
import path from 'path';
|
|
50
|
-
import { spawnSync } from 'child_process';
|
|
51
|
-
import { loadConfig } from '../core/Config.js';
|
|
52
|
-
|
|
53
|
-
function processDirectory(source, dest) {
|
|
45
|
+
function processDirectory(source, dest, rootDepth = 0) {
|
|
54
46
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
55
47
|
fs.readdirSync(source).forEach(item => {
|
|
56
48
|
const srcPath = path.join(source, item);
|
|
57
49
|
const destPath = path.join(dest, item);
|
|
58
50
|
if (fs.statSync(srcPath).isDirectory()) {
|
|
59
|
-
processDirectory(srcPath, destPath);
|
|
51
|
+
processDirectory(srcPath, destPath, rootDepth + 1);
|
|
60
52
|
} else {
|
|
61
53
|
if (item.endsWith('.lmp') || item.endsWith('.js')) {
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
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
|
+
content = transpileSemarangan(content);
|
|
64
71
|
const finalDest = destPath.replace('.lmp', '.js');
|
|
65
|
-
fs.writeFileSync(finalDest,
|
|
72
|
+
fs.writeFileSync(finalDest, content);
|
|
66
73
|
} else {
|
|
67
74
|
fs.copyFileSync(srcPath, destPath);
|
|
68
75
|
}
|
|
@@ -75,10 +82,15 @@ export class Controller {
|
|
|
75
82
|
constructor() { this.params={}; }
|
|
76
83
|
async tampil(viewName, data={}) {
|
|
77
84
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
// Fetch view relative to root (we assume <base> tag is set or we detect root)
|
|
86
|
+
// Biar aman, kita cari 'views' relatif terhadap posisi script ini? Gak bisa.
|
|
87
|
+
// Kita asumsi deployment di root atau subfolder dengan <base> tag HTML yang benar.
|
|
88
|
+
|
|
89
|
+
const res = await fetch('views/'+viewName+'.lmp');
|
|
90
|
+
if(!res.ok) throw new Error('View 404: ' + viewName);
|
|
80
91
|
let html = await res.text();
|
|
81
92
|
|
|
93
|
+
// ... (Parsing logic same as before) ...
|
|
82
94
|
const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
|
|
83
95
|
const matchIsi = html.match(/<isi>([\\s\\S]*?)<\\/isi>/);
|
|
84
96
|
const matchKlambi = html.match(/<klambi>([\\s\\S]*?)<\\/klambi>/);
|
|
@@ -97,7 +109,6 @@ export class Controller {
|
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
if(script) {
|
|
100
|
-
// Client-side transpile
|
|
101
112
|
const kamus = [
|
|
102
113
|
{f:/paten\\s/g,t:'const '}, {f:/ono\\s/g,t:'let '}, {f:/fungsi\\s/g,t:'function '},
|
|
103
114
|
{f:/nteni\\s/g,t:'await '}, {f:/mengko\\s/g,t:'async '}, {f:/balek\\s/g,t:'return '},
|
|
@@ -105,7 +116,7 @@ export class Controller {
|
|
|
105
116
|
{f:/aku->/g,t:'this.'}, {f:/aku\\./g,t:'this.'}
|
|
106
117
|
];
|
|
107
118
|
kamus.forEach(r => script = script.replace(r.f, r.t));
|
|
108
|
-
new Function(script)();
|
|
119
|
+
try { new Function(script)(); } catch(e){ console.error(e); }
|
|
109
120
|
}
|
|
110
121
|
} catch(e) { document.getElementById('app').innerHTML = e.message; }
|
|
111
122
|
}
|
|
@@ -115,42 +126,84 @@ export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
|
|
|
115
126
|
|
|
116
127
|
const indexHtml = `<!DOCTYPE html>
|
|
117
128
|
<html lang="en">
|
|
118
|
-
<head
|
|
119
|
-
<
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
<head>
|
|
130
|
+
<meta charset="UTF-8">
|
|
131
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
132
|
+
<title>LumpiaJS</title>
|
|
133
|
+
<!-- BASE HREF AUTOMATIC DETECTION MAGIC -->
|
|
134
|
+
<script>
|
|
135
|
+
// Set base href to current folder so imports work in subfolders
|
|
136
|
+
// But for SPA routing, this might fight with History API.
|
|
137
|
+
// Let's rely on relative imports './' everywhere.
|
|
138
|
+
// document.write("<base href='" + document.location.pathname.replace(/index\\.html$/,'') + "' />");
|
|
139
|
+
</script>
|
|
140
|
+
<link rel="stylesheet" href="public/css/style.css">
|
|
141
|
+
</head>
|
|
142
|
+
<body>
|
|
143
|
+
<div id="app">Loading...</div>
|
|
144
|
+
|
|
145
|
+
<script type="module">
|
|
146
|
+
// Import paths WITHOUT leading slash to be relative to index.html
|
|
147
|
+
import { Jalan } from './routes/web.js';
|
|
148
|
+
|
|
149
|
+
async function navigate() {
|
|
150
|
+
// Normalize path: Remove base folder logic needed?
|
|
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.
|
|
154
|
+
|
|
155
|
+
// Hacky detection of Base Path (where index.html sits)
|
|
156
|
+
const basePath = window.location.pathname.replace(/\/index\.html$/, '').replace(/\/$/, '');
|
|
157
|
+
let currentPath = window.location.pathname.replace(/\/index\.html$/, '');
|
|
158
|
+
if (basePath && currentPath.startsWith(basePath)) {
|
|
159
|
+
currentPath = currentPath.substring(basePath.length);
|
|
160
|
+
}
|
|
161
|
+
if (!currentPath) currentPath = '/'; // Root
|
|
162
|
+
|
|
163
|
+
let m = null, args = {};
|
|
164
|
+
for(let r of Jalan.routes) {
|
|
165
|
+
// Route defined as '/home'
|
|
166
|
+
let reg = new RegExp('^'+r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)')+'$');
|
|
167
|
+
let res = currentPath.match(reg);
|
|
168
|
+
if(res){ m=r; args=res.slice(1); break; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(m) {
|
|
172
|
+
const [cName, fName] = m.a.split('@');
|
|
173
|
+
try {
|
|
174
|
+
// Import Relative to index.html
|
|
175
|
+
const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
|
|
176
|
+
const C = mod.default; const i = new C(); i.params=args;
|
|
177
|
+
await i[fName](...args);
|
|
178
|
+
} catch(e) {
|
|
179
|
+
console.error(e);
|
|
180
|
+
document.getElementById('app').innerHTML='<h1>Error Loading Controller</h1><pre>'+e.message+'</pre>';
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Path: '+currentPath+'</p>';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
window.addEventListener('popstate', navigate);
|
|
188
|
+
document.body.addEventListener('click', e => {
|
|
189
|
+
if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
// Push relative path?
|
|
192
|
+
history.pushState(null,'',e.target.href);
|
|
193
|
+
navigate();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
navigate();
|
|
197
|
+
</script>
|
|
198
|
+
</body>
|
|
199
|
+
</html>`;
|
|
147
200
|
|
|
148
201
|
export function buildProject() {
|
|
149
202
|
const root = process.cwd();
|
|
150
203
|
const dist = path.join(root, 'dist');
|
|
151
204
|
const config = loadConfig(root);
|
|
152
205
|
|
|
153
|
-
console.log('🍳 Goreng Project (Mode Bahasa Semarangan)...');
|
|
206
|
+
console.log('🍳 Goreng Project (Mode Bahasa Semarangan + Relative Paths)...');
|
|
154
207
|
if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
|
|
155
208
|
fs.mkdirSync(dist);
|
|
156
209
|
|
|
@@ -159,15 +212,24 @@ export function buildProject() {
|
|
|
159
212
|
spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
|
|
160
213
|
}
|
|
161
214
|
|
|
162
|
-
|
|
163
|
-
|
|
215
|
+
// Process using Relative Path Logic
|
|
216
|
+
// We pass 'rootDepth' to calculate imports to 'core'
|
|
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
|
|
219
|
+
|
|
164
220
|
fs.cpSync(path.join(root, 'views'), path.join(dist, 'views'), { recursive: true });
|
|
165
221
|
if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
|
|
166
222
|
|
|
167
223
|
fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
|
|
168
224
|
fs.writeFileSync(path.join(dist, 'core', 'lumpia.js'), browserCore);
|
|
169
225
|
fs.writeFileSync(path.join(dist, 'index.html'), indexHtml);
|
|
170
|
-
|
|
226
|
+
|
|
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
|
+
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>`);
|
|
171
233
|
|
|
172
|
-
console.log('✅ Mateng! (Support
|
|
234
|
+
console.log('✅ Mateng! (Support Subfolder Hosting)');
|
|
173
235
|
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
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';
|
|
1
7
|
|
|
2
|
-
// KAMUS SEMARANGAN (Regex Replacement Rules)
|
|
3
8
|
const KAMUS = [
|
|
4
9
|
{ from: /paten\s/g, to: 'const ' },
|
|
5
10
|
{ from: /ono\s/g, to: 'let ' },
|
|
@@ -17,15 +22,6 @@ const KAMUS = [
|
|
|
17
22
|
{ from: /->/g, to: '.' }
|
|
18
23
|
];
|
|
19
24
|
|
|
20
|
-
import fs from 'fs';
|
|
21
|
-
import path from 'path';
|
|
22
|
-
import http from 'http';
|
|
23
|
-
import { spawn } from 'child_process';
|
|
24
|
-
import { loadEnv } from '../core/Env.js';
|
|
25
|
-
import { loadConfig } from '../core/Config.js';
|
|
26
|
-
|
|
27
|
-
// ... Same helper functions (matchRoute, backgroundProcess, startTailwindWatcher) ...
|
|
28
|
-
|
|
29
25
|
function matchRoute(definedRoute, method, pathname) {
|
|
30
26
|
if (definedRoute.method !== method) return null;
|
|
31
27
|
if (definedRoute.path === pathname) return { params: {} };
|
|
@@ -61,9 +57,18 @@ async function loadLumpiaModule(filePath) {
|
|
|
61
57
|
const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
|
|
62
58
|
const destPath = path.join(cacheDir, flatName);
|
|
63
59
|
|
|
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
|
+
|
|
64
66
|
let transcoded = originalContent;
|
|
67
|
+
|
|
68
|
+
// DEV MODE: Keep .lmp imports as .js imports
|
|
65
69
|
transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
66
|
-
|
|
70
|
+
// DEV MODE: Do NOT rewrite 'lumpiajs' import because Node handles it via package.json resolution
|
|
71
|
+
|
|
67
72
|
transcoded = transcoded.split('\n').map(line => {
|
|
68
73
|
let l = line;
|
|
69
74
|
if (l.trim().startsWith('//')) return l;
|
|
@@ -89,8 +94,18 @@ export async function serveProject() {
|
|
|
89
94
|
await loadLumpiaModule(routesFile);
|
|
90
95
|
const activeRoutes = global.LumpiaRouter || [];
|
|
91
96
|
console.log(`🛣️ Routes registered: ${activeRoutes.length}`);
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
|
|
98
|
+
// Cek URL, jika '/' tapi masih loading, itu karena logic view belum sempurna?
|
|
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
|
+
|
|
94
109
|
const server = http.createServer(async (req, res) => {
|
|
95
110
|
const method = req.method;
|
|
96
111
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
@@ -134,7 +149,9 @@ export async function serveProject() {
|
|
|
134
149
|
res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
|
|
135
150
|
res.end(result.content);
|
|
136
151
|
} else {
|
|
137
|
-
|
|
152
|
+
// FIX: Jika result null/undefined (lupa return balek), kirim response kosong atau error
|
|
153
|
+
if(result === undefined) res.end('');
|
|
154
|
+
else res.end(String(result));
|
|
138
155
|
}
|
|
139
156
|
} catch (e) {
|
|
140
157
|
res.writeHead(500); res.end(`<pre>${e.stack}</pre>`);
|
|
@@ -143,6 +160,7 @@ export async function serveProject() {
|
|
|
143
160
|
res.writeHead(404); res.end('404 Not Found');
|
|
144
161
|
}
|
|
145
162
|
});
|
|
146
|
-
|
|
163
|
+
const port = 3000;
|
|
164
|
+
server.listen(port, () => console.log(`🚀 Server running at http://localhost:${port}`));
|
|
147
165
|
} catch (e) { console.error(e); }
|
|
148
166
|
}
|