lumpiajs 1.0.13 → 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 +10 -39
- 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 same
|
|
2
|
+
// ... (KAMUS same) ...
|
|
3
3
|
const KAMUS = [
|
|
4
4
|
{ from: /paten\s/g, to: 'const ' },
|
|
5
5
|
{ from: /ono\s/g, to: 'let ' },
|
|
@@ -24,13 +24,7 @@ import { loadConfig } from '../core/Config.js';
|
|
|
24
24
|
|
|
25
25
|
function transpileSemarangan(content) {
|
|
26
26
|
let code = content;
|
|
27
|
-
// 1. Keep .lmp import relative for now, or assume .js
|
|
28
27
|
code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
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.
|
|
33
|
-
|
|
34
28
|
code = code.split('\n').map(line => {
|
|
35
29
|
let l = line;
|
|
36
30
|
if (l.trim().startsWith('//')) return l;
|
|
@@ -60,26 +54,16 @@ function processDirectory(source, dest, rootDepth = 0) {
|
|
|
60
54
|
});
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
// BROWSER CORE (Exported so serve.js can use it too if needed)
|
|
58
|
+
export const browserCore = `
|
|
64
59
|
export class Controller {
|
|
65
60
|
constructor() { this.params={}; }
|
|
66
61
|
async tampil(viewName, data={}) {
|
|
67
62
|
try {
|
|
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).
|
|
76
|
-
|
|
77
|
-
// SOLUSI: Selalu fetch dari ROOT relative (pakai Window Base yang kita set di index.html)
|
|
78
63
|
const basePath = window.LUMPIA_BASE || '';
|
|
79
|
-
// Ensure slash
|
|
80
64
|
const url = basePath + '/views/' + viewName + '.lmp';
|
|
81
|
-
|
|
82
|
-
const res = await fetch(url.replace(
|
|
65
|
+
// Simple double-slash cleanup
|
|
66
|
+
const res = await fetch(url.replace(/([^:])\\/\\//g, '$1/'));
|
|
83
67
|
if(!res.ok) throw new Error('View 404: ' + viewName);
|
|
84
68
|
let html = await res.text();
|
|
85
69
|
|
|
@@ -122,7 +106,6 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
122
106
|
<meta charset="UTF-8">
|
|
123
107
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
124
108
|
<title>LumpiaJS</title>
|
|
125
|
-
<!-- IMPORT MAP: KUNCI SUKSES LOAD MODULE DI SUBFOLDER -->
|
|
126
109
|
<script type="importmap">
|
|
127
110
|
{
|
|
128
111
|
"imports": {
|
|
@@ -132,25 +115,20 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
132
115
|
</script>
|
|
133
116
|
<link rel="stylesheet" href="public/css/style.css">
|
|
134
117
|
<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
118
|
window.LUMPIA_BASE = window.location.pathname
|
|
138
119
|
.replace(new RegExp('/index\\\\.html$'), '')
|
|
139
120
|
.replace(new RegExp('/$'), '');
|
|
140
|
-
// console.log('Base:', window.LUMPIA_BASE);
|
|
141
121
|
</script>
|
|
142
122
|
</head>
|
|
143
123
|
<body>
|
|
144
124
|
<div id="app">Loading...</div>
|
|
145
|
-
|
|
146
125
|
<script type="module">
|
|
147
|
-
// Import
|
|
148
|
-
import { Jalan } from '
|
|
126
|
+
// FIX: Import Jalan from CORE, not module. Module has side effects only.
|
|
127
|
+
import { Jalan } from 'lumpiajs';
|
|
128
|
+
import './routes/web.js';
|
|
149
129
|
|
|
150
130
|
async function navigate() {
|
|
151
131
|
let currentPath = window.location.pathname;
|
|
152
|
-
|
|
153
|
-
// Normalize: Strip Base Path
|
|
154
132
|
if (window.LUMPIA_BASE && currentPath.startsWith(window.LUMPIA_BASE)) {
|
|
155
133
|
currentPath = currentPath.substring(window.LUMPIA_BASE.length);
|
|
156
134
|
}
|
|
@@ -167,7 +145,6 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
167
145
|
if(m) {
|
|
168
146
|
const [cName, fName] = m.a.split('@');
|
|
169
147
|
try {
|
|
170
|
-
// Import Relative to index.html (selalu root dist)
|
|
171
148
|
const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
|
|
172
149
|
const C = mod.default; const i = new C(); i.params=args;
|
|
173
150
|
await i[fName](...args);
|
|
@@ -181,21 +158,15 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
181
158
|
}
|
|
182
159
|
|
|
183
160
|
window.addEventListener('popstate', navigate);
|
|
184
|
-
|
|
185
|
-
// SPA LINK INTERCEPTOR (SAT SET MODE)
|
|
186
161
|
document.body.addEventListener('click', e => {
|
|
187
|
-
// Cari elemen <a> (bisa jadi klik di span dalam a)
|
|
188
162
|
const link = e.target.closest('a');
|
|
189
163
|
if (link && link.href.startsWith(window.location.origin)) {
|
|
190
|
-
// Ignore if target blank or control click
|
|
191
164
|
if (link.target === '_blank' || e.ctrlKey) return;
|
|
192
|
-
|
|
193
165
|
e.preventDefault();
|
|
194
166
|
history.pushState(null, '', link.href);
|
|
195
167
|
navigate();
|
|
196
168
|
}
|
|
197
169
|
});
|
|
198
|
-
|
|
199
170
|
navigate();
|
|
200
171
|
</script>
|
|
201
172
|
</body>
|
|
@@ -206,7 +177,7 @@ export function buildProject() {
|
|
|
206
177
|
const dist = path.join(root, 'dist');
|
|
207
178
|
const config = loadConfig(root);
|
|
208
179
|
|
|
209
|
-
console.log('🍳 Goreng Project (
|
|
180
|
+
console.log('🍳 Goreng Project (Fix Import Jalan)...');
|
|
210
181
|
if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
|
|
211
182
|
fs.mkdirSync(dist);
|
|
212
183
|
|
|
@@ -227,5 +198,5 @@ export function buildProject() {
|
|
|
227
198
|
|
|
228
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>`);
|
|
229
200
|
|
|
230
|
-
console.log('✅ Mateng!
|
|
201
|
+
console.log('✅ Mateng!');
|
|
231
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
|
}
|