@gaonjs/vue 0.2.0 → 0.3.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/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/layout.d.ts +17 -0
- package/dist/layout.js +48 -0
- package/dist/runtime.d.ts +42 -0
- package/dist/runtime.js +68 -0
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export declare const status: "planned";
|
|
|
3
3
|
export { pageProps } from "./pageProps.js";
|
|
4
4
|
export { serializeProps, type Serialized } from "./serialize.js";
|
|
5
5
|
export { api, registerRoutes, type ApiParams, type ApiOptions, type GaonRouteInfo, } from "./api.js";
|
|
6
|
+
export { createGaonApp, type CreateGaonAppOptions, type PageMap } from "./runtime.js";
|
|
7
|
+
export { findDefaultLayout, resolvePageLayout } from "./layout.js";
|
package/dist/index.js
CHANGED
|
@@ -14,3 +14,7 @@ export { pageProps } from "./pageProps.js";
|
|
|
14
14
|
export { serializeProps } from "./serialize.js";
|
|
15
15
|
// 타입드 클라이언트 (errata E-3) — 라우트 키 → Serialized 반환
|
|
16
16
|
export { api, registerRoutes, } from "./api.js";
|
|
17
|
+
// Inertia SPA 부팅 (§6.4 · M3 최소 웹 레이어) — apps/<앱>/main.ts 진입점.
|
|
18
|
+
export { createGaonApp } from "./runtime.js";
|
|
19
|
+
// 레이아웃 자동 적용 (E-5 §2.3) — layouts/Default.vue 결정 로직.
|
|
20
|
+
export { findDefaultLayout, resolvePageLayout } from "./layout.js";
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* 페이지 모듈 지도(import.meta.glob 결과)에서 `layouts/Default.vue` 를
|
|
4
|
+
* 뽑는다. glob 은 페이지 지도와 같은 root(main.ts)에서 만들어져 넘어오므로,
|
|
5
|
+
* 키는 './layouts/Default.vue' 형식이다.
|
|
6
|
+
*/
|
|
7
|
+
export declare function findDefaultLayout(layouts: Record<string, unknown> | undefined): Component | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* 페이지 컴포넌트에 레이아웃을 결정해 붙인다(E-5 §2.3).
|
|
10
|
+
* · pageLayout === null → 명시 opt-out. 그대로 null 유지.
|
|
11
|
+
* · pageLayout 값 존재 → 명시 지정. 그대로.
|
|
12
|
+
* · pageLayout === undefined → Default.vue 있으면 그것, 없으면 그대로.
|
|
13
|
+
*
|
|
14
|
+
* 반환은 실제로 붙일 레이아웃(Component) 또는 undefined(레이아웃 없음).
|
|
15
|
+
* 결정 로직만 담고, 붙이는 것은 호출자(runtime.createGaonApp)가 한다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePageLayout(pageLayout: unknown, defaultLayout: Component | undefined): Component | undefined;
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @gaonjs/vue · Vue 레이아웃 자동 적용 (v0.16 §6.6 · errata E-5 §2.3)
|
|
2
|
+
//
|
|
3
|
+
// 정본: "layouts/Default.vue 가 존재하면 그 앱의 모든 페이지에 자동
|
|
4
|
+
// 적용된다(파일 존재 = 등록 원칙). 다른 레이아웃이 필요한 페이지만
|
|
5
|
+
// 페이지 파일에서 명시적으로 바꾼다."
|
|
6
|
+
//
|
|
7
|
+
// 페이지 컴포넌트의 정적 프로퍼티 `layout` 이 다음 세 값을 갖는다:
|
|
8
|
+
// · 없음(undefined) → Default.vue 가 있으면 자동 적용, 없으면 raw.
|
|
9
|
+
// · null → 명시 opt-out(로그인 페이지처럼 레이아웃 없음).
|
|
10
|
+
// · Component → 그 컴포넌트를 명시 지정.
|
|
11
|
+
//
|
|
12
|
+
// Inertia 의 persistent layout 규약과 정합 — 페이지 컴포넌트 자체가
|
|
13
|
+
// `layout` 을 들고 있으면 어댑터가 페이지 전환 시 레이아웃 상태를 유지한다.
|
|
14
|
+
/**
|
|
15
|
+
* 페이지 모듈 지도(import.meta.glob 결과)에서 `layouts/Default.vue` 를
|
|
16
|
+
* 뽑는다. glob 은 페이지 지도와 같은 root(main.ts)에서 만들어져 넘어오므로,
|
|
17
|
+
* 키는 './layouts/Default.vue' 형식이다.
|
|
18
|
+
*/
|
|
19
|
+
export function findDefaultLayout(layouts) {
|
|
20
|
+
if (!layouts)
|
|
21
|
+
return undefined;
|
|
22
|
+
const mod = layouts['./layouts/Default.vue'];
|
|
23
|
+
if (!mod)
|
|
24
|
+
return undefined;
|
|
25
|
+
return extractDefault(mod);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 페이지 컴포넌트에 레이아웃을 결정해 붙인다(E-5 §2.3).
|
|
29
|
+
* · pageLayout === null → 명시 opt-out. 그대로 null 유지.
|
|
30
|
+
* · pageLayout 값 존재 → 명시 지정. 그대로.
|
|
31
|
+
* · pageLayout === undefined → Default.vue 있으면 그것, 없으면 그대로.
|
|
32
|
+
*
|
|
33
|
+
* 반환은 실제로 붙일 레이아웃(Component) 또는 undefined(레이아웃 없음).
|
|
34
|
+
* 결정 로직만 담고, 붙이는 것은 호출자(runtime.createGaonApp)가 한다.
|
|
35
|
+
*/
|
|
36
|
+
export function resolvePageLayout(pageLayout, defaultLayout) {
|
|
37
|
+
if (pageLayout === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (pageLayout !== undefined)
|
|
40
|
+
return pageLayout;
|
|
41
|
+
return defaultLayout;
|
|
42
|
+
}
|
|
43
|
+
function extractDefault(mod) {
|
|
44
|
+
if (mod && typeof mod === 'object' && 'default' in mod) {
|
|
45
|
+
return mod.default;
|
|
46
|
+
}
|
|
47
|
+
return mod;
|
|
48
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type GaonRouteInfo } from './api.js';
|
|
2
|
+
/**
|
|
3
|
+
* import.meta.glob 이 반환하는 페이지 지도.
|
|
4
|
+
* 값은 즉시(eager) 로드된 모듈이거나 지연 로드 함수다 — 둘 다 지원.
|
|
5
|
+
*/
|
|
6
|
+
export type PageMap = Record<string, unknown | (() => Promise<unknown>)>;
|
|
7
|
+
export interface CreateGaonAppOptions {
|
|
8
|
+
/**
|
|
9
|
+
* 페이지 모듈 지도. main.ts 에서
|
|
10
|
+
* `import.meta.glob('./pages/**\/*.vue')` (지연 로드)
|
|
11
|
+
* 또는
|
|
12
|
+
* `import.meta.glob('./pages/**\/*.vue', { eager: true })`
|
|
13
|
+
* 로 만들어 넘긴다. 키는 './pages/<name>.vue' 형식.
|
|
14
|
+
*/
|
|
15
|
+
readonly pages: PageMap;
|
|
16
|
+
/**
|
|
17
|
+
* 레이아웃 모듈 지도. Default.vue 가 있으면 자동 적용(E-5 §2.3).
|
|
18
|
+
* `import.meta.glob('./layouts/*.vue', { eager: true })`
|
|
19
|
+
* eager 인 편이 편하다 — 레이아웃은 소수·최초 진입에도 필요.
|
|
20
|
+
*/
|
|
21
|
+
readonly layouts?: PageMap;
|
|
22
|
+
/**
|
|
23
|
+
* 앱의 라우트 매니페스트(method + path). api() 클라이언트가 소비한다
|
|
24
|
+
* (E-3 §3). .gaon/routes.manifest.ts 가 생성해 이 옵션으로 넘긴다.
|
|
25
|
+
* 미지정 시 api() 는 라우트 조회에서 명확한 에러를 던진다.
|
|
26
|
+
*/
|
|
27
|
+
readonly routes?: Record<string, GaonRouteInfo>;
|
|
28
|
+
/** 브라우저 탭 타이틀 조합자. Inertia 옵션 그대로 통과. */
|
|
29
|
+
readonly title?: (title: string) => string;
|
|
30
|
+
/** Inertia 진행 표시자 옵션(색상·delay 등). false 로 끌 수 있다. */
|
|
31
|
+
readonly progress?: false | {
|
|
32
|
+
readonly color?: string;
|
|
33
|
+
readonly delay?: number;
|
|
34
|
+
readonly includeCSS?: boolean;
|
|
35
|
+
readonly showSpinner?: boolean;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Inertia SPA 부팅(브라우저 전용). 페이지·레이아웃 지도와 라우트 매니페스트를
|
|
40
|
+
* 받아 Vue 앱을 마운트한다. 서버는 apps/<앱>/main.ts 에서 이 함수를 부른다.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createGaonApp(opts: CreateGaonAppOptions): Promise<void>;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
// @gaonjs/vue · Vue 3 클라이언트 런타임 (v0.16 §6.4 · M3 최소 웹 레이어)
|
|
3
|
+
//
|
|
4
|
+
// 이 파일은 브라우저에서 Inertia SPA 를 부팅한다:
|
|
5
|
+
// 1) apps/<앱>/main.ts 가 페이지·레이아웃 지도를 만든다(Vite 정합).
|
|
6
|
+
// 2) `createGaonApp` 에 지도와 라우트 매니페스트를 넘긴다.
|
|
7
|
+
// 3) 내부에서 @inertiajs/vue3 의 createInertiaApp 을 호출해 마운트.
|
|
8
|
+
//
|
|
9
|
+
// 정본 준수:
|
|
10
|
+
// · Inertia SPA + 서버 라우팅 · **SSR 아님** (§6.1 · CLAUDE.md 규칙 12).
|
|
11
|
+
// · 자동 import 금지 — 페이지·레이아웃 지도는 사용자 코드가 명시적으로
|
|
12
|
+
// import.meta.glob 으로 만들어 넘긴다(E-5 §2.4).
|
|
13
|
+
// · `layouts/Default.vue` 존재 = 자동 적용(E-5 §2.3, ./layout.ts).
|
|
14
|
+
//
|
|
15
|
+
// 서버 응답의 data-page (packages/web/src/inertia.ts) 를 Inertia 어댑터가
|
|
16
|
+
// 읽어 마운트한다 — 초기 페이지 · 이후 SPA 네비게이션 모두 같은 경로.
|
|
17
|
+
import { createApp, h } from 'vue';
|
|
18
|
+
import { createInertiaApp } from '@inertiajs/vue3';
|
|
19
|
+
import { registerRoutes } from './api.js';
|
|
20
|
+
import { findDefaultLayout, resolvePageLayout } from './layout.js';
|
|
21
|
+
/**
|
|
22
|
+
* Inertia SPA 부팅(브라우저 전용). 페이지·레이아웃 지도와 라우트 매니페스트를
|
|
23
|
+
* 받아 Vue 앱을 마운트한다. 서버는 apps/<앱>/main.ts 에서 이 함수를 부른다.
|
|
24
|
+
*/
|
|
25
|
+
export async function createGaonApp(opts) {
|
|
26
|
+
if (opts.routes)
|
|
27
|
+
registerRoutes(opts.routes);
|
|
28
|
+
const defaultLayout = findDefaultLayout(opts.layouts);
|
|
29
|
+
await createInertiaApp({
|
|
30
|
+
title: opts.title,
|
|
31
|
+
progress: opts.progress,
|
|
32
|
+
resolve: async (name) => {
|
|
33
|
+
const key = `./pages/${name}.vue`;
|
|
34
|
+
const entry = opts.pages[key];
|
|
35
|
+
if (entry === undefined) {
|
|
36
|
+
// 에러가 곧 수리 안내서(§7.5.3): 어디 파일을 만들어야 하는지 말해준다.
|
|
37
|
+
throw new Error(`[gaonjs/vue] Inertia 페이지 '${name}' 를 찾을 수 없습니다 (looked up '${key}').\n` +
|
|
38
|
+
`→ apps/<앱>/pages/${name}.vue 를 만들거나, 컨트롤러의 this.render('${name}', ...) 인자를 확인하세요.`);
|
|
39
|
+
}
|
|
40
|
+
const mod = await loadPageModule(entry);
|
|
41
|
+
const page = mod.default;
|
|
42
|
+
// 레이아웃 자동 적용(E-5 §2.3) — 페이지가 layout 을 명시 안 했으면 Default.
|
|
43
|
+
const chosen = resolvePageLayout(page.layout, defaultLayout);
|
|
44
|
+
if (chosen !== undefined) {
|
|
45
|
+
;
|
|
46
|
+
page.layout = chosen;
|
|
47
|
+
}
|
|
48
|
+
else if (page.layout === null) {
|
|
49
|
+
// 명시 opt-out 은 그대로 null 로 남긴다 — Inertia 는 null 을 "레이아웃 없음"으로 취급.
|
|
50
|
+
}
|
|
51
|
+
return mod;
|
|
52
|
+
},
|
|
53
|
+
setup({ el, App, props, plugin }) {
|
|
54
|
+
if (el === null)
|
|
55
|
+
return;
|
|
56
|
+
createApp({ render: () => h(App, props) })
|
|
57
|
+
.use(plugin)
|
|
58
|
+
.mount(el);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function loadPageModule(entry) {
|
|
63
|
+
if (typeof entry === 'function') {
|
|
64
|
+
const loaded = await entry();
|
|
65
|
+
return loaded;
|
|
66
|
+
}
|
|
67
|
+
return entry;
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Gaon 프론트엔드 어댑터: Vue 3 · Inertia 브리지 · 타입 전파 · SSR (구현 예정)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,8 +24,12 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@inertiajs/vue3": "^3.6.1",
|
|
27
28
|
"@gaonjs/core": "0.1.4",
|
|
28
|
-
"@gaonjs/web": "0.
|
|
29
|
+
"@gaonjs/web": "0.5.1"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vue": "^3.5.0"
|
|
29
33
|
},
|
|
30
34
|
"scripts": {
|
|
31
35
|
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json"
|