Current section

Files

Jump to
Raw

README.md

# MendixWidgetGleam
**Gleam μ–Έμ–΄λ‘œ Mendix Pluggable Widget을 κ°œλ°œν•˜λŠ” ν”„λ‘œμ νŠΈ.**
JSXλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , Gleam μ½”λ“œλ§ŒμœΌλ‘œ React μ»΄ν¬λ„ŒνŠΈλ₯Ό μž‘μ„±ν•˜μ—¬ Mendix Studio Proμ—μ„œ λ™μž‘ν•˜λŠ” μœ„μ ―μ„ λ§Œλ“ λ‹€. React와 Mendix API 바인딩은 [glendix](https://hexdocs.pm/glendix/) νŒ¨ν‚€μ§€κ°€ μ œκ³΅ν•œλ‹€.
## μ™œ Gleam인가?
- **정적 νƒ€μž… μ•ˆμ „μ„±** β€” Gleam의 κ°•λ ₯ν•œ νƒ€μž… μ‹œμŠ€ν…œμœΌλ‘œ λŸ°νƒ€μž„ μ—λŸ¬λ₯Ό 컴파일 νƒ€μž„μ— λ°©μ§€
- **λΆˆλ³€ 데이터** β€” 예츑 κ°€λŠ₯ν•œ μƒνƒœ 관리
- **JavaScript νƒ€κ²Ÿ 지원** β€” `gleam build --target javascript`둜 ES λͺ¨λ“ˆ 좜λ ₯
- **glendix νŒ¨ν‚€μ§€** β€” React + Mendix API의 νƒ€μž… μ•ˆμ „ν•œ Gleam 바인딩. `EditableValue`, `ActionValue`, `ListValue` λ“± λͺ¨λ“  Mendix Pluggable Widget APIλ₯Ό 지원
## μ•„ν‚€ν…μ²˜
```
src/
mendix_widget_gleam.gleam # μœ„μ ― 메인 λͺ¨λ“ˆ
editor_config.gleam # Studio Pro 속성 νŒ¨λ„ μ„€μ •
editor_preview.gleam # Studio Pro λ””μžμΈ λ·° 미리보기
components/
hello_world.gleam # Hello World 곡유 μ»΄ν¬λ„ŒνŠΈ
MendixWidget.xml # μœ„μ ― 속성 μ •μ˜
package.xml # Mendix νŒ¨ν‚€μ§€ λ§€λ‹ˆνŽ˜μŠ€νŠΈ
widgets/ # .mpk μœ„μ ― 파일 (glendix/widget둜 바인딩)
bindings.json # μ™ΈλΆ€ React μ»΄ν¬λ„ŒνŠΈ 바인딩 μ„€μ •
package.json # npm μ˜μ‘΄μ„± (React, μ™ΈλΆ€ 라이브러리 λ“±)
gleam.toml # glendix >= 2.0.4 μ˜μ‘΄μ„± 포함
```
React/Mendix FFI 바인딩은 이 ν”„λ‘œμ νŠΈμ— ν¬ν•¨λ˜μ§€ μ•ŠμœΌλ©°, [glendix](https://hexdocs.pm/glendix/) Hex νŒ¨ν‚€μ§€λ‘œ μ œκ³΅λœλ‹€.
### λΉŒλ“œ νŒŒμ΄ν”„λΌμΈ
```
μœ„μ ― μ½”λ“œ (.gleam) + glendix νŒ¨ν‚€μ§€ (Hex)
↓ gleam run -m glendix/build (Gleam 컴파일 μžλ™ μˆ˜ν–‰)
ES λͺ¨λ“ˆ (.mjs) β€” build/dev/javascript/...
↓ λΈŒλ¦Ώμ§€ JS (μžλ™ 생성)κ°€ import
↓ Rollup (pluggable-widgets-tools)
.mpk μœ„μ ― νŒ¨ν‚€μ§€ β€” dist/
```
### 핡심 원리
Gleam ν•¨μˆ˜ `fn(JsProps) -> ReactElement`λŠ” React ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ™€ λ™μΌν•œ μ‹œκ·Έλ‹ˆμ²˜λ‹€. glendixκ°€ React μ›μ‹œ ν•¨μˆ˜μ™€ Mendix λŸ°νƒ€μž„ νƒ€μž… μ ‘κ·Όμžλ₯Ό νƒ€μž… μ•ˆμ „ν•˜κ²Œ μ œκ³΅ν•˜λ―€λ‘œ, μœ„μ ― ν”„λ‘œμ νŠΈμ—μ„œλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—λ§Œ μ§‘μ€‘ν•˜λ©΄ λœλ‹€.
```gleam
// src/mendix_widget_gleam.gleam
import glendix/mendix
import glendix/react.{type JsProps, type ReactElement}
import glendix/react/attribute
import glendix/react/html
pub fn widget(props: JsProps) -> ReactElement {
let sample_text = mendix.get_string_prop(props, "sampleText")
html.div([attribute.class("widget-hello-world")], [
react.text("Hello " <> sample_text),
])
}
```
Mendix 볡합 νƒ€μž…λ„ Gleamμ—μ„œ νƒ€μž… μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ‹€:
```gleam
import glendix/mendix
import glendix/mendix/editable_value
import glendix/mendix/action
pub fn widget(props: JsProps) -> ReactElement {
// EditableValue μ ‘κ·Ό
let name_attr: EditableValue = mendix.get_prop_required(props, "name")
let display = editable_value.display_value(name_attr)
// ActionValue μ‹€ν–‰
let on_save: Option(ActionValue) = mendix.get_prop(props, "onSave")
action.execute_action(on_save)
// ...
}
```
## μ‹œμž‘ν•˜κΈ°
### 사전 μš”κ΅¬μ‚¬ν•­
- [Gleam](https://gleam.run/getting-started/installing/) (v1.0+)
- [Node.js](https://nodejs.org/) (v16+)
- [Mendix Studio Pro](https://marketplace.mendix.com/link/studiopro/) (μœ„μ ― ν…ŒμŠ€νŠΈμš©)
### μ„€μΉ˜
```bash
gleam run -m glendix/install # Gleam μ˜μ‘΄μ„± μžλ™ λ‹€μš΄λ‘œλ“œ + npm μ˜μ‘΄μ„± μ„€μΉ˜ + 바인딩 μ½”λ“œ 생성 (μ™ΈλΆ€ React νŒ¨ν‚€μ§€λŠ” 사전에 μˆ˜λ™ μ„€μΉ˜ ν•„μš”)
```
### λΉŒλ“œ
```bash
gleam run -m glendix/build # Gleam 컴파일 + μœ„μ ― λΉŒλ“œ (.mpk 생성)
```
λΉŒλ“œ 결과물은 `dist/` 디렉토리에 `.mpk` 파일둜 μƒμ„±λœλ‹€.
### 개발
```bash
gleam run -m glendix/dev # Gleam 컴파일 + 개발 μ„œλ²„ (HMR, port 3000)
gleam run -m glendix/start # Mendix ν…ŒμŠ€νŠΈ ν”„λ‘œμ νŠΈμ™€ 연동 개발
```
## λͺ…λ Ήμ–΄ λͺ¨μŒ
λͺ¨λ“  λͺ…λ Ήμ–΄λŠ” `gleam`으둜 톡일. `gleam run -m`은 Gleam μ»΄νŒŒμΌμ„ μžλ™ μˆ˜ν–‰ν•œ λ’€ 슀크립트λ₯Ό μ‹€ν–‰ν•œλ‹€.
| λͺ…λ Ήμ–΄ | μ„€λͺ… |
|--------|------|
| `gleam run -m glendix/install` | μ˜μ‘΄μ„± μ„€μΉ˜ (Gleam + npm) + 바인딩 μ½”λ“œ 생성 |
| `gleam run -m glendix/build` | ν”„λ‘œλ•μ…˜ λΉŒλ“œ (.mpk 생성) |
| `gleam run -m glendix/dev` | 개발 μ„œλ²„ (HMR, port 3000) |
| `gleam run -m glendix/start` | Mendix ν…ŒμŠ€νŠΈ ν”„λ‘œμ νŠΈ 연동 |
| `gleam run -m glendix/lint` | ESLint μ‹€ν–‰ |
| `gleam run -m glendix/lint_fix` | ESLint μžλ™ μˆ˜μ • |
| `gleam run -m glendix/release` | 릴리즈 λΉŒλ“œ |
| `gleam run -m glendix/marketplace` | Mendix Marketplace μœ„μ ― 검색/λ‹€μš΄λ‘œλ“œ |
| `gleam build --target javascript` | Gleam β†’ JS 컴파일만 |
| `gleam test` | Gleam ν…ŒμŠ€νŠΈ μ‹€ν–‰ |
| `gleam format` | Gleam μ½”λ“œ ν¬λ§·νŒ… |
## μ™ΈλΆ€ React μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©
npm νŒ¨ν‚€μ§€λ‘œ μ œκ³΅λ˜λŠ” React μ»΄ν¬λ„ŒνŠΈ 라이브러리λ₯Ό `.mjs` FFI 파일 μž‘μ„± 없이 순수 Gleamμ—μ„œ μ‚¬μš©ν•  수 μžˆλ‹€.
### 1단계: npm νŒ¨ν‚€μ§€ μ„€μΉ˜
```bash
npm install recharts
```
### 2단계: `bindings.json` μž‘μ„±
ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— `bindings.json`을 μƒμ„±ν•˜κ³ , μ‚¬μš©ν•  μ»΄ν¬λ„ŒνŠΈλ₯Ό λ“±λ‘ν•œλ‹€:
```json
{
"recharts": {
"components": ["PieChart", "Pie", "Cell", "Tooltip", "ResponsiveContainer"]
}
}
```
### 3단계: 바인딩 생성
```bash
gleam run -m glendix/install
```
`binding_ffi.mjs`κ°€ μžλ™ μƒμ„±λœλ‹€. 이후 `gleam run -m glendix/build` λ“± λΉŒλ“œ μ‹œμ—λ„ μžλ™ κ°±μ‹ λœλ‹€.
### 4단계: Gleamμ—μ„œ μ‚¬μš©
```gleam
import glendix/binding
import glendix/react.{type ReactElement}
import glendix/react/attribute.{type Attribute}
fn m() { binding.module("recharts") }
pub fn pie_chart(attrs: List(Attribute), children: List(ReactElement)) -> ReactElement {
react.component_el(binding.resolve(m(), "PieChart"), attrs, children)
}
pub fn tooltip(attrs: List(Attribute)) -> ReactElement {
react.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
}
```
`html.div`와 λ™μΌν•œ 호좜 νŒ¨ν„΄μœΌλ‘œ μ™ΈλΆ€ React μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
## Mendix Marketplace μœ„μ ― λ‹€μš΄λ‘œλ“œ
Mendix Marketplaceμ—μ„œ μœ„μ ―(.mpk)을 μΈν„°λž™ν‹°λΈŒν•˜κ²Œ κ²€μƒ‰ν•˜κ³  λ‹€μš΄λ‘œλ“œν•  수 μžˆλ‹€. λ‹€μš΄λ‘œλ“œ μ™„λ£Œ ν›„ 바인딩 `.gleam` 파일이 μžλ™ μƒμ„±λ˜μ–΄ λ°”λ‘œ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.
### 사전 μ€€λΉ„
`.env` νŒŒμΌμ— Mendix Personal Access Token을 μ„€μ •ν•œλ‹€:
```
MENDIX_PAT=your_personal_access_token
```
> PATλŠ” [Mendix Developer Settings](https://user-settings.mendix.com/link/developersettings)μ—μ„œ **Personal Access Tokens** μ„Ήμ…˜μ˜ **New Token**을 ν΄λ¦­ν•˜μ—¬ λ°œκΈ‰. ν•„μš”ν•œ scope: `mx:marketplace-content:read`
### μ‹€ν–‰
```bash
gleam run -m glendix/marketplace
```
μΈν„°λž™ν‹°λΈŒ TUIμ—μ„œ μœ„μ ―μ„ 검색/μ„ νƒν•˜λ©΄ `widgets/` 디렉토리에 `.mpk`κ°€ λ‹€μš΄λ‘œλ“œλ˜κ³ , `src/widgets/`에 바인딩 `.gleam` 파일이 μžλ™ μƒμ„±λœλ‹€.
## .mpk μœ„μ ― μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©
`widgets/` 디렉토리에 `.mpk` 파일(Mendix μœ„μ ― λΉŒλ“œ κ²°κ³Όλ¬Ό)을 λ°°μΉ˜ν•˜λ©΄, λ‹€λ₯Έ μœ„μ ― μ•ˆμ—μ„œ κΈ°μ‘΄ Mendix μœ„μ ―μ„ React μ»΄ν¬λ„ŒνŠΈλ‘œ λ Œλ”λ§ν•  수 μžˆλ‹€.
### 1단계: `.mpk` 파일 배치
```
ν”„λ‘œμ νŠΈ 루트/
β”œβ”€β”€ widgets/
β”‚ β”œβ”€β”€ Switch.mpk
β”‚ └── Badge.mpk
β”œβ”€β”€ src/
└── gleam.toml
```
### 2단계: 바인딩 생성
```bash
gleam run -m glendix/install
```
μ‹€ν–‰ μ‹œ λ‹€μŒμ΄ μžλ™ μ²˜λ¦¬λœλ‹€:
- `.mpk`μ—μ„œ `.mjs`/`.css`λ₯Ό μΆ”μΆœν•˜κ³  `widget_ffi.mjs`κ°€ μƒμ„±λœλ‹€
- `.mpk` XML의 `<property>` μ •μ˜λ₯Ό νŒŒμ‹±ν•˜μ—¬ `src/widgets/`에 바인딩 `.gleam` 파일이 μžλ™ μƒμ„±λœλ‹€ (이미 μ‘΄μž¬ν•˜λ©΄ κ±΄λ„ˆλœ€)
### 3단계: μžλ™ μƒμ„±λœ `src/widgets/*.gleam` 파일 확인
```gleam
// src/widgets/switch.gleam (μžλ™ 생성)
import glendix/mendix
import glendix/react.{type JsProps, type ReactElement}
import glendix/react/attribute
import glendix/widget
/// Switch μœ„μ ― λ Œλ”λ§ - propsμ—μ„œ 속성을 읽어 μœ„μ ―μ— 전달
pub fn render(props: JsProps) -> ReactElement {
let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
let action = mendix.get_prop_required(props, "action")
let comp = widget.component("Switch")
react.component_el(
comp,
[
attribute.attribute("booleanAttribute", boolean_attribute),
attribute.attribute("action", action),
],
[],
)
}
```
required/optional 속성이 μžλ™ κ΅¬λΆ„λ˜λ©°, ν•„μš”μ— 따라 μƒμ„±λœ νŒŒμΌμ„ 자유둭게 μˆ˜μ •ν•  수 μžˆλ‹€.
### 4단계: μœ„μ ―μ—μ„œ μ‚¬μš©
```gleam
import widgets/switch
// μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ—μ„œ
switch.render(props)
```
μœ„μ ― 이름은 `.mpk` λ‚΄λΆ€ XML의 `<name>` 값을, property keyλŠ” `.mpk` XML의 원본 keyλ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©ν•œλ‹€.
## 기술 μŠ€νƒ
- **Gleam** β€” μœ„μ ― 둜직, UI (JavaScript νƒ€κ²Ÿ 컴파일)
- **[glendix](https://hexdocs.pm/glendix/)** β€” React + Mendix API Gleam 바인딩 (Hex νŒ¨ν‚€μ§€)
- **React 19** β€” Mendix Pluggable Widget λŸ°νƒ€μž„
- **Rollup** β€” `@mendix/pluggable-widgets-tools` 기반 λ²ˆλ“€λ§
## μ œμ•½μ‚¬ν•­
- Gleam β†’ JS β†’ Mendix Widget νŒŒμ΄ν”„λΌμΈμ€ 곡식 μ§€μ›λ˜μ§€ μ•ŠλŠ” μ‘°ν•©μ΄λ―€λ‘œ λΉŒλ“œ μ„€μ • μ»€μŠ€ν„°λ§ˆμ΄μ§•μ΄ ν•„μš”ν•  수 μžˆλ‹€
- JSX νŒŒμΌμ„ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€ β€” λͺ¨λ“  React λ‘œμ§μ€ Gleam + glendix둜 κ΅¬ν˜„
- Redraw λ“± μ™ΈλΆ€ Gleam React λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€
- μœ„μ ― ν”„λ‘œμ νŠΈμ— FFI νŒŒμΌμ„ 직접 μž‘μ„±ν•˜μ§€ μ•ŠλŠ”λ‹€ β€” React/Mendix FFIλŠ” glendixκ°€ 제곡
## λΌμ΄μ„ μŠ€
Apache License 2.0 β€” [LICENSE](./LICENSE) μ°Έμ‘°