Files
Gnuboard7/docs/frontend/g7core-api.md
T
HeuJung 0833bd1f6a fix(core,engine): 확장 번들 503 판정을 선언 축에서 소실 축으로 교체
빈 번들 503 을 "에셋을 선언한 활성 확장이 있는데 결과가 비었다" 로 판정해,
스타일 규칙이 아직 없는 0바이트 CSS 만 선언된 기본 구성이 통째로 503 이 됐다.
file_get_contents 는 0바이트에서 false 가 아니라 '' 를 돌려주므로 읽기 실패
분기도 타지 않는다. 선언 축 게터는 전부 file_exists 게이트라 부재를 셀 수 없어,
존재 게이트가 없는 getDeclaredAssetAbsolutePaths 를 통로로 두고 503 의 근거를
소실 축(findMissingDeclaredAssets)으로 옮긴다. 존재하되 비었으면 빈 200 이다.

곁들여: 실패 배너 항목명을 내부 구분 키에서 사용자 어휘로(engine-v1.64.7),
봇 화면이 없는 템플릿 CSS 를 링크하지 않도록 실재 게이트, 매니페스트 자산
선언 드리프트 4건 정정.
2026-09-04 15:36:33 +09:00

38 KiB

G7Core 전역 API 레퍼런스

버전: engine-v1.4.0+ 관련 문서: state-management.md | components.md | data-binding.md


TL;DR (5초 요약)

1. G7Core.state: get/set/subscribe 전역 상태 관리
2. G7Core.toast: success/error/info/warning 알림
3. G7Core.modal: open/close 모달 제어
4. G7Core.t(): 다국어 번역 함수
5. G7Core.dispatch(): 액션 프로그래밍 실행

분리된 문서

이 문서는 가독성을 위해 다음과 같이 분리되었습니다:

문서 내용
g7core-api.md (현재) 개요, 상태 관리, 토스트 알림, 모달 관리, 네비게이션, 스타일 헬퍼, 플러그인/모듈 설정, 확장 자산, 위지윅 편집기
g7core-api-advanced.md 다국어, 액션 실행, 컴포넌트 이벤트, 이벤트 생성 헬퍼, 렌더링 헬퍼, 인증/API, WebSocket, 반응형, React Hooks, 타입 정의

목차

  1. 개요
  2. 상태 관리 (G7Core.state)
  3. 토스트 알림 (G7Core.toast)
  4. 모달 관리 (G7Core.modal)
  5. 네비게이션 (G7Core.navigation)
  6. 스타일 헬퍼 (G7Core.style)
  7. 플러그인 설정 (G7Core.plugin)
  8. 모듈 설정 (G7Core.module)
  9. 확장 자산 (G7Core.asset, G7Core.assets)
  10. 위지윅 편집기 (G7Core.wysiwyg)

개요

G7Core는 템플릿 엔진이 전역으로 노출하는 API 네임스페이스입니다. 컴포넌트 코드(TypeScript/JavaScript)에서 템플릿 엔진의 기능에 접근할 때 사용합니다.

API 카테고리

카테고리 API 설명 문서
상태 관리 G7Core.state 전역 상태 조회/설정/구독 현재 문서
UI 알림 G7Core.toast 토스트 알림 표시 현재 문서
모달 G7Core.modal 모달 열기/닫기 현재 문서
네비게이션 G7Core.navigation 페이지 전환 상태 현재 문서
스타일 G7Core.style Tailwind 클래스 병합 현재 문서
플러그인 G7Core.plugin 플러그인 설정 조회 현재 문서
모듈 G7Core.module 모듈 설정 조회 현재 문서
다국어 G7Core.locale, G7Core.t 로케일 관리, 번역 고급 API
액션 G7Core.dispatch 액션 실행 고급 API
이벤트 G7Core.componentEvent 컴포넌트 간 이벤트 고급 API
헬퍼 G7Core.create*Event 이벤트 객체 생성 고급 API
렌더링 G7Core.renderItemChildren 반복 아이템 렌더링 고급 API
인증 G7Core.AuthManager 인증 상태 관리 고급 API
API G7Core.api API 클라이언트 고급 API
WebSocket G7Core.websocket 실시간 통신 고급 API
확장 자산 URL G7Core.asset 템플릿/모듈/플러그인 자산 URL 생성, 재시도 로더 현재 문서
자산 실패 안내 G7Core.assets 자산 로드 실패 표면화 및 재시도 현재 문서
위지윅 G7Core.wysiwyg 레이아웃 편집기 현재 문서
React Hooks G7Core.useControllableState 상태 패턴 훅 고급 API

사용 컨텍스트

// G7Core는 window 객체에 노출됨
const G7Core = (window as any).G7Core;

// 또는 타입 안전하게 접근
const toast = (window as any).G7Core?.toast;

상태 관리 (G7Core.state)

전역 상태를 관리하는 API입니다.

상세 내용: state-management.md

API 목록

메서드 설명 반환값
get() 현재 전역 상태 반환 Record<string, any>
set(updates, options?) 상태 객체로 업데이트 (dot notation 지원, merge 옵션) void
getGlobal() get()의 별칭 (하위 호환성) Record<string, any>
setGlobal(updates) set()의 별칭 (하위 호환성) void
setLocal(updates, options?) 컴포넌트 로컬 상태 업데이트 (dot notation, scope 옵션 지원) void
getLocal() 컴포넌트 로컬 상태 조회 Record<string, any>
getIsolated(scopeId) 격리된 스코프 상태 조회 (engine-v1.14.0+) Record<string, any> | null
setIsolated(scopeId, updates, options?) 격리된 스코프 상태 업데이트 (engine-v1.14.0+, merge 옵션) void
update(updater) 함수형 업데이트 void
subscribe(listener) 상태 변경 구독 구독 해제 함수
getDataSource(id) 데이터 소스 값 조회 DataSourceValue | undefined

setLocal / getLocal (engine-v1.12.0+)

커스텀 핸들러에서 컴포넌트의 로컬 상태(_local)를 직접 업데이트할 때 사용합니다.

시그니처:

setLocal(updates: Record<string, any>, options?: {
  scope?: 'current' | 'parent' | 'root';
  merge?: 'deep' | 'shallow' | 'replace';  // engine-v1.18.0+ (기본: 'deep')
  debounce?: number;         // engine-v1.41.0+ — 디바운스 지연 시간 (ms)
  debounceKey?: string;      // engine-v1.41.0+ — 디바운스 고유 키
  render?: boolean;          // engine-v1.42.0+ (기본: true) — false이면 React 리렌더 건너뜀
}): void
getLocal(): Record<string, any>

동작 원리:

  • 액션 실행 중: 컴포넌트의 dynamicState를 직접 업데이트하여 즉시 UI에 반영
  • 액션 외부: 전역 _local 업데이트 (fallback)

render: false (engine-v1.42.0+): 값은 globalState._local에 저장하되 React 리렌더를 건너뜁니다. 외부 라이브러리(CKEditor 등)가 자체 DOM을 관리하는 경우, React 트리 리렌더가 불필요하므로 성능을 대폭 개선합니다. flushPendingDebounceTimers 실행 시(저장 직전)에는 항상 render: true로 강제되어 데이터 정합성이 보장됩니다.

// 외부 라이브러리에서 자체 DOM을 관리하는 경우 — 리렌더 없이 값만 저장
G7Core.state.setLocal({
  [`form.${name}.${locale}`]: html,
  hasChanges: true,
}, {
  debounce: 300,
  debounceKey: `editor-sync-${name}`,
  render: false,
});

dot notation 지원 (engine-v1.2.0+): 중첩된 객체 경로를 dot notation으로 표현할 수 있습니다.

// 커스텀 핸들러에서 사용
export function toggleOptionHandler(action: any, context: any): void {
  const G7Core = (window as any).G7Core;

  // 현재 로컬 상태 가져오기
  const currentLocal = G7Core.state.getLocal();

  // 로컬 상태 업데이트
  G7Core.state.setLocal({
    selectedItems: [...currentLocal.selectedItems, newItem],
    isLoading: false,
  });

  // dot notation 지원 (중첩 객체)
  G7Core.state.setLocal({
    "filter.orderStatus": ["paid", "shipped"]
  });
  // 결과: { filter: { orderStatus: ["paid", "shipped"] } }
}

scope 옵션 (engine-v1.15.0+): 모달이나 중첩 레이아웃에서 부모/루트 컨텍스트의 _local에 접근할 때 사용합니다.

scope 값 설명 사용 시점
'current' (기본값) 현재 컨텍스트의 _local 업데이트 일반적인 상태 업데이트
'parent' 부모 레이아웃의 _local 업데이트 모달에서 부모 상태 업데이트 시
'root' 최상위 레이아웃의 _local 업데이트 중첩 모달에서 최상위 상태 업데이트 시
// 모달 확인 버튼 핸들러에서 부모 상태 업데이트
export function updateParentFormHandler(action: any, context: any): void {
  const G7Core = (window as any).G7Core;
  const form = action.params?.form;

  // 부모 레이아웃의 _local.form.fields 업데이트
  G7Core.state.setLocal({
    'form.fields': updatedFields,
  }, { scope: 'parent' });

  G7Core.toast.success('변경되었습니다');
}

scope 사용 시 주의사항:

  • openModal 시 부모 컨텍스트가 __g7LayoutContextStack에 push됨
  • closeModal 시 스택에서 pop됨
  • sequence 순서가 중요: 핸들러가 closeModal보다 먼저 실행되어야 함
    • ❌ closeModal → handler: 핸들러 실행 시 스택이 비어있음
    • ✅ handler → closeModal: 핸들러 실행 시 부모 컨텍스트 존재

주의사항:

  • ActionDispatcher의 액션 실행 중에만 dynamicState 업데이트가 동작합니다
  • 액션 외부에서 호출하면 전역 _local이 업데이트됩니다
  • expandChildren 내부 액션에서도 정상 동작합니다 (componentContext 자동 전달)
  • dot notation 경로는 자동으로 중첩 객체로 변환되어 깊은 병합됩니다
  • scope: 'parent'/'root' 사용 시 컨텍스트 스택이 비어있으면 current로 폴백됩니다
  • merge 옵션 (engine-v1.18.0+): 병합 방식을 지정합니다 ('deep' | 'shallow' | 'replace', 기본: 'deep')

merge 옵션 사용 예시:

// replace: 기존 _local을 완전히 교체
G7Core.state.setLocal({
  formData: { name: '', price: 0 }
}, { merge: 'replace' });

// shallow: 최상위 키만 덮어쓰기
G7Core.state.setLocal({
  filter: { status: 'all', keyword: '' }
}, { merge: 'shallow' });

getIsolated / setIsolated (engine-v1.14.0+)

격리된 스코프의 상태를 조회하거나 업데이트합니다. scopeId로 특정 격리 스코프를 식별합니다.

사용 시점:

  • 커스텀 핸들러에서 특정 격리 스코프의 상태에 접근해야 할 때
  • 컴포넌트 외부에서 격리된 상태를 프로그래밍 방식으로 조작해야 할 때
// 커스텀 핸들러에서 사용
export function categorySelectHandler(action: any, context: any): void {
  const G7Core = (window as any).G7Core;

  // 격리 상태 조회
  const state = G7Core.state.getIsolated('category-selector');
  console.log(state?.selectedCategories);

  // 격리 상태 업데이트
  G7Core.state.setIsolated('category-selector', {
    currentStep: 2,
    selectedCategories: [...state.selectedCategories, newCategory],
  });
}

주의사항:

  • scopeId는 레이아웃 JSON의 isolatedScopeId 속성과 일치해야 합니다
  • 해당 scopeId의 격리 스코프가 마운트되지 않은 경우 getIsolated는 null을 반환합니다
  • setIsolated는 스코프가 없으면 경고 로그를 출력하고 무시합니다
  • 대부분의 경우 액션 context의 isolatedContext를 사용하는 것이 권장됩니다
  • options.merge (engine-v1.18.0+): 병합 방식 지정 ('deep' | 'shallow' | 'replace', 기본: 'deep')

레이아웃에서 scopeId 정의:

{
  "type": "Div",
  "isolatedState": {
    "selectedCategories": [],
    "currentStep": 1
  },
  "isolatedScopeId": "category-selector",
  "children": [...]
}

사용 예시

// 상태 조회
const state = G7Core.state.get();
console.log(state.sidebarOpen);

// 상태 설정 (기본: 깊은 병합)
G7Core.state.set({ theme: 'dark' });

// merge 옵션 (engine-v1.18.0+)
G7Core.state.set({ theme: 'dark', sidebar: { open: true } }, { merge: 'shallow' });
G7Core.state.set({ theme: 'light' }, { merge: 'replace' });

// 함수형 업데이트 (현재 상태 기반)
G7Core.state.update(prev => ({
  count: (prev.count || 0) + 1
}));

// 상태 변경 구독
const unsubscribe = G7Core.state.subscribe((state) => {
  console.log('상태 변경:', state);
});

// 데이터 소스 조회 (deprecated - G7Core.dataSource.get() 사용 권장)
const users = G7Core.state.getDataSource('users');
console.log(users?.data);

데이터소스 관리 (G7Core.dataSource)

데이터소스의 조회, 설정, refetch를 관리하는 API입니다.

engine-v1.5.0+ 추가

API 목록

메서드 설명 반환값
get(id) 데이터소스 값 조회 any | undefined
set(id, data, options?) 데이터소스 값 설정 + UI 리렌더링 void
updateItem(id, path, itemId, updates, options?) 배열 내 특정 아이템 부분 업데이트 boolean
refetch(id, options?) 서버에서 데이터 다시 가져오기 Promise<any>

set() 옵션

옵션 타입 기본값 설명
merge boolean false true면 기존 데이터와 shallow merge
sync boolean false true면 동기 업데이트

updateItem() (engine-v1.13.0+)

배열 내 특정 아이템만 업데이트하여 성능을 최적화합니다.

시그니처:

updateItem(
  dataSourceId: string,
  itemPath: string,
  itemId: string | number,
  updates: Record<string, any>,
  options?: { idField?: string; merge?: boolean; skipRender?: boolean }
): boolean

파라미터:

파라미터 타입 설명
dataSourceId string 데이터소스 ID
itemPath string 배열 경로 (예: "data.data", "data.data[0].options")
itemId string | number 아이템 식별자
updates object 업데이트할 필드

옵션:

옵션 타입 기본값 설명
idField string "id" ID 필드명
merge boolean true 깊은 병합 여부
skipRender boolean false 렌더링 스킵 여부

예시:

// 상품 옵션의 판매가 업데이트
G7Core.dataSource.updateItem(
  'products',
  'data.data[0].options',
  123,
  { selling_price: 15000, _modified: true }
);

// uuid 필드 기준 사용자 업데이트
G7Core.dataSource.updateItem(
  'users',
  'data',
  'user-456',
  { name: '홍길동' },
  { idField: 'uuid' }
);

사용 예시

// 데이터소스 조회
const products = G7Core.dataSource.get('products');
console.log(products?.data?.data);  // API 응답 구조에 따라 다름

// 데이터소스 설정 (전체 교체)
G7Core.dataSource.set('products', {
    ...currentData,
    data: {
        ...currentData.data,
        data: updatedProductsArray,
    },
});

// 데이터소스 설정 (병합)
G7Core.dataSource.set('products', { data: updatedData }, { merge: true });

// 서버에서 다시 가져오기
await G7Core.dataSource.refetch('products');

// 캐시 무시하고 다시 가져오기
await G7Core.dataSource.refetch('products', { skipCache: true });

데이터소스 vs 전역 상태

┌─────────────────────────────────────────────────────────────┐
│  두 가지 데이터 저장소                                        │
├─────────────────────────────────────────────────────────────┤
│  1. 데이터소스 (G7Core.dataSource)                           │
│     - API 응답 저장                                          │
│     - {{products?.data?.data}}로 바인딩                      │
│     - 인라인 편집 등 런타임 수정 시 사용                       │
│                                                              │
│  2. 전역 상태 (G7Core.state)                                 │
│     - _global, _local 상태 저장                              │
│     - UI 상태 (필터, 선택 등) 관리                            │
│     - initGlobal로 데이터소스 복사 가능 (읽기 전용 용도)       │
└─────────────────────────────────────────────────────────────┘

사용 시점

상황 사용 API
인라인 편집, 실시간 데이터 수정 G7Core.dataSource.set()
서버 동기화 필요 G7Core.dataSource.refetch()
UI 상태 (필터, 선택, 토글) G7Core.state.set()
초기 데이터 읽기 전용 복사 initGlobal 옵션

토스트 알림 (G7Core.toast)

토스트 알림을 표시하는 편의 API입니다.

API 목록

메서드 설명 파라미터
show(message, options?) 토스트 표시 message, { type, duration }
success(message, duration?) 성공 토스트 message, duration(ms)
error(message, duration?) 에러 토스트 message, duration(ms)
warning(message, duration?) 경고 토스트 message, duration(ms)
info(message, duration?) 정보 토스트 message, duration(ms)

토스트 타입

타입 설명 색상
success 성공 메시지 녹색
error 에러 메시지 빨간색
warning 경고 메시지 노란색
info 정보 메시지 파란색

사용 예시

// 편의 메서드 (권장)
G7Core.toast.success('저장되었습니다');
G7Core.toast.error('오류가 발생했습니다');
G7Core.toast.warning('주의가 필요합니다');
G7Core.toast.info('정보 메시지');

// duration 지정 (밀리초)
G7Core.toast.success('저장 완료', 3000);  // 3초 후 자동 닫힘

// 상세 옵션
G7Core.toast.show('메시지', {
  type: 'info',
  duration: 5000
});

내부 동작

토스트 API는 내부적으로 G7Core.dispatch를 호출하여 toast 핸들러를 실행합니다:

// G7Core.toast.success('메시지')는 내부적으로:
G7Core.dispatch({
  handler: 'toast',
  params: {
    message: '메시지',
    type: 'success'
  }
});

모달 관리 (G7Core.modal)

모달을 관리하는 편의 API입니다.

API 목록

메서드 설명 파라미터
open(modalId) 모달 열기 modalId: string
close(modalId?) 모달 닫기 modalId?: string (생략 시 최상위)
closeAll() 모든 모달 닫기 -
isOpen(modalId) 모달 열림 확인 modalId: string
getStack() 모달 스택 반환 -

사용 예시

// 모달 열기
G7Core.modal.open('confirm_modal');

// 특정 모달 닫기
G7Core.modal.close('confirm_modal');

// 최상위 모달 닫기 (스택에서 pop)
G7Core.modal.close();

// 모든 모달 닫기
G7Core.modal.closeAll();

// 모달 열림 상태 확인
if (G7Core.modal.isOpen('confirm_modal')) {
  console.log('모달이 열려 있습니다');
}

// 현재 모달 스택 조회
const stack = G7Core.modal.getStack();
// ['first_modal', 'second_modal'] - 순서대로 쌓인 모달 ID

멀티 모달 (중첩 모달) 지원

G7Core는 스택 기반 멀티 모달을 지원합니다:

// 1. 첫 번째 모달 열기
G7Core.modal.open('confirm_modal');
// stack: ['confirm_modal']

// 2. 두 번째 모달 열기 (첫 번째 위에 중첩)
G7Core.modal.open('error_modal');
// stack: ['confirm_modal', 'error_modal']

// 3. 최상위 모달 닫기
G7Core.modal.close();
// stack: ['confirm_modal'] - 첫 번째 모달 다시 표시

// 4. 남은 모달 닫기
G7Core.modal.close();
// stack: []

네비게이션 (G7Core.navigation)

페이지 전환 상태를 관리하는 API입니다.

API 목록

메서드 설명 반환값
isPending() 전환 진행 중 여부 boolean
onComplete(callback) 전환 완료 시 콜백 실행 구독 해제 함수

사용 예시

// 현재 페이지 전환 중인지 확인
if (G7Core.navigation.isPending()) {
  console.log('페이지 전환 중...');
}

// 전환 완료 시 콜백 실행
const unsubscribe = G7Core.navigation.onComplete(() => {
  console.log('페이지 전환 완료');
  // DOM 조작, 포커스 설정 등
});

// 구독 해제
unsubscribe();

onComplete 동작 방식

onComplete는 전환이 시작(isPending=true)된 후 완료(isPending=false)될 때 콜백을 실행합니다:

// 페이지 이동 후 특정 요소에 포커스
G7Core.navigation.onComplete(() => {
  document.getElementById('main-content')?.focus();
});

// 네비게이션 실행
G7Core.dispatch({
  handler: 'navigate',
  params: { path: '/users' }
});

스타일 헬퍼 (G7Core.style)

Tailwind CSS 클래스를 런타임에서 병합하는 헬퍼 API입니다.

컴포넌트에서 기본 클래스와 외부 className이 충돌할 때, 외부 클래스가 기본 클래스를 올바르게 오버라이드할 수 있도록 합니다.

API 목록

메서드 설명 반환값
mergeClasses(base, override) 충돌하는 Tailwind 클래스 병합 string
conditionalClass(conditions) 조건부 클래스 적용 string
joinClasses(...classes) 여러 클래스 문자열 결합 string

mergeClasses

같은 CSS 속성을 제어하는 클래스가 충돌하면 override 클래스를 우선 적용합니다.

// 기본 사용: justify-center가 justify-between으로 대체됨
G7Core.style.mergeClasses('justify-center items-center', 'justify-between')
// 결과: 'items-center justify-between'

// variant가 다르면 충돌하지 않음
G7Core.style.mergeClasses('text-gray-900 dark:text-white', 'text-blue-500')
// 결과: 'dark:text-white text-blue-500'

// 같은 variant는 충돌
G7Core.style.mergeClasses('dark:text-white dark:bg-gray-800', 'dark:text-gray-100')
// 결과: 'dark:bg-gray-800 dark:text-gray-100'

지원하는 클래스 그룹

그룹 설명 예시
justify Flexbox justify-content justify-center, justify-between
items Flexbox align-items items-center, items-start
textAlign 텍스트 정렬 text-left, text-center
display 디스플레이 속성 flex, block, hidden
bgColor 배경색 bg-white, bg-gray-100
textColor 텍스트색 text-gray-900, text-blue-500
width/height 크기 w-full, h-10
padding/margin 여백 p-4, m-2
... 대부분의 Tailwind 클래스 지원 -

conditionalClass

조건에 따라 클래스를 적용합니다.

G7Core.style.conditionalClass({
  'bg-blue-500': isPrimary,
  'bg-gray-500': !isPrimary,
  'opacity-50': isDisabled,
})
// isPrimary=true, isDisabled=true: 'bg-blue-500 opacity-50'

joinClasses

여러 클래스 문자열을 결합합니다. falsy 값은 무시됩니다.

G7Core.style.joinClasses('flex', isActive && 'bg-blue-500', 'p-4')
// isActive=true: 'flex bg-blue-500 p-4'
// isActive=false: 'flex p-4'

컴포넌트에서 사용 패턴

// G7Core 접근 헬퍼
const G7Core = () => (window as any).G7Core;

export const Button: React.FC<Props> = ({ className = '', ...props }) => {
  const baseClasses = 'inline-flex items-center justify-center';

  // 외부 className이 baseClasses를 오버라이드할 수 있음
  const mergedClassName = G7Core()?.style?.mergeClasses?.(baseClasses, className)
    ?? `${baseClasses} ${className}`;

  return <button className={mergedClassName} {...props} />;
};

플러그인 설정 (G7Core.plugin)

플러그인 설정을 조회하는 API입니다. 백엔드에서 전달된 플러그인 설정값을 프론트엔드에서 사용할 수 있습니다.

API 목록

메서드 설명 반환값
getSettings(pluginId) 플러그인 전체 설정 반환 Record<string, any> | undefined
get(pluginId, key, defaultValue?) 특정 설정값 반환 any
getAll() 모든 플러그인 설정 반환 Record<string, Record<string, any>>

사용 예시

// 플러그인 전체 설정 조회
const daumSettings = G7Core.plugin.getSettings('sirsoft-daum_postcode');
// { display_mode: 'layer', popup_width: 900, popup_height: 600 }

// 특정 설정값 조회
const displayMode = G7Core.plugin.get('sirsoft-daum_postcode', 'display_mode');
// 'layer'

// 기본값 지정
const timeout = G7Core.plugin.get('sirsoft-daum_postcode', 'timeout', 3000);
// 3000 (설정이 없을 경우)

// 모든 플러그인 설정 조회
const allPlugins = G7Core.plugin.getAll();
// { 'sirsoft-daum_postcode': {...}, 'sirsoft-analytics': {...} }

컴포넌트에서 사용 패턴

// 주소 검색 컴포넌트
const AddressInput: React.FC = () => {
  const displayMode = G7Core.plugin.get('sirsoft-daum_postcode', 'display_mode', 'popup');

  const handleSearch = () => {
    if (displayMode === 'layer') {
      // 레이어 모드로 표시
    } else {
      // 팝업 모드로 표시
    }
  };

  return <Button onClick={handleSearch}>주소 검색</Button>;
};

모듈 설정 (G7Core.module)

모듈 설정을 조회하는 API입니다. 백엔드에서 전달된 모듈 설정값을 프론트엔드에서 사용할 수 있습니다.

API 목록

메서드 설명 반환값
getSettings(moduleId) 모듈 전체 설정 반환 Record<string, any> | undefined
get(moduleId, key, defaultValue?) 특정 설정값 반환 any
getAll() 모든 모듈 설정 반환 Record<string, Record<string, any>>

사용 예시

// 모듈 전체 설정 조회
const ecommerceSettings = G7Core.module.getSettings('sirsoft-ecommerce');
// { default_currency: 'KRW', tax_rate: 10, shipping_methods: ['standard', 'express'] }

// 특정 설정값 조회
const currency = G7Core.module.get('sirsoft-ecommerce', 'default_currency');
// 'KRW'

// 기본값 지정
const taxRate = G7Core.module.get('sirsoft-ecommerce', 'tax_rate', 0);
// 10

// 모든 모듈 설정 조회
const allModules = G7Core.module.getAll();
// { 'sirsoft-ecommerce': {...}, 'sirsoft-board': {...} }

컴포넌트에서 사용 패턴

// 가격 표시 컴포넌트
const PriceDisplay: React.FC<{ price: number }> = ({ price }) => {
  const currency = G7Core.module.get('sirsoft-ecommerce', 'default_currency', 'KRW');
  const taxRate = G7Core.module.get('sirsoft-ecommerce', 'tax_rate', 0);

  const priceWithTax = price * (1 + taxRate / 100);

  return (
    <Span>
      {priceWithTax.toLocaleString()} {currency}
    </Span>
  );
};

확장 자산 (G7Core.asset, G7Core.assets)

버전: engine-v1.62.0+

확장(템플릿·모듈·플러그인)이 자기 자산을 런타임에 불러올 때 쓰는 API입니다. 확장 번들은 코어 모듈을 import 할 수 없으므로, 자산 URL 생성과 실패 표면화는 이 전역을 통해 제공됩니다.

구동에 필요한 자산(js·css·웹폰트)은 제3자 CDN 에서 실시간으로 받지 않고 확장이 함께 담아 dist/vendor/{라이브러리}/{버전}/ 에서 자체 제공합니다. 자세한 규약은 module-assets.md를 참조하세요.

G7Core.asset — 자산 URL 생성

메서드 시그니처 설명
template (identifier, path, version?) => string 템플릿 자산 URL
templateDir (identifier, path) => string 템플릿 자산 디렉토리 URL
module (identifier, path, version?) => string 모듈 자산 URL
plugin (identifier, path, version?) => string 플러그인 자산 URL
convertToCurrentMode (url) => string 서버가 확장자 형태로 굳혀 내려준 URL 을 현재 모드로 보정
loadScript (url, attrs?, options?) => Promise<void> 재시도 계층을 갖춘 스크립트 로더 (출처 게이트 적용)
loadStylesheet (url, attrs?, options?) => Promise<void> 재시도 계층을 갖춘 스타일시트 로더
isAllowedScriptSrc (url) => boolean 스크립트 URL 이 주입 허용 대상인지 판정

loadScript 의 url 은 레이아웃 scripts[] 와 같은 출처 정책을 받습니다 — same-origin 절대 경로이거나 확장이 manifest(trusted_script_hosts)로 선언한 신뢰 호스트여야 하며, 그 밖의 원격 URL 은 reject 됩니다. 로더를 쓸 수 없는 주입(iframe document.write 등)은 isAllowedScriptSrc 로 같은 판정을 재사용하세요. 상세: security.md

path 기준이 확장 타입마다 다릅니다. 템플릿은 서버가 dist/ 를 자동으로 붙이므로 path 에 dist/ 를 포함하지 않고, 모듈·플러그인은 확장 루트 기준이라 dist/ 를 직접 포함합니다. 서버측 App\Support\AssetUrl 과 같은 비대칭입니다.

// 템플릿 — dist/ 를 붙이지 않는다
G7Core.asset.template('sirsoft-admin_basic', 'vendor/monaco-editor/0.54.0/vs/loader.js');

// 플러그인 — 확장 루트 기준이라 dist/ 를 포함한다
G7Core.asset.plugin('sirsoft-ckeditor5', 'dist/vendor/ckeditor5/43.3.1/ckeditor5.umd.js');

// 모듈
G7Core.asset.module('sirsoft-board', 'dist/js/board.js');

자산 URL 을 문자열로 조립하지 않습니다

// ❌ 금지 — 확장자를 정적 location 이 가로채는 서버에서 404 가 된다
const url = '/api/plugins/assets/sirsoft-ckeditor5/dist/vendor/ckeditor5/43.3.1/ckeditor5.umd.js';

// ✅ 올바른 사용
const url = G7Core.asset.plugin('sirsoft-ckeditor5', 'dist/vendor/ckeditor5/43.3.1/ckeditor5.umd.js');

자산 URL 은 서버 설정(general.asset_url_mode)과 정적 게시 상태에 따라 확장자 형태 (.../ckeditor5.umd.js)와 쿼리 형태(...?file=...), 정적 게시본 경로 (/build/ext/{버전}/...) 중 하나로 해석됩니다. 문자열로 조립하면 그 판정을 건너뛰어, 정규식 location 이 확장자를 먼저 가로채는 서버에서 그 자산만 조용히 404 가 됩니다.

templateDir — 디렉토리 접두가 필요한 소비자

AMD 로더나 워커처럼 디렉토리 접두 뒤에 파일명을 이어 붙이는 소비자는 template() 대신 templateDir() 을 씁니다. 쿼리 형태(?file=)는 뒤에 파일명을 이어 붙일 수 없기 때문입니다. 확장자 없는 모드에서 404 일 수 있으므로 소비자가 폴백을 갖춰야 합니다.

loader.config({
  paths: { vs: G7Core.asset.templateDir('sirsoft-admin_basic', 'vendor/monaco-editor/0.54.0/vs') },
});

G7Core.assets — 자산 실패 표면화

로드에 끝내 실패한 자산을 화면 상단 배너로 알리고 [다시 시도] 를 제공합니다. 실패를 console.error 한 줄로 끝내면 사용자에게는 "빈 자리" 로만 나타나고 자체 서버 로그에도 흔적이 남지 않아 운영자가 원인을 특정할 수 없습니다.

메서드 시그니처 설명
notifyFailure (failure: AssetFailure) => void 실패 등록 (같은 id 는 갱신)
clearFailure (id: string) => void 해당 실패 해제
clearAll () => void 전체 해제
getFailures () => AssetFailure[] 현재 등록된 실패 목록
retryAll () => Promise<void> 등록된 실패 전부 재시도 — 각 retry 를 순차 await 하므로 완료를 기다리려면 반환값을 await 한다

AssetFailure 필드:

필드 타입 설명
id string 중복 누적을 막는 식별자 (필수)
label string 짧은 항목명 — 여러 건 합산 표시에 쓰인다. 사용자 어휘여야 한다: 내부 구분 키·식별자를 그대로 넘기면 배너에 「module을(를) 불러오지 못했습니다」처럼 해석 불가한 문구가 노출된다
message string? 사용자에게 보일 안내 문장. 생략하면 label 기반 기본 문구
retry () => Promise<void> | void 생략하면 [다시 시도] 버튼을 렌더하지 않는다
try {
  await G7Core.asset.loadStylesheet(url, { id: 'my-plugin-css' });
  G7Core.assets.clearFailure('my-plugin-css');
} catch (error) {
  G7Core.assets.notifyFailure({
    id: 'my-plugin-css',
    label: '편집기 스타일',
    retry: () => G7Core.asset.loadStylesheet(url, { id: 'my-plugin-css' }),
  });
}

배너는 독립 레이아웃(extends 없음)에서도 떠야 하므로 호스트 컴포넌트에 의존하지 않고 DOM 에 직접 주입됩니다. Toast 와 달리 사용자가 닫을 때까지 유지되며 role="alert" 를 갖습니다. retry 가 resolve 되면 그 실패는 자동으로 해제되고, reject 되면 배너를 유지한 채 재시도 실패를 알립니다.


위지윅 편집기 (G7Core.wysiwyg)

engine-v1.11.0+ 추가

레이아웃 위지윅 편집기 관련 API입니다.

API 목록

메서드 설명 반환값
isEditMode() 편집 모드 여부 반환 boolean
setEditMode(layoutName, templateId) 편집 모드 활성화 void
clearEditMode() 편집 모드 비활성화 void
getCurrentLayoutName() 현재 편집 중인 레이아웃명 string | null
getCurrentTemplateId() 현재 편집 중인 템플릿 ID string | null
isEditModeFromUrl() URL에서 편집 모드 여부 확인 boolean
getEditModeUrl(route, templateId) 편집 모드 URL 생성 string
enterEditMode(route, templateId) 편집 모드로 페이지 이동 void
exitEditMode() 편집 모드 종료 (일반 페이지로) void
getVersion() 위지윅 모듈 버전 string
getPhase() 현재 구현 Phase number

사용 예시

// 편집 모드 확인
if (G7Core.wysiwyg.isEditMode()) {
  console.log('현재 편집 모드입니다');
  console.log('레이아웃:', G7Core.wysiwyg.getCurrentLayoutName());
  console.log('템플릿:', G7Core.wysiwyg.getCurrentTemplateId());
}

// URL에서 편집 모드 여부 확인 (쿼리 파라미터 기반)
if (G7Core.wysiwyg.isEditModeFromUrl()) {
  // ?mode=edit 파라미터가 있는 경우
}

// 편집 모드 URL 생성
const editUrl = G7Core.wysiwyg.getEditModeUrl('/shop', 'sirsoft-basic');
// 결과: 'https://example.com/shop?mode=edit&template=sirsoft-basic'

// 편집 모드로 진입
G7Core.wysiwyg.enterEditMode('/shop', 'sirsoft-basic');

// 편집 모드 종료
G7Core.wysiwyg.exitEditMode();

편집 모드 상태 관리

// 프로그래밍 방식으로 편집 모드 설정
G7Core.wysiwyg.setEditMode('home', 'sirsoft-basic');

// 편집 모드 해제
G7Core.wysiwyg.clearEditMode();

커스텀 핸들러 Stale State 방지 규칙

engine-v1.17.0+ 추가

커스텀 핸들러에서 비동기 작업 후 상태를 사용할 때 발생하는 stale state 문제를 방지하는 규칙입니다.

await 후 상태 재조회 필수

비동기 작업(API 호출, setTimeout 등) 후에는 캡처된 상태가 오래된 값(stale)일 수 있습니다. 반드시 await 후 최신 상태를 재조회하세요.

// ❌ 금지: await 전에 캡처한 상태를 await 후에 사용
const state = G7Core.state.getLocal();
const currentUi = state.ui || {};
await someAsyncOperation();  // 이 동안 다른 곳에서 상태가 변경될 수 있음
G7Core.state.setLocal({ ui: { ...currentUi, loading: false } });  // STALE!

// ✅ 필수: await 후 최신 상태 재조회
await someAsyncOperation();
const latestState = G7Core.state.getLocal() || {};  // 재조회
const latestUi = latestState.ui || {};
G7Core.state.setLocal({ ui: { ...latestUi, loading: false } });

실전 패턴

API 호출 후 상태 업데이트

// 복사 중 상태 설정 (await 전)
const initialState = G7Core.state.getLocal() || {};
G7Core.state.setLocal({ ui: { ...initialState.ui, isCopying: true } });

try {
  // API 호출
  const response = await G7Core.api.post('/api/copy', data);

  // await 후 최신 상태 재조회
  const latestState = G7Core.state.getLocal() || {};
  G7Core.state.setLocal({
    ui: { ...latestState.ui, isCopying: false },
    form: { ...latestState.form, ...response.data },
  });
} catch (error) {
  // 에러 시에도 최신 상태 재조회
  const errorState = G7Core.state.getLocal() || {};
  G7Core.state.setLocal({
    ui: { ...errorState.ui, isCopying: false, error: true },
  });
}

루프 내 await (순차 처리)

// 각 반복에서 최신 상태 조회 (권장)
for (const file of files) {
  // 매번 최신 상태 조회
  const currentState = G7Core.state.getLocal() || {};
  G7Core.state.setLocal({
    ui: { ...currentState.ui, uploadProgress: { [file.name]: 0 } },
  });

  const response = await G7Core.api.upload('/api/upload', file);

  // 업로드 후 최신 상태 재조회
  const successState = G7Core.state.getLocal() || {};
  const existingImages = successState.form?.images || [];
  G7Core.state.setLocal({
    form: { ...successState.form, images: [...existingImages, response.data] },
  });
}

왜 이 문제가 발생하는가?

  1. JavaScript 클로저: 함수가 생성될 때 외부 변수를 캡처합니다.
  2. React setState 비동기성: setLocal 호출 후 즉시 상태가 반영되지 않습니다.
  3. 비동기 작업 중 상태 변경: await 중에 사용자가 다른 UI 조작을 할 수 있습니다.

관련 엔진 보호 메커니즘

그누보드7 템플릿 엔진은 다음 메커니즘으로 stale state 문제를 완화합니다:

메커니즘 설명 파일
stateRef.current useCallback 캐싱 우회 DynamicRenderer.tsx
__g7PendingLocalState setLocal 후 dispatch 동기화 G7CoreGlobals.ts
G7Core.state.getLocal() 최신 로컬 상태 조회 G7CoreGlobals.ts

하지만 커스텀 핸들러에서는 개발자가 직접 최신 상태 재조회를 구현해야 합니다.

체크리스트

커스텀 핸들러 작성 시 확인:

□ await 후 상태를 사용하는 모든 곳에서 G7Core.state.getLocal() 재조회
□ try-catch 블록의 catch에서도 최신 상태 재조회
□ 루프 내 await 시 각 반복에서 상태 재조회
□ 모달 컨텍스트인 경우 getParent()도 재조회 고려

관련 문서