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,14 @@
|
|
|
1
|
+
import { Controller } from 'lumpiajs';
|
|
2
|
+
|
|
3
|
+
export default class HomeController extends Controller {
|
|
4
|
+
mengko index() {
|
|
5
|
+
// Pake Bahasa Semarangan, Lur!
|
|
6
|
+
paten pesen = 'Sugeng Rawuh di Website Statis!';
|
|
7
|
+
|
|
8
|
+
// 'aku' menggantikan 'this'
|
|
9
|
+
balek aku->tampil('home', {
|
|
10
|
+
message: pesen,
|
|
11
|
+
info: 'Dibuat dengan LumpiaJS'
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Controller } from 'lumpiajs';
|
|
2
|
+
|
|
3
|
+
export default class ProductController extends Controller {
|
|
4
|
+
mengko tampilBarang() {
|
|
5
|
+
// Contoh API
|
|
6
|
+
ono data = [];
|
|
7
|
+
|
|
8
|
+
jajal {
|
|
9
|
+
paten respon = nteni fetch('https://fakestoreapi.com/products?limit=3');
|
|
10
|
+
data = nteni respon.json();
|
|
11
|
+
kandani('Data sukses!');
|
|
12
|
+
} gagal (e) {
|
|
13
|
+
kandani(e);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
balek aku->tampil('product', {
|
|
17
|
+
daftar: data.map(i => '<li>' + i.title + '</li>').join('')
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@tailwind base; @tailwind components; @tailwind utilities;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Controller } from 'lumpiajs';
|
|
2
|
+
|
|
3
|
+
export default class HomeController extends Controller {
|
|
4
|
+
async index() {
|
|
5
|
+
// Pake Bahasa Semarangan, Lur!
|
|
6
|
+
const pesen = 'Sugeng Rawuh di Website Statis!';
|
|
7
|
+
|
|
8
|
+
// 'aku' menggantikan 'this'
|
|
9
|
+
return this.tampil('home', {
|
|
10
|
+
message: pesen,
|
|
11
|
+
info: 'Dibuat dengan LumpiaJS'
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Controller } from 'lumpiajs';
|
|
2
|
+
|
|
3
|
+
export default class ProductController extends Controller {
|
|
4
|
+
async tampilBarang() {
|
|
5
|
+
// Contoh API
|
|
6
|
+
let data = [];
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const respon = await fetch('https://fakestoreapi.com/products?limit=3');
|
|
10
|
+
data = await respon.json();
|
|
11
|
+
console.log('Data sukses!');
|
|
12
|
+
} catch(e) {
|
|
13
|
+
console.log(e);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return this.tampil('product', {
|
|
17
|
+
daftar: data.map(i => '<li>' + i.title + '</li>').join('')
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
export class Controller {
|
|
3
|
+
constructor() { this.params={}; }
|
|
4
|
+
async tampil(viewName, data={}) {
|
|
5
|
+
try {
|
|
6
|
+
// Fetch view must be relative to root of dist.
|
|
7
|
+
// Since we use Import Map, we are modernized.
|
|
8
|
+
// Better to assume deployment at known base or use explicit relative if possible.
|
|
9
|
+
// But fetch('views/...') is relative to current page URL.
|
|
10
|
+
// If current page is /2025/lumpiajs/produk, fetch('views/home.lmp') -> /2025/lumpiajs/views/home.lmp (Works!)
|
|
11
|
+
// Wait, if /produk is virtual path, browser thinks we are in folder /lumpiajs/.
|
|
12
|
+
// If /produk/detail/1 -> browser thinks folder is /lumpiajs/produk/detail/.
|
|
13
|
+
// Fetch 'views/...' will fail (404).
|
|
14
|
+
|
|
15
|
+
// SOLUSI: Selalu fetch dari ROOT relative (pakai Window Base yang kita set di index.html)
|
|
16
|
+
const basePath = window.LUMPIA_BASE || '';
|
|
17
|
+
// Ensure slash
|
|
18
|
+
const url = basePath + '/views/' + viewName + '.lmp';
|
|
19
|
+
|
|
20
|
+
const res = await fetch(url.replace('//', '/')); // simple cleanup
|
|
21
|
+
if(!res.ok) throw new Error('View 404: ' + viewName);
|
|
22
|
+
let html = await res.text();
|
|
23
|
+
|
|
24
|
+
const matchKulit = html.match(/<kulit>([\s\S]*?)<\/kulit>/);
|
|
25
|
+
const matchIsi = html.match(/<isi>([\s\S]*?)<\/isi>/);
|
|
26
|
+
const matchKlambi = html.match(/<klambi>([\s\S]*?)<\/klambi>/);
|
|
27
|
+
|
|
28
|
+
let body = matchKulit?matchKulit[1]:'', script=matchIsi?matchIsi[1]:'', css=matchKlambi?matchKlambi[1]:'';
|
|
29
|
+
|
|
30
|
+
for(const [k,v] of Object.entries(data)) {
|
|
31
|
+
let s = typeof v === 'object' ? JSON.stringify(v) : v;
|
|
32
|
+
body = body.replace(new RegExp('{{\\s*'+k+'\\s*}}','g'), s);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
document.getElementById('app').innerHTML = body;
|
|
36
|
+
if(css && !document.getElementById('css-'+viewName)) {
|
|
37
|
+
const s = document.createElement('style'); s.id='css-'+viewName; s.textContent=css;
|
|
38
|
+
document.head.appendChild(s);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if(script) {
|
|
42
|
+
const kamus = [
|
|
43
|
+
{f:/paten\s/g,t:'const '}, {f:/ono\s/g,t:'let '}, {f:/fungsi\s/g,t:'function '},
|
|
44
|
+
{f:/nteni\s/g,t:'await '}, {f:/mengko\s/g,t:'async '}, {f:/balek\s/g,t:'return '},
|
|
45
|
+
{f:/yen\s*\(/g,t:'if('}, {f:/liyane\s/g,t:'else '}, {f:/kandani\(/g,t:'console.log('},
|
|
46
|
+
{f:/aku->/g,t:'this.'}, {f:/aku\./g,t:'this.'}
|
|
47
|
+
];
|
|
48
|
+
kamus.forEach(r => script = script.replace(r.f, r.t));
|
|
49
|
+
try { new Function(script)(); } catch(e){ console.error(e); }
|
|
50
|
+
}
|
|
51
|
+
} catch(e) { document.getElementById('app').innerHTML = e.message; }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
6
|
+
<title>LumpiaJS</title>
|
|
7
|
+
<!-- IMPORT MAP: KUNCI SUKSES LOAD MODULE DI SUBFOLDER -->
|
|
8
|
+
<script type="importmap">
|
|
9
|
+
{
|
|
10
|
+
"imports": {
|
|
11
|
+
"lumpiajs": "./core/lumpia.js"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
<link rel="stylesheet" href="public/css/style.css">
|
|
16
|
+
<script>
|
|
17
|
+
// DETEKSI BASE PATH DENGAN LEBIH AMAN (Via current script location fallback)
|
|
18
|
+
// Kita ambil lokasi index.html ini sebagai 'Source of Truth' root aplikasi.
|
|
19
|
+
window.LUMPIA_BASE = window.location.pathname
|
|
20
|
+
.replace(new RegExp('/index\\.html$'), '')
|
|
21
|
+
.replace(new RegExp('/$'), '');
|
|
22
|
+
// console.log('Base:', window.LUMPIA_BASE);
|
|
23
|
+
</script>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<div id="app">Loading...</div>
|
|
27
|
+
|
|
28
|
+
<script type="module">
|
|
29
|
+
// Import Map makes this work:
|
|
30
|
+
import { Jalan } from './routes/web.js';
|
|
31
|
+
|
|
32
|
+
async function navigate() {
|
|
33
|
+
let currentPath = window.location.pathname;
|
|
34
|
+
|
|
35
|
+
// Normalize: Strip Base Path
|
|
36
|
+
if (window.LUMPIA_BASE && currentPath.startsWith(window.LUMPIA_BASE)) {
|
|
37
|
+
currentPath = currentPath.substring(window.LUMPIA_BASE.length);
|
|
38
|
+
}
|
|
39
|
+
if (!currentPath || currentPath === '/index.html') currentPath = '/';
|
|
40
|
+
|
|
41
|
+
let m = null, args = {};
|
|
42
|
+
for(let r of Jalan.routes) {
|
|
43
|
+
let regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
|
|
44
|
+
let reg = new RegExp(regexStr);
|
|
45
|
+
let res = currentPath.match(reg);
|
|
46
|
+
if(res){ m=r; args=res.slice(1); break; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if(m) {
|
|
50
|
+
const [cName, fName] = m.a.split('@');
|
|
51
|
+
try {
|
|
52
|
+
// Import Relative to index.html (selalu root dist)
|
|
53
|
+
const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
|
|
54
|
+
const C = mod.default; const i = new C(); i.params=args;
|
|
55
|
+
await i[fName](...args);
|
|
56
|
+
} catch(e) {
|
|
57
|
+
console.error(e);
|
|
58
|
+
document.getElementById('app').innerHTML='<h1>Error</h1><pre>'+e.message+'</pre>';
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Route: '+currentPath+'</p>';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
window.addEventListener('popstate', navigate);
|
|
66
|
+
|
|
67
|
+
// SPA LINK INTERCEPTOR (SAT SET MODE)
|
|
68
|
+
document.body.addEventListener('click', e => {
|
|
69
|
+
// Cari elemen <a> (bisa jadi klik di span dalam a)
|
|
70
|
+
const link = e.target.closest('a');
|
|
71
|
+
if (link && link.href.startsWith(window.location.origin)) {
|
|
72
|
+
// Ignore if target blank or control click
|
|
73
|
+
if (link.target === '_blank' || e.ctrlKey) return;
|
|
74
|
+
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
history.pushState(null, '', link.href);
|
|
77
|
+
navigate();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
navigate();
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
@@ -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,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>
|