vfunc.js

SPA: 라우터, store, 다국어

앱 구조

index.html       <div id="app"> + <script type="module" src="app.js">
app.js           레이아웃 + 라우터 + 페이지 전환 (모든 페이지를 아는 유일한 곳)
store.js         vf.store와 그것을 바꾸는 함수
api.js           모든 서버 호출
pages/*.js       화면마다 (ctx, router) => component
components/*.js  재사용 부품

스타터와 예제 10이 이 구조입니다.

라우터 — vf.router

const router = vf.router({
  mode: 'hash',                                 // 기본. 어떤 정적 서버·IE에서도 동작
  routes: {
    '/': (ctx) => show(homePage(ctx, router)),
    '/orders/:id': (ctx) => show(orderPage(ctx.params.id, ctx.query.tab))
  },
  notFound: (ctx) => show(notFoundPage(ctx)),
  onChange: (ctx) => { document.title = ctx.route || 'Not found'; },
  focus: '#view'                                // 이동 후 포커스(스크린리더)
});
router.start();

클라이언트 라우트는 접근 제어가 아닙니다. 관리자 화면을 숨겨도 권한은 서버에서 검사해야 합니다.

공용 상태 — vf.store

export const cart = vf.store({ items: [] });
export function add(item) { cart.set((s) => ({ items: s.items.concat(item) })); }

// 컴포넌트에서
onMount: (inst) => { off = cart.subscribe((s) => inst.setState({ count: s.items.length })); },
onDestroy: () => off()

다국어 — vf.i18n, vf.t, vf.fmt

await vf.i18n.setup({
  locales: ['ko', 'en'], fallback: 'en',
  load: (l) => fetch(new URL('./locales/' + l + '.json', import.meta.url)).then((r) => r.json()),
  persist: true
});
vf.t('cart.items', { count: 3 });       // { zero, one, other } 중에서 복수형 선택
vf.fmt.currency(12900, 'KRW');           // 현재 로케일의 Intl 포맷
vf.i18n.apply();                         // 퍼블리싱 HTML의 data-i18n / data-i18n-attr 번역