프로그래밍/PWA

PWA(Progressive Web App) on github pages

IJIJIJ 2020. 6. 30. 13:19

아주 간단한 PWA 만들기

PWA를 Github page에 올리고 스마트폰에 다운로드하기까

가족이 이틀씩 오전 근무와 야간 근무를 돌아가면서 하기 때문에 폰에서 쉽게 확인하기 위한 방법을 찾다가

PWA로 한 번 해보고자 정리합니다.

[소스코드]

- 아래의 저장소에서 확인하실 수 있습니다.

https://github.com/in3166/sunnyNight

[참고 사이트]

https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/?hl=ko

- 공부하기 위해서는 위 사이트를 차근차근 따라 해 보는 게 가장 좋을 것 같습니다.

본 게시글은 제가 구현했던 과정들만 나열하기 때문에 자세한 설명은 없습니다. (설명할 주제도 못되고.. ㅜ)

정말 대충 짠 코드들로 구성되므로 참고(?)만 하시고 참고 사이트를 확인해 주세요..

가능한 한 빨리 간단한 PWA를 만들고자 하시면 app.js와 index.html, inline.css 정도만 수정하시고 사용하시면 될 것 같습니다.

[파일 구성]

- images 폴더

└ icons 폴더

└ 아이콘.ico

- scripts 폴더

└ app.js

└ install.js

- styles 폴더

└ inline.css

-index.html

-manifest.json

-service-worker.js

1. manifest.json 만들기

- 사용하고자 하는 아이콘을 준비하시고 아래의 사이트에 들어갑니다.

https://app-manifest.firebaseapp.com/

"name": "SUNandNight", "short_name": "SN", "theme_color": "#e8f321", "background_color": "#888888", "display": "standalone", "start_url": "https://in3166.github.io/sunnyNight/" (추후 본인의 github 주소를 입력하세요.)

- 빈칸을 작성 후 자신이 원하는 아이콘을 올리고 GENERATE.ZIP 클릭합니다.

- 생성된 manifest.json와 이미지들을 작업 중인 프로젝트에 넣습니다.

2. 화면 만들기 (index.html, inline.css)

- 저는 표 형식으로 텍스트만 띄울 것이기 때문에 정말 간단하게 만들었습니다.

- 아래 서비스 워커를 위한 스크립트를 추가해 줍니다.

 

<index.html>

<body>
    <button id="butInstall" aria-label="Install" hidden>설치</button> 
    // hidden을 없애면 설치 버튼을 화면에 표시하여 버튼 클릭으로 설치할 수 있습니다. 
    // 하지만 버튼이 없어도 설치는 가능합니다.
    
    <div class="layer">
        <label id="la1" name="la1" class="la1">오늘</label><br><br>
        <strong> <label id="la2" name="la2" class="la2"></strong></label><br><br>
        <hr>
        <label id="la3" name="la3" class="la3">내일</label><br><br>
        <strong><label id="la4" name="la4" class="la4"></strong></label>
    </div>

    <div class="table1">
        <table>
            <tr>
                <th><label id="d0" name="d0" class="d0"></label></th>
                <th><label id="d1" name="d1" class="d1"></label></th>
                <th><label id="d2" name="d2" class="d2"></label></th>
                <th><label id="d3" name="d3" class="d3"></label></th>
                <th><label id="d4" name="d4" class="d4"></label></th>
                <th><label id="d5" name="d5" class="d5"></label></th>
                <th><label id="d6" name="d6" class="d6"></label></th>
            </tr>
            <tr>
                <td><strong><label id="l0" name="l0" class="l0"></label></strong></td>
                <td><strong><label id="l1" name="l1" class="l1"></label></strong></td>
                <td><strong><label id="l2" name="l2" class="l2"></label></strong></td>
                <td><strong><label id="l3" name="l3" class="l3"></label></strong></td>
                <td><strong><label id="l4" name="l4" class="l4"></label></strong></td>
                <td><strong><label id="l5" name="l5" class="l5"></label></strong></td>
                <td><strong><label id="l6" name="l6" class="l6"></label></strong></td>
            </tr>
        </table>
    </div>

    <script src="https://in3166.github.io/sunnyNight/scripts/app.js"></script>
    <!-- CODELAB: Add the install script here -->
    <script src="https://in3166.github.io/sunnyNight/scripts/install.js"></script>
    <script>
        // navigator 객체에 serviceWorker라는 프로퍼티가 있는지 확인하는 조건입니다. //만약 지원하지 않는 브라우저라면 serviceWorker라는 프로퍼티가 있을 경우에만 서비스워커를 등록 // CODELAB: Register service worker. if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('https://in3166.github.io/sunnyNight/service-worker.js') .then((reg) => { console.log('Service worker registered.', reg); }); }); }
    </script>
</body>

 


<inline.css>

.layer {
    padding: 20px;
    position: absolute;
    top: 30%;
    left: 35%;
    width: 200px;
    height: auto;
    justify-content: center;
    align-items: center;
    color: darkgoldenrod;
    border: solid darkgoldenrod 1px;
    margin: -50px 0 0 -50px;
    text-align: center;
}

body {
    background: rgb(48, 48, 48);
}

#la2,
#la4 {
    font-size: 35px;
}

#la1,
#la3 {
    font-size: 30px;
}

@media (max-width: 480px) {
    header h1 {
        font-size: 35px;
    }
    header p {
        font-size: 16px;
    }
    .pads {
        margin-bottom: 100px;
    }
}

 

2. 기능 구현 (app.js)

- 오늘 날짜와 특정 날짜를 비교해서 오늘과 내일의 근무를 출력, 오늘부터 7일간의 근무도 출력

window.addEventListener('load', () => {
    //serviceWorker.register();

// 오늘과 내일의 근무 형태를 출력할 엘리먼트
    var label2 = document.getElementById('la2');
    var label4 = document.getElementById('la4');
// 요일을 출력할 엘리먼트
    var l0 = document.getElementById('l0');
    var l1 = document.getElementById('l1');
    var l2 = document.getElementById('l2');
    var l3 = document.getElementById('l3');
    var l4 = document.getElementById('l4');
    var l5 = document.getElementById('l5');
    var l6 = document.getElementById('l6');
// 근무 형태를 출력할 엘리먼트
    var d0 = document.getElementById('d0');
    var d1 = document.getElementById('d1');
    var d2 = document.getElementById('d2');
    var d3 = document.getElementById('d3');
    var d4 = document.getElementById('d4');
    var d5 = document.getElementById('d5');
    var d6 = document.getElementById('d6');

// 특정 요일을 기준으로 오늘의 근무형태를 알아냅니다.
    let day = new Date(2020, 5, 13);
    let today = new Date();

    console.log("to: " + moment(today).format('YYYY MM DD HH:mm:ss'));
    console.log("day: " + moment(day).format('YYYY MM DD HH:mm:ss'));

    var cDay = 24 * 60 * 60 * 1000; // 시 * 분 * 초 * 밀리세컨
    var cMonth = cDay * 30; // 월 만듬
    var cYear = cMonth * 12; // 년 만듬

// 특정 날짜와 오늘의 일수 차이를 계산
    var dif1 = today - day;
    var dif = parseInt(dif1 / cDay)

// 나머지를 저장할 변수
    var namu = dif;
    var text, text2;
    console.log("일차이: " + parseInt(dif1 / cDay));

    namu = dif % 6;

// 텍스트 뿌려줌
    label2.innerText = swit(namu);
    label4.innerText = swit((namu + 1) % 6);
    l0.innerText = swit(namu);
    l1.innerText = swit((namu + 1) % 6);
    l2.innerText = swit((namu + 2) % 6);
    l3.innerText = swit((namu + 3) % 6);
    l4.innerText = swit((namu + 4) % 6);
    l5.innerText = swit((namu + 5) % 6);
    l6.innerText = swit((namu + 6) % 6);
    
    d0.innerText = getTodayLabel(today.getDay());
    d1.innerText = getTodayLabel(today.getDay() + 1);
    d2.innerText = getTodayLabel(today.getDay() + 2);
    d3.innerText = getTodayLabel(today.getDay() + 3);
    d4.innerText = getTodayLabel(today.getDay() + 4);
    d5.innerText = getTodayLabel(today.getDay() + 5);
    d6.innerText = getTodayLabel(today.getDay() + 6);
});

// 나머지에 따라 오늘의 근무 형태 출력하는 함수
function swit(namu) {
    switch (namu) {
        case 0:
        case 1:
            text = "오전";
            break;
        case 2:
        case 3:
            text = "야간";
            break;
        case 5:
        case 4:
            text = "휴일";
            break;
        default:
            text = "??";
            break;
    }
    return text;
}

//요일 구하기
function getTodayLabel(today) {

    var week = new Array('일', '월', '화', '수', '목', '금', '토');
    today = today % 7;
    var todayLabel = week[today];

    return todayLabel;
}

3. install.js

- install.js는 참고 사이트의 예제 파일을 긁어 왔습니다.

'use strict';

let deferredInstallPrompt = null;
const installButton = document.getElementById('butInstall');
installButton.addEventListener('click', installPWA);

// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

/**
 * Event handler for beforeinstallprompt event.
 *   Saves the event & shows install button.
 *
 * @param {Event} evt
 */
function saveBeforeInstallPromptEvent(evt) {
    // CODELAB: Add code to save event & show the install button.
    deferredInstallPrompt = evt;
    installButton.removeAttribute('hidden');
}


/**
 * Event handler for butInstall - Does the PWA installation.
 *
 * @param {Event} evt
 */
function installPWA(evt) {
    // CODELAB: Add code show install prompt & hide the install button.
    deferredInstallPrompt.prompt();
    // Hide the install button, it can't be called twice.
    evt.srcElement.setAttribute('hidden', true);
    // CODELAB: Log user response to prompt.
    deferredInstallPrompt.userChoice
        .then((choice) => {
            if (choice.outcome === 'accepted') {
                console.log('User accepted the A2HS prompt', choice);
            } else {
                console.log('User dismissed the A2HS prompt', choice);
            }
            deferredInstallPrompt = null;
        });
}

// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);
/**
 * Event handler for appinstalled event.
 *   Log the installation to analytics or save the event somehow.
 *
 * @param {Event} evt
 */
function logAppInstalled(evt) {
    // CODELAB: Add code to log the event
    console.log('Weather App was installed.', evt);
}

4. service-worker.js

- service worker 또한 예제 파일을 긁어와 필요 없는 부분만 삭제했습니다.

- 서비스 워커의 역할을 간단히 말하자면 오프라인 상태에서도 해당 어플을 작동하게 해줍니다.

'use strict';
// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v6';
const DATA_CACHE_NAME = 'data-cache-v5';

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
    '.',
    './index.html',
    './scripts/app.js',
    './scripts/install.js',
    './styles/inline.css'
];

self.addEventListener('install', (evt) => {
    console.log('[ServiceWorker] Install');
    // CODELAB: Precache static resources here.
    evt.waitUntil(
        caches.open(CACHE_NAME).then((cache) => {
            console.log('[ServiceWorker] Pre-caching offline page');
            return cache.addAll(FILES_TO_CACHE);
        })
    );
    self.skipWaiting();
});

self.addEventListener('activate', (evt) => {
    console.log('[ServiceWorker] Activate');
    // CODELAB: Remove previous cached data from disk.
    evt.waitUntil(
        caches.keys().then((keyList) => {
            return Promise.all(keyList.map((key) => {
                if (key !== CACHE_NAME) {
                    console.log('[ServiceWorker] Removing old cache', key);
                    return caches.delete(key);
                }
            }));
        })
    );

    self.clients.claim();
});

self.addEventListener('fetch', (evt) => {
    console.log('[ServiceWorker] Fetch', evt.request.url);

    evt.respondWith(
        caches.open(CACHE_NAME).then((cache) => {
            return cache.match(evt.request)
                .then((response) => {
                    return response || fetch(evt.request);
                });
        })
    );

    if (evt.request.mode !== 'navigate') {
        // Not a page navigation, bail.
        return;
    }

    evt.respondWith(
        fetch(evt.request)
        .catch(() => {
            return caches.open(CACHE_NAME)
                .then((cache) => {
                    return cache.match('offline.html');
                });
        })
    );
});

5. Github pages

https://github.com/

- 우선 Github에 로그인합니다.

- Repository 생성

New 버튼을 클릭하여 새로운 Repositorie를 만들어 줍니다.


Repository 이름을 입력하고 Public으로 설정 후 Create repository를 클릭합니다.


Quick setup 하단의 파란색 글씨, uploading an exsting file 클릭 후 본인이 작업한 폴더를 업로드합니다.


파일이 Repository에 모두 올라갔으면 Setting을 클릭합니다.


하단으로 조금 내리면 Github Pages가 보입니다. Source 아래의 None을 master branch로 바꿔줍니다.


상단에 본인의 주소가 뜨는 것을 확인할 수 있습니다. 클릭하시면 브라우저에 화면이 표시됩니다. 브라우저에서도 다운로드할 수 있습니다.

6. 스마트폰에 다운로드하기

- 위의 주소를 스마트폰에서 열어줍니다.

- 그럼 아래와 같이 화면이 뜨고 다운로드 버튼을 클릭하면 다운로드하실 수 있습니다.