Svelte Kit

YunKuk Park·2022년 2월 7일
3

svelte

목록 보기
5/5
post-thumbnail

What is SveltKit?

ReactNext.js 가 있다면
Svelte 에는 SvelteKit 이 있다.

SvelteKit 은 고성능 웹 앱을 구축하기 위한 프레임워크 이다.

  • SSR / SSG
  • load
  • hook
  • endpoint

Svelte 는 UI를 쉽게 만들게 해줄 수 있는 컴파일러 or 라이브러리의 느낌이 있고, (like React)
SvelteKit 은 Svelete 를 이용해서 SSR 웹앱을 구축한다거나 Routing 을 한다거나 등을 손쉽게 핸들링 할 수 있게 해준다. (like NextJS)

또한, Svelte Kit 에서 정제를 위해 client 에서 해줄 것인지, server에서 해줄 것인지 알아서 판단 해준다. (load)

Routing

SvelteKit 은 기본적으로 src/routes 폴더 안에 들어가는 컴포넌트가 페이지가 되고 자동으로 routing 이 되도록 지원한다.

route 에는 2가지 type 이 있는데, pagesendpoints 로 나뉘어 진다.

pages

💡 우리가 일반적으로 생각하는 routing 된 page

page 는 .svelte 파일이다.

routes
├── route_a
│   ├── index.svelte  ## 자동으로 index를 찾으므로 path는 다음과 같다 (url/route_a)
│   └── c.svelte      ## path: /route_a/c
├── index.svelte      ## path: /
└── route_b.svelte    ## path: /route_b

동적 라우팅 (pathParam)

동적 라우팅은 [] 기호를 사용하면 된다.

예를 들어, src/routes/blog/[id].svelte 이런식으로 하면 동적라우팅이 가능해진다.

이렇게 동적 라우팅을 하면 react useParam 처럼 id 값에 대한 접근도 load function 에 의해 접근 가능하다.

endpoint

💡 server 에서만 실행되는 무언가들이 있다.
database에 접근하거나, API 작업을 수행 하는 등의 작업을 수행하는 곳이다.

endpoint 는 .js or .ts 파일이다.

HTTP method 와 통신하는 함수이고, 서버단 에서만 사용 할 수 있는 데이터 읽고 쓰기도 가능하게 한다.

pages ~ endpoint

💡 page는 endpoint 에게 data를 요청할 수 있고, endpoint는 JSON 으로 반환하여 준다.

src/routes/items/[id].svelte 페이지는 src/routes/items/[id].js 에서 데이터를 가져올 수 있다.

또한, src/routes/api/items/[id].js 에서도 가져올 수 있다.

import db from '$lib/database';

export const get = async ({ params }) => {
  // 'params.id' comes from [id].js
  const item = await db.get(params.id);

  if (item) {
    return { body: { item } }; 
  }

  return { status: 404 };
}

endpoint 를 포함한 모든 server-side코드들은 외부 API 에서 데이터를 요청 해야 할 경우에도 fetch에 access 할 수 있다.

💡 endpoint 의 함수들은 return 값으로 response를 나타내는 { status, headers, body } 객체를 반환한다.
또한, return 된 body 는 page 에서 prop으로 사용 할 수 있다.

<script>
	// populated with data from the endpoint
	export let item;
</script>

<h1>{item.title}</h1>

EndPoint 에서의 POST, PUT, PATCH, DELETE

Endpoint 에서는 get 말고도 모든 HTTP 메서드를 처리할 수 있다.

export function post(event) {...}
export function put(event) {...}
export function patch(event) {...}
export function del(event) {...} // `delete` is a reserved word

위의 get 예제 처럼 body 를 리턴하고 그 body를 page의 props 로 사용 할 수도 있다.

// src/routes/items/js

import * as db from '$lib/database';

export async function get() {
	const items = await db.list();

	return {
		body: { items }
	};
}

export async function post({ request }) {
	const [errors, item] = await db.create(request);

	if (errors) {
		// return validation errors
		return {
			status: 400,
			body: { errors }
		};
	}

	// redirect to the newly created item
	return {
		status: 303,
		headers: {
			location: `/items/${item.id}`
		}
	};
}
<!-- src/routes/items.svelte -->
<script>
	// The page always has access to props from `get`...
	export let items;

	// ...plus props from `post` when the page is rendered
	// in response to a POST request, for example after
	// submitting the form below
	export let errors;
</script>

{#each items as item}
	<Preview item={item}/>
{/each}

<form method="post">
	<input name="title">

	{#if errors?.title}
		<p class="error">{errors.title}</p>
	{/if}

	<button type="submit">Create item</button>
</form>

headeraccept: application/json 으로 보낸다면, data 를 HTML 이 아닌 JSON 으로 받을 수도 있다.

__layout.svelte

Navigation bar 이나 전체 reset.css 등 앱 전체에 공통으로 들어가는 요소들을

src/routes/__layout.svelte 파일을 만들어서 넣어준다

React 에서의 Router.tsx 와 반쯤? 비슷하다.

// React 에서의 느낌
function Router() {
  return (
    <BrowserRouter>
      <GNB />
      <Layout>
        <Routes>
          <Route path="/users/login" element={<Login />} />
          <Route path="/oauth" element={<KakaoRequest />} />
          <Route path="/" element={<Main />} />
          <Route path="/postings/:id" element={<Detail />} />
          <Route path="/postings/:id/comments" element={<Detail />} />
          <Route path="/contents/new" element={<NewPost />} />
        </Routes>
      </Layout>
    </BrowserRouter>
  );
}
// Svelte 에서의 __layout.svelte
<script>
  import GNB from '$lib/GNB';
  import Layout from '$lib/Layout';
  import "../app.css";
</script>

<GNB />
<Layout>
  <slot /> 
</Layout>

이렇게 하면 모든 페이지에 app.css 에 있는 style 내용과
<GNB/> 가 들어가게 된다.

<slot /> 은 필수로 들어가야 한다.

Nested layouts

만약 settings 페이지안에 /setting/profile /settings/notifications 같이 공유 하위 메뉴가 있다고 가정했을 때,

/settings 아래 페이지에만 적용되는 레이아웃을 만들 수도 있다.

<!-- src/routes/settings/__layout.svelte -->
<h1>Settings</h1>

<div class="submenu">
	<a href="/settings/profile">Profile</a>
	<a href="/settings/notifications">Notifications</a>
</div>

<slot></slot>

Reset layouts

만약 admin 페이지를 만든다고 가정해보면, admin 페이지(back-office)는 client page 의 레이아웃을 가져갈 필요가 없다. 오히려 새로 짜야 하는데 이때 reset layout 을 사용하면 된다.

예를 들어서 /admin/* 페이지가 root layout 을 상속받지 않으려면
src/routes/admin/_layout.reset.svelte 라는 파일을 만들면 된다.
그 파일 내에서 새로운 layout 을 짜주면 된다.

Store

💡 svelte 에서 전역상태 관리를 하기 위함이다.
react 에서 recoil, redux 와 비슷하다.

1. store 설정

/src/lib/mystore.ts
import { writable } from 'svelte/store';

export const menuOpen = new writable(true);

2. 사용하는 곳에 import

import { menuOpen } from '$lib/mystore';

3. 사용 시

import { menuOpen } from '$lib/mystore';

const handleToggle = () => {
  $menuOpen = !$menuOpen;
}

<div on:click={handleToggle}>
	hihi
</div>

{$menuOpen} // 이렇게 하면 전역관리 변수에 접근 할 수 있다.

만약 전역에서 관리해야 하는 데이터가 백엔드와 통신해서 가져와야 한다면 다음과 같이 사용 할 수 있다.

1. store 설정

/src/lib/mystore.ts
import { writable } from 'svelte/store';

export const products = new writable([]);

const fetchProduct = async ():void => {
  const data = await fetch(`${BASE_URL}/product`)
    .then(res => res.json())
    .then(data => data.slice(0, 100));

  products.set(data);
};

fetchProduct();

2. 사용하는 곳

<script>
import { products } from '$lib/mystore';
console.log($products);
</script>

{#each $products as product}
  <p>{product.name}</p>
{/each}

load

💡 load function은 url, params, props, fetch, session, stuff 등 이 포함된 object 를 받는다.

그리고 꼭 return 이 있어야 한다!!

보통 페이지의 데이터가 endpoint 에서 나오는 경우는 필요 하지 않을 수 있다.
endpoint 와 비슷하게 페이지가 렌더링되기 전에 데이터를 fetch하고 조작 할 수 있는 함수인데,
외부 API에서 데이터를 로드하는 등 client 단에서 처리 할 수 있는 일을 할 때 더 유용하다.

외부 API 를 통해 data 를 받아오는 예시

<!-- src/routes/blog/[slug].svelte -->
<script context="module">
  /** @type {import('@sveltejs/kit').Load} */
  export async function load({ params, fetch, session, stuff }) {
    const response = await fetch(`https://cms.example.com/article/${params.slug}.json`);

    return {
      status: response.status,
      props: {
        article: response.ok && (await response.json())
      }
    };
  }
</script>

<script context=”module”><script> 보다 위에 있어야 한다.
component 를 render 하기 전에 load 를 실행 시키기 때문

SvelteKit’s load 는

  • Cookies 에 Access 할 수 있다.
  • HTTP 호출을 실행하지 않고 앱 자체의 endpoint 에 대해 요청 할 수 있다.
  • page 와 layout component 에서만 사용 할 수 있다.

native fetch 를 쓰는게 아니라 SvelteKit-provided fetch 를 사용해야 한다.
window 나 document 같은 browser 객체를 건드리면 안된다.
API 키나 암호를 직접 쓰지 말고 쓰려면 endpoint 를 통해 호출해야 한다.

url

origin,  hostnamepathname , searchParams

<script context="module">
  export const load = ({ url, params }) => {
    console.log('url: ', url);
    console.log(params.index);
    return {
      props: {
        param: params.index
      }
    };
  };
</script>

params

url.pathname 에서 / 빼고 나옴
동적라우팅 한 페이지에서 사용 할 것! (아니면 어차피 빈 값 뱉어냄)

만약 src/routes/a/[b]/[...c] 이렇게 생겨먹은 주소가 있다면
/a/x/y/z 의  params 는 다음과 같다.

{
  "b": "x",
  "c": "y/z"
}

fetch

load function 에서의 fetch 함수는 우리가 원래 알고 있던 fetch함수와 동일하다.

session

session 은 hooksgetSession 과 맞물려서 사용되는 load function 이다.

hooks

💡 HOOK :: 갈고리
무언가 중간에 참견 하는 느낌

src/hooks.ts jssrc/hooks/index.ts 로 만들어진 파일

이 파일은 서버에서 run 되는 4가지 함수를 가지고 있다. (export)

handle , handleError, getSession, externalFetch

getSession

// src/hooks.js
export const getSession = (event) => {
  return {
    user: {
      id: 'yunkukPark',
      name: 'yunkukPark',
      access: 'admin'
    }
  };
};

// src/routes/index.svelte
<script>
  import { session } from '$app/stores';
  console.log($session);
</script>

hooks 의 getSession에 request response 를 설정해놓으면

Session을 가져오는 방법이 여러가지 있는데 그 중에서

session store 를 사용할 수 있다. 그걸 가지고 사용자의 정보 같은 것을 가져 올 수 있다.

handle function 에서 request 를 변경한 다음 getSession 의 req 에 보낼 수도 있고,
사용자가 인증되어있는지 등을 이용하여 인증 할 수 있다.

또한, 이런 식으로 load 기능을 통해 세션을 가져오는 방법도 있다.

<script context="module">
  export const load = ({ session }) => {
    console.log(session);
    return {};
  };
</script>

유저의 avatar나 nickname 을 가지고 와야 하는 경우

<script context="module">
  export const load = ({ session }) => {
    console.log(session);
    return {
      props: {
        session
      }
    };
  };
</script>

<script>
  export let session;
</script>

<h2>{JSON.stringify(session)}</h2>

다음과 같이 module 쪽에서 props 로 session을 return 해주고 받아 쓰면 된다.

💡 사용자의 인증 유무를 session 자체에서 확인해서 좋다!

handle

💡 handle function은 Svelte Kit이 request 를 보낼 떄 마다 실행되고 request 를 보내준다.
return 되는 값은 event Object 이다.
⇒ 사용자를 인증하기 더욱 쉬워짐

기본적인 사용 방법은 아래와 같다.

export const handle = async ({ event, resolve }) => {
  const response = await resolve(event);

  return response;
};

event 와 resolve(event) 안에는 다음과 같은게 있다.

event:  {
  request: Request {
    size: 0,
    follow: 20,
    compress: true,
    counter: 0,
    agent: undefined,
    highWaterMark: 16384,
    insecureHTTPParser: false,
    [Symbol(Body internals)]: {
      body: null,
      stream: null,
      boundary: null,
      disturbed: false,
      error: null
    },
    [Symbol(Request internals)]: {
      method: 'GET',
      redirect: 'follow',
      headers: [Object],
      parsedURL: [URL],
      signal: null,
      referrer: undefined,
      referrerPolicy: ''
    }
  },
  url: URL {
    href: 'http://localhost:3000/',
    origin: 'http://localhost:3000',
    protocol: 'http:',
    username: '',
    password: '',
    host: 'localhost:3000',
    hostname: 'localhost',
    port: '3000',
    pathname: '/',
    search: '',
    searchParams: URLSearchParams {},
    hash: ''
  },
  params: {},
  locals: {},
  platform: undefined
}

response:  Response {
  size: 0,
  [Symbol(Body internals)]: {
    body: null,
    stream: null,
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    type: 'default',
    url: undefined,
    status: 304,
    statusText: '',
    headers: { etag: '"zk3ik8"' },
    counter: undefined,
    highWaterMark: undefined
  }
}

그럼 event.local 을 이용해서 user정보를 getSession 에게 보낼 수 있다.

쿠키를 설정하고 쿠키사용하는 경우에 유용 할 것이다.

FLOW

  1. Login을 하면 User 인증을 하고 Cookie 를 심는다.

  2. 어떤 페이지에서 요청을 할 때 Cookie 와 함께 보내줘서 User 인증을 자동으로 하게 한다.

  3. login.js (endpoint) 파일 만들기

export const post = async ({ request }) => {
  const body = await request.json();
  const user = body;

  if (!(user.username === 'Yunkuk' && user.password === '!@#$@^#$')) {
    return { body: { message: 'NOT AUTH USER' } };
  }

  return { body: { message: 'SUCCESS' } };
};
  1. endpoint 파일과 연결해주는 script 생성
<script>
  let message = '';

  const login = async () => {
    const res = await fetch('api/login', {
      method: 'POST',
      body: JSON.stringify({
        username: 'Yunkuk',
        password: '!@#$@^#$'
      })
    });

    const data = await res.json();
    message = data.message;
  };
</script>

<h1>Login Page</h1>
<p>{message}</p>
<button on:click={login}>Login</button>

<style>
  p {
    color: tomato;
  }
</style>
  1. npm i cookie uuid

    uuid :: 랜덤 스트링 생성을 위한 dependency

  1. cookie set 하기
import * as cookie from 'cookie';
import { v4 as uuidv4 } from 'uuid';

export const post = async ({ request }) => {
  const body = await request.json();
  const user = body;

  if (!(user.username === 'Yunkuk' && user.password === '!@#$@^#$')) {
    return { body: { message: 'NOT AUTH USER' } };
  }

  const sessionId = uuidv4();

  const setCookie = cookie.serialize('session_id', sessionId, {
    httpOnly: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7,
    path: '/'
  });

  const headers = {
    'set-cookie': setCookie
  };

  return {
    status: 200,
    headers,
    body: {
      message: 'SUCCESS'
    }
  };
};
  1. 이제 hooks의 handle을 통해 cookie를 읽을 수 있다.
export const handle = async ({ event, resolve }) => {
	const cookies = cookie.parse(event.request.headers.get('cookie') || '');
	const response = await resolve(event);
	return response;
};
profile
( • .̮ •)◞⸒⸒

0개의 댓글