Максим Сальников
@webmaxru

Нативные веб-приложения:
готовность номер один!

Что делает приложение нативным,

и что из этого доступно в вебе

Максим Сальников

  • Организатор Mobile-/Web-/PWA-митапов и конференций в Норвегии

  • Спикер, тренер, автор публикаций о современном вебе

  • Google Developer Expert по веб-технологиям

Ответственный за успех Azure-разработчиков в Microsoft

Веб как платформа для приложений

  • Подразумевает постоянное подключение к сети

  • Исполняется в контексте браузера, а не непосредственно ОС

  • Автообновляемые браузеры

  • Оптимизированные движки

  • Отличные инструменты

  • Огромное сообщество

🎂

Секция PWA в Web Almanac 2021

Устанавливаемые вебсайты

~1 500 000 вебсайтов могут быть установлены на мобильные устройства как приложения.

Охват

"Нативность"

Нативные

Веб

Веб-приложения доступны всем, везде, на любом устройстве, с единой кодовой базой

Нативность = Количество и глубина интеграций с платформой

Нативные или веб?

Что мы хотим от платформы?

  • Установка и запуск

  • Внешний вид

  • Исходящие интеграции

  • Входящие интеграции

  • Уведомления

  • Фоновые задачи

  • Аппаратный доступ

edge://flags

Установка и запуск

  • Установка

  • Удаление

  • Запуск при старте

  • Контекстное меню

  • Режим запуска

Установка и запуск

  • Установка

  • Удаление

  • Запуск при старте

  • Контекстное меню

  • Режим запуска

Web App Manifest

Сервис-воркер с событием "fetch"

{

{
  "name": "BPM Techno",
  "short_name": "BPM Techno Counter",
  "start_url": "?utm_source=homescreen",
  "display": "standalone",
  "background_color": "#fff",
  "description": "A free online BPM counter",
  "icons": [{
    "src": "images/touch/48x48.png",
    "sizes": "48x48",
    "type": "image/png"
  }]
}

site.webmanifest

Установка и запуск

  • Установка

  • Удаление

  • Запуск при старте

  • Контекстное меню

  • Режим запуска

"shortcuts": [
  {
    "name": "Upload MP3 File",
    "short_name": "Upload MP3r",
    "description": "Count BPM of the uploaded file",
    "url": "/upload-mp3?utm_source=homescreen",
    "icons": [{ "src": "/icon-mp3.png", "sizes": "192x192" }]
  }
]

site.webmanifest

Установка и запуск

  • Установка

  • Удаление

  • Запуск при старте

  • Контекстное меню

  • Режим запуска

"launch_handler": {
  "route_to": "new-client" | "existing-client" | "auto",
  "navigate_existing_client": "always" | "never",
}

site.webmanifest

launchQueue.setConsumer(launchParams => {
  const url = launchParams.targetURL;
});

main.js

Внешний вид

  • Оконный режим

  • Заголовок окна

  • Строка статуса

  • Режим вкладок

Внешний вид

  • Оконный режим

  • Заголовок окна

  • Строка статуса

  • Режим вкладок

{
  "display": "standalone",
  "display_override": ["window-control-overlay", "minimal-ui"],
}

site.webmanifest

Внешний вид

  • Оконный режим

  • Заголовок окна

  • Строка статуса

  • Режим вкладок

titlebar-area-x
titlebar-area-y
titlebar-area-width
titlebar-area-height

CSS-переменные

navigator.windowControlsOverlay.getBoundingClientRect()
navigator.windowControlsOverlay.visible

JavaScript API

Внешний вид

  • Оконный режим

  • Заголовок окна

  • Строка статуса

  • Режим вкладок

{
  "display": "tabbed"
}

site.webmanifest

Исходящие интеграции

  • Web Share API

  • Contact Picker API

  • File Access API

Исходящие интеграции

  • Web Share API

  • Contact Picker API

  • File Access API

if (navigator.share) {
  navigator.share({
    title: 'BPM Techno',
    text: 'Check out BPMTech.no',
    url: 'https://bpmtech.no',
  })
    .then(() => console.log('Successful share'))
    .catch((err) => console.error(err));
}

main.js

Исходящие интеграции

  • Web Share API

  • Contact Picker API

  • File Access API

const props = ['name', 'email', 'tel'];
const opts = {multiple: true};

try {
  const contacts =
        await navigator.contacts.select(props, opts);
  handleResults(contacts);
} catch (err) {
  console.error(err));
}

main.js

Исходящие интеграции

  • Web Share API

  • Contact Picker API

  • File Access API

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
});

main.js

const saveFileHandle = await window.showSaveFilePicker();
const directoryHandle = await window.showDirectoryPicker();

Входящие интеграции

  • Protocol handlers

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

Входящие интеграции

  • Protocol handlers

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

"protocol_handlers": [
  {
    "protocol": "web+bpm",
    "url": "index.html?bpm=%s"
  }
]

site.webmanifest

web+bpm://bpm=120

Входящие интеграции

  • Protocol handlers

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

"capture_links":
	"none" | "new-client" | "existing-client-navigate"

site.webmanifest

Входящие интеграции

  • Protocol handlers

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

"url_handlers": [
  {
    "origin": "https://bpmtech.no"
  },
  {
    "origin": "https://partnerapp.com"
  }
]

site.webmanifest

"web_apps": [
  {
    "manifest": "https://partnerapp.com/manifest.json",
    "details": {
      "paths": [
        "/public/data/*"
      ]
    }
  }
]

.well-known/web-app-origin-association

Входящие интеграции

  • Protocol handlers

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

"file_handlers": [
  {
    "action": "/open-mp3",
    "accept": {
      "text/csv": [".mp3"]
    },
    "icons": [
      {
        "src": "./images/mp3-file.png",
        "sizes": "144x144"
      }
    ]
  }
]

site.webmanifest

window.launchQueue.setConsumer(launchParams => {
  const fileHandle = launchParams.files[0];
});

main.js

Входящие интеграции

  • Protocol handlers'

  • Declarative Link Capturing

  • URL handlers

  • File type handlers

  • Share target

"share_target": {
  "action": "/share",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url"
  }
}

site.webmanifest

window.addEventListener('DOMContentLoaded', () => {
  const parsedUrl = new URL(window.location);
  const url = parsedUrl.searchParams.get('url');
});

main.js

Уведомления

  • Web Push API

  • Badging

  • Notification Triggers

Уведомления

  • Web Push API

  • Badging

  • Notification Triggers

navigator.setAppBadge(42).catch((err) => {
  console.error(err)
});

navigator.clearAppBadge().catch((err) => {
  console.error(err)
});

main.js

Уведомления

  • Web Push API

  • Badging

  • Notification Triggers

const registration =
      await navigator.serviceWorker.getRegistration();
registration.showNotification(title, {
  tag: tag,
  body: 'This notification was scheduled 30 seconds ago',
  showTrigger: new TimestampTrigger(timestamp + 30 * 1000),
});

main.js

Фоновые задачи

  • Background Sync

  • Periodic BG Sync

  • Background Fetch

  • Payment Handler

Аппаратный доступ

  • Audio & Video Capture
  • Geolocation
  • Web Bluetooth
  • Web MIDI API
  • Magnetometer API
  • Web NFC API
  • Device Memory API
  • Network Information API
  • Battery Status API
  • Ambient Light Sensor
  • Proximity Sensor
  • WebHID
  • Serial API
  • Web USB
  • User Idle Detection

Где отслеживать?

🐡

Где отслеживать?

Нативный веб: готовы ли мы?

  • Установка и запуск

  • Внешний вид

  • Исходящие интеграции

  • Входящие интеграции

  • Уведомления

  • Фоновые задачи

  • Аппаратный доступ

  • Русскоязычное сообщество PWA

  • Ресурсы на русском языке

  • Примеры в продакшн

Спасибо!

@webmaxru

Максим Сальников

Made with Slides.com