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.
- package/be-tester/app/controllers/HomeController.lmp +14 -0
- package/be-tester/app/controllers/ProductController.lmp +20 -0
- package/be-tester/aset/css/style.css +1 -0
- package/be-tester/config.lmp +3 -0
- package/be-tester/dist/.htaccess +7 -0
- package/be-tester/dist/app/controllers/HomeController.js +14 -0
- package/be-tester/dist/app/controllers/ProductController.js +20 -0
- package/be-tester/dist/core/lumpia.js +54 -0
- package/be-tester/dist/index.html +84 -0
- package/be-tester/dist/public/css/style.css +1 -0
- package/be-tester/dist/routes/web.js +4 -0
- package/be-tester/dist/views/home.lmp +12 -0
- package/be-tester/dist/views/product.lmp +9 -0
- package/be-tester/package-lock.json +1224 -0
- package/be-tester/package.json +15 -0
- package/be-tester/public/css/style.css +1 -0
- package/be-tester/routes/web.lmp +4 -0
- package/be-tester/tailwind.config.js +1 -0
- package/be-tester/views/home.lmp +12 -0
- package/be-tester/views/product.lmp +9 -0
- package/lib/commands/build.js +30 -47
- package/lib/commands/serve.js +138 -125
- package/package.json +1 -1
|
@@ -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 @@
|
|
|
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>
|
package/lib/commands/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// ... (KAMUS
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,'',
|
|
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 (
|
|
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!
|
|
201
|
+
console.log('✅ Mateng!');
|
|
219
202
|
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
}
|