@jogak/ui 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +24 -0
- package/README.md +108 -36
- package/dist/app/App.d.ts +11 -1
- package/dist/components/Preview/IframeMount.d.ts +37 -0
- package/dist/components/Preview/ShadowMount.d.ts +26 -0
- package/dist/components/Preview/index.d.ts +9 -1
- package/dist/host/index.d.ts +20 -0
- package/dist/host/index.js +1 -1
- package/dist/host/index.mjs +51 -45
- package/dist/index.js +2 -1
- package/dist/index.mjs +456 -330
- package/package.json +4 -3
- package/preview-frame.html +17 -0
- package/src/app/App.tsx +12 -0
- package/src/app/main.tsx +5 -2
- package/src/app/preview-frame.tsx +46 -0
- package/src/components/Preview/IframeMount.tsx +82 -0
- package/src/components/Preview/ShadowMount.tsx +101 -0
- package/src/components/Preview/index.tsx +138 -34
- package/src/vite-env.d.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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] — 2026-05-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`JogakHostOptionsBase`에 `globalCss` / `previewIsolation` 필드** — `runHost({ globalCss: true,
|
|
13
|
+
previewIsolation: 'shadow' })` 형태로 programmatic API에서도 옵션 사용 가능.
|
|
14
|
+
- **`Preview` Shadow / iframe 마운트 분기**
|
|
15
|
+
- `previewIsolation: 'none'` (default, 알파.6과 동일) — 같은 document에 마운트
|
|
16
|
+
- `'shadow'` — `attachShadow` + `createPortal` + `adoptedStyleSheets`로 외부 css/font 흡수,
|
|
17
|
+
`MutationObserver`로 외부 `<style>` HMR 동기화
|
|
18
|
+
- `'iframe'` — `/preview-frame.html` + `contentWindow.__jogak_setProps__` 직접 호출,
|
|
19
|
+
완벽 격리 (props 직렬화 불필요)
|
|
20
|
+
- **`preview-frame.html` + `src/app/preview-frame.tsx`** — iframe-mode 전용 최소 entry.
|
|
21
|
+
`virtual:jogak/global-css`만 import (jogak chrome css는 미포함, 격리 보장).
|
|
22
|
+
- **README "previewIsolation 모드 비교" + "알파.6 → 알파.7 마이그레이션" 섹션** — 3 모드의
|
|
23
|
+
trade-off / Radix portal 한계 / `jogak.config.ts` 패턴으로의 마이그레이션 안내.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **알파.6 README의 `vite.config.ts` 가이드 정정** — 알파.6는 `vite.config.ts`에서
|
|
28
|
+
`jogak({ globalCss: true })`를 호출하라고 안내했으나 `runHost`가 `configFile: false`로
|
|
29
|
+
사용자 vite config를 무시해 옵션이 적용되지 않았음. 알파.7부터는 `jogak.config.ts`에
|
|
30
|
+
`defineJogakConfig({ globalCss: true })`로 작성.
|
|
31
|
+
|
|
8
32
|
## [0.1.0-alpha.6] — 2026-05-09
|
|
9
33
|
|
|
10
34
|
### 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
|
-
###
|
|
15
|
+
### `jogak.config.ts` 사용 (권장)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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: 'none', // 'none' | 'shadow' | 'iframe' (default 'none')
|
|
27
|
+
codeTheme: 'vsDark',
|
|
28
|
+
port: 5173, // dev server (CLI --port로 override)
|
|
23
29
|
})
|
|
24
30
|
```
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import { JogakApp } from '@jogak/ui'
|
|
31
|
-
import { createRoot } from 'react-dom/client'
|
|
32
|
+
CLI는 본 config의 옵션을 읽어 jogak SPA 빌드/dev에 전달합니다. CLI 플래그가
|
|
33
|
+
명시되면 config 값을 override합니다 (Vite 패턴).
|
|
34
|
+
|
|
35
|
+
### `jogak.config.ts` 미사용 (CLI flag만)
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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,24 @@ 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
|
|
84
|
+
| alpha.4 | ✅ 완료 | jogak UI 빌드에 Tailwind v4 + `jogak:` prefix 도입 |
|
|
70
85
|
| alpha.5 | ✅ 완료 | jogak UI 컴포넌트를 Tailwind class로 마이그레이션 (4 PR) |
|
|
71
|
-
|
|
|
72
|
-
| alpha.7
|
|
86
|
+
| alpha.6 | ✅ 완료 | `JogakPluginOptions.globalCss` 옵션 + chrome 보호 rule (단, 통로 부재로 사용자 환경에서 무효 — 알파.7에서 정정) |
|
|
87
|
+
| **alpha.7** | ✅ **본 릴리즈** | **`jogak.config.ts` 통로 + `previewIsolation` 옵션 + `JogakHostOptionsBase` 확장** |
|
|
88
|
+
| alpha.8+ | 예정 | `globalCss: { layer, scope }` 객체 union, multi-baseline VR |
|
|
73
89
|
|
|
74
|
-
### 사용자 globalCss 적용
|
|
90
|
+
### 사용자 globalCss 적용
|
|
75
91
|
|
|
76
92
|
`runHost`는 vite root를 `@jogak/ui` 패키지로 두고 사용자 `vite.config.ts` / `main.tsx`를 무시하므로(`configFile: false`), 사용자 `index.css`(Tailwind/shadcn 디자인 토큰)가 jogak SPA에 자동 적용되지 않습니다. `globalCss` 옵션은 이를 opt-in으로 해결합니다.
|
|
77
93
|
|
|
78
94
|
#### 사용법
|
|
79
95
|
|
|
80
96
|
```ts
|
|
81
|
-
//
|
|
82
|
-
import {
|
|
83
|
-
import react from '@vitejs/plugin-react'
|
|
84
|
-
import { jogak } from '@jogak/core/vite'
|
|
97
|
+
// jogak.config.ts (사용자 프로젝트 root)
|
|
98
|
+
import { defineJogakConfig } from '@jogak/core'
|
|
85
99
|
|
|
86
|
-
export default
|
|
87
|
-
|
|
100
|
+
export default defineJogakConfig({
|
|
101
|
+
globalCss: true,
|
|
88
102
|
})
|
|
89
103
|
```
|
|
90
104
|
|
|
@@ -103,19 +117,51 @@ export default defineConfig({
|
|
|
103
117
|
|
|
104
118
|
```ts
|
|
105
119
|
// 명시 경로 1개
|
|
106
|
-
|
|
120
|
+
defineJogakConfig({ globalCss: './src/index.css' })
|
|
107
121
|
|
|
108
122
|
// 다중 import (디자인 토큰 + reset 분리)
|
|
109
|
-
|
|
123
|
+
defineJogakConfig({ globalCss: ['./src/tokens.css', './src/reset.css'] })
|
|
110
124
|
```
|
|
111
125
|
|
|
112
|
-
#### 격리 보장
|
|
126
|
+
#### 격리 보장 (default `previewIsolation: 'none'`)
|
|
113
127
|
|
|
114
128
|
- **Tailwind utility class**: jogak UI는 `prefix=jogak`로 빌드되어 사용자 utility와 충돌 zero (예: 사용자 `bg-primary` ≠ jogak `jogak:bg-...`).
|
|
115
129
|
- **CSS variable**: jogak은 `--jogak-*` prefix로 namespace 격리 → 사용자 `:root { --primary }` 같은 디자인 토큰은 영향 없음.
|
|
116
130
|
- **Form element 보호**: `[data-jogak-shell]` 안의 button/input/select/textarea는 사용자 reset의 `border` / `background` / `color` 침범을 받지 않도록 `:where()` 보호 rule 적용. specificity 0이라 사용자가 명시적으로 `[data-jogak-shell] button { ... }`를 작성하면 정상 override됩니다.
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
### previewIsolation 사용 가이드 (알파.7)
|
|
133
|
+
|
|
134
|
+
Preview 콘텐츠를 chrome으로부터 격리하는 모드를 선택합니다. 대부분 default `'none'` + 알파.6 chrome 보호 rule로 충분하며, 사용자 reset이 chrome을 침범하는 강한 시나리오에서만 `'shadow'` / `'iframe'`을 사용합니다.
|
|
135
|
+
|
|
136
|
+
#### 모드 비교
|
|
137
|
+
|
|
138
|
+
| 모드 | mount | jogak.css | 사용자 globalCss | Radix portal | HMR | cold start |
|
|
139
|
+
|------|-------|-----------|------------------|--------------|-----|-----------|
|
|
140
|
+
| `'none'` | 같은 document | 외부 document | 외부 document | document.body (정상) | Vite 표준 | ★★★ |
|
|
141
|
+
| `'shadow'` | ShadowRoot | adoptedStyleSheets | adoptedStyleSheets | 외부 document.body (utility 적용 OK, 단 z-index/focus 분리) | Vite 표준 + MutationObserver sync | ★★★ |
|
|
142
|
+
| `'iframe'` | iframe document | (제외 — chrome 전용) | iframe document에 import | iframe document (정상) | iframe도 Vite dev module | ★★ (iframe load 추가) |
|
|
143
|
+
|
|
144
|
+
#### `'shadow'` 모드 한계
|
|
145
|
+
|
|
146
|
+
1. **Radix UI portal**: Radix(shadcn)의 Dialog/Popover/Tooltip은 기본 `document.body`로 portal합니다. utility class는 외부 document에 정의되어 적용에는 문제 없지만, focus 관리 / scroll lock / aria 검증에 알려진 이슈가 있습니다 (Radix issues #3674, #3483, #3814). 명시적으로 portal target을 ShadowRoot 안 element로 지정하면 회피 가능:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<Dialog.Portal container={shadowRootEl}>
|
|
150
|
+
<Dialog.Content>...</Dialog.Content>
|
|
151
|
+
</Dialog.Portal>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
2. **외부 stylesheet sync 비용**: `adoptedStyleSheets`로 외부 document의 모든 stylesheet를 ShadowRoot에 share합니다. Vite dev에서 `<style>` HMR이 발생하면 MutationObserver가 ShadowRoot도 갱신 — 약간의 latency가 생길 수 있습니다.
|
|
155
|
+
|
|
156
|
+
3. **CSS variable**: 외부 `:root`의 변수는 ShadowRoot 내부에서도 inherit. 즉 사용자 `:root { --primary: ... }` 토큰은 ShadowRoot 안에서도 작동합니다.
|
|
157
|
+
|
|
158
|
+
#### `'iframe'` 모드 주의
|
|
159
|
+
|
|
160
|
+
> ⚠️ jogak의 핵심 차별점은 "single Vite, no iframe" — Storybook 대비 빠른 cold start와 단순 디버깅이 강점입니다. `previewIsolation: 'iframe'`을 활성화하면 이 강점이 일부 상실됩니다 (iframe load + 별도 module graph). 사용자 reset CSS를 chrome에서 완전히 격리해야만 하는 시나리오에서만 사용하세요.
|
|
161
|
+
|
|
162
|
+
iframe은 `<iframe src="/preview-frame.html">`로 로드되며, 부모-자식 동일 origin이라 부모가 `iframe.contentWindow.__jogak_setProps__({ entry, args })`를 직접 호출하는 방식으로 props를 전달합니다 (postMessage 미사용).
|
|
163
|
+
|
|
164
|
+
### scope 가이드 — 알려진 영향 영역 (`'none'` 모드 기준)
|
|
119
165
|
|
|
120
166
|
사용자 css의 다음 패턴은 jogak chrome에도 영향을 줄 수 있습니다 (전역 import이므로):
|
|
121
167
|
|
|
@@ -123,7 +169,7 @@ jogak({ globalCss: ['./src/tokens.css', './src/reset.css'] })
|
|
|
123
169
|
- `* { ... }` 또는 `*, *::before { ... }` — 모든 요소
|
|
124
170
|
- 글로벌 reset (`button { all: unset }`, `* { border: 1px solid var(--border) }` 등)
|
|
125
171
|
|
|
126
|
-
이를 사용자 콘텐츠 영역(preview)으로만 한정하려면 셀렉터를 `[data-jogak-content] *` scope로
|
|
172
|
+
이를 사용자 콘텐츠 영역(preview)으로만 한정하려면 셀렉터를 `[data-jogak-content] *` scope로 작성하거나, `previewIsolation: 'shadow' | 'iframe'`을 사용하세요.
|
|
127
173
|
|
|
128
174
|
```css
|
|
129
175
|
/* 권장: preview 영역으로만 한정 */
|
|
@@ -139,18 +185,44 @@ jogak({ globalCss: ['./src/tokens.css', './src/reset.css'] })
|
|
|
139
185
|
}
|
|
140
186
|
```
|
|
141
187
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
##### selector hint
|
|
188
|
+
#### selector hint
|
|
145
189
|
|
|
146
190
|
- `[data-jogak-shell]` — `JogakApp` 최상위 wrapper (chrome + preview 모두 포함)
|
|
147
191
|
- `[data-jogak-content]` — Preview의 사용자 콘텐츠 영역 (사용자 컴포넌트가 렌더되는 div)
|
|
148
192
|
|
|
149
|
-
|
|
193
|
+
### 알파.6 → 알파.7 마이그레이션
|
|
194
|
+
|
|
195
|
+
알파.6 README는 다음 가이드를 안내했습니다:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// alpha.6 README (무효)
|
|
199
|
+
// vite.config.ts
|
|
200
|
+
import { jogak } from '@jogak/core/vite'
|
|
201
|
+
export default defineConfig({
|
|
202
|
+
plugins: [react(), jogak({ globalCss: true })],
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
이 설정은 사용자 일반 vite 빌드용에만 적용되고 jogak SPA에는 전달되지 않았습니다.
|
|
207
|
+
알파.7부터 `jogak.config.ts`로 옮기세요:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// alpha.7+ — jogak.config.ts (사용자 프로젝트 root)
|
|
211
|
+
import { defineJogakConfig } from '@jogak/core'
|
|
212
|
+
|
|
213
|
+
export default defineJogakConfig({
|
|
214
|
+
globalCss: true,
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`vite.config.ts`에 `jogak()` plugin이 그대로 있어도 jogak SPA에는 영향 없으므로
|
|
219
|
+
당장 제거할 필요는 없습니다 (alpha.6 동작 유지). 하지만 jogak.config.ts로 옮긴 후
|
|
220
|
+
중복 제거를 권장합니다.
|
|
221
|
+
|
|
222
|
+
### 알려진 한계
|
|
150
223
|
|
|
151
|
-
-
|
|
224
|
+
- `globalCss` 옵션은 **opt-in**입니다 (default `false`). 알파.5까지의 동작은 동일합니다.
|
|
152
225
|
- `globalCss: true` 자동 감지는 dev 시작 시점에 한 번만 수행됩니다. 후보 css 파일이 dev 시작 후에 새로 추가되면 dev 서버를 재시작해야 합니다. 명시 경로(`globalCss: './src/index.css'`)는 파일이 나중에 생성되어도 정상 hot reload됩니다.
|
|
153
|
-
- preview 영역만 사용자 css로 한정하려면 알파.7+의 `previewIsolation: 'shadow'` 옵션을 기다려 주세요.
|
|
154
226
|
- CSS module(`.module.css`)을 자동 감지 후보에서 직접 import하지 않습니다 — 명시 경로로 넘기면 Vite가 module 처리합니다.
|
|
155
227
|
|
|
156
228
|
- 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,26 @@
|
|
|
1
|
+
import { ReactElement, ReactNode, CSSProperties } from 'react';
|
|
2
|
+
export interface ShadowMountProps {
|
|
3
|
+
readonly children: ReactNode;
|
|
4
|
+
readonly className?: string;
|
|
5
|
+
readonly style?: CSSProperties;
|
|
6
|
+
/** 외부 테스트 hook (호스트 div에 부여). */
|
|
7
|
+
readonly 'data-testid'?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 알파.7: previewIsolation='shadow' 모드의 mount 컴포넌트.
|
|
11
|
+
*
|
|
12
|
+
* 역할:
|
|
13
|
+
* - 호스트 `<div>`에 `attachShadow({ mode: 'open' })`로 ShadowRoot를 부착.
|
|
14
|
+
* - `createPortal`로 children을 ShadowRoot에 렌더 (React tree 유지).
|
|
15
|
+
* - 외부 document의 모든 `<style>` / cross-origin 가능 stylesheet를
|
|
16
|
+
* `adoptedStyleSheets`로 ShadowRoot에 share (jogak.css + virtual:jogak/global-css
|
|
17
|
+
* 둘 다 자동 포함).
|
|
18
|
+
* - Vite dev에서 `<style>` HMR 시 MutationObserver로 ShadowRoot도 갱신.
|
|
19
|
+
*
|
|
20
|
+
* Radix portal 한계 (사용자 인지 필요):
|
|
21
|
+
* - 사용자 컴포넌트가 `Dialog.Portal` / `Popover.Portal` 등을 default로 쓰면
|
|
22
|
+
* portal target은 `document.body` — ShadowRoot 외부. utility class는 외부
|
|
23
|
+
* document에 정의되어 적용됨, 단 z-index/focus/event boundary가 분리될 수 있음.
|
|
24
|
+
* - 회피: 사용자가 명시적으로 `<Portal container={shadowRootEl}>`을 전달.
|
|
25
|
+
*/
|
|
26
|
+
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 };
|
package/dist/host/index.d.ts
CHANGED
|
@@ -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: Preview 영역 격리 모드.
|
|
53
|
+
*
|
|
54
|
+
* `'none'` (default), `'shadow'`, `'iframe'` 중 하나.
|
|
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';
|
package/dist/host/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var S=Object.create;var
|
|
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;
|
package/dist/host/index.mjs
CHANGED
|
@@ -1,94 +1,100 @@
|
|
|
1
|
-
import { fileURLToPath as
|
|
2
|
-
import { resolve as
|
|
3
|
-
const
|
|
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
|
|
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 && (
|
|
11
|
-
const
|
|
12
|
-
root:
|
|
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(),
|
|
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
|
|
21
|
+
const _ = {
|
|
22
22
|
port: t.port ?? 5173,
|
|
23
23
|
host: t.host ?? "localhost",
|
|
24
24
|
open: t.open ?? !1,
|
|
25
|
-
fs: { allow: [
|
|
26
|
-
},
|
|
27
|
-
...
|
|
28
|
-
server:
|
|
29
|
-
},
|
|
30
|
-
await
|
|
31
|
-
const f =
|
|
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:
|
|
34
|
+
url: k,
|
|
35
35
|
port: f,
|
|
36
36
|
close: async () => {
|
|
37
|
-
d || (d = !0, await
|
|
37
|
+
d || (d = !0, await a.close());
|
|
38
38
|
},
|
|
39
39
|
printUrls: () => {
|
|
40
|
-
|
|
40
|
+
a.printUrls();
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
const
|
|
45
|
-
...
|
|
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
|
-
},
|
|
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:
|
|
62
|
+
elapsedMs: D,
|
|
57
63
|
assetCount: R,
|
|
58
|
-
totalBytes:
|
|
64
|
+
totalBytes: I
|
|
59
65
|
};
|
|
60
66
|
}
|
|
61
|
-
function
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
67
|
+
function x(t) {
|
|
68
|
+
const e = A(t);
|
|
69
|
+
if (e === void 0)
|
|
64
70
|
return { assetCount: 0, totalBytes: 0 };
|
|
65
|
-
let
|
|
66
|
-
for (const
|
|
67
|
-
|
|
68
|
-
return { assetCount:
|
|
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
|
|
79
|
+
function A(t) {
|
|
74
80
|
if (Array.isArray(t)) {
|
|
75
|
-
const
|
|
76
|
-
for (const
|
|
77
|
-
p(
|
|
78
|
-
return
|
|
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
|
|
89
|
+
function L(t) {
|
|
84
90
|
if (typeof t != "object" || t === null) return 0;
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
return Buffer.byteLength(
|
|
88
|
-
if (
|
|
89
|
-
const
|
|
90
|
-
if (typeof
|
|
91
|
-
if (
|
|
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
|
}
|