npm install vue-router
ํ์ด์ง ์ ์ฒด ์ ๋ณด๊ฐ routes ๋ผ๋ ๊ณณ์ ๋ค์ด๊ฐ๊ฒ ๋๋ค.
// src/router/index.js
{
path: '/home',
component: HomePage
},
์ด ์ ๋๋ก path๋ง๋ค ๋ฟ๋ ค์ง๋ component ๊ตฌํ์ผ๋ก ์ถฉ๋ถํ์ง๋ง, tag๋ฅผ ์ ์ํ๊ธฐ ์ํด์ router view๋ฅผ ์ ์ํ๋ค. ๋ ๋ทฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ผ์ ๋ ์ ๊ณตํ๋ router ํ๊ทธ์ด๋ค.
- mode
vue-router์ ๊ธฐ๋ณธ ๋ชจ๋๋ hash mode. URL์ด ๋ณ๊ฒฝ๋ ๋ ํ์ด์ง๊ฐ ๋ค์ ๋ก๋๋์ง ์์
๋ณ๊ฒฝ์ ์ํด history ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ. ํ์ด์ง๋ฅผ ๋ค์ ๋ก๋ํ์ง ์์๋ URL ํ์ ๊ฐ๋ฅ
const router = new VueRouter({
mode: 'history',
routes: [...]
})
- router-link
a href์์ ์ฐจ์ด : to, replace ๋ฑ์ vue-router์์ ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ฅ ์ ๊ณต ์ฌ๋ถ. router-link๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ํ์คํ ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ํ์ง ์๋ ๊ธฐ๋ฅ์ด๋ผ๋ ์ง, to ์์ฑ์ parameter ๋ฑ์ ๋๊ธธ ์ ์๊ฒ ๋๋ค.
url์ ๋ฐ๊ฟจ๋๋ฐ๋ ์๋ฒ๋ก ๊ฐ์ง ์๊ณ jsํ์ผ๋ก ์กฐ์์ด ์ด๋ป๊ฒ ๊ฐ๋ฅํ๊ฐ? -> window history
- router ๊ตฌ์ฑ๋ฐฉ๋ฒ
- ๊ธฐ๋ณธ ๋ฐฉ์ : ์๊ตฌ์ฌํญ์ด ๋ง์ด ๋ฐ๋๋ ๊ฒฝ์ฐ์ ์ฌ์ฉ (๋จ์ํ๊ฒ ๊ตฌ์ฑ)
- nested router
- named view
- SPA(Single Page Application)
์์ ์๋ ํ๋ฉด์ ๋ฐ๋๋ ๊ฒ์ ์์ ํ๊ธฐ ์ํด์ ์ ์ฒด ํ์ผ์ ๋ฐ์์ค๊ธฐ ์ํด ์๋ฒ๊น์ง ์ ๊ทผํ๋๋ฐ, ์์ฆ์๋ ๋ฐ๋๋ ๋ถ๋ถ๋ง ์๋ฒ์ ๊ฐ์ ์์ฒญํจ. ํด๋น ์ญํ ์ vue router๊ฐ ์ํ
routes: [
{
path: "/home",
component: Home
beforeEnter: (to, from, next) => {
if (localStorage.getItem("key") == null) {
return next({
path: "/login",
query: { redirect: to.fullPath }
});
}
next();
};
}
]
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ key๊ฐ ์๋์ง ํ์ธํ๊ณ ์์ผ๋ฉด login ํ๋ฉด์ผ๋ก ๋๊ธฐ๋ ๋ก์ง์ด๋ค.
- ์ ์์์ ๊ฐ์ด ๋ค๋น๊ฒ์ด์ ๊ฐ๋๋ฅผ ํตํด ํน์ ๋ผ์ฐํ ์ ๋ํด ๊ฐ๋๋ฅผ ์ค์ ํ ์ ์๋ค.
- ์ฝ๊ฒ ๋งํด, ํน์ ์ปดํฌ๋ํธ๋ก ์ด๋ํ๊ธฐ ์ ์ ์กฐ๊ฑด ์ฒ๋ฆฌ๋ฅผ ํตํด ๋ก๊ทธ์ธ์ด ๋์๋์ง ๋ฑ ๊ฒ์ฆ ๋ก์ง์ ์ํํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
Workbox๊ฐ ๊ฐ์ฅ ์ต์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
- ๋ชจ๋ฐ์ผ ์ฑ๊ณผ ๊ฐ์ ๊ฒฝํ์ ์ฃผ๋ ์ต์ ์น ์ฑ
- ์ฑ icon, ์ฑ ์ค์น banner, ๋ชจ๋ฐ์ผ push(notification), offline ๊ฒฝํ ์ ๊ณต
- html, css, js๋ก ๋ชจ๋ฐ์ผ๊ณผ ๊ฐ์ ์น์ฑ ๊ตฌํ
- ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํ ์น์ฑ (ํ์ด์ง ์๋๋ฅผ ์ฌ๋ฆฌ๋ ๋ฑ)
https://vuejsdevelopers.com, https://dev.to ์ ๊ฐ์ ์ฌ์ดํธ๋ค์ด PWA๋ก ๊ฐ๋ฐ์ด ๋์๋ค. ์น ์ดํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ค์น๊ฐ ๊ฐ๋ฅํ ๊ฒ์ ํ์ธํ ์ ์๋ค. https://pwa.rocks/ ์ฌ๊ธฐ์์ ๋ ๋ง์ PWA ์ฌ์ดํธ๋ฅผ ํ์ธ ํ ์ ์๋ค.
- App-like (Mobile App๊ณผ ๋์ผํ icon based ์คํ๋ฐฉ์๊ณผ UX์ ์ ๊ณต)
- Discoverable (url๋ก ์ฌ์ดํธ ์ ๊ทผ ํ banner๋ฅผ ํตํด oneclick ์ค์น ๊ฐ๋ฅ)
- Engagable (push ์๋์ด ์์ ๋ ๋ค์ ์ฑ์ผ๋ก ์ ์ )
- Connectivity (์คํ๋ผ์ธ ์น์ฑ - ์ธํฐ๋ท์ด ๋๊ฒจ๋ ์น์ฌ์ดํธ๊ฐ ์ ๋์ํจ)
- https ์์์๋ง ๋์ํ๋ค.
- ํฌ๋ณด์ค๋ ์ต์คํ๋์ ๋ฑ ๋ง์ ๊ธฐ์ ์์ ์ฌ์ฉํ๋ค.
- ์ผ๋ฐ ์น์ฌ์ดํธ์ ๋์์ด ๋ค๋ฅด๋ค. PWA๋ ์ฑ์ผ๋ก ์ค์นํ๊ณ ์คํํ์ ๋, launching image๊ฐ ์์ด ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ์ ๋ณด์ด๊ณ , ์์ url bar๊ฐ ์กด์ฌํ์ง ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- Web App Manifest (manifest.json)
- Service Workers (client-side javascript proxy) - ์น์ฑ๊ณผ webserver ์ฌ์ด์ ์กด์ฌ (caching)
- ํ์ด์ดํญ์ค, ์คํ๋ผ, ํฌ๋กฌ, ์ผ์ฑ ์ธํฐ๋ท, ์ฃ์ง, ์ฌํ๋ฆฌ(push ์ง์X) ๋ฑ
- ์ผ์ฑ ์ธํฐ๋ท๊ณผ ํฌ๋กฌ์ ๋๋ถ๋ถ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
- OS๋ ๋ฒจ๊น์ง ๋ด๋ ค์์
- application ๊ด๋ฆฌ tab์ผ๋ก ์กด์ฌํจ
- ํ๋ ์ด์คํ ์ด์ ๋ฐฐํฌํ๊ธฐ ์ํด์๋ mapping ํ๋ ์์ ์ด ์ถ๊ฐ๋ก ํ์ํ ๋ฟ์ด๋ค.
- Google I/O 2019 ์น ์ธ์ ์์ TWA (Trusted Web Activity) ๋ผ๋ ์ฉ์ด๊ฐ ๋ฑ์ฅํ๋๋ฐ, ์ด๊ฒ์ Google Play์ PWA๋ฅผ ์ฌ๋ฆด ์ ์๋ App Frame ์ด๋ค. App Store๋ ์ง์ ์์ ์ด ์๋ค.
PWA์ ์ค์น์ ์ฑ ๊ตฌ์ฑ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ํ์ผ (์ฑ ์์ด์ฝ, ๋ฐ์ณ ๋ฐฉ์, ๋ฐฐ๊ฒฝ, ์์ํ์ด์ง ๋ฑ)
<link rel="manifest" href="manifest.json" />
์๋ ๊ฒ๋ค์ด ์ค์ ๋ ์ ์์.
- start url
- launch image
- display type : ์น์ฑ์ด ๋ชจ๋ฐ์ผ ์ฑ์ ๋๋์ ๊ฐ์ง ์ ์๋๋ก ๊ฒฐ์ ์ง๋ ์์ฑ (์ ์ฒด ๋ชจ์)
- Standalone (url์ฐฝ ์ฌ๋ผ์ง)
- Browser
- Fullscreen (non-app UI ์ฌ๋ผ์ง)
- Minimal-ui
- display orientation
- App icon
- ๋ฐฐ๊ฒฝ ์ : background color์ ๋น์ทํ๊ฒ ํ๋ ๊ฒ์ด ์ข๋ค.
PWA๊ฐ ๋ชจ๋ฐ์ผ์ ์ธ ํน์ง์ ๊ฐ์ง๋ ํฐ ๋ถ๋ถ
- ๊ธฐ์กด ์ฑ ๊ฐ๋ฐ์ฃผ๊ธฐ : ๊ตฌํ -> SDK ๋น๋ -> ์คํ ์ด ๋ฐฐํฌ -> ๊ฒ์ -> ์ฑ ๋ค์ด๋ก๋ -> ์ค์น -> ์ฌ์ฉ
- PWA ์ฑ ๊ฐ๋ฐ์ฃผ๊ธฐ : ๊ตฌํ -> ์ฌ์ดํธ ๋ฐฐํฌ -> ๊ฒ์ -> ์ฌ์ฉ (์๋์ค์น, ํญ์ ์ต์ ์ํ ์ ์ง)
- short name
- name
- icons (including a 192px and a 512px version)
- https
์ฌ์ฉ์๊ฐ 30์ด๋ง ์ฌ์ดํธ์ ๋จธ๋ฌด๋ฅด๋ฉด, install banner๊ฐ ๋ฌ๋ค. (ํ์ด๋ฐ์ ์กฐ์ ๊ฐ๋ฅ)
- ์คํ๋ผ์ธ ์๋น์ค, ํธ์ฌ์๋ ๋ฑ Mobile ๊ธฐ๋ฅ์ ์น์์ ๊ฐ๋ฅํ๊ฒ ํ๋ ์ฝ์ด ๊ธฐ์
- PWA์์ ๊ฐ์ฅ ์ค์ํ ์ญํ ์ ํ๋ค.
- ์ฝ์๋ก sw.js์ ๊ฐ์ด ๋ง์ด ์ฐ์
- ์๋ฒ์ ๋ฐฐํฌํ ๋ service worker๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด https๋ฅผ ์ฌ์ฉํด์ผํ๋ค. Netlify๋ Firebase Hosting์ ์ฌ์ฉํ๋ฉด ๊ฐ๋จํ https๋ก ๋ฐฐํฌํ ์ ์๋ค.
chrome://inspect/#service-workers -> ํ์ฌ ๋ธ๋ผ์ฐ์ ์์ ๋์๊ฐ๊ณ ์๋ service worker๋ฅผ ์ ๋ถ ๋ณผ ์ ์๋ ๊ฐ๋ฐ์๋๊ตฌ
์ฃผ์ํน์ง
- Caching
- Offline (๋ฆฌ์์ค๋ฅผ ์บ์ฑํจ์ผ๋ก์จ ๊ฐ๋ฅ)
- Native features (push ๋ฑ)
Service worker๋ ๋ธ๋ผ์ฐ์ ์ ์๋ฒ ์ฌ์ด์ ๋ณ๋์ js๊ฐ ๋์๊ฐ๋ ๊ฒ (๋ฏธ๋ค์จ์ด)
- ์ค์ ์ ์ผ๋ก๋ ๋ธ๋ผ์ฐ์ ๋ค์ ๋ณ๋์ ์ฐ๋ ๋ (๋ฉํฐ ์ฐ๋ ๋ ๊ฐ๋ )๊ฐ ๋์๊ฐ๊ณ ์๋ค. (scope๋ ๋ค๋ฅด๋ค)
- ๊ฐ์ ๋์์ ํ์์๋ ๊ธฐ์กด js์ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๊ฒ ๋์จ๋ค.
์ด์ ์๋ Service worker์ ๋น์ทํ ๊ฐ๋ ์ ์กด์ฌํ๋ค.
- AppCache
- ์คํ๋ผ์ธ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํ ์บ์ ์ ๊ณต, HTML ํ์ค
- ํ์ด์ง๊ฐ ์ฌ๋ฌ ๊ฐ ์ผ๋ ์ค๋์, ํ์ผ ๋ณํํ ๋ ๋ฌธ์
- Shared Workers
- js์ ๋ณ๋์ ์ฐ๋ ๋
- ํ์ด์ง์ ๋น์ข ์์ (ํ์ด์ง๊ฐ ์๋ก๊ณ ์นจ๋๋๋ผ๋, jsํ์ผ์ ์๋ก๊ณ ์นจ๋์ง ์์)
์ด๋ฐ ๊ฒ๋ค์ ๋ณด์ํ๊ธฐ ์ํด์ ๋ฑ์ฅํ ๊ฒ์ด service worker์ด๋ค.
service-worker.js(sw.js) ํ์ผ์ ์ค์ ํ๊ธฐ ์ด์ ์,index.html ํ์ผ์ body ํ๊ทธ ์์ ์๋น์ค์์ปค๋ฅผ ๋ฑ๋กํ๋ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('../service-worker.js')
.then(reg => {
console.log('[Service worker registered]', reg);
})
.catch(error => {
console.log(error);
});
});
}
</script>
// #1 - ์๋น์ค ์์ปค ์ค์น(์บ์ฑ ํ์ผ ์์ฑ)
self.addEventListener('install', function(event) {
// self : app.js์์์ window์ ๋์ผ - ๋ฐ๋ก window ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ
console.log('์๋น์ค ์์ปค ์ค์น ์๋ฃ');
event.waitUntil(
caches
.open(cacheName)
.then(function(cache) {
console.log('์บ์ ์์ฑ ์๋ฃ');
cache.addAll(cacheFilelist);
})
.catch(function(error) {
console.log(error);
}),
);
});
์คํ๋ผ์ธ์์๋ ๋์๊ฐ๊ฒ๋ ์บ์ ์์ ์ ํ ๊ฒ์ด๋ค.
// #2 - ์๋น์ค ์์ปค์ ๋คํธ์ํฌ ์์ฒญ ๊ฐ๋ก์ฑ๊ธฐ ์ฝ๋ ์์ฑ
self.addEventListener('fetch', function(event) {
event.respondWith(
caches
.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
})
.catch(function() {
console.log(error);
}),
);
});
- Vue create๋ก ๋ง๋ค ๋ PWA Support ์ต์ ์ผ๋ก ์์ฑ
- ์ด๋ฏธ ๋ง๋ค์ด์ง app์ ์ ์ฉํ๋ ค๋ฉด,
- public ํด๋ ๋ฐ์ index.html์ link tag๋ก manifest ์ฐ๊ฒฐ
- manifest.json ์์ฑ
- main.js์ service worker ๋ฑ๋ก