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.
@@ -1,10 +1,11 @@
1
1
 
2
- // KAMUS SEMARANGAN (Regex Replacement Rules)
3
- // Urutan penting! Keyword panjang dulu baru pendek.
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 ' }, // Changed from 'gawe'
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.' }, // NEW: this -> aku
17
- { from: /aku\./g, to: 'this.' }, // Support dot notation too
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 '/core/lumpia.js'");
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
- // ... Rest of build.js code (processDirectory, browserCore, indexHtml, buildProject) ...
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
- const content = fs.readFileSync(srcPath, 'utf8');
63
- const jsContent = transpileSemarangan(content);
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, jsContent);
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
- const res = await fetch('/views/'+viewName+'.lmp');
79
- if(!res.ok) throw new Error('View 404');
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><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>LumpiaJS</title><link rel="stylesheet" href="/public/css/style.css"></head>
119
- <body><div id="app">Loading...</div>
120
- <script type="module">
121
- import { Jalan } from '/routes/web.js';
122
- async function navigate() {
123
- const p = window.location.pathname;
124
- let m = null, args = {};
125
- for(let r of Jalan.routes) {
126
- let reg = new RegExp('^'+r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)')+'$');
127
- let res = p.match(reg);
128
- if(res){ m=r; args=res.slice(1); break; }
129
- }
130
- if(m) {
131
- const [cName, fName] = m.a.split('@');
132
- try {
133
- const mod = await import('/app/controllers/'+cName+'.js?'+Date.now());
134
- const C = mod.default; const i = new C(); i.params=args;
135
- await i[fName](...args);
136
- } catch(e) { console.error(e); document.getElementById('app').innerHTML='Error'; }
137
- } else { document.getElementById('app').innerHTML='404'; }
138
- }
139
- window.addEventListener('popstate', navigate);
140
- document.body.addEventListener('click', e => {
141
- if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
142
- e.preventDefault(); history.pushState(null,'',e.target.href); navigate();
143
- }
144
- });
145
- navigate();
146
- </script></body></html>`;
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
- processDirectory(path.join(root, 'app'), path.join(dist, 'app'));
163
- processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'));
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
- fs.writeFileSync(path.join(dist, '.htaccess'), `<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.html$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.html [L]\n</IfModule>`);
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 Bahasa Semarangan)');
234
+ console.log('✅ Mateng! (Support Subfolder Hosting)');
173
235
  }
@@ -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
- console.log(`✨ Mode: Semarangan (paten, ono, fungsi, aku)`);
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
- res.end(String(result));
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
- server.listen(3000, () => console.log('🚀 Server: http://localhost:3000'));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",