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.
@@ -1,7 +1,5 @@
1
1
 
2
- // ... (KAMUS dan Helper functions sama seperti sebelumnya) ...
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
- 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.
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 (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.
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
- const res = await fetch('views/'+viewName+'.lmp');
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
- <!-- 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$/,'') + "' />");
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 paths WITHOUT leading slash to be relative to index.html
147
+ // Import Map makes this work:
147
148
  import { Jalan } from './routes/web.js';
148
149
 
149
150
  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.
151
+ let currentPath = window.location.pathname;
154
152
 
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);
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 = '/'; // Root
157
+ if (!currentPath || currentPath === '/index.html') currentPath = '/';
162
158
 
163
159
  let m = null, args = {};
164
160
  for(let r of Jalan.routes) {
165
- // Route defined as '/home'
166
- let reg = new RegExp('^'+r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)')+'$');
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 Loading Controller</h1><pre>'+e.message+'</pre>';
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>Path: '+currentPath+'</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
- if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
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
- // Push relative path?
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 Bahasa Semarangan + Relative Paths)...');
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
- // 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
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! (Support Subfolder Hosting)');
230
+ console.log('✅ Mateng! Siap dihidangkan di folder manapun.');
235
231
  }
@@ -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}" Ready!`);
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
  }
@@ -1,10 +1,4 @@
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';
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
- const module = await import('file://' + destPath + '?t=' + Date.now());
81
- return module;
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
- // 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
-
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 running at http://localhost:${port}`));
165
- } catch (e) { console.error(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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",