Progressive Web Apps с Hyperapp

Hyperapp — это минималистичный JavaScript-фреймворк для создания реактивных пользовательских интерфейсов. Его архитектура строится вокруг трёх основных концепций: state, actions и view.

  • State — это глобальный объект, содержащий данные приложения. Любые изменения интерфейса происходят через обновление state, что обеспечивает предсказуемость поведения.
  • Actions — функции, изменяющие state. Каждое действие может быть синхронным или асинхронным, возвращать новые данные или выполнять побочные эффекты.
  • View — чистая функция, описывающая DOM на основе текущего состояния. Hyperapp использует виртуальный DOM для минимизации реальных изменений в DOM и повышения производительности.

Пример базовой структуры:

import { h, app } from "hyperapp";

const state = {
  count: 0
};

const actions = {
  increment: () => state => ({ count: state.count + 1 }),
  decrement: () => state => ({ count: state.count - 1 })
};

const view = (state, actions) => (
  h("div", {}, [
    h("h1", {}, state.count),
    h("button", { onclick: actions.increment }, "Увеличить"),
    h("button", { onclick: actions.decrement }, "Уменьшить")
  ])
);

app({ init: state, view, actions, node: document.getElementById("app") });

Интеграция Hyperapp с Progressive Web Apps

Progressive Web Apps (PWA) — это веб-приложения с функциональностью, схожей с нативными мобильными приложениями, включая offline-доступ, push-уведомления и установку на рабочий стол. Hyperapp легко интегрируется с PWA благодаря своей лёгкости и модульной архитектуре.

Service Worker

Service Worker — ключевой элемент PWA. Он перехватывает сетевые запросы и обеспечивает кеширование ресурсов для офлайн-режима. В Hyperapp его можно подключить следующим образом:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then(reg => console.log('Service Worker зарегистрирован:', reg.scope))
      .catch(err => console.error('Ошибка регистрации Service Worker:', err));
  });
}

В файле service-worker.js можно реализовать кеширование статических ресурсов:

const CACHE_NAME = 'hyperapp-pwa-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/main.js',
  '/styles.css'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => response || fetch(event.request))
  );
});

Управление состоянием в PWA

Для PWA важно сохранять состояние приложения между сессиями. В Hyperapp это можно реализовать через localStorage или IndexedDB:

const loadState = () => JSON.parse(localStorage.getItem('appState')) || { count: 0 };

const saveState = state => {
  localStorage.setItem('appState', JSON.stringify(state));
};

const state = loadState();

const actions = {
  increment: () => state => {
    const newState = { count: state.count + 1 };
    saveState(newState);
    return newState;
  }
};

Такой подход обеспечивает постоянство данных, даже если пользователь закрывает браузер или теряет интернет-соединение.


Асинхронные действия и работа с API

PWA часто требуют взаимодействия с внешними API для загрузки данных. В Hyperapp асинхронные действия реализуются через возвращение функции с dispatch:

const actions = {
  fetchData: () => async (state, actions) => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return { ...state, data };
  }
};

Можно комбинировать офлайн-режим и API-запросы, сначала отдавая кешированные данные, а затем обновляя их из сети:

const actions = {
  fetchData: () => async (state) => {
    const cached = JSON.parse(localStorage.getItem('data'));
    if (cached) return { ...state, data: cached };

    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    localStorage.setItem('data', JSON.stringify(data));
    return { ...state, data };
  }
};

Поддержка установки и push-уведомлений

Для добавления возможности установки PWA на устройство необходимо обработать событие beforeinstallprompt:

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  document.getElementById('install-btn').style.display = 'block';
});

document.getElementById('install-btn').addEventListener('click', async () => {
  deferredPrompt.prompt();
  const { outcome } = await deferredPrompt.userChoice;
  console.log('Установка приложения:', outcome);
  deferredPrompt = null;
});

Push-уведомления на клиенте можно реализовать через Push API и Service Worker:

navigator.serviceWorker.ready.then(registration => {
  registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: '<ключ>' })
    .then(subscription => console.log('Подписка на пуш уведомления:', subscription));
});

Оптимизация Hyperapp для PWA

  1. Минификация и бандлинг: Hyperapp имеет небольшой размер, но в PWA рекомендуется использовать сборщики вроде Vite или Webpack для объединения и минимизации скриптов.
  2. Lazy Loading компонентов: Разделение кода на динамически загружаемые модули уменьшает первоначальный вес приложения.
  3. Кеширование ресурсов: Service Worker должен обрабатывать как статические файлы, так и динамически загружаемые данные.
  4. Рендер на клиенте: Hyperapp рендерит интерфейс быстро, но критично использовать виртуальный DOM только для компонентов с динамическим обновлением, чтобы минимизировать работу браузера.

Организация проекта Hyperapp PWA

Структура проекта может выглядеть следующим образом:

/public
  index.html
  service-worker.js
/src
  app.js
  state.js
  actions.js
  view.js
  components/
    Header.js
    Footer.js
    Counter.js
/package.json
  • state.js — глобальный state приложения.
  • actions.js — все действия, включая асинхронные и побочные эффекты.
  • view.js — корневой компонент интерфейса.
  • components/ — переиспользуемые UI-компоненты.

Hyperapp позволяет легко масштабировать проект, добавлять модули и интегрировать PWA-функции без излишней сложности.