고급 가이드
명령형 스타일 플러그인
defineElement가 권장되지만, 직접 DOM을 조작하는 방식도 여전히 지원됩니다.
직접 DOM 조작
// @id my-legacy-plugin
if (dmn.window.type !== "overlay") return;
// 요소 생성 및 추가
const panel = document.createElement("div");
panel.className = "my-panel";
panel.innerHTML = `<h1>Hello</h1>`;
document.body.appendChild(panel);
// 클린업 등록
dmn.plugin.registerCleanup(() => {
panel.remove();
});직접 DOM 조작 시 반드시 dmn.plugin.registerCleanup으로 요소를 정리해야
합니다.
스타일 시트 직접 주입
// @id style-injection
if (dmn.window.type !== "overlay") return;
const style = document.createElement("style");
style.textContent = `
.my-panel {
background: rgba(0, 0, 0, 0.8);
padding: 16px;
border-radius: 8px;
}
`;
document.head.appendChild(style);
dmn.plugin.registerCleanup(() => {
style.remove();
});이벤트 버스 활용
키 입력에 반응하는 로우 레벨 이벤트 처리가 필요할 때:
// @id event-bus-example
// onKeyState - 등록된 키만
const unsub1 = dmn.keys.onKeyState(({ key, state }) => {
if (state === "DOWN") {
console.log(`Key ${key} pressed`);
}
});
// onRawInput - 모든 입력 (키보드 + 마우스)
const unsub2 = dmn.keys.onRawInput(({ device, label, state }) => {
console.log(`[${device}] ${label} ${state}`);
});
dmn.plugin.registerCleanup(() => {
unsub1();
unsub2();
});디버깅 팁
DevTools 열기
// 모든 윈도우의 DevTools 열기
dmn.window.openDevtoolsAll();상태 검사
// 현재 윈도우 타입
console.log("윈도우 타입:", dmn.window.type);
// 현재 설정
dmn.settings.get().then((s) => console.log("설정:", s));
// 키 매핑
dmn.keys.get().then((k) => console.log("키:", k));
// 스토리지 키 목록
dmn.plugin.storage.keys().then((keys) => console.log("저장된 키:", keys));에러 추적
// 전역 에러 핸들러
window.addEventListener("error", (e) => {
console.error("Plugin error:", e.error);
});
window.addEventListener("unhandledrejection", (e) => {
console.error("Unhandled promise:", e.reason);
});보안 고려사항
샌드박싱 없음
플러그인은 앱과 동일한 권한으로 실행됩니다:
- 모든 dmn.* API에 접근 가능
- DOM 전체에 접근 가능
- 외부 네트워크 요청 가능
신뢰할 수 없는 코드를 실행하지 마세요. 플러그인 코드는 항상 직접 검토 후 사용하세요.
외부 요청
// 일반 fetch 예시 (서버 CORS 정책에 따라 실패할 수 있음)
const response = await fetch("https://api.example.com/data", {
headers: { "Content-Type": "application/json" },
});
const data = await response.json();성능 최적화
상태 업데이트 최적화
defineElement에서 상태 업데이트 시 setState를 사용합니다:
dmn.plugin.defineElement({
name: "Optimized Panel",
template: (state, settings, { html }) => html`
<div>Count: ${state.count ?? 0}</div>
`,
onMount: ({ setState }) => {
// 여러 값을 한 번에 업데이트
setState({
count: 0,
history: [],
lastUpdate: Date.now(),
});
},
});불필요한 렌더링 방지
상태가 실제로 변경될 때만 setState를 호출하세요:
onMount: ({ setState, getSettings }) => {
let lastKps = 0;
const interval = setInterval(() => {
const newKps = calculateKps();
// 값이 변경되었을 때만 업데이트
if (newKps !== lastKps) {
lastKps = newKps;
setState({ kps: newKps });
}
}, 100);
return () => clearInterval(interval);
};멀티 윈도우 패턴
메인에서 오버레이 제어
// 메인 윈도우에서
if (dmn.window.type === "main") {
dmn.bridge.sendTo("overlay", "UPDATE_OVERLAY_VALUE", { value: 42 });
}
// 오버레이에서
if (dmn.window.type === "overlay") {
const node = document.createElement("div");
node.style.cssText =
"position: absolute; left: 8px; top: 8px; padding: 8px 12px; border-radius: 8px; background: rgba(0,0,0,0.7); color: white;";
node.textContent = "Value: 0";
document.body.appendChild(node);
const unsub = dmn.bridge.on("UPDATE_OVERLAY_VALUE", ({ value }) => {
node.textContent = `Value: ${value}`;
});
dmn.plugin.registerCleanup(() => {
unsub();
node.remove();
});
}양방향 동기화
const SYNC_KEY = "shared-state";
// 상태 변경 시 브로드캐스트
function updateShared(newState) {
dmn.bridge.send(SYNC_KEY, newState);
// 로컬도 업데이트
localState = newState;
}
// 수신 시 로컬 업데이트
dmn.bridge.on(SYNC_KEY, (state) => {
localState = state;
render();
});제한사항
| 제한 | 설명 |
|---|---|
| Web Workers | 미지원 |
| ES Modules | 동적 import 제한적 지원 |
| LocalStorage | dmn.plugin.storage 사용 권장 |
| 외부 라이브러리 | CDN에서 직접 로드 필요 |
외부 라이브러리 로드
async function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// 사용
await loadScript("https://unpkg.com/lodash@4.17.21/lodash.min.js");
const result = _.chunk([1, 2, 3, 4], 2);