@jogak/ui 0.1.0-alpha.6 → 0.1.0-alpha.7.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,60 @@ All notable changes to Jogak packages are documented here. The repository follow
5
5
 
6
6
  Version numbers apply to all packages in the workspace (synchronized release).
7
7
 
8
+ ## [0.1.0-alpha.7.1] — 2026-05-09
9
+
10
+ ### Fixed
11
+
12
+ - **`previewIsolation` 격리 통로 정정** — 알파.7은 `main.tsx`가 isolation 모드와
13
+ 무관하게 사용자 globalCss를 outer document에 무조건 inject해서 jogak chrome
14
+ utility를 사용자 reset/preflight가 무력화하던 결함이 있었음. 알파.7.1:
15
+ - `main.tsx`: `_jogakPreviewIsolation === 'none'`일 때만 사용자 globalCss를
16
+ dynamic import. 다른 모드에서는 outer document inject 차단 → chrome 침범 zero.
17
+ - `ShadowMount`: `adoptedStyleSheets` 흡수 + MutationObserver HMR sync 로직 제거.
18
+ ShadowMount는 양방향 격리만 책임 (사용자 globalCss는 shadow scope에 inject 안 됨).
19
+ - 사용자 컴포넌트 styling 통로(사용자 디자인 토큰/Tailwind utility 적용)는 알파.8
20
+ 사이클에서 사용자 vite 통합으로 별도 도입 예정.
21
+ - **`Preview` cleanup race condition** — `NoneAdapterContent` / `ShadowAdapterContent`
22
+ / `IframeMount`의 unmount race(`Attempted to synchronously unmount...`)를
23
+ `queueMicrotask`로 defer해 회피.
24
+
25
+ ### Changed (의도된 default 변경)
26
+
27
+ - **`JogakHostOptionsBase.previewIsolation` default `'none'` → `'shadow'`** —
28
+ 양방향 격리가 default. back-compat은 `previewIsolation: 'none'` 명시.
29
+ - **`JogakApp` / `Preview` `previewIsolation` prop default `'none'` → `'shadow'`**.
30
+
31
+ ### README 업데이트
32
+
33
+ - "previewIsolation 사용 가이드" 섹션 재작성 — 3 모드 비교표 + `'shadow'` default
34
+ 동작/한계 + `'iframe'` 주의 + `'none'` back-compat 사용법.
35
+ - "격리 보장" 섹션 — default 표시 갱신 (`'none'` → `'shadow'`).
36
+ - 로드맵 표 — alpha.7.1 entry 추가.
37
+
38
+ ## [0.1.0-alpha.7] — 2026-05-09
39
+
40
+ ### Added
41
+
42
+ - **`JogakHostOptionsBase`에 `globalCss` / `previewIsolation` 필드** — `runHost({ globalCss: true,
43
+ previewIsolation: 'shadow' })` 형태로 programmatic API에서도 옵션 사용 가능.
44
+ - **`Preview` Shadow / iframe 마운트 분기**
45
+ - `previewIsolation: 'none'` (default, 알파.6과 동일) — 같은 document에 마운트
46
+ - `'shadow'` — `attachShadow` + `createPortal` + `adoptedStyleSheets`로 외부 css/font 흡수,
47
+ `MutationObserver`로 외부 `<style>` HMR 동기화
48
+ - `'iframe'` — `/preview-frame.html` + `contentWindow.__jogak_setProps__` 직접 호출,
49
+ 완벽 격리 (props 직렬화 불필요)
50
+ - **`preview-frame.html` + `src/app/preview-frame.tsx`** — iframe-mode 전용 최소 entry.
51
+ `virtual:jogak/global-css`만 import (jogak chrome css는 미포함, 격리 보장).
52
+ - **README "previewIsolation 모드 비교" + "알파.6 → 알파.7 마이그레이션" 섹션** — 3 모드의
53
+ trade-off / Radix portal 한계 / `jogak.config.ts` 패턴으로의 마이그레이션 안내.
54
+
55
+ ### Fixed
56
+
57
+ - **알파.6 README의 `vite.config.ts` 가이드 정정** — 알파.6는 `vite.config.ts`에서
58
+ `jogak({ globalCss: true })`를 호출하라고 안내했으나 `runHost`가 `configFile: false`로
59
+ 사용자 vite config를 무시해 옵션이 적용되지 않았음. 알파.7부터는 `jogak.config.ts`에
60
+ `defineJogakConfig({ globalCss: true })`로 작성.
61
+
8
62
  ## [0.1.0-alpha.6] — 2026-05-09
9
63
 
10
64
  ### Added
package/README.md CHANGED
@@ -12,27 +12,31 @@ pnpm add @jogak/ui @jogak/core @jogak/react react react-dom
12
12
 
13
13
  ## Usage
14
14
 
15
- ### Embed into a Vite SPA
15
+ ### `jogak.config.ts` 사용 (권장)
16
16
 
17
- ```ts
18
- // vite.config.ts
19
- import { jogak } from '@jogak/core/vite'
17
+ 알파.7부터 `jogak` CLI는 사용자 프로젝트 root의 `jogak.config.{ts,mts,mjs,js,json}`을
18
+ 자동 발견해 옵션을 읽습니다. shadcn/ui 사용자가 자주 만지는 옵션은 한 곳에 선언하세요:
20
19
 
21
- export default defineConfig({
22
- plugins: [react(), jogak({ codeTheme: 'vsDark' })],
20
+ ```ts
21
+ // jogak.config.ts (사용자 프로젝트 root)
22
+ import { defineJogakConfig } from '@jogak/core'
23
+
24
+ export default defineJogakConfig({
25
+ globalCss: true, // 사용자 globalCss 자동 감지 + import
26
+ // previewIsolation: 'shadow', // 'shadow' (default, 알파.7.1) | 'iframe' | 'none'
27
+ codeTheme: 'vsDark',
28
+ port: 5173, // dev server (CLI --port로 override)
23
29
  })
24
30
  ```
25
31
 
26
- ```tsx
27
- // main.tsx
28
- import 'virtual:jogak'
29
- import { _jogakCodeTheme } from 'virtual:jogak'
30
- import { JogakApp } from '@jogak/ui'
31
- import { createRoot } from 'react-dom/client'
32
+ CLI는 본 config의 옵션을 읽어 jogak SPA 빌드/dev에 전달합니다. CLI 플래그가
33
+ 명시되면 config 값을 override합니다 (Vite 패턴).
32
34
 
33
- createRoot(document.getElementById('root')!).render(
34
- <JogakApp codeTheme={_jogakCodeTheme} />,
35
- )
35
+ ### `jogak.config.ts` 미사용 (CLI flag만)
36
+
37
+ ```bash
38
+ jogak dev --global-css --preview-isolation=none
39
+ jogak build --global-css --preview-isolation=shadow
36
40
  ```
37
41
 
38
42
  ### Static catalog (Next.js / any host bundler)
@@ -48,6 +52,17 @@ export default function Page() {
48
52
 
49
53
  `@jogak/ui` ships pre-built ESM/CJS — no `transpilePackages` required for Next.js.
50
54
 
55
+ ### legacy: `vite.config.ts`에 `jogak()` plugin 직접 사용
56
+
57
+ > 알파.6까지의 README는 사용자 `vite.config.ts`에 `jogak({ globalCss: true })`를
58
+ > 작성하라 안내했지만, 이는 사용자 일반 vite 빌드용에만 적용되고 jogak SPA에는
59
+ > 무효였습니다 (`runHost`가 `configFile: false`로 사용자 vite config 무시).
60
+ > 알파.7부터는 위 `jogak.config.ts` 패턴을 사용하세요.
61
+
62
+ `@jogak/core/vite`의 `jogak()` plugin은 사용자가 vite로 직접 jogak 카탈로그를
63
+ embed하는 고급 시나리오(예: Next.js page 안에서 `<JogakApp entries={...} />`로
64
+ 정적 entries를 전달할 때 빌드 타임 generate)에서만 사용합니다.
65
+
51
66
  ### Sub-paths
52
67
 
53
68
  ```ts
@@ -66,25 +81,25 @@ See the [main README](https://github.com/devclib/jogak#readme) for the full host
66
81
 
67
82
  | 단계 | 상태 | 내용 |
68
83
  |------|------|------|
69
- | alpha.4 | ✅ 완료 | jogak UI 빌드 파이프라인에 Tailwind v4 + `jogak:` prefix 도입 (인프라) |
84
+ | alpha.4 | ✅ 완료 | jogak UI 빌드에 Tailwind v4 + `jogak:` prefix 도입 |
70
85
  | alpha.5 | ✅ 완료 | jogak UI 컴포넌트를 Tailwind class로 마이그레이션 (4 PR) |
71
- | **alpha.6** | ✅ **본 릴리즈** | **사용자 `globalCss` 옵션 (`JogakPluginOptions.globalCss`)** |
72
- | alpha.7+ | 예정 | preview Shadow DOM / iframe 격리 옵션 (`previewIsolation`) |
86
+ | alpha.6 | ✅ 완료 | `JogakPluginOptions.globalCss` 옵션 + chrome 보호 rule (단, 통로 부재로 사용자 환경에서 무효 — 알파.7에서 정정) |
87
+ | alpha.7 | 완료 | `jogak.config.ts` 통로 + `previewIsolation` 옵션 + `JogakHostOptionsBase` 확장 (단, isolation 통로 결함으로 격리 무효 — 알파.7.1에서 정정) |
88
+ | **alpha.7.1** | ✅ **본 릴리즈** | **isolation 통로 hotfix + default `previewIsolation: 'shadow'` + main.tsx isolation-aware import** |
89
+ | alpha.8+ | 예정 | 사용자 vite 통합으로 사용자 Tailwind utility 컴파일 통로, multi-baseline VR |
73
90
 
74
- ### 사용자 globalCss 적용 (alpha.6)
91
+ ### 사용자 globalCss 적용
75
92
 
76
93
  `runHost`는 vite root를 `@jogak/ui` 패키지로 두고 사용자 `vite.config.ts` / `main.tsx`를 무시하므로(`configFile: false`), 사용자 `index.css`(Tailwind/shadcn 디자인 토큰)가 jogak SPA에 자동 적용되지 않습니다. `globalCss` 옵션은 이를 opt-in으로 해결합니다.
77
94
 
78
95
  #### 사용법
79
96
 
80
97
  ```ts
81
- // vite.config.ts — 자동 감지 (shadcn/ui Vite 시나리오)
82
- import { defineConfig } from 'vite'
83
- import react from '@vitejs/plugin-react'
84
- import { jogak } from '@jogak/core/vite'
98
+ // jogak.config.ts (사용자 프로젝트 root)
99
+ import { defineJogakConfig } from '@jogak/core'
85
100
 
86
- export default defineConfig({
87
- plugins: [react(), jogak({ globalCss: true })],
101
+ export default defineJogakConfig({
102
+ globalCss: true,
88
103
  })
89
104
  ```
90
105
 
@@ -103,19 +118,55 @@ export default defineConfig({
103
118
 
104
119
  ```ts
105
120
  // 명시 경로 1개
106
- jogak({ globalCss: './src/index.css' })
121
+ defineJogakConfig({ globalCss: './src/index.css' })
107
122
 
108
123
  // 다중 import (디자인 토큰 + reset 분리)
109
- jogak({ globalCss: ['./src/tokens.css', './src/reset.css'] })
124
+ defineJogakConfig({ globalCss: ['./src/tokens.css', './src/reset.css'] })
110
125
  ```
111
126
 
112
- #### 격리 보장
127
+ #### 격리 보장 (default `previewIsolation: 'shadow'`, 알파.7.1)
113
128
 
114
129
  - **Tailwind utility class**: jogak UI는 `prefix=jogak`로 빌드되어 사용자 utility와 충돌 zero (예: 사용자 `bg-primary` ≠ jogak `jogak:bg-...`).
115
130
  - **CSS variable**: jogak은 `--jogak-*` prefix로 namespace 격리 → 사용자 `:root { --primary }` 같은 디자인 토큰은 영향 없음.
116
131
  - **Form element 보호**: `[data-jogak-shell]` 안의 button/input/select/textarea는 사용자 reset의 `border` / `background` / `color` 침범을 받지 않도록 `:where()` 보호 rule 적용. specificity 0이라 사용자가 명시적으로 `[data-jogak-shell] button { ... }`를 작성하면 정상 override됩니다.
117
132
 
118
- #### scope 가이드 — 알려진 영향 영역
133
+ ### previewIsolation 사용 가이드 (알파.7.1)
134
+
135
+ jogak chrome ↔ 사용자 영역의 **양방향 격리** 모드. 알파.7.1부터 default가 `'shadow'`로 변경되어 사용자 reset/preflight가 jogak chrome을 침범하는 결함이 자동 차단됩니다.
136
+
137
+ #### 모드 비교
138
+
139
+ | 모드 | mount | 사용자 globalCss inject 위치 | chrome 침범 | Radix portal | cold start |
140
+ |------|-------|---------------------------|-----------|--------------|-----------|
141
+ | `'shadow'` (default) | ShadowRoot | **inject 안 됨** (격리) | zero | document.body (shadow 외부) | ★★★ |
142
+ | `'iframe'` | iframe document | iframe document | zero | iframe document (정상) | ★★ |
143
+ | `'none'` (back-compat) | 같은 document | outer document | **있음 (의도된 허용)** | document.body | ★★★ |
144
+
145
+ #### `'shadow'` 모드 (default) 동작과 한계
146
+
147
+ 알파.7.1에서 ShadowMount는 **사용자 css를 shadow root에 inject하지 않습니다**. outer document에도 inject 안 됨 (main.tsx 가드). 결과적으로:
148
+
149
+ - **chrome 보호**: 사용자 `* { ... }` reset이나 Tailwind preflight가 jogak utility를 덮어쓸 수 없음
150
+ - **사용자 컴포넌트 styling**: shadow scope 안에서는 사용자 globalCss가 적용되지 않으므로 디자인 토큰/Tailwind utility 미적용 (raw HTML 형태). **사용자 컴포넌트의 styling 통로는 알파.8 사이클**에서 사용자 vite 통합으로 별도 도입 예정
151
+ - **Radix portal**: default Portal target은 `document.body` (shadow 외부) — portal 내용은 shadow scope 외부라 사용자 토큰 미적용. 회피: `<Dialog.Portal container={shadowRootEl}>` 명시 전달
152
+
153
+ #### `'iframe'` 모드
154
+
155
+ iframe document scope에 사용자 globalCss가 import되어 사용자 컴포넌트가 토큰 적용을 받습니다. outer chrome은 사용자 css 영향 zero.
156
+
157
+ > ⚠️ jogak 차별점("single Vite, no iframe")과 일부 상충 — iframe load + 별도 module graph 비용. Radix portal까지 완벽 격리해야 하는 시나리오 한정.
158
+
159
+ iframe은 `<iframe src="/preview-frame.html">`로 로드되며, 부모-자식 동일 origin이라 부모가 `iframe.contentWindow.__jogak_setProps__({ entry, args })`를 직접 호출하는 방식으로 props를 전달합니다.
160
+
161
+ #### `'none'` 모드 (back-compat opt-in)
162
+
163
+ 사용자 globalCss를 outer document에 inject. 사용자 reset이 jogak chrome에 영향을 주는 것을 의도적으로 허용하는 경우만 사용:
164
+
165
+ ```ts
166
+ defineJogakConfig({ globalCss: true, previewIsolation: 'none' })
167
+ ```
168
+
169
+ ### scope 가이드 — 알려진 영향 영역 (`'none'` 모드 기준)
119
170
 
120
171
  사용자 css의 다음 패턴은 jogak chrome에도 영향을 줄 수 있습니다 (전역 import이므로):
121
172
 
@@ -123,7 +174,7 @@ jogak({ globalCss: ['./src/tokens.css', './src/reset.css'] })
123
174
  - `* { ... }` 또는 `*, *::before { ... }` — 모든 요소
124
175
  - 글로벌 reset (`button { all: unset }`, `* { border: 1px solid var(--border) }` 등)
125
176
 
126
- 이를 사용자 콘텐츠 영역(preview)으로만 한정하려면 셀렉터를 `[data-jogak-content] *` scope로 작성하세요:
177
+ 이를 사용자 콘텐츠 영역(preview)으로만 한정하려면 셀렉터를 `[data-jogak-content] *` scope로 작성하거나, `previewIsolation: 'shadow' | 'iframe'`을 사용하세요.
127
178
 
128
179
  ```css
129
180
  /* 권장: preview 영역으로만 한정 */
@@ -139,18 +190,44 @@ jogak({ globalCss: ['./src/tokens.css', './src/reset.css'] })
139
190
  }
140
191
  ```
141
192
 
142
- 또는 jogak chrome과 preview 모두에 일관된 사용자 디자인 시스템을 적용하려는 의도라면 그대로 두면 됩니다 — jogak chrome도 사용자 토큰/타이포를 함께 체험하게 됩니다.
143
-
144
- ##### selector hint
193
+ #### selector hint
145
194
 
146
195
  - `[data-jogak-shell]` — `JogakApp` 최상위 wrapper (chrome + preview 모두 포함)
147
196
  - `[data-jogak-content]` — Preview의 사용자 콘텐츠 영역 (사용자 컴포넌트가 렌더되는 div)
148
197
 
149
- #### 알려진 한계
198
+ ### 알파.6 → 알파.7 마이그레이션
199
+
200
+ 알파.6 README는 다음 가이드를 안내했습니다:
201
+
202
+ ```ts
203
+ // alpha.6 README (무효)
204
+ // vite.config.ts
205
+ import { jogak } from '@jogak/core/vite'
206
+ export default defineConfig({
207
+ plugins: [react(), jogak({ globalCss: true })],
208
+ })
209
+ ```
210
+
211
+ 이 설정은 사용자 일반 vite 빌드용에만 적용되고 jogak SPA에는 전달되지 않았습니다.
212
+ 알파.7부터 `jogak.config.ts`로 옮기세요:
213
+
214
+ ```ts
215
+ // alpha.7+ — jogak.config.ts (사용자 프로젝트 root)
216
+ import { defineJogakConfig } from '@jogak/core'
217
+
218
+ export default defineJogakConfig({
219
+ globalCss: true,
220
+ })
221
+ ```
222
+
223
+ `vite.config.ts`에 `jogak()` plugin이 그대로 있어도 jogak SPA에는 영향 없으므로
224
+ 당장 제거할 필요는 없습니다 (alpha.6 동작 유지). 하지만 jogak.config.ts로 옮긴 후
225
+ 중복 제거를 권장합니다.
226
+
227
+ ### 알려진 한계
150
228
 
151
- - 옵션은 **opt-in**입니다 (default `false`). 알파.5까지의 동작은 동일합니다.
229
+ - `globalCss` 옵션은 **opt-in**입니다 (default `false`). 알파.5까지의 동작은 동일합니다.
152
230
  - `globalCss: true` 자동 감지는 dev 시작 시점에 한 번만 수행됩니다. 후보 css 파일이 dev 시작 후에 새로 추가되면 dev 서버를 재시작해야 합니다. 명시 경로(`globalCss: './src/index.css'`)는 파일이 나중에 생성되어도 정상 hot reload됩니다.
153
- - preview 영역만 사용자 css로 한정하려면 알파.7+의 `previewIsolation: 'shadow'` 옵션을 기다려 주세요.
154
231
  - CSS module(`.module.css`)을 자동 감지 후보에서 직접 import하지 않습니다 — 명시 경로로 넘기면 Vite가 module 처리합니다.
155
232
 
156
233
  - Repository: https://github.com/devclib/jogak
package/dist/app/App.d.ts CHANGED
@@ -14,5 +14,15 @@ export interface JogakAppProps {
14
14
  readonly entries?: readonly RegistryEntry[];
15
15
  readonly metas?: readonly RegistryEntryMeta[];
16
16
  readonly codeTheme?: string;
17
+ /**
18
+ * 알파.7: Preview 영역 격리 모드. default `'none'`.
19
+ *
20
+ * - `'none'` — Preview 콘텐츠를 chrome 같은 document에 렌더 (알파.6까지의 동작).
21
+ * - `'shadow'` — ShadowRoot 안에 마운트. 사용자 globalCss/reset이 chrome 침범 차단.
22
+ * - `'iframe'` — 별도 document(iframe)에 마운트. 가장 강한 격리.
23
+ *
24
+ * 자세한 트레이드오프는 `@jogak/ui` README의 "previewIsolation 사용 가이드" 참조.
25
+ */
26
+ readonly previewIsolation?: 'none' | 'shadow' | 'iframe';
17
27
  }
18
- export declare function JogakApp({ entries, metas, codeTheme, }?: JogakAppProps): ReactElement;
28
+ export declare function JogakApp({ entries, metas, codeTheme, previewIsolation, }?: JogakAppProps): ReactElement;
@@ -0,0 +1,37 @@
1
+ import { ReactElement } from 'react';
2
+ import { RegistryEntry } from '@jogak/core';
3
+ export interface IframeMountProps {
4
+ readonly entry: RegistryEntry;
5
+ readonly args: Readonly<Record<string, unknown>>;
6
+ readonly className?: string;
7
+ readonly 'data-testid'?: string;
8
+ }
9
+ interface SetPropsArgs {
10
+ readonly entry: RegistryEntry;
11
+ readonly args: Readonly<Record<string, unknown>>;
12
+ }
13
+ declare global {
14
+ interface Window {
15
+ __jogak_setProps__?: (args: SetPropsArgs) => void;
16
+ __jogak_unmount__?: () => void;
17
+ }
18
+ }
19
+ /**
20
+ * 알파.7: previewIsolation='iframe' 모드의 mount 컴포넌트.
21
+ *
22
+ * - `<iframe src="/preview-frame.html">`을 마운트.
23
+ * - iframe load 후 `iframe.contentWindow.__jogak_setProps__({ entry, args })`를
24
+ * 호출해 entry/args를 주입한다 (postMessage 미사용 — 동일 origin이므로
25
+ * contentWindow 직접 접근 가능).
26
+ * - entry/args 변경 시 setProps 재호출 (load 완료 이후).
27
+ *
28
+ * HMR:
29
+ * - iframe document 자체도 Vite dev server module을 import하므로 사용자 컴포넌트
30
+ * 파일 변경 시 fast refresh가 iframe 안에서 작동.
31
+ * - previewIsolation 모드 자체 변경은 가상 모듈 invalidate → full reload.
32
+ *
33
+ * sandbox 미설정:
34
+ * - 사용자 컴포넌트가 fetch/clipboard/storage 등 자유롭게 사용해야 하므로 sandbox X.
35
+ */
36
+ export declare function IframeMount({ entry, args, className, 'data-testid': dataTestId, }: IframeMountProps): ReactElement;
37
+ export {};
@@ -0,0 +1,25 @@
1
+ import { ReactElement, ReactNode, CSSProperties } from 'react';
2
+ export interface ShadowMountProps {
3
+ readonly children: ReactNode;
4
+ readonly className?: string;
5
+ readonly style?: CSSProperties;
6
+ readonly 'data-testid'?: string;
7
+ }
8
+ /**
9
+ * 알파.7.1: previewIsolation='shadow' 모드의 mount 컴포넌트.
10
+ *
11
+ * 책임: 양방향 격리만 제공 (Preview ↔ outer document 양방향 cascade 차단).
12
+ * - 사용자 globalCss는 main.tsx 가드로 outer document에 inject되지 않음.
13
+ * - shadow root 안에는 jogak chrome css도 사용자 css도 없음 (둘 다 외부에서 격리).
14
+ * - 사용자 컴포넌트의 utility class 컴파일은 결함 B (알파.8 사이클).
15
+ *
16
+ * 알파.7 결함 정정:
17
+ * - `syncStyleSheets`/`MutationObserver`/`adoptedStyleSheets` 흡수 로직 제거.
18
+ * 알파.7은 outer document에 사용자 css가 있는 한 chrome을 침범했고, shadow
19
+ * 안의 흡수 로직은 의미가 없었음. 알파.7.1: outer에 사용자 css 자체가 없음.
20
+ *
21
+ * Radix portal 한계 (사용자 인지 필요, README 명시):
22
+ * - default Portal target = document.body (shadow 외부). 사용자가 명시적으로
23
+ * `<Portal container={shadowRootEl}>`을 전달해야 portal 내용도 격리됨.
24
+ */
25
+ export declare function ShadowMount({ children, className, style, 'data-testid': dataTestId, }: ShadowMountProps): ReactElement;
@@ -12,6 +12,14 @@ export interface PreviewProps {
12
12
  * 첫 jogak로 자동 보정하기 위한 콜백. 부모가 selectedJogakName / URL을 갱신.
13
13
  */
14
14
  readonly onResolveJogak?: (entryId: string, jogakName: string) => void;
15
+ /**
16
+ * 알파.7: Preview 영역 격리 모드. default `'none'`.
17
+ *
18
+ * - `'none'` — 기존 동작 (chrome과 같은 document, 알파.6 chrome 보호 rule 적용).
19
+ * - `'shadow'` — ShadowRoot에 마운트. 사용자 globalCss reset이 chrome 침범 차단.
20
+ * - `'iframe'` — `/preview-frame.html` iframe에 마운트. 강한 격리.
21
+ */
22
+ readonly previewIsolation?: 'none' | 'shadow' | 'iframe';
15
23
  }
16
24
  /**
17
25
  * Preview — `useEntry(entryId)`의 status에 따라 분기 (계약 §5.4).
@@ -23,5 +31,5 @@ export interface PreviewProps {
23
31
  *
24
32
  * Layout shift 방지를 위해 캔버스 영역 minHeight 유지.
25
33
  */
26
- export declare function Preview({ entryId, jogakName, overrideArgs, onArgChange, onReset, codeTheme, onResolveJogak, }: PreviewProps): ReactElement;
34
+ export declare function Preview({ entryId, jogakName, overrideArgs, onArgChange, onReset, codeTheme, onResolveJogak, previewIsolation, }: PreviewProps): ReactElement;
27
35
  export type { UseEntryState };
@@ -38,6 +38,26 @@ export interface JogakHostOptionsBase {
38
38
  readonly extraPlugins?: readonly unknown[];
39
39
  /** stdout/stderr 출력 stream (테스트 시 주입). */
40
40
  readonly logger?: HostLogger;
41
+ /**
42
+ * 알파.7: 사용자 globalCss를 jogak SPA에 import한다 (algfa.6 옵션의 host 통로).
43
+ *
44
+ * `false`/`undefined` (default): 미주입.
45
+ * `true`: `<userRoot>/src/{...}.css` 자동 감지 후 첫 발견 1개 import.
46
+ * `string` / `readonly string[]`: 명시 경로.
47
+ *
48
+ * 본 옵션은 jogak() Vite plugin의 `globalCss` 옵션으로 그대로 전달된다.
49
+ */
50
+ readonly globalCss?: boolean | string | readonly string[];
51
+ /**
52
+ * 알파.7.1: Preview 영역 격리 모드.
53
+ *
54
+ * `'shadow'` (default, 알파.7.1), `'iframe'`, `'none'` 중 하나.
55
+ * jogak() Vite plugin의 `previewIsolation` 옵션으로 그대로 전달된다 + UI 측
56
+ * Preview 컴포넌트가 해당 모드별 분기로 렌더한다.
57
+ *
58
+ * 자세한 모드 설명은 `JogakPluginOptions.previewIsolation` JSDoc 참조.
59
+ */
60
+ readonly previewIsolation?: 'none' | 'shadow' | 'iframe';
41
61
  }
42
62
  export interface JogakDevOptions extends JogakHostOptionsBase {
43
63
  readonly mode: 'dev';
@@ -1 +1 @@
1
- "use strict";var S=Object.create;var y=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var N=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of A(e))!H.call(t,n)&&n!==o&&y(t,n,{get:()=>e[n],enumerable:!(r=k(e,n))||r.enumerable});return t};var i=(t,e,o)=>(o=t!=null?S(B(t)):{},N(e||!t||!t.__esModule?y(o,"default",{value:t,enumerable:!0}):o,t));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("node:url"),u=require("node:path");var l=typeof document<"u"?document.currentScript:null;const F=x.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:l&&l.tagName.toUpperCase()==="SCRIPT"&&l.src||new URL("host/index.js",document.baseURI).href),c=u.resolve(u.dirname(F),"..",".."),E=u.resolve(c,"index.html"),Y=u.resolve(c,"src/app/main.tsx");async function q(t){const e=await import("vite"),o=await import("@vitejs/plugin-react"),r=await import("@jogak/core/vite"),n=await import("@tailwindcss/vite"),{createServer:h,build:b}=e,v=o.default,_=n.default,{jogak:w}=r,R=t.codeTheme??"vsDark",f={patterns:t.patterns,codeTheme:R,cwd:t.userRoot};t.tsConfigFilePath!==void 0&&(f.tsConfigFilePath=t.tsConfigFilePath);const C=t.extraPlugins??[],d={root:c,configFile:!1,plugins:[v(),_(),w(f),...C],optimizeDeps:{include:["react","react-dom/client","@jogak/core","@jogak/react"]}};if(t.mode==="dev"){const D={port:t.port??5173,host:t.host??"localhost",open:t.open??!1,fs:{allow:[c,t.userRoot]}},L={...d,server:D},s=await h(L);await s.listen();const p=s.config.server.port??t.port??5173,a=t.host??"localhost",M=`http://${typeof a=="boolean"?a?"0.0.0.0":"localhost":a}:${p.toString()}/`;let g=!1;return{url:M,port:p,close:async()=>{g||(g=!0,await s.close())},printUrls:()=>{s.printUrls()}}}const P={...d,base:t.base??"./",build:{outDir:t.outDir,emptyOutDir:!0,sourcemap:t.sourcemap??!1,minify:t.minify??"esbuild"}},T=Date.now(),U=await b(P),I=Date.now()-T,{assetCount:O,totalBytes:j}=z(U);return{outDir:t.outDir,elapsedMs:I,assetCount:O,totalBytes:j}}function z(t){const e=$(t);if(e===void 0)return{assetCount:0,totalBytes:0};let o=0,r=0;for(const n of e)o+=1,r+=G(n);return{assetCount:o,totalBytes:r}}function m(t){return typeof t=="object"&&t!==null&&Array.isArray(t.output)}function $(t){if(Array.isArray(t)){const e=[];for(const o of t)m(o)&&e.push(...o.output);return e}if(m(t))return t.output}function G(t){if(typeof t!="object"||t===null)return 0;const e=t;if(e.type==="chunk"&&typeof e.code=="string")return Buffer.byteLength(e.code,"utf8");if(e.type==="asset"){const o=e.source;if(typeof o=="string")return Buffer.byteLength(o,"utf8");if(o instanceof Uint8Array)return o.byteLength}return 0}exports.UI_HTML_ENTRY=E;exports.UI_MAIN_ENTRY=Y;exports.runHost=q;
1
+ "use strict";var S=Object.create;var m=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var N=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of A(e))!H.call(t,n)&&n!==o&&m(t,n,{get:()=>e[n],enumerable:!(r=k(e,n))||r.enumerable});return t};var c=(t,e,o)=>(o=t!=null?S(B(t)):{},N(e||!t||!t.__esModule?m(o,"default",{value:t,enumerable:!0}):o,t));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("node:url"),s=require("node:path");var f=typeof document<"u"?document.currentScript:null;const F=x.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:f&&f.tagName.toUpperCase()==="SCRIPT"&&f.src||new URL("host/index.js",document.baseURI).href),i=s.resolve(s.dirname(F),"..",".."),E=s.resolve(i,"index.html"),Y=s.resolve(i,"src/app/main.tsx");async function q(t){const e=await import("vite"),o=await import("@vitejs/plugin-react"),r=await import("@jogak/core/vite"),n=await import("@tailwindcss/vite"),{createServer:h,build:v}=e,b=o.default,w=n.default,{jogak:_}=r,C=t.codeTheme??"vsDark",u={patterns:t.patterns,codeTheme:C,cwd:t.userRoot};t.tsConfigFilePath!==void 0&&(u.tsConfigFilePath=t.tsConfigFilePath),t.globalCss!==void 0&&(u.globalCss=t.globalCss),t.previewIsolation!==void 0&&(u.previewIsolation=t.previewIsolation);const R=t.extraPlugins??[],d={root:i,configFile:!1,plugins:[b(),w(),_(u),...R],optimizeDeps:{include:["react","react-dom/client","@jogak/core","@jogak/react"]}};if(t.mode==="dev"){const D={port:t.port??5173,host:t.host??"localhost",open:t.open??!1,fs:{allow:[i,t.userRoot]}},L={...d,server:D},a=await h(L);await a.listen();const p=a.config.server.port??t.port??5173,l=t.host??"localhost",M=`http://${typeof l=="boolean"?l?"0.0.0.0":"localhost":l}:${p.toString()}/`;let g=!1;return{url:M,port:p,close:async()=>{g||(g=!0,await a.close())},printUrls:()=>{a.printUrls()}}}const I={...d,base:t.base??"./",build:{outDir:t.outDir,emptyOutDir:!0,sourcemap:t.sourcemap??!1,minify:t.minify??"esbuild",rollupOptions:{input:{main:s.resolve(i,"index.html"),preview:s.resolve(i,"preview-frame.html")}}}},P=Date.now(),T=await v(I),U=Date.now()-P,{assetCount:O,totalBytes:j}=z(T);return{outDir:t.outDir,elapsedMs:U,assetCount:O,totalBytes:j}}function z(t){const e=$(t);if(e===void 0)return{assetCount:0,totalBytes:0};let o=0,r=0;for(const n of e)o+=1,r+=G(n);return{assetCount:o,totalBytes:r}}function y(t){return typeof t=="object"&&t!==null&&Array.isArray(t.output)}function $(t){if(Array.isArray(t)){const e=[];for(const o of t)y(o)&&e.push(...o.output);return e}if(y(t))return t.output}function G(t){if(typeof t!="object"||t===null)return 0;const e=t;if(e.type==="chunk"&&typeof e.code=="string")return Buffer.byteLength(e.code,"utf8");if(e.type==="asset"){const o=e.source;if(typeof o=="string")return Buffer.byteLength(o,"utf8");if(o instanceof Uint8Array)return o.byteLength}return 0}exports.UI_HTML_ENTRY=E;exports.UI_MAIN_ENTRY=Y;exports.runHost=q;
@@ -1,94 +1,100 @@
1
- import { fileURLToPath as T } from "node:url";
2
- import { resolve as u, dirname as U } from "node:path";
3
- const A = T(import.meta.url), i = u(U(A), "..", ".."), N = u(i, "index.html"), S = u(i, "src/app/main.tsx");
1
+ import { fileURLToPath as B } from "node:url";
2
+ import { resolve as r, dirname as T } from "node:path";
3
+ const U = B(import.meta.url), n = r(T(U), "..", ".."), N = r(n, "index.html"), S = r(n, "src/app/main.tsx");
4
4
  async function $(t) {
5
- const o = await import("vite"), e = await import("@vitejs/plugin-react"), n = await import("@jogak/core/vite"), s = await import("@tailwindcss/vite"), { createServer: g, build: m } = o, y = e.default, h = s.default, { jogak: b } = n, w = t.codeTheme ?? "vsDark", a = {
5
+ const e = await import("vite"), o = await import("@vitejs/plugin-react"), i = await import("@jogak/core/vite"), l = await import("@tailwindcss/vite"), { createServer: m, build: g } = e, y = o.default, h = l.default, { jogak: v } = i, w = t.codeTheme ?? "vsDark", s = {
6
6
  patterns: t.patterns,
7
7
  codeTheme: w,
8
8
  cwd: t.userRoot
9
9
  };
10
- t.tsConfigFilePath !== void 0 && (a.tsConfigFilePath = t.tsConfigFilePath);
11
- const v = t.extraPlugins ?? [], l = {
12
- root: i,
10
+ t.tsConfigFilePath !== void 0 && (s.tsConfigFilePath = t.tsConfigFilePath), t.globalCss !== void 0 && (s.globalCss = t.globalCss), t.previewIsolation !== void 0 && (s.previewIsolation = t.previewIsolation);
11
+ const b = t.extraPlugins ?? [], u = {
12
+ root: n,
13
13
  configFile: !1,
14
14
  // ui/vite.config.ts 무시
15
- plugins: [y(), h(), b(a), ...v],
15
+ plugins: [y(), h(), v(s), ...b],
16
16
  optimizeDeps: {
17
17
  include: ["react", "react-dom/client", "@jogak/core", "@jogak/react"]
18
18
  }
19
19
  };
20
20
  if (t.mode === "dev") {
21
- const j = {
21
+ const _ = {
22
22
  port: t.port ?? 5173,
23
23
  host: t.host ?? "localhost",
24
24
  open: t.open ?? !1,
25
- fs: { allow: [i, t.userRoot] }
26
- }, k = {
27
- ...l,
28
- server: j
29
- }, r = await g(k);
30
- await r.listen();
31
- const f = r.config.server.port ?? t.port ?? 5173, c = t.host ?? "localhost", B = `http://${typeof c == "boolean" ? c ? "0.0.0.0" : "localhost" : c}:${f.toString()}/`;
25
+ fs: { allow: [n, t.userRoot] }
26
+ }, j = {
27
+ ...u,
28
+ server: _
29
+ }, a = await m(j);
30
+ await a.listen();
31
+ const f = a.config.server.port ?? t.port ?? 5173, c = t.host ?? "localhost", k = `http://${typeof c == "boolean" ? c ? "0.0.0.0" : "localhost" : c}:${f.toString()}/`;
32
32
  let d = !1;
33
33
  return {
34
- url: B,
34
+ url: k,
35
35
  port: f,
36
36
  close: async () => {
37
- d || (d = !0, await r.close());
37
+ d || (d = !0, await a.close());
38
38
  },
39
39
  printUrls: () => {
40
- r.printUrls();
40
+ a.printUrls();
41
41
  }
42
42
  };
43
43
  }
44
- const P = {
45
- ...l,
44
+ const C = {
45
+ ...u,
46
46
  base: t.base ?? "./",
47
47
  build: {
48
48
  outDir: t.outDir,
49
49
  emptyOutDir: !0,
50
50
  sourcemap: t.sourcemap ?? !1,
51
- minify: t.minify ?? "esbuild"
51
+ minify: t.minify ?? "esbuild",
52
+ rollupOptions: {
53
+ input: {
54
+ main: r(n, "index.html"),
55
+ preview: r(n, "preview-frame.html")
56
+ }
57
+ }
52
58
  }
53
- }, C = Date.now(), D = await m(P), O = Date.now() - C, { assetCount: R, totalBytes: _ } = L(D);
59
+ }, P = Date.now(), O = await g(C), D = Date.now() - P, { assetCount: R, totalBytes: I } = x(O);
54
60
  return {
55
61
  outDir: t.outDir,
56
- elapsedMs: O,
62
+ elapsedMs: D,
57
63
  assetCount: R,
58
- totalBytes: _
64
+ totalBytes: I
59
65
  };
60
66
  }
61
- function L(t) {
62
- const o = M(t);
63
- if (o === void 0)
67
+ function x(t) {
68
+ const e = A(t);
69
+ if (e === void 0)
64
70
  return { assetCount: 0, totalBytes: 0 };
65
- let e = 0, n = 0;
66
- for (const s of o)
67
- e += 1, n += x(s);
68
- return { assetCount: e, totalBytes: n };
71
+ let o = 0, i = 0;
72
+ for (const l of e)
73
+ o += 1, i += L(l);
74
+ return { assetCount: o, totalBytes: i };
69
75
  }
70
76
  function p(t) {
71
77
  return typeof t == "object" && t !== null && Array.isArray(t.output);
72
78
  }
73
- function M(t) {
79
+ function A(t) {
74
80
  if (Array.isArray(t)) {
75
- const o = [];
76
- for (const e of t)
77
- p(e) && o.push(...e.output);
78
- return o;
81
+ const e = [];
82
+ for (const o of t)
83
+ p(o) && e.push(...o.output);
84
+ return e;
79
85
  }
80
86
  if (p(t))
81
87
  return t.output;
82
88
  }
83
- function x(t) {
89
+ function L(t) {
84
90
  if (typeof t != "object" || t === null) return 0;
85
- const o = t;
86
- if (o.type === "chunk" && typeof o.code == "string")
87
- return Buffer.byteLength(o.code, "utf8");
88
- if (o.type === "asset") {
89
- const e = o.source;
90
- if (typeof e == "string") return Buffer.byteLength(e, "utf8");
91
- if (e instanceof Uint8Array) return e.byteLength;
91
+ const e = t;
92
+ if (e.type === "chunk" && typeof e.code == "string")
93
+ return Buffer.byteLength(e.code, "utf8");
94
+ if (e.type === "asset") {
95
+ const o = e.source;
96
+ if (typeof o == "string") return Buffer.byteLength(o, "utf8");
97
+ if (o instanceof Uint8Array) return o.byteLength;
92
98
  }
93
99
  return 0;
94
100
  }