@gaonjs/cli 0.15.0 → 0.18.0
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/dist/commands/g.js +8 -0
- package/dist/commands/new.js +5 -0
- package/dist/dev/health.d.ts +4 -1
- package/dist/dev/health.js +5 -3
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor/types.js +3 -3
- package/dist/doctor/ui-kit-wiring.d.ts +5 -0
- package/dist/doctor/ui-kit-wiring.js +93 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +7 -2
- package/dist/generate.js +8 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -4
- package/dist/scaffold/app-wiring.d.ts +12 -0
- package/dist/scaffold/app-wiring.js +68 -0
- package/dist/templates/auth/Dashboard.vue.tpl +21 -5
- package/dist/templates/auth/Login.vue.tpl +37 -8
- package/dist/templates/auth/Signup.vue.tpl +40 -9
- package/dist/templates/project/AGENTS.md.tpl +3 -2
- package/dist/templates/project/CLAUDE.md.tpl +3 -2
- package/dist/templates/project/agents/async.md.tpl +15 -7
- package/dist/templates/project/agents/frontend.md.tpl +62 -4
- package/dist/templates/project/apps/web/composables/useGaonHealth.ts.tpl +2 -0
- package/dist/templates/project/apps/web/layouts/Default.vue.tpl +19 -94
- package/dist/templates/project/apps/web/main.ts.tpl +4 -0
- package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +68 -251
- package/dist/templates/project/apps/web/style.css.tpl +66 -0
- package/dist/templates/project/package.json.tpl +3 -0
- package/dist/templates/project/postcss.config.js.tpl +15 -0
- package/dist/templates/project/tailwind.config.ts.tpl +56 -0
- package/dist/templates/ui-kit/Alert.vue.tpl +23 -0
- package/dist/templates/ui-kit/AlertDescription.vue.tpl +9 -0
- package/dist/templates/ui-kit/AlertTitle.vue.tpl +9 -0
- package/dist/templates/ui-kit/Badge.vue.tpl +25 -0
- package/dist/templates/ui-kit/Button.vue.tpl +39 -0
- package/dist/templates/ui-kit/Card.vue.tpl +10 -0
- package/dist/templates/ui-kit/CardContent.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardDescription.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardFooter.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardHeader.vue.tpl +9 -0
- package/dist/templates/ui-kit/CardTitle.vue.tpl +9 -0
- package/dist/templates/ui-kit/Dialog.vue.tpl +68 -0
- package/dist/templates/ui-kit/Form.vue.tpl +13 -0
- package/dist/templates/ui-kit/FormField.vue.tpl +16 -0
- package/dist/templates/ui-kit/FormMessage.vue.tpl +9 -0
- package/dist/templates/ui-kit/Input.vue.tpl +22 -0
- package/dist/templates/ui-kit/Label.vue.tpl +9 -0
- package/dist/templates/ui-kit/Sheet.vue.tpl +73 -0
- package/dist/templates/ui-kit/utils.ts.tpl +26 -0
- package/dist/uikit.d.ts +28 -0
- package/dist/uikit.js +138 -0
- package/package.json +6 -6
|
@@ -36,8 +36,9 @@ Gaon 프레임웍 문서: https://gaonjs.dev
|
|
|
36
36
|
9. **실시간은 v1 포함**(§7): 웹서버 ↔ 허브는 TCP 지속 연결 · NATS 는
|
|
37
37
|
broadcast 전용(errata E-2). 운영 프로세스는 serve·work·hub 3종.
|
|
38
38
|
10. **인증·폼은 Inertia SPA**(§6 · SSR 아님). 로그인/회원가입은
|
|
39
|
-
`this.render('Auth/Login')` + `
|
|
40
|
-
|
|
39
|
+
`this.render('Auth/Login')` + `gaonjs/vue` 의 `useForm(...).post()`
|
|
40
|
+
(DELETE 등은 `router`) → 서버 redirect(결정 64). REST + `fetch()`
|
|
41
|
+
는 API 앱(JWT) 전용.
|
|
41
42
|
11. **컴포저블·레이아웃**(errata E-5): 컴포저블은 컴포넌트와 대칭
|
|
42
43
|
(`apps/<앱>/composables/` + `shared/composables/`, `use` 접두사).
|
|
43
44
|
shared 컴포저블은 인자로만 받는 순수 로직(api·pageProps 금지).
|
|
@@ -115,21 +115,29 @@ await OrderPlaced.emit({ orderId: 1n })
|
|
|
115
115
|
### 4. 아웃박스 (트랜잭션 정합)
|
|
116
116
|
|
|
117
117
|
이벤트를 DB 트랜잭션과 **원자적으로** 발행하려면 아웃박스를 쓴다.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
트랜잭션 안에서 발행한 이벤트는 같은 트랜잭션의 아웃박스 테이블에
|
|
119
|
+
스테이징되고, 트랜잭션이 커밋돼야 릴레이가 실제로 NATS 에 발행한다.
|
|
120
|
+
트랜잭션이 롤백되면 이벤트도 사라진다. `service()` 본문 전체가 한
|
|
121
|
+
트랜잭션이므로(§서비스), 본문 안 `emit` 이 곧 아웃박스 스테이징이다.
|
|
121
122
|
|
|
122
123
|
```ts
|
|
123
|
-
|
|
124
|
+
// domain/services/placeOrder.ts — service() 본문 = 한 트랜잭션.
|
|
125
|
+
import { service } from 'gaonjs/service'
|
|
126
|
+
import { Order } from '../models/Order.js'
|
|
127
|
+
import { OrderPlaced } from '../events/orderPlaced.js'
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
await Order.create(
|
|
127
|
-
await OrderPlaced.emit({ orderId }) // 커밋돼야
|
|
129
|
+
export const PlaceOrder = service(async (input: { name: string }) => {
|
|
130
|
+
const order = await Order.create(input)
|
|
131
|
+
await OrderPlaced.emit({ orderId: order.id }) // 트랜잭션 안 → 아웃박스 스테이징 · 커밋돼야 발행
|
|
132
|
+
return order
|
|
128
133
|
})
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
- 트랜잭션 안의 `emit` 은 `AsyncLocalStorage` 로 투명하게 감지돼
|
|
132
137
|
아웃박스에 스테이징된다(별도 API 호출 불필요).
|
|
138
|
+
- 저수준 원시는 `runInTransaction(db, fn)`(커넥션을 넘긴다) — `service()`
|
|
139
|
+
가 내부에서 이를 쓴다. 대부분의 도메인 코드는 `service()` 본문에서
|
|
140
|
+
emit 하거나, "커밋 후 즉시 발행"이면 `afterCommit(fn)`(§서비스)을 쓴다.
|
|
133
141
|
- 릴레이(`gaon work` 내장)가 `SKIP LOCKED` 로 아웃박스를 폴링해 발행
|
|
134
142
|
한다 (기본 폴 1000ms · 배치 100).
|
|
135
143
|
- at-least-once — 발행 후 표시하므로 중복 가능성이 있고, dedup(msgID)이
|
|
@@ -160,8 +160,13 @@ import 한다. `gaon doctor` 의 **no-auto-import** 검사가 자동 import
|
|
|
160
160
|
이 엔드포인트는 `gaon dev` 에서만 등록되고(운영 빌드엔 없음), 404 면 컴포저블이
|
|
161
161
|
`available=false` 로 우아하게 degrade 한다.
|
|
162
162
|
|
|
163
|
-
-
|
|
164
|
-
|
|
163
|
+
- **HUB 카드는 실 introspection(결정 71)** — jobs = JetStream 스트림 대기(pending)
|
|
164
|
+
수 · channels = 프레즌스 KV 권위의 채널별 실 접속자 수. 조회 실패·미접속을 0/true
|
|
165
|
+
로 가장하지 않는다(과대 약속 금지) — 버킷·스트림 미생성이면 각각 null 로 degrade.
|
|
166
|
+
- **스타일은 Tailwind 유틸 + UI 킷(결정 74·75)** — 스캐폴드는 Tailwind
|
|
167
|
+
(`tailwind.config.ts`·`postcss.config.js`·`apps/<앱>/style.css`)를 편입한다.
|
|
168
|
+
랜딩·auth 페이지는 Tailwind 유틸과 UI 킷 컴포넌트(§8)로 그린다(결정 69 의
|
|
169
|
+
scoped-CSS 미편입 방침은 결정 74 로 뒤집혔다).
|
|
165
170
|
- **auth 링크는 수동(결정 70)** — `gaon g auth` 는 auth 페이지·라우트만 신설하고
|
|
166
171
|
랜딩·레이아웃 nav 를 편집하지 않는다. 헤더에 로그인 링크를 두려면 `Default.vue`
|
|
167
172
|
의 nav 에 `<a href="/session/new">로그인</a>` 을 직접 추가한다(Rails 관례).
|
|
@@ -169,6 +174,55 @@ import 한다. `gaon doctor` 의 **no-auto-import** 검사가 자동 import
|
|
|
169
174
|
·`Auth/Signup.vue` 를 내고 컨트롤러는 `this.render('Auth/Login')` 로 부른다.
|
|
170
175
|
소문자 `auth/` 는 doctor page-filename 이 잡는다(결정 32·46).
|
|
171
176
|
|
|
177
|
+
### 8. UI 킷 (결정 74·75 · shadcn 참조 · 복사-소유)
|
|
178
|
+
|
|
179
|
+
`gaon new` 는 기본 앱에 UI 킷을 심고(배터리 포함), `gaon g ui-kit [--app <앱>]`
|
|
180
|
+
로 다른 앱에도 추가한다. 컴포넌트는 **프로젝트에 복사된 여러분 코드**다 — npm
|
|
181
|
+
의존이 아니라 파일이므로 자유롭게 고친다(shadcn 방식). 외부 런타임 의존이 없다
|
|
182
|
+
(clsx·tailwind-merge·cva·reka-ui 미도입 · `cn` 은 자작 · Dialog/Sheet 는 Teleport+
|
|
183
|
+
Transition 자작).
|
|
184
|
+
|
|
185
|
+
배치·import:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
apps/<앱>/lib/utils.ts # cn() — 조건부 클래스 병합
|
|
189
|
+
apps/<앱>/components/ui/*.vue # Button·Input·Label·Badge·Card…·Alert…·Form…·Dialog·Sheet
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
<script setup lang="ts">
|
|
194
|
+
import Button from '../../components/ui/Button.vue' // 앱 로컬 · 상대 경로
|
|
195
|
+
import Card from '../../components/ui/Card.vue'
|
|
196
|
+
import CardContent from '../../components/ui/CardContent.vue'
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<template>
|
|
200
|
+
<Card>
|
|
201
|
+
<CardContent>
|
|
202
|
+
<Button variant="default">저장</Button> <!-- variant·size 로 모양 선택 -->
|
|
203
|
+
</CardContent>
|
|
204
|
+
</Card>
|
|
205
|
+
</template>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- **폼은 UI 킷 Form + gaonjs `useForm`(결정 64)** — `Form` 은 얇은 `<form>` 래퍼로
|
|
209
|
+
`@submit` 을 `useForm` 의 `post/put/delete` 로 넘긴다. vee-validate 를 끌어오지
|
|
210
|
+
않는다(검증·상태는 `useForm`). `FormField label error` + `FormMessage` 로 라벨·
|
|
211
|
+
오류를 붙이고, `:error="form.errors.<field>"` 로 서버 검증을 표시한다.
|
|
212
|
+
- **`class` 는 폴스루로 병합** — 단일 루트 컴포넌트는 `<Button class="w-full">` 처럼
|
|
213
|
+
넘긴 클래스가 루트로 흘러간다(별도 `class` prop 선언 없음). `cn` 은 충돌 클래스
|
|
214
|
+
자동 해소를 하지 않는다 — 오버라이드가 잦으면 tailwind-merge 를 설치해 `cn` 만 교체.
|
|
215
|
+
- **디자인 토큰은 `style.css` 한 곳(결정 74)** — 컴포넌트는 `bg-primary`·
|
|
216
|
+
`text-muted-foreground` 같은 의미 토큰만 쓰고, 실색은 `apps/<앱>/style.css` 의
|
|
217
|
+
`:root`/`.dark` CSS 변수에서 바꾼다(다크 모드 = `<html class="dark">`).
|
|
218
|
+
- **멀티앱은 앱마다 배선이 따로다(결정 76)** — `gaon g app admin` 은 새 앱에
|
|
219
|
+
프론트 배선(`apps/admin/{style.css,main.ts,index.html}`)을 web 앱과 동등하게
|
|
220
|
+
함께 심는다. 그래서 `gaon g ui-kit --app admin` 으로 심은 UI 킷이 Tailwind
|
|
221
|
+
유틸을 그대로 받는다(`g ui-kit --app` 은 배선이 없으면 멱등으로 보정한다).
|
|
222
|
+
`tailwind.config.ts`·`postcss.config.js` 는 프로젝트 루트 공유(앱마다 두지
|
|
223
|
+
않는다). 앱이 UI 킷을 import 하는데 그 앱에 `style.css` 배선이 없으면 doctor
|
|
224
|
+
**ui-kit-wiring** 이 경고한다.
|
|
225
|
+
|
|
172
226
|
## 정본 예시
|
|
173
227
|
|
|
174
228
|
```vue
|
|
@@ -204,7 +258,7 @@ async function runSearch(q: string) {
|
|
|
204
258
|
- **shared 컴포넌트/컴포저블에서 `pageProps`/`api` 호출 금지** — doctor
|
|
205
259
|
shared-composable-purity 위반. 데이터는 props/인자로.
|
|
206
260
|
- **Vue 페이지에서 `fetch()` 로 폼 구현 금지** — 세션 앱 폼은
|
|
207
|
-
`
|
|
261
|
+
`gaonjs/vue` 의 `useForm(...).post()` (`agents/web.md` §4 · 결정 64).
|
|
208
262
|
- **레이아웃을 shared 에 두지 않는다** — 앱별이 정상.
|
|
209
263
|
- **페이지 파일명은 PascalCase** — `pages/Posts/Index.vue`(폴더 세그먼트도
|
|
210
264
|
Route 이름). 소문자(`posts/index.vue`)는 doctor **page-filename** 이 잡는다
|
|
@@ -221,6 +275,10 @@ async function runSearch(q: string) {
|
|
|
221
275
|
| 결정 25 (E-5) | 컴포저블·레이아웃 관례 · 프론트 로직 배치 3규칙 · 자동 import 금지 |
|
|
222
276
|
| 결정 37 | bigint PK 컨트롤러 `String()` 정규화 |
|
|
223
277
|
| 결정 46 | doctor page-filename(페이지 PascalCase)·model/column 검사 3종 |
|
|
224
|
-
| 결정 69 | 랜딩 정본(라이브 헬스 카드 · 다크 헤더 레이아웃 ·
|
|
278
|
+
| 결정 69 | 랜딩 정본(라이브 헬스 카드 · 다크 헤더 레이아웃 · 실 상태 · 코드 블록) |
|
|
225
279
|
| 결정 70 | auth 통합 = 수동(`gaon g auth` 는 랜딩·nav 를 안 건드림 · Rails 관례) |
|
|
280
|
+
| 결정 71 | HUB 카드 실 introspection(jobs=스트림 pending · channels=프레즌스 실 접속자 · 과대 약속 금지) |
|
|
281
|
+
| 결정 74 | Tailwind 스캐폴드 편입(tailwind.config.ts·postcss.config.js·style.css · 결정 69 scoped-CSS 방침 뒤집기) |
|
|
282
|
+
| 결정 75 | shadcn 식 UI 킷(`gaon g ui-kit` · 복사-소유 · Vue 3 신작 · 외부 런타임 의존 0) |
|
|
283
|
+
| 결정 76 | 멀티앱 UI 킷 배선 자동화(`g app`·`g ui-kit --app` 이 앱별 Tailwind 배선 동봉·멱등 보정 · doctor ui-kit-wiring) |
|
|
226
284
|
| E-3 §C | 타입드 `api()` 클라이언트 (routes.d.ts 브리지 재사용) |
|
|
@@ -28,7 +28,9 @@ export interface HealthDatabase {
|
|
|
28
28
|
export interface HealthHub {
|
|
29
29
|
configured: boolean
|
|
30
30
|
connected?: boolean
|
|
31
|
+
// jobs: JetStream 스트림 대기 수 · channels: 채널별 실 접속자 수(결정 71).
|
|
31
32
|
streams?: { stream: string; waiting: number | null }[]
|
|
33
|
+
channels?: { channel: string; members: number }[] | null
|
|
32
34
|
error?: string
|
|
33
35
|
}
|
|
34
36
|
export interface HealthDoctor {
|
|
@@ -6,114 +6,39 @@
|
|
|
6
6
|
// 레이아웃은 shared 에 두지 않는다(E-5 §2.3) — 앱마다 레이아웃이 다른 것이
|
|
7
7
|
// 정상이고, 공용 조각(로고·푸터 등) 만 shared/components 로 뽑는다.
|
|
8
8
|
//
|
|
9
|
-
// 다크 헤더 + 라이트 본문(결정 69).
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// 헤더에 두려면 아래 nav 에 <a href="/session/new">로그인</a> 을 직접 추가한다.
|
|
9
|
+
// 다크 헤더 + 라이트 본문(결정 69). 스타일은 Tailwind 유틸(결정 74) — 다크
|
|
10
|
+
// 헤더는 본문 테마와 무관하게 항상 어둡게 두려고 브랜드 색을 명시값으로 박는다.
|
|
11
|
+
// 버전은 스캐폴드 시점 gaonjs 버전이 박힌다.
|
|
13
12
|
|
|
14
13
|
// package.json 의 gaonjs 의존 범위(예: ^0.9.2)에서 캐럿·틸드를 벗겨 표기.
|
|
15
14
|
const version = '{{GAONJS_VERSION}}'.replace(/^[\^~]/, '')
|
|
16
15
|
</script>
|
|
17
16
|
|
|
18
17
|
<template>
|
|
19
|
-
<div class="
|
|
20
|
-
<header
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<span
|
|
18
|
+
<div class="flex min-h-screen flex-col bg-background text-foreground">
|
|
19
|
+
<header
|
|
20
|
+
class="flex items-center justify-between gap-4 border-b border-[#21262d] bg-[#0d1117] px-6 py-3.5 text-[#e6edf3]"
|
|
21
|
+
>
|
|
22
|
+
<a href="/" class="inline-flex items-baseline gap-2.5 text-inherit no-underline">
|
|
23
|
+
<span
|
|
24
|
+
class="inline-block rounded-md bg-gradient-to-br from-[#4f8cff] to-[#7c5cff] px-1.5 py-0.5 text-xs font-bold tracking-wide text-white"
|
|
25
|
+
>가온</span>
|
|
26
|
+
<span class="text-[0.95rem] font-bold tracking-[0.08em]">GAONJS</span>
|
|
27
|
+
<span class="text-xs tabular-nums text-[#8b949e]">v{{ version }}</span>
|
|
25
28
|
</a>
|
|
26
|
-
<nav class="
|
|
27
|
-
<a href="https://gaonjs.dev" target="_blank" rel="noreferrer">문서</a>
|
|
28
|
-
<a href="https://github.com/gaonjs" target="_blank" rel="noreferrer">GitHub</a>
|
|
29
|
+
<nav class="flex gap-[1.1rem] text-sm">
|
|
30
|
+
<a href="https://gaonjs.dev" target="_blank" rel="noreferrer" class="text-[#c9d1d9] no-underline hover:text-white">문서</a>
|
|
31
|
+
<a href="https://github.com/gaonjs" target="_blank" rel="noreferrer" class="text-[#c9d1d9] no-underline hover:text-white">GitHub</a>
|
|
29
32
|
</nav>
|
|
30
33
|
</header>
|
|
31
34
|
|
|
32
|
-
<main class="
|
|
35
|
+
<main class="w-full flex-1">
|
|
33
36
|
<slot />
|
|
34
37
|
</main>
|
|
35
38
|
|
|
36
|
-
<footer class="
|
|
39
|
+
<footer class="border-t border-border px-6 py-5 text-center text-muted-foreground">
|
|
37
40
|
<small>{{PROJECT_NAME}} · Powered by
|
|
38
|
-
<a href="https://gaonjs.dev" target="_blank" rel="noreferrer">Gaon</a></small>
|
|
41
|
+
<a href="https://gaonjs.dev" target="_blank" rel="noreferrer" class="text-[#4f8cff] no-underline">Gaon</a></small>
|
|
39
42
|
</footer>
|
|
40
43
|
</div>
|
|
41
44
|
</template>
|
|
42
|
-
|
|
43
|
-
<style scoped>
|
|
44
|
-
.gaon-layout {
|
|
45
|
-
min-height: 100vh;
|
|
46
|
-
display: flex;
|
|
47
|
-
flex-direction: column;
|
|
48
|
-
background: #ffffff;
|
|
49
|
-
color: #1b1f24;
|
|
50
|
-
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* 다크 헤더 — 본문 테마와 무관하게 브랜드 바는 어둡게. */
|
|
54
|
-
.gaon-header {
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: center;
|
|
57
|
-
justify-content: space-between;
|
|
58
|
-
gap: 1rem;
|
|
59
|
-
padding: 0.85rem 1.5rem;
|
|
60
|
-
background: #0d1117;
|
|
61
|
-
color: #e6edf3;
|
|
62
|
-
border-bottom: 1px solid #21262d;
|
|
63
|
-
}
|
|
64
|
-
.gaon-brand {
|
|
65
|
-
display: inline-flex;
|
|
66
|
-
align-items: baseline;
|
|
67
|
-
gap: 0.55rem;
|
|
68
|
-
text-decoration: none;
|
|
69
|
-
color: inherit;
|
|
70
|
-
}
|
|
71
|
-
.gaon-brand-mark {
|
|
72
|
-
display: inline-block;
|
|
73
|
-
padding: 0.15rem 0.45rem;
|
|
74
|
-
border-radius: 6px;
|
|
75
|
-
background: linear-gradient(135deg, #4f8cff, #7c5cff);
|
|
76
|
-
color: #fff;
|
|
77
|
-
font-weight: 700;
|
|
78
|
-
font-size: 0.8rem;
|
|
79
|
-
letter-spacing: 0.02em;
|
|
80
|
-
}
|
|
81
|
-
.gaon-brand-word {
|
|
82
|
-
font-weight: 700;
|
|
83
|
-
letter-spacing: 0.08em;
|
|
84
|
-
font-size: 0.95rem;
|
|
85
|
-
}
|
|
86
|
-
.gaon-brand-ver {
|
|
87
|
-
font-size: 0.72rem;
|
|
88
|
-
color: #8b949e;
|
|
89
|
-
font-variant-numeric: tabular-nums;
|
|
90
|
-
}
|
|
91
|
-
.gaon-nav {
|
|
92
|
-
display: flex;
|
|
93
|
-
gap: 1.1rem;
|
|
94
|
-
font-size: 0.85rem;
|
|
95
|
-
}
|
|
96
|
-
.gaon-nav a {
|
|
97
|
-
color: #c9d1d9;
|
|
98
|
-
text-decoration: none;
|
|
99
|
-
}
|
|
100
|
-
.gaon-nav a:hover {
|
|
101
|
-
color: #fff;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.gaon-main {
|
|
105
|
-
flex: 1;
|
|
106
|
-
width: 100%;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.gaon-footer {
|
|
110
|
-
padding: 1.25rem 1.5rem;
|
|
111
|
-
text-align: center;
|
|
112
|
-
color: #6a737d;
|
|
113
|
-
border-top: 1px solid #eaecef;
|
|
114
|
-
}
|
|
115
|
-
.gaon-footer a {
|
|
116
|
-
color: #4f8cff;
|
|
117
|
-
text-decoration: none;
|
|
118
|
-
}
|
|
119
|
-
</style>
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
// import.meta.glob 으로 만든다. 심볼 출처가 코드에 그대로 보인다.
|
|
10
10
|
import { createGaonApp } from 'gaonjs/vue'
|
|
11
11
|
|
|
12
|
+
// 전역 스타일 — Tailwind 레이어 + 디자인 토큰(결정 74). 부수효과 import 라
|
|
13
|
+
// 번들에 CSS 가 실린다. 앱마다 하나(관례 = 배치).
|
|
14
|
+
import './style.css'
|
|
15
|
+
|
|
12
16
|
// 페이지는 지연 로드(코드 스플리팅) — 큰 앱에서도 첫 페이지 로딩이 빠르다.
|
|
13
17
|
// eager 로 바꿔도 되지만, One Way 의 기본은 지연 로드다.
|
|
14
18
|
const pages = import.meta.glob('./pages/**/*.vue')
|