@junnyontop-pixel/neo-app 2.0.0 → 2.1.0
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/compiler/NeoParser.js +3 -43
- package/compiler/main.js +17 -30
- package/core/NeoCore.js +13 -62
- package/index.html +14 -6
- package/package.json +1 -1
- package/src/App.neo +3 -11
- package/src/actions.js +3 -0
- package/src/state.js +9 -0
- package/scripts/init.js +0 -57
package/compiler/NeoParser.js
CHANGED
|
@@ -3,25 +3,19 @@ export class NeoParser {
|
|
|
3
3
|
const result = {
|
|
4
4
|
id: "",
|
|
5
5
|
tag: "",
|
|
6
|
-
script: "",
|
|
7
6
|
styles: [],
|
|
8
7
|
innerHTML: "",
|
|
9
8
|
events: []
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
//
|
|
13
|
-
const extracted = NeoParser.extractScript(rawCode);
|
|
14
|
-
result.script = extracted.script;
|
|
15
|
-
rawCode = extracted.rest;
|
|
16
|
-
|
|
17
|
-
// 1. @ID:Tag
|
|
11
|
+
// @ID:Tag
|
|
18
12
|
const tagMatch = rawCode.match(/@([\w-]+):([\w-]+)/);
|
|
19
13
|
if (tagMatch) {
|
|
20
14
|
result.id = tagMatch[1];
|
|
21
15
|
result.tag = tagMatch[2];
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
//
|
|
18
|
+
// [styles]
|
|
25
19
|
const styleMatch = rawCode.match(/\[(.*?)\]/);
|
|
26
20
|
if (styleMatch) {
|
|
27
21
|
result.styles = styleMatch[1]
|
|
@@ -30,7 +24,7 @@ export class NeoParser {
|
|
|
30
24
|
.filter(Boolean);
|
|
31
25
|
}
|
|
32
26
|
|
|
33
|
-
//
|
|
27
|
+
// { content }
|
|
34
28
|
const contentMatch = rawCode.match(/\{([\s\S]*?)\}$/);
|
|
35
29
|
if (contentMatch) {
|
|
36
30
|
const lines = contentMatch[1].trim().split('\n');
|
|
@@ -40,15 +34,12 @@ export class NeoParser {
|
|
|
40
34
|
|
|
41
35
|
if (text.startsWith('innerHTML:')) {
|
|
42
36
|
let value = text.slice('innerHTML:'.length).trim();
|
|
43
|
-
|
|
44
|
-
// ⭐ 문자열 리터럴이면 바깥 따옴표만 제거
|
|
45
37
|
if (
|
|
46
38
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
47
39
|
(value.startsWith("'") && value.endsWith("'"))
|
|
48
40
|
) {
|
|
49
41
|
value = value.slice(1, -1);
|
|
50
42
|
}
|
|
51
|
-
|
|
52
43
|
result.innerHTML = value;
|
|
53
44
|
}
|
|
54
45
|
|
|
@@ -64,35 +55,4 @@ export class NeoParser {
|
|
|
64
55
|
|
|
65
56
|
return result;
|
|
66
57
|
}
|
|
67
|
-
|
|
68
|
-
// ⭐ 핵심: #Script depth 파서
|
|
69
|
-
static extractScript(rawCode) {
|
|
70
|
-
const start = rawCode.indexOf('#Script');
|
|
71
|
-
if (start === -1) {
|
|
72
|
-
return { script: '', rest: rawCode };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const open = rawCode.indexOf('{', start);
|
|
76
|
-
if (open === -1) {
|
|
77
|
-
return { script: '', rest: rawCode };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let depth = 1;
|
|
81
|
-
let i = open + 1;
|
|
82
|
-
|
|
83
|
-
while (i < rawCode.length && depth > 0) {
|
|
84
|
-
if (rawCode[i] === '{') depth++;
|
|
85
|
-
if (rawCode[i] === '}') depth--;
|
|
86
|
-
i++;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const script = rawCode.slice(open + 1, i - 1);
|
|
90
|
-
const rest =
|
|
91
|
-
rawCode.slice(0, start) + rawCode.slice(i);
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
script: script.trim(),
|
|
95
|
-
rest: rest.trim()
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
58
|
}
|
package/compiler/main.js
CHANGED
|
@@ -1,50 +1,37 @@
|
|
|
1
1
|
import { NeoParser } from './NeoParser.js';
|
|
2
2
|
import { NeoCore } from '../core/NeoCore.js';
|
|
3
3
|
|
|
4
|
-
// 현재 페이지
|
|
5
4
|
let currentPath = 'App.neo';
|
|
6
|
-
let pageCtx = null; // ⭐ 추가
|
|
7
5
|
|
|
8
6
|
async function render() {
|
|
9
7
|
const app = document.getElementById('app');
|
|
10
8
|
|
|
11
9
|
try {
|
|
12
10
|
const response = await fetch(`../src/${currentPath}`);
|
|
13
|
-
if (!response.ok) {
|
|
14
|
-
throw new Error(`Failed to load ${currentPath}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
const rawCode = await response.text();
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
// ⭐ 최초 1회만 Script 실행
|
|
23
|
-
if (!pageCtx) {
|
|
24
|
-
pageCtx = NeoCore.createScriptContext(parsedData.script);
|
|
25
|
-
console.log("CTX (init):", pageCtx);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// UI 생성
|
|
29
|
-
const ui = NeoCore.create(parsedData, pageCtx);
|
|
13
|
+
const parsed = NeoParser.parse(rawCode);
|
|
14
|
+
const ui = NeoCore.create(parsed);
|
|
30
15
|
|
|
31
|
-
// 화면 교체
|
|
32
16
|
app.replaceChildren(ui);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.error("렌더링 중 에러 발생:", err);
|
|
37
|
-
app.innerHTML = `<pre style="color:red">${err.message}</pre>`;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
app.innerHTML = `<pre style="color:red">${e.message}</pre>`;
|
|
19
|
+
console.error(e);
|
|
38
20
|
}
|
|
39
21
|
}
|
|
40
22
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
// async function loadUserCode(path) {
|
|
24
|
+
// const res = await fetch(path);
|
|
25
|
+
// const code = await res.text();
|
|
26
|
+
|
|
27
|
+
// // ⭐ window 스코프에서 실행
|
|
28
|
+
// new Function(`
|
|
29
|
+
// with (window) {
|
|
30
|
+
// ${code}
|
|
31
|
+
// }
|
|
32
|
+
// `)();
|
|
33
|
+
// }
|
|
47
34
|
|
|
48
|
-
// 최초 렌더
|
|
49
35
|
window.__neoRender = render;
|
|
36
|
+
// await loadUserCode('../src/actions.js');
|
|
50
37
|
render();
|
package/core/NeoCore.js
CHANGED
|
@@ -1,32 +1,24 @@
|
|
|
1
1
|
export class NeoCore {
|
|
2
|
-
static create(data
|
|
3
|
-
const
|
|
4
|
-
const el = document.createElement(tag);
|
|
2
|
+
static create(data) {
|
|
3
|
+
const el = document.createElement(data.tag || 'div');
|
|
5
4
|
|
|
6
5
|
if (data.id) el.id = data.id;
|
|
7
|
-
if (Array.isArray(data.styles)
|
|
8
|
-
el.className = data.styles.join(
|
|
6
|
+
if (Array.isArray(data.styles)) {
|
|
7
|
+
el.className = data.styles.join(' ');
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
el.innerHTML = NeoCore.renderTemplate(data.innerHTML, ctx);
|
|
10
|
+
el.innerHTML = NeoCore.renderTemplate(data.innerHTML);
|
|
13
11
|
|
|
14
12
|
(data.events || []).forEach(evt => {
|
|
15
13
|
el.addEventListener(evt.type, () => {
|
|
16
14
|
try {
|
|
17
|
-
|
|
18
|
-
const run = new Function('ctx', `
|
|
19
|
-
with (ctx) {
|
|
20
|
-
${evt.action}
|
|
21
|
-
}
|
|
22
|
-
`);
|
|
23
|
-
run(ctx);
|
|
15
|
+
new Function(evt.action)();
|
|
24
16
|
|
|
25
17
|
if (window.__neoRender) {
|
|
26
18
|
window.__neoRender();
|
|
27
19
|
}
|
|
28
20
|
} catch (e) {
|
|
29
|
-
console.error("
|
|
21
|
+
console.error("Neo Event Error:", e);
|
|
30
22
|
}
|
|
31
23
|
});
|
|
32
24
|
});
|
|
@@ -34,52 +26,11 @@ export class NeoCore {
|
|
|
34
26
|
return el;
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
static
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const runner = new Function('ctx', transformed);
|
|
45
|
-
runner(ctx);
|
|
46
|
-
|
|
47
|
-
console.log("🧠 ctx after script:", ctx);
|
|
48
|
-
} catch (e) {
|
|
49
|
-
console.error("Script 실행 에러:", e);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return ctx;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static renderTemplate(template, ctx) {
|
|
56
|
-
if (!template) return "";
|
|
57
|
-
if (!ctx) return template;
|
|
58
|
-
|
|
59
|
-
return template.replace(/\$([\w]+)/g, (_, key) =>
|
|
60
|
-
Object.prototype.hasOwnProperty.call(ctx, key)
|
|
61
|
-
? ctx[key]
|
|
62
|
-
: ""
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ⭐ let / const → ctx.xxx 브리지
|
|
67
|
-
static transformScript(code = "") {
|
|
68
|
-
return code
|
|
69
|
-
// let / const → ctx.xxx
|
|
70
|
-
.replace(
|
|
71
|
-
/\b(let|const)\s+([a-zA-Z_$][\w$]*)\s*=/g,
|
|
72
|
-
'ctx.$2 ='
|
|
73
|
-
)
|
|
74
|
-
// function add() { → ctx.add = function () {
|
|
75
|
-
.replace(
|
|
76
|
-
/\bfunction\s+([a-zA-Z_$][\w$]*)\s*\(\)\s*\{/g,
|
|
77
|
-
'ctx.$1 = function () {'
|
|
78
|
-
)
|
|
79
|
-
// count++ → ctx.count++
|
|
80
|
-
.replace(
|
|
81
|
-
/\b([a-zA-Z_$][\w$]*)\s*\+\+/g,
|
|
82
|
-
'ctx.$1++'
|
|
83
|
-
);
|
|
29
|
+
static renderTemplate(template = "") {
|
|
30
|
+
return template.replace(/\$([\w.]+)/g, (_, path) => {
|
|
31
|
+
return path
|
|
32
|
+
.split('.')
|
|
33
|
+
.reduce((obj, key) => obj?.[key], window) ?? '';
|
|
34
|
+
});
|
|
84
35
|
}
|
|
85
36
|
}
|
package/index.html
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Neo App</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
|
|
12
|
+
<!-- 유저 로직 (ESM) -->
|
|
13
|
+
<script type="module">
|
|
14
|
+
import './src/state.js';
|
|
15
|
+
import './src/actions.js';
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<!-- Neo 런타임 -->
|
|
19
|
+
<script type="module" src="./compiler/main.js"></script>
|
|
12
20
|
</body>
|
|
13
21
|
</html>
|
package/package.json
CHANGED
package/src/App.neo
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function add() {
|
|
5
|
-
count++
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
@Upbtn:button [px-4, py-2, bg-black, text-white, rounded]{
|
|
10
|
-
innerHTML: "현재 숫자: $count"
|
|
11
|
-
on:click: add()
|
|
1
|
+
@Btn:button [px-4, py-2, bg-black, text-white, rounded] {
|
|
2
|
+
innerHTML: "현재 숫자: $Store.count"
|
|
3
|
+
on:click: Store.add()
|
|
12
4
|
}
|
package/src/actions.js
ADDED
package/src/state.js
ADDED
package/scripts/init.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
// 1. 템플릿 정의
|
|
6
|
-
const htmlContent = `<!DOCTYPE html>
|
|
7
|
-
<html lang="en">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="UTF-8">
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
-
<title>Neo App</title>
|
|
12
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<div id="app"></div>
|
|
16
|
-
<script type="module" src="./compiler/main.js"></script>
|
|
17
|
-
</body>
|
|
18
|
-
</html>`;
|
|
19
|
-
|
|
20
|
-
const neoContent = `#Script {
|
|
21
|
-
let count = 0;
|
|
22
|
-
const add = () => count++;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@Main:div [flex, flex-col, items-center, p-10, bg-white] {
|
|
26
|
-
|
|
27
|
-
@Title:h1 [text-3xl, font-bold] {
|
|
28
|
-
innerHTML: "Neo v2 New Syntax"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@Counter:p [my-4, text-blue-500] {
|
|
32
|
-
innerHTML: "현재 숫자: $count"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@UpBtn:button [px-4, py-2, bg-black, text-white, rounded] {
|
|
36
|
-
innerHTML: "증가"
|
|
37
|
-
on:click: add()
|
|
38
|
-
on:mouseover: console.log('hovered!')
|
|
39
|
-
}
|
|
40
|
-
}`;
|
|
41
|
-
|
|
42
|
-
// 2. 파일 생성 로직
|
|
43
|
-
const targetDir = process.cwd(); // 명령어를 실행한 현재 폴더
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
if (!fs.existsSync(path.join(targetDir, 'src'))) {
|
|
47
|
-
fs.mkdirSync(path.join(targetDir, 'src'));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fs.writeFileSync(path.join(targetDir, 'index.html'), htmlContent);
|
|
51
|
-
fs.writeFileSync(path.join(targetDir, 'src/App.neo'), neoContent);
|
|
52
|
-
|
|
53
|
-
console.log("✅ [Neo] index.html 및 src/App.neo 생성이 완료되었습니다!");
|
|
54
|
-
console.log("👉 Neo v2는 컴파일할 필요가 없습니다!");
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.error("❌ 초기화 중 에러 발생:", err.message);
|
|
57
|
-
}
|