Создаем прогрессивное веб-приложение на React и размещаем его в Netlify и PWA Store

    Доброго времени суток, друзья!

    Представляю Вашему вниманию перевод серии статей автора K G Prajwal, посвященных созданию Weather PWA на ReactJS, разворачиванию приложения в Netlify и его загрузке в PWA Store.

    React


    React — продукт Facebook. Это очень гибкая и эффективная JS-библиотека для создания интерактивных пользовательских интерфейсов. React формирует слой представления веб-приложения, который технически является фронтендом. Это позволяет создавать приложения с использованием таких полезных возможностей, как переиспользуемые компоненты, управление состоянием, частичный рендеринг DOM и т.д. React используется в основном для создания одностраничных приложений (Single Page Applications, SPA).



    Возможности


    Компоненты

    React позволяет использовать компоненты — независимые и переиспользуемые части кода. Другими словами, компоненты — это функции, работающие изолировано друг от друга, каждый компонент имеет собственное состояние, которым можно управлять. Это существенно облегчает создание больших приложений, поскольку отдельные блоки кода не зависят друг от друга и поломка одного из них не влияет на другие.

    Виртуальный DOM

    При внесении изменений в DOM сначала меняется виртуальный DOM, затем виртуальный и оригинальный DOM сравниваются между собой и изменения вносятся лишь в ту часть оригинального DOM, которая отличается от виртуального DOM. Это существенно повышает производительность приложения, поскольку изменения не приводят к обновлению страницы.

    Декларативность

    Проектирование любого представления в React не составляет труда, и эти представления позволяют управлять их состоянием. React позволяет обеспечить рендеринг отдельных компонентов, что облегчает поддержку кода и устранение неполадок.

    JSX

    JSX совмещаем в себе логику JavaScript и разметку HTML/XML. С его помощью создаются компоненты. Такие файлы имеют расширение .jsx.

    Производительность

    React является очень быстрым. Виртуальный DOM обеспечивает перерендеринг оригинального DOM только по необходимости. Кроме того, в React используется одностороннее связывание (однонаправленный поток данных) — шаблон проектирования Flux. Это делает компоненты неизменяемыми (иммутабельными), предоставляя больше гибкости и эффективности.

    Почему React?


    Легок в изучении

    В отличие от других фреймворков, таких как Angular или VueJS, React легок в изучении и использовании. Имея базовые знания HTML, CSS и JavaScript, вы можете начать работать с React.

    Простота

    React очень прост в использовании. В нашем распоряжении имеется несколько мощных пакетов для сборки проектов, таких как Webpack. С помощью нескольких простых JSX вы получаете компоненты, рендеринг которых осуществляется раздельно. React становится еще более мощным инструментом при совместном использовании с другими библиотеками, такими как Redux, Material-UI, Materialize, GraphQL и т.д.

    Любовь разработчиков

    React является самой популярной библиотекой среди разработчиков. Он является опенсорным и над ним работает много людей. Его репозиторий насчитывает 150 тыс. звезд, а количество скачиваний приближается к 4 млн.

    React Native

    React также используется в мобильной разработке. Для этого используется React Native. Это показывает гибкость React с точки зрения адаптации. С помощью React разработчик может создавать приложения для android, iOS и веба.

    Установка


    Перед установкой React, убедитесь, что у вас установлен NodeJS и NPM (пакетный менеджер).

    Для установки React открываем терминал и вводим следующую команду:

    npx create-react-app <название-вашего-приложения>

    После завершения установки переходим в директорию проекта:

    cd <название-вашего-приложения>

    Запускаем приложение:

    npm start



    Прим. пер.: при запуске приложения у меня возникла ошибка Error: Package exports for '...\node_modules\autoprefixer\node_modules\kleur' do not define a valid './colors' target. Лечение: открываем терминал в директории проекта и набираем npm i autoprefixer@9.8.0.

    Приложение


    Устанавливаем приложение:

    npx create-react-app weather-app

    Переходим в директорию проекта:

    cd weather-app

    Структура проекта


    После установки директория проекта выглядит так:



    Большая часть файлов нам не пригодится. Мы будем работать с index.html, App.js, index.css и index.js. Все, что мы хотим вывести на экран, содержится в App.js (вместе со стилями в index.css), который передается в index.js, в котором компоненты оборачивается в div с id=«root» и передаются в index.html, который и отображается на экране. Фух! Примерно так работает React: структура разбивается на компоненты, которые используются по необходимости и собираются в html-файле.

    Weather App


    Для создания приложения необходимо выполнить следующее:

    • Зарегистрироваться на OpenWeatherMap и получить ключ интерфейса (API Key)
    • Создать файл keys.js в папке src:

    module.exports = {
        API_KEY: "YOUR_API_KEY",
        BASE_URL: "https://api.openweathermap.org/data/2.5/",
    }
    

    Добавляем в App.js следующий код:

    JS:
    import React, { useState } from "react";
    import keys from "./keys";
    
    const api = {
        key: keys.API_KEY,
        base: keys.BASE_URL,
    };
    
    function App() {
        const dateBuild = (d) => {
            let date = String(new window.Date());
            date = date.slice(3, 15);
            return date;
        };
    
        const [query, setQuery] = useState("");
        const [weather, setWeather] = useState({});
        const search = (e) => {
        if (e.key === "Enter") {
                fetch(`${api.base}weather?q=${query}&units=metric&APPID=${api.key}`)
                .then((res) => res.json())
                .then((result) => {
                    setQuery("");
                    setWeather(result);
                    console.log(result);
                });
            }
        };
    
        return (
            <div
                className={
                typeof weather.main != "undefined"
                    ? weather.main.temp > 18
                    ? "App hot"
                    : "App cold"
                    : "App"
                }
            >
                <main>
                <div className="search-container">
                    <input
                    type="text"
                    placeholder="Search..."
                    className="search-bar"
                    onChange={(e) => setQuery(e.target.value)}
                    value={query}
                    onKeyPress={search}
                    />
                </div>
                {typeof weather.main != "undefined" ? (
                    <div>
                    <div className="location-container">
                        <div className="location">
                        {weather.name}, {weather.sys.country}
                        </div>
                        <div className="date"> {dateBuild(new Date())}</div>
                    </div>
                    <div className="weather-container">
                        <div className="temperature">
                        {Math.round(weather.main.temp)}°C
                        </div>
                        <div className="weather">{weather.weather[0].main}</div>
                    </div>
                    </div>
                ) : (
                    ""
                )}
                </main>
            </div>
        );
    }
    
    export default App;
    


    Далее добавляем этот код в index.css:

    CSS:
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    
    body {
        font-family: "Ubuntu", sans-serif;
    }
    
    .App {
        background-image: linear-gradient(
            to right,
            rgba(255, 0, 0, 0.52),
            rgba(0, 195, 255, 0.73)
        ),
        url("img/app.jpg");
        background-repeat: no-repeat;
        transition: 0.2s ease;
    }
    
    .App.hot {
        background-image: linear-gradient(
            to bottom,
            rgba(255, 16, 16, 0.52),
            rgba(0, 0, 0, 0.73)
        ),
        url("img/hot.jpg");
        background-repeat: no-repeat;
        background-size: cover;
        transition: 0.2s ease;
    }
    
    main {
        min-height: 100vh;
        padding: 25px;
    }
    
    .App.cold {
        background-image: linear-gradient(
            to bottom,
            rgba(0, 255, 213, 0.52),
            rgba(0, 0, 0, 0.73)
        ),
        url("img/cold.jpg");
        background-repeat: no-repeat;
        background-size: cover;
        transition: 0.2s ease;
    }
    
    .search-container {
        width: 100%;
        margin: 0 0 75px;
    }
    
    .search-bar {
        color: black;
        font-size: 15px;
        display: block;
        width: 100%;
        padding: 15px;
        border: none;
        outline: none;
        appearance: none;
        border-radius: 15px 15px 15px 15px;
        box-shadow: 0px 5px rgba(58, 53, 53, 0.73);
        background: rgba(255, 255, 255, 0.52);
        transition: 0.4s ease;
    }
    
    .search-container .search-bar:focus {
        background-color: white;
    }
    
    .location-container {
        color: white;
        font-size: 30px;
        text-align: center;
        text-shadow: 3px 3px rgba(58, 53, 53, 0.73);
    }
    
    .location-container .date {
        color: white;
        font-size: 20px;
        text-align: center;
        text-shadow: 3px 3px rgba(58, 53, 53, 0.73);
    }
    
    .weather-container {
        text-align: center;
    }
    
    .weather-container .temperature {
        color: white;
        position: relative;
        display: inline-block;
        margin: 30px auto;
        padding: 15px 25px;
        font-size: 100px;
        font-weight: 700;
        background-color: rgb(255, 255, 255, 0.1);
        border-radius: 16px;
        box-shadow: 3px 3px rgba(58, 53, 53, 0.73);
        text-shadow: 3px 3px rgba(58, 53, 53, 0.73);
    }
    
    .weather-container .weather {
        color: white;
        font-size: 50px;
        font-weight: 700;
        text-shadow: 3px 3px rgba(58, 53, 53, 0.73);
    }
    


    Объяснение


    Кода довольно много, но сейчас я все объясню.

    HTML

    HTML-элементы в App.js: контейнер для поиска, местонахождение, температура и погодные условия.

    CSS

    Стилизуем названные элементы через их классы.

    Функция получения даты

    В контейнере для даты мы вызываем функцию dateBuild. Эта функция возвращает дату в строковом формате. Мы извлекаем из этой строки месяц, день и год с помощью метода slice().

    Fetch и хуки

    Мы используем useState для отслеживания отображения элементов на экране. Один хук для значения поисковой строки и еще один для отображаемого контента.

    В контейнере для поиска мы вызываем функцию search, когда пользователь нажимает Enter. В этой функции мы обращаемся к URL, используя наши заготовки (credentials типа api.key) и строку запроса (query) для получения данных от сервера, после этого осуществляется рендеринг. Когда речь идет об управлении состоянием, значение useState трудно переоценить.

    Динамическое изменение фона

    Это простая демонстрация возможностей JSX. Ранее я упоминал, что JSX совмещает в себе возможности JS и HTML. Динамическое изменение фона делает приложение более отзывчивым. В классе, отвечающем за рендеринг всего приложения, мы добавили проверку получаемой температуры на соответствие определенному числу. В зависимости от результатов проверки мы присваиваем контейнеру тот или иной класс. При желании можно добавить еще несколько условий для обработки всех возможных сценариев.

    Результат


    Прим. пер.: автор забыл добавить, что в src у нас должна быть папка с изображениями. В противном случае, приложение не запустится.

    Открываем терминал в директории проекта и вводим npm start:







    Введение в PWA


    Прогрессивные веб приложения — это сайты, обладающие возможностями нативных приложений. Вместо того, чтобы создавать отдельное мобильное приложение, вы можете создать прогрессивное веб приложение и получить 2 в 1. Думайте о PWA как о комбинации веб и мобильного приложений.

    Преимущества PWA:

    • они очень быстрые
    • легче посетить сайт нежели устанавливать приложение
    • нативные приложения лучше интегрируются и наcтраиваются под операционнyю систему, что обеспечивает повышение производительности
    • обладают возможностью работы в режиме оффлайн
    • используют меньший объем данных

    При создании приложения мы работали с директорией src. Теперь сосредоточимся на public. Оставляем в ней только index.html.

    Начнем с создания сервис-воркера. Сервис-воркер — это скрипт, выполняемый в фоновом процессе. Он обеспечивает работу приложения в оффлайн-режиме. Создаем новый сервис-воркер под контейнером с идентификатором root:

    if ('serviceWorker' in navigator) {
        window.addEventListener('load', () => {
            navigator.serviceWorker.register('./serviceworker.js')
            .then(reg => console.log(reg.scope))
            .catch(err => console.log(err))
        })
    }
    

    Создаем файл serviceworker.js в директории public:

    const CACHE = "dev";
    const URL = ["index.html", "offline.html"];
    const self = this;
    

    Как я упоминал ранее, PWA являются очень производительными. Причина этого состоит в использовании кэша. Мы создаем переменную CACHE с заданным именем. Затем в переменной URL мы перечисляем файлы, подлежащие кэшированию.

    Существует три события, которые необходимо обработать в этом файле:

    • Установка сервис-воркера

    Помещаем URL в хранилище кэша:

    self.addEventListener("install", (e) => {
        e.waitUntil(
            caches.open(CACHE).then((cache) => {
                return cache.addAll(URL);
            })
        )
    });
    

    • Обработка запросов

    Следим за запросами и возвращаем ответ, если запрос завершился успешно, иначе возвращаем offline.html:

    self.addEventListener("fetch", (e) => {
        e.respondWith(
            caches.match(e.request).then(() => {
                return fetch(e.request).catch(() => caches.match("offline.html"));
            })
        );
    });
    

    • Активация сервис-воркера

    Удаляем старый кэш и добавляем новый:

    self.addEventListener("activate", (e) => {
        const newCache = [];
        newCache.push(CACHE);
        e.waitUntil(
            caches.keys().then((cacheNames) =>
                Promise.all(
                    cacheNames.map((cacheName) => {
                        if (!newCache.includes(cacheName)) {
                            return caches.delete(cacheName);
                        }
                    })
                )
            )
        );
    });
    

    Для того, чтобы убедиться в работоспособности сервис-воркера, запускаем приложение с помощью npm start, открываем вкладку «Application» инструментов разработчика Chrome, переходим к разделу «Service Workers», ставим галочку в поле «Update on reload», затем переходим к разделу «Clear storage» и нажимаем кнопку «Clear site data».





    Пришло время поработать над offline.html. Я собираюсь использовать минимальные стили для этой страницы, однако вы можете себе ни в чем не отказывать:

    HTML:
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Weather App</title>
            <link href="https://fonts.googleapis.com/css2?family=Balsamiq+Sans:wght@700&display=swap" rel="stylesheet">
            <style type="text/css">
                html {
                    height: 100%;
                }
                body {
                    height: 100%;
                    background-image: linear-gradient( to right, rgba(255, 100, 0, 0.52), rgba(0, 195, 255, 0.73) );
                    display: flex;
                    font-size: 30px;
                    color: white;
                    font-family: 'Balsamiq Sans', cursive;
                    align-items: center;
                    text-align: center;
                    justify-content: center;
                }
                .lds-ripple {
                    display: inline-block;
                    position: relative;
                    width: 80px;
                    height: 80px;
                }
                .lds-ripple div {
                    position: absolute;
                    border: 4px solid #fff;
                    opacity: 1;
                    margin: -120px 170px;
                    border-radius: 50%;
                    animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
                }
                .lds-ripple div:nth-child(2) {
                    animation-delay: -0.5s;
                }
                    @keyframes lds-ripple {
                    0% {
                        top: 36px;
                        left: 36px;
                        width: 0;
                        height: 0;
                        opacity: 1;
                    }
                    100% {
                        top: 0px;
                        left: 0px;
                        width: 72px;
                        height: 72px;
                        opacity: 0;
                    }
                }
            </style>
        </head>
        <body>
            <div class="lds-ripple"><div></div><div></div></div>
            <span>You are currently offline. Please go online to check the weather.</span>
        </body>
    </html>
    

    Перезагрузите страницу и загляните в хранилище кэша. Если вы видите что-то похожее на изображение внизу, примите поздравления, вы только что закэшировали свое приложение, обеспечив ему высокую производительность и возможность работы в оффлайне.



    В целях дальнейшего тестирования перейдите во вкладку «Network» и выберите Offline.



    Перезагрузите страницу, чтобы увидеть offline.html.



    Manifest


    Файл манифеста — это простой JSON, содержащий информацию о приложении. Создайте файл manifest.json:

    {
        "name": "Weather App PWA",
        "short_name": "Weather App",
        "icons": [
            {
            "src": "./logo.png",
            "type": "image/png",
            "sizes": "1024x1024"
            }
        ],
        "start_url": ".",
        "display": "standalone",
        "theme_color": "#000",
        "background_color": "#fff"
    }
    

    Прим. пер.: файл logo.png находится здесь.

    Lighthouse


    Lighthouse — это инструмент для проверки качества PWA. Откройте соответствующую вкладку в инструментах разработчика Chrome (или установите расширение) и нажмите кнопку «Generate report». Проверим наше приложение:



    Мы получили неплохой результат. Однако один из тестов не проходит, а именно: тест производительности.



    Разворачиваем приложение


    Для решения проблемы передачи данных по HTTPS, нам необходимо развернуть приложение. Откройте терминал и наберите npm run build для сборки проекта. Создайте аккаунт на Netlify. Ваша панель управления должна выглядеть примерно так:



    Перемещаем папку build на dashboard — это развернет приложение на Netlify. Нажмите на сгенерированный URL для того, чтобы увидеть приложение в сети. Отключите Интернет для того, чтобы убедиться, что все работает корректно.

    Результат


    Снова протестируем приложение с помощью Lighthouse.



    На этот раз мы получили отличные результаты. Если вы все сделали правильно, то должны увидеть значок "+" рядом со значком добавления в избранное в поисковой панели Chrome.



    Этот значок-кнопка позволяет устанавливать приложение на девайс. Нажмите на него и установите приложение.



    Отключите соединение:



    Круто! Приложение теперь является частью системы, вы также можете установить его на смартфон.

    Самая интересная часть — публикация приложения в PWA хранилище. Зайдите на PWA Store и зарегистрируйтесь. После регистрации, зайдите на страницу профиля и нажмите на иконку меню справа, затем кликните на "+ Add PWA". Заполните информационные поля, такие как название, описание, скриншоты приложения и т.д. Наконец, запустите проверку Lighthouse. После этого можете отправить приложение на ревью.

    После ревью приложение будет опубликовано в хранилище. Спустя некоторое время оно будет доступно в разделе «Discover».

    Длинная статья получилась, но, согласитесь, довольно информативная. Я надеюсь, вы поняли что такое прогрессивное веб приложение, как его создать, протестировать с помощью Lighthouse и развернуть на стороннем сервисе.

    Код проекта на GitHub.

    Благодарю за потраченное время. Надеюсь оно было потрачено не зря.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 2

      +2
      В отличие от других фреймворков, таких как Angular или VueJS, React легок в изучении и использовании.

      Забавно такое читать. Насколько я знаю, как раз Vue держит пальму первенства по простоте использования, а отнюдь не React.

        +1

        Мне кажется, это очень зависит от бекграунда. И тут все очень индивидуально.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое