@junnyontop-pixel/neo-app 1.1.7 → 1.1.9

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.
@@ -1,23 +1,15 @@
1
1
  export class NeoParser {
2
2
  static parse(source) {
3
- const lines = source.split('\n');
4
- let root = null;
5
3
  let scriptContent = "";
6
- const stack = [];
7
-
8
- // 1. 스크립트 영역 추출 (@Script { ... })
9
4
  const scriptMatch = source.match(/@Script\s*\{([\s\S]*?)\}/);
10
5
  if (scriptMatch) {
11
6
  scriptContent = scriptMatch[1].trim();
12
7
  }
13
8
 
14
- // 2. 태그 파싱
15
- const tagRegex = /@(\w+):(\w+)\s*\{/g;
16
- let match;
17
- const tags = [];
18
-
19
- // 소스에서 태그 블록들을 찾아내기 위한 단순화된 로직
9
+ // 태그 블록 분리 로직 강화
20
10
  const tokens = source.split(/(@\w+:\w+\s*\{)/).filter(t => t.trim());
11
+ let root = null;
12
+ const stack = [];
21
13
 
22
14
  tokens.forEach(token => {
23
15
  const tagMatch = token.match(/@(\w+):(\w+)\s*\{/);
@@ -28,31 +20,30 @@ export class NeoParser {
28
20
  tag: tagMatch[2],
29
21
  styles: {},
30
22
  events: {},
31
- innerHtml: "",
23
+ innerHtml: "", // 파서 내부 저장용
32
24
  children: []
33
25
  };
34
26
 
35
- // Style 추출 (안전 장치 추가)
36
- const styleMatch = token.match(/Style\((.*?)\)/s);
27
+ // 대소문자 무시(i) 및 줄바꿈 허용(s) 플래그 적용
28
+ const styleMatch = token.match(/Style\((.*?)\)/si);
37
29
  if (styleMatch && styleMatch[1]) {
38
30
  node.styles = this.parseKV(styleMatch[1]);
39
31
  }
40
32
 
41
- // Event 추출 (안전 장치 추가)
42
- const eventMatch = token.match(/Event\((.*?)\)/s);
33
+ const eventMatch = token.match(/Event\((.*?)\)/si);
43
34
  if (eventMatch && eventMatch[1]) {
44
35
  node.events = this.parseKV(eventMatch[1]);
45
36
  }
46
37
 
47
- // Innerhtml 추출
48
- const htmlMatch = token.match(/Innerhtml:\s*"(.*?)"/);
38
+ // Innerhtml, innerHTML, innerhtml 모두 대응
39
+ const htmlMatch = token.match(/innerHTML:\s*"(.*?)"/i);
49
40
  if (htmlMatch) {
50
41
  node.innerHtml = htmlMatch[1];
51
42
  }
52
43
 
53
44
  if (!root) {
54
45
  root = node;
55
- } else {
46
+ } else if (stack.length > 0) {
56
47
  stack[stack.length - 1].children.push(node);
57
48
  }
58
49
  stack.push(node);
package/compiler/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'fs';
3
- import path from 'path'; // 경로 처리를 위해 추가
3
+ import path from 'path';
4
4
  import { NeoParser } from './NeoParser.js';
5
5
 
6
6
  const inputFile = process.argv[2];
7
7
 
8
- // 인자가 없을 경우 에러 처리
9
8
  if (!inputFile) {
10
- console.error("❌ 컴파일할 .neo 파일을 입력해주세요. (예: node index.js App.neo)");
9
+ console.error("❌ 컴파일할 .neo 파일을 입력해주세요.");
11
10
  process.exit(1);
12
11
  }
13
12
 
@@ -15,54 +14,45 @@ const source = fs.readFileSync(inputFile, 'utf8');
15
14
  const { root, scriptContent } = NeoParser.parse(source);
16
15
 
17
16
  function generateCode(node, indent = " ") {
18
- // 자식 노드들을 재귀적으로 호출하며 들여쓰기를 한 단계 더 깊게(indent + " ") 적용합니다.
19
17
  const childrenCode = node.children
20
18
  .map(child => generateCode(child, indent + " "))
21
19
  .join(',\n');
22
20
 
23
- // 이벤트 처리 로직 (보내주신 로직 유지)
24
21
  const eventProps = {};
25
22
  for (const [evt, action] of Object.entries(node.events)) {
26
23
  const propName = `on${evt.charAt(0).toUpperCase() + evt.slice(1)}`;
27
24
  let processedAction = action;
28
25
 
29
- // 단순 연산(++) 대응
30
26
  if (action.includes('++')) processedAction = `state.${action}`;
31
-
32
- // 괄호 자동 보정
33
27
  if (processedAction.includes('(') && !processedAction.includes(')')) {
34
28
  processedAction += ')';
35
29
  }
36
- eventProps[propName] = `() => { ${processedAction}; renderApp(); }`; // renderApp() 호출 추가로 화면 갱신 유도
30
+ eventProps[propName] = `() => { ${processedAction} }`;
37
31
  }
38
32
 
39
33
  const eventString = Object.entries(eventProps)
40
34
  .map(([k, v]) => `${k}: ${v}`)
41
- .join(', ');
35
+ .join(',\n' + indent + ' ');
42
36
 
43
- // 템플릿 리터럴을 사용하여 들여쓰기가 적용된 문자열 생성
37
+ // 💡 생성 키값을 innerHTML로 고정 (중요)
44
38
  return `${indent}h('${node.tag}', {
45
39
  ${indent} id: '${node.id}',
46
40
  ${indent} style: ${JSON.stringify(node.styles)},
47
- ${indent} innerHtml: \`${node.innerHtml.replace(/\$(\w+)/g, '${state.$1}')}\`${eventString ? ',\n' + indent + ' ' + eventString : ''}
41
+ ${indent} innerHTML: \`${node.innerHtml.replace(/\$(\w+)/g, '${state.$1}')}\`${eventString ? ',\n' + indent + ' ' + eventString : ''}
48
42
  ${indent}}, [${childrenCode ? '\n' + childrenCode + '\n' + indent : ''}])`;
49
43
  }
50
44
 
51
- // compiler/index.js 내부
52
45
  const finalJS = `
53
- // 상대 경로 대신 패키지 이름을 직접 사용합니다.
54
- // Vite가 node_modules에서 알아서 찾아줍니다.
55
46
  import { h } from '@junnyontop-pixel/neo-app/core/NeoCore.js';
56
47
 
57
48
  // [User Script]
58
49
  ${scriptContent}
59
50
 
60
51
  export default function render(state) {
61
- return ${generateCode(root)};
52
+ return ${generateCode(root).trimStart()};
62
53
  }
63
54
  `.trim();
64
55
 
65
- // 파일 생성 위치를 입력 파일과 동일한 위치의 .js로 지정
66
56
  const outputPath = inputFile.replace('.neo', '.js');
67
57
  fs.writeFileSync(outputPath, finalJS);
68
58
 
package/core/NeoCore.js CHANGED
@@ -3,11 +3,10 @@ export class NeoCore {
3
3
  this.container = document.getElementById(containerId);
4
4
  this.rootRenderFn = rootRenderFn;
5
5
 
6
- // Proxy를 사용하여 state가 바뀔 때마다 자동으로 mount() 호출
7
6
  this.state = new Proxy(state, {
8
7
  set: (target, key, value) => {
9
8
  target[key] = value;
10
- this.mount(); // 상태 변화 시 자동으로 리렌더링
9
+ this.mount();
11
10
  return true;
12
11
  }
13
12
  });
@@ -15,37 +14,39 @@ export class NeoCore {
15
14
 
16
15
  mount() {
17
16
  if (!this.container) return;
18
- this.container.innerHTML = ''; // 화면 초기화
17
+ this.container.innerHTML = '';
19
18
  const domTree = this.rootRenderFn(this.state);
20
19
  this.container.appendChild(domTree);
21
20
  }
22
21
  }
23
22
 
24
- // 가상 노드를 실제 DOM 요소로 변환하는 함수
25
23
  export function h(tag, props, children = []) {
26
24
  const el = document.createElement(tag);
27
25
 
28
- // 1. 속성 및 스타일 설정 (대소문자 주의: innerHTML)
29
26
  if (props.id) el.id = props.id;
30
27
  if (props.style) Object.assign(el.style, props.style);
31
28
 
32
- // 컴파일러(index.js)와 이름을 맞춘 innerHTML 사용
33
- if (props.innerHTML) el.innerHTML = props.innerHTML;
29
+ // 컴파일러가 주는 innerHTML 파서가 주는 innerHtml 모두 대응
30
+ const content = props.innerHTML || props.innerHtml;
31
+ if (content !== undefined) {
32
+ el.innerHTML = content;
33
+ }
34
34
 
35
- // 2. 이벤트 연결 (onClick, onInput 등)
36
35
  Object.keys(props).forEach(key => {
37
36
  if (key.startsWith('on') && typeof props[key] === 'function') {
38
- const eventType = key.toLowerCase().substring(2); // 'onClick' -> 'click'
37
+ const eventType = key.toLowerCase().substring(2);
39
38
  el.addEventListener(eventType, props[key]);
40
39
  }
41
40
  });
42
41
 
43
- // 3. 자식 요소 추가 (문자열과 HTMLElement 모두 대응)
44
- children.forEach(child => {
45
- if (typeof child === 'string') {
46
- el.appendChild(document.createTextNode(child));
47
- } else if (child instanceof HTMLElement) {
42
+ const flattenChildren = Array.isArray(children) ? children.flat() : [children];
43
+
44
+ flattenChildren.forEach(child => {
45
+ if (child === null || child === undefined) return;
46
+ if (child instanceof HTMLElement) {
48
47
  el.appendChild(child);
48
+ } else {
49
+ el.appendChild(document.createTextNode(String(child)));
49
50
  }
50
51
  });
51
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junnyontop-pixel/neo-app",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "나만의 커스텀 프레임워크 Neo",
5
5
  "main": "core/NeoCore.js",
6
6
  "type": "module",
package/src/App.js CHANGED
@@ -1,5 +1,3 @@
1
- // 상대 경로 대신 패키지 이름을 직접 사용합니다.
2
- // Vite가 node_modules에서 알아서 찾아줍니다.
3
1
  import { h } from '@junnyontop-pixel/neo-app/core/NeoCore.js';
4
2
 
5
3
  // [User Script]
@@ -7,9 +5,9 @@ function sayHello() {
7
5
  alert("안녕하세요!");
8
6
 
9
7
  export default function render(state) {
10
- return h('button', {
8
+ return h('button', {
11
9
  id: 'Btn',
12
10
  style: {},
13
- innerHtml: ``
11
+ innerHTML: ``
14
12
  }, []);
15
13
  }