lumpiajs 1.0.12 → 1.0.14

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.
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "be-tester",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "lumpia kukus",
7
+ "build": "lumpia goreng"
8
+ },
9
+ "dependencies": {
10
+ "lumpiajs": "latest"
11
+ },
12
+ "devDependencies": {
13
+ "tailwindcss": "^3.4.0"
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}
@@ -0,0 +1,4 @@
1
+ import { Jalan } from 'lumpiajs';
2
+
3
+ Jalan->get('/', 'HomeController@index');
4
+ Jalan->get('/toko', 'ProductController@tampilBarang');
@@ -0,0 +1 @@
1
+ module.exports={content:["./views/**/*.lmp"],theme:{extend:{}},plugins:[]}
@@ -0,0 +1,12 @@
1
+ <lump>
2
+ <klambi>
3
+ h1 { color: #d35400; text-align: center; }
4
+ </klambi>
5
+ <kulit>
6
+ <div style="text-align: center; font-family: sans-serif; margin-top: 50px;">
7
+ <h1>{{ message }}</h1>
8
+ <p>{{ info }}</p>
9
+ <a href="/toko">Cek Toko Sebelah</a>
10
+ </div>
11
+ </kulit>
12
+ </lump>
@@ -0,0 +1,9 @@
1
+ <lump>
2
+ <kulit>
3
+ <div style="padding: 20px; font-family: sans-serif;">
4
+ <h1>Daftar Barang</h1>
5
+ <ul>{{ daftar }}</ul>
6
+ <a href="/">Balik Omah</a>
7
+ </div>
8
+ </kulit>
9
+ </lump>
@@ -1,5 +1,5 @@
1
1
 
2
- // ... (KAMUS & Imports same as before) ...
2
+ // ... (KAMUS same) ...
3
3
  const KAMUS = [
4
4
  { from: /paten\s/g, to: 'const ' },
5
5
  { from: /ono\s/g, to: 'let ' },
@@ -25,8 +25,6 @@ import { loadConfig } from '../core/Config.js';
25
25
  function transpileSemarangan(content) {
26
26
  let code = content;
27
27
  code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
28
- code = code.replace(/from\s+['"]lumpiajs['"]/g, "from './core/lumpia.js'");
29
-
30
28
  code = code.split('\n').map(line => {
31
29
  let l = line;
32
30
  if (l.trim().startsWith('//')) return l;
@@ -46,18 +44,6 @@ function processDirectory(source, dest, rootDepth = 0) {
46
44
  } else {
47
45
  if (item.endsWith('.lmp') || item.endsWith('.js')) {
48
46
  let content = fs.readFileSync(srcPath, 'utf8');
49
- let backSteps = rootDepth > 0 ? '../'.repeat(rootDepth) : './';
50
- if(rootDepth === 0) backSteps = './'; // core is in dist/core/.
51
-
52
- // Fix path calculation to Core
53
- // dist/routes/web.js (depth 1) -> ../core/lumpia.js
54
- // dist/app/controllers/Home.js (depth 2) -> ../../core/lumpia.js
55
-
56
- // Let's rely on simple replace logic based on file type location usually known
57
- // But dynamic is better.
58
-
59
- content = content.replace(/from\s+['"]lumpiajs['"]/g, `from '${backSteps}../core/lumpia.js'`);
60
-
61
47
  content = transpileSemarangan(content);
62
48
  const finalDest = destPath.replace('.lmp', '.js');
63
49
  fs.writeFileSync(finalDest, content);
@@ -68,12 +54,16 @@ function processDirectory(source, dest, rootDepth = 0) {
68
54
  });
69
55
  }
70
56
 
71
- const browserCore = `
57
+ // BROWSER CORE (Exported so serve.js can use it too if needed)
58
+ export const browserCore = `
72
59
  export class Controller {
73
60
  constructor() { this.params={}; }
74
61
  async tampil(viewName, data={}) {
75
62
  try {
76
- const res = await fetch('views/'+viewName+'.lmp');
63
+ const basePath = window.LUMPIA_BASE || '';
64
+ const url = basePath + '/views/' + viewName + '.lmp';
65
+ // Simple double-slash cleanup
66
+ const res = await fetch(url.replace(/([^:])\\/\\//g, '$1/'));
77
67
  if(!res.ok) throw new Error('View 404: ' + viewName);
78
68
  let html = await res.text();
79
69
 
@@ -116,42 +106,36 @@ const indexHtml = `<!DOCTYPE html>
116
106
  <meta charset="UTF-8">
117
107
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
118
108
  <title>LumpiaJS</title>
109
+ <script type="importmap">
110
+ {
111
+ "imports": {
112
+ "lumpiajs": "./core/lumpia.js"
113
+ }
114
+ }
115
+ </script>
119
116
  <link rel="stylesheet" href="public/css/style.css">
117
+ <script>
118
+ window.LUMPIA_BASE = window.location.pathname
119
+ .replace(new RegExp('/index\\\\.html$'), '')
120
+ .replace(new RegExp('/$'), '');
121
+ </script>
120
122
  </head>
121
123
  <body>
122
124
  <div id="app">Loading...</div>
123
-
124
125
  <script type="module">
125
- import { Jalan } from './routes/web.js';
126
+ // FIX: Import Jalan from CORE, not module. Module has side effects only.
127
+ import { Jalan } from 'lumpiajs';
128
+ import './routes/web.js';
126
129
 
127
130
  async function navigate() {
128
- // FIX: Gunakan new RegExp string biar aman dari escaping hell
129
- const cleanPath = (p) => p.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
130
-
131
- const basePath = cleanPath(window.location.pathname);
132
- // Tapi tunggu, basePath itu harusnya folder ROOT project.
133
- // Misal: /2025/lumpiajs/
134
- // URL saat ini: /2025/lumpiajs/index.html -> Base: /2025/lumpiajs
135
- // Kalau kita navigate ke sub-route (via history pushState): /2025/lumpiajs/produk
136
- // Maka window.location.pathname adalah /2025/lumpiajs/produk.
137
- // Kita HARUS tau base path awalnya apa. Kita bisa tebak dari script tag src location?
138
- // Atau kita asumsikan saat pertama load (yang biasanya index.html), itu lah base path.
139
-
140
- // Trik: Simpan base path di window variable saat pertama load (index.html)
141
- if (!window.LUMPIA_BASE) {
142
- window.LUMPIA_BASE = window.location.pathname.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
143
- }
144
-
145
131
  let currentPath = window.location.pathname;
146
- // Remove Base Path from Current Path to get "Route Path"
147
- if (currentPath.startsWith(window.LUMPIA_BASE)) {
132
+ if (window.LUMPIA_BASE && currentPath.startsWith(window.LUMPIA_BASE)) {
148
133
  currentPath = currentPath.substring(window.LUMPIA_BASE.length);
149
134
  }
150
135
  if (!currentPath || currentPath === '/index.html') currentPath = '/';
151
136
 
152
137
  let m = null, args = {};
153
138
  for(let r of Jalan.routes) {
154
- // Regex: Replace {slug} -> ([^/]+)
155
139
  let regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
156
140
  let reg = new RegExp(regexStr);
157
141
  let res = currentPath.match(reg);
@@ -166,7 +150,7 @@ const indexHtml = `<!DOCTYPE html>
166
150
  await i[fName](...args);
167
151
  } catch(e) {
168
152
  console.error(e);
169
- document.getElementById('app').innerHTML='<h1>Error Loading Controller</h1><pre>'+e.message+'</pre>';
153
+ document.getElementById('app').innerHTML='<h1>Error</h1><pre>'+e.message+'</pre>';
170
154
  }
171
155
  } else {
172
156
  document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Route: '+currentPath+'</p>';
@@ -175,15 +159,14 @@ const indexHtml = `<!DOCTYPE html>
175
159
 
176
160
  window.addEventListener('popstate', navigate);
177
161
  document.body.addEventListener('click', e => {
178
- if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
162
+ const link = e.target.closest('a');
163
+ if (link && link.href.startsWith(window.location.origin)) {
164
+ if (link.target === '_blank' || e.ctrlKey) return;
179
165
  e.preventDefault();
180
- history.pushState(null,'',e.target.href);
166
+ history.pushState(null, '', link.href);
181
167
  navigate();
182
168
  }
183
169
  });
184
-
185
- // Init Base Path immediately
186
- window.LUMPIA_BASE = window.location.pathname.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
187
170
  navigate();
188
171
  </script>
189
172
  </body>
@@ -194,7 +177,7 @@ export function buildProject() {
194
177
  const dist = path.join(root, 'dist');
195
178
  const config = loadConfig(root);
196
179
 
197
- console.log('🍳 Goreng Project (Mode Aman)...');
180
+ console.log('🍳 Goreng Project (Fix Import Jalan)...');
198
181
  if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
199
182
  fs.mkdirSync(dist);
200
183
 
@@ -215,5 +198,5 @@ export function buildProject() {
215
198
 
216
199
  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>`);
217
200
 
218
- console.log('✅ Mateng! Siap upload ke Laragon/Hosting.');
201
+ console.log('✅ Mateng!');
219
202
  }
@@ -1,4 +1,12 @@
1
- // ... (Same KAMUS & Imports) ...
1
+
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import http from 'http';
5
+ import { spawn } from 'child_process';
6
+ import { loadEnv } from '../core/Env.js';
7
+ import { loadConfig } from '../core/Config.js';
8
+ import { browserCore } from './build.js'; // Use the same Browser Core logic
9
+
2
10
  const KAMUS = [
3
11
  { from: /paten\s/g, to: 'const ' },
4
12
  { from: /ono\s/g, to: 'let ' },
@@ -16,30 +24,16 @@ const KAMUS = [
16
24
  { from: /->/g, to: '.' }
17
25
  ];
18
26
 
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
-
26
- function matchRoute(definedRoute, method, pathname) {
27
- if (definedRoute.method !== method) return null;
28
- if (definedRoute.path === pathname) return { params: {} };
29
- const paramNames = [];
30
- const regexPath = definedRoute.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
31
- paramNames.push(name);
32
- return '([^/]+)';
33
- });
34
- if (regexPath === definedRoute.path) return null;
35
- const regex = new RegExp(`^${regexPath}$`);
36
- const match = pathname.match(regex);
37
- if (match) {
38
- const params = {};
39
- paramNames.forEach((name, index) => { params[name] = match[index + 1]; });
40
- return { params };
41
- }
42
- return null;
27
+ function transpileOnTheFly(content) {
28
+ let code = content;
29
+ code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
30
+ code = code.split('\n').map(line => {
31
+ let l = line;
32
+ if (l.trim().startsWith('//')) return l;
33
+ KAMUS.forEach(rule => { l = l.replace(rule.from, rule.to); });
34
+ return l;
35
+ }).join('\n');
36
+ return code;
43
37
  }
44
38
 
45
39
  const backgroundProcess = [];
@@ -49,119 +43,138 @@ function startTailwindWatcher(root) {
49
43
  backgroundProcess.push(tailwind);
50
44
  }
51
45
 
52
- async function loadLumpiaModule(filePath) {
53
- const originalContent = fs.readFileSync(filePath, 'utf-8');
54
- const cacheDir = path.join(process.cwd(), '.lumpia', 'cache');
55
- if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
56
-
57
- const relativePath = path.relative(process.cwd(), filePath);
58
- const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
59
- const destPath = path.join(cacheDir, flatName);
46
+ // Dev Template (Similar to Build index.html)
47
+ const devIndexHtml = `<!DOCTYPE html>
48
+ <html lang="en">
49
+ <head>
50
+ <meta charset="UTF-8">
51
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
52
+ <title>LumpiaJS (Dev)</title>
53
+ <script type="importmap">
54
+ {
55
+ "imports": {
56
+ "lumpiajs": "/core/lumpia.js"
57
+ }
58
+ }
59
+ </script>
60
+ <link rel="stylesheet" href="/public/css/style.css">
61
+ <script>window.LUMPIA_BASE = '';</script>
62
+ </head>
63
+ <body>
64
+ <div id="app">Loading...</div>
65
+ <script type="module">
66
+ import { Jalan } from 'lumpiajs';
67
+ import '/routes/web.js';
68
+
69
+ async function navigate() {
70
+ let currentPath = window.location.pathname;
71
+ if (!currentPath || currentPath === '/index.html') currentPath = '/';
60
72
 
61
- let transcoded = originalContent;
62
- transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
63
- transcoded = transcoded.split('\n').map(line => {
64
- let l = line;
65
- if (l.trim().startsWith('//')) return l;
66
- KAMUS.forEach(rule => l = l.replace(rule.from, rule.to));
67
- return l;
68
- }).join('\n');
73
+ let m = null, args = {};
74
+ for(let r of Jalan.routes) {
75
+ let regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
76
+ let reg = new RegExp(regexStr);
77
+ let res = currentPath.match(reg);
78
+ if(res){ m=r; args=res.slice(1); break; }
79
+ }
69
80
 
70
- fs.writeFileSync(destPath, transcoded);
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);
81
+ if(m) {
82
+ const [cName, fName] = m.a.split('@');
83
+ try {
84
+ // In Dev, we serve /app/... URLs directly map to filesystem
85
+ const mod = await import('/app/controllers/'+cName+'.js?'+Date.now());
86
+ const C = mod.default; const i = new C(); i.params=args;
87
+ await i[fName](...args);
88
+ } catch(e) {
89
+ console.error(e);
90
+ document.getElementById('app').innerHTML='<h1>Dev Error</h1><pre>'+e.message+'</pre>';
91
+ }
92
+ } else {
93
+ document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Route: '+currentPath+'</p>';
94
+ }
82
95
  }
83
- throw e;
84
- }
85
- }
96
+
97
+ window.addEventListener('popstate', navigate);
98
+ document.body.addEventListener('click', e => {
99
+ const link = e.target.closest('a');
100
+ if (link && link.href.startsWith(window.location.origin)) {
101
+ if (link.target === '_blank' || e.ctrlKey) return;
102
+ e.preventDefault();
103
+ history.pushState(null, '', link.href);
104
+ navigate();
105
+ }
106
+ });
107
+ navigate();
108
+ </script>
109
+ </body>
110
+ </html>`;
86
111
 
87
112
  export async function serveProject() {
88
113
  const root = process.cwd();
89
114
  const routesFile = path.join(root, '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
- }
115
+ if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project");
116
+ if (!fs.existsSync(path.join(root, 'node_modules'))) return console.log("⚠️ Please run 'npm install'");
98
117
 
99
- const env = loadEnv(root);
100
118
  const config = loadConfig(root);
101
119
  if (config.klambi === 'tailwindcss') startTailwindWatcher(root);
102
120
 
103
- try {
104
- await loadLumpiaModule(routesFile);
105
- const activeRoutes = global.LumpiaRouter || [];
106
- console.log(`🛣️ Routes registered: ${activeRoutes.length}`);
107
- console.log(`✨ Mode: Semarangan (paten, ono, fungsi, aku)`);
108
-
109
- const server = http.createServer(async (req, res) => {
110
- const method = req.method;
111
- const url = new URL(req.url, `http://${req.headers.host}`);
112
- const pathname = url.pathname;
113
-
114
- const publicMap = { '/css/': path.join(root, 'public', 'css'), '/vendor/': path.join(root, 'public', 'vendor') };
115
- for (const [prefix, localPath] of Object.entries(publicMap)) {
116
- if (pathname.startsWith(prefix)) {
117
- const filePath = path.join(localPath, pathname.slice(prefix.length));
118
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
119
- const ext = path.extname(filePath);
120
- const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
121
- res.writeHead(200, {'Content-Type': mime});
122
- res.end(fs.readFileSync(filePath));
123
- return;
124
- }
125
- }
126
- }
121
+ console.log('🍳 Kukus Project (SPA Mode)...');
122
+
123
+ http.createServer((req, res) => {
124
+ const url = new URL(req.url, `http://${req.headers.host}`);
125
+ let pathname = url.pathname;
126
+
127
+ // 1. Core Library
128
+ if (pathname === '/core/lumpia.js') {
129
+ res.writeHead(200, {'Content-Type': 'text/javascript'});
130
+ return res.end(browserCore);
131
+ }
127
132
 
128
- let match = null, params = {};
129
- const currentRoutes = global.LumpiaRouter || [];
130
- for (const route of currentRoutes) {
131
- const result = matchRoute(route, method, pathname);
132
- if (result) { match = route; params = result.params; break; }
133
+ // 2. Static Assets (Public)
134
+ if (pathname.startsWith('/public/')) {
135
+ const f = path.join(root, pathname);
136
+ if(fs.existsSync(f)) {
137
+ res.writeHead(200, {'Content-Type': pathname.endsWith('.css')?'text/css':'application/javascript'});
138
+ return res.end(fs.readFileSync(f));
133
139
  }
140
+ }
141
+
142
+ // 3. Views (Static .lmp serve)
143
+ // In browserCore, we fetch 'views/home.lmp'
144
+ // If pathname starts with /views/, serve it.
145
+ if (pathname.startsWith('/views/') && pathname.endsWith('.lmp')) {
146
+ const f = path.join(root, pathname);
147
+ if(fs.existsSync(f)) return res.end(fs.readFileSync(f));
148
+ else { res.writeHead(404); return res.end('View 404'); }
149
+ }
150
+
151
+ // 4. JS/LMP Modules (Transpile on fly)
152
+ if (pathname.endsWith('.js')) {
153
+ // Map .js request to .lmp file in User Project
154
+ // /routes/web.js -> /routes/web.lmp
155
+ // /app/controllers/Home.js -> /app/controllers/Home.lmp
134
156
 
135
- if (match) {
136
- try {
137
- const [cName, mName] = match.action.split('@');
138
- const cPath = path.join(root, 'app', 'controllers', cName + '.lmp');
139
- if (!fs.existsSync(cPath)) throw new Error('Controller Not Found');
140
-
141
- const module = await loadLumpiaModule(cPath);
142
- const Ctrl = module.default;
143
- const instance = new Ctrl();
144
- instance.env = env; instance.params = params; instance.config = config;
145
-
146
- const result = await instance[mName](...Object.values(params));
147
-
148
- if (result && result.type) {
149
- res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
150
- res.end(result.content);
151
- } else {
152
- if(result === undefined) res.end('');
153
- else res.end(String(result));
154
- }
155
- } catch (e) {
156
- res.writeHead(500); res.end(`<pre>${e.stack}</pre>`);
157
- }
158
- } else {
159
- res.writeHead(404); res.end('404 Not Found');
157
+ // Check if .lmp exists
158
+ const lmpPath = path.join(root, pathname.replace('.js', '.lmp'));
159
+ if (fs.existsSync(lmpPath)) {
160
+ const content = fs.readFileSync(lmpPath, 'utf8');
161
+ const js = transpileOnTheFly(content);
162
+ res.writeHead(200, {'Content-Type': 'text/javascript'});
163
+ return res.end(js);
160
164
  }
161
- });
162
- const port = 3000;
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
- }
165
+ // Check if .js exists natively? (Maybe helpers)
166
+ const jsPath = path.join(root, pathname);
167
+ if (fs.existsSync(jsPath)) {
168
+ res.writeHead(200, {'Content-Type': 'text/javascript'});
169
+ return res.end(fs.readFileSync(jsPath));
170
+ }
171
+ }
172
+
173
+ // 5. Default Fallback -> SPA Shell (index.html)
174
+ // For any other route (/, /produk, etc), serve HTML
175
+ // Browser will then run JS, which does routing.
176
+ res.writeHead(200, {'Content-Type': 'text/html'});
177
+ res.end(devIndexHtml);
178
+
179
+ }).listen(3000, () => console.log('🚀 Server: http://localhost:3000'));
167
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",