Технопарк, весна, 2019 г.
<!-- резолвит DNS заранее --><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="//api.myawesomegame.io"><!-- dns-prefetch на стероидах, также инициирует коннект --><link rel="preconnect" href="//api.myawesomegame.io"><!-- загружает ресурс с низким приоритетом и кладет в кеш --><link rel="prefetch" href="https://myawesomegame.io/img/sprite.jpg"><!-- загружает ресурс с высоким приоритетом и кладет в кеш --><link rel="subresource" href="https://myawesomegame.io/img/sprite.jpg"><!-- загружает страницу со всем содержимым в фоне, строит DOM --><link rel="prerender" href="https://myawesomegame.io/about.html">
const img = new Image();img.src = 'https://myawesomegame.io/img/sprite.jpg';
window.performance.timing
// Покажет всю сетевую активностьwindow.performance.getEntries()// Отобразит все загружаемые ресурсыperformance.getEntriesByType('resource')
document.addEventListener("DOMContentLoaded", () => {// DOM дерево построено, но загружены не все ресурсы});
document.addEventListener("load", () => {// Все ресурсы загружены});
Плюсы:// В запросеAccept-Encoding: gzip, deflate, br// В ответеContent-Encoding: gzip
Необходимо включать отдачу сжатой статики на сервере# Заранее сжимаем статику./zopfli -c app.js > app.js.gz
# пример для nginxgzip_static on;
# пример для nginxbrotli on;
| WebP (4,9 KB) | GIF (85,2 KB) |
|
|
Компилируем в jssyntax = "proto3";message User {string name = 1;int32 age = 2;}
Можно использовать webpack-loader 😏npm install -g protobufjspbjs -t static-module -w commonjs -o user.js user.proto
Кэширование — повторное использование ресурсов для работы приложения. Использование кэшей позволяет уменьшить задержку и расходование сетевого трафика и тем самым уменьшить время, необходимое для отображения ресурса. HTTP-кэширование — кэширование ответов на HTTP-запросы
// Запрос обыкновенныйGET /script.js HTTP/1.1Host: example.comAccept: */*
// Ответ на запросHTTP/1.1 200 OKContent-Type: application/javascript; charset=UTF-8Cache-Control: public, max-age=86400Last-Modified: Sat, 25 Mar 2017 12:00:00 GMTETag: W/"7349b-15b075b6d60"
Cache-ControlВозможные значения
no-cacheno-storemust-revalidateprivate/publicmax-age=31536000// Ответ на запросHTTP/1.1 200 OK...Last-Modified: Sat, 25 Mar 2017 12:00:00 GMT...
GET /script.js HTTP/1.1Host: example.comIf-Modified-Since: Sat, 25 Mar 2017 12:00:00 GMT
HTTP/1.1 304 Not Modified // Контент не изменилсяHTTP/1.1 200 OK // Новый контент
// Ответ на запросHTTP/1.1 200 OK...ETag: W/"7349b-15b075b6d60"...
GET /script.js HTTP/1.1Host: example.comIf-None-Match: W/"7349b-15b075b6d60"
HTTP/1.1 304 Not Modified // Контент не изменилсяHTTP/1.1 200 OK // Новый контент
GET /script.js?_=1492172324776GET /bundle.026f8e459c8f89ef75fa7a78265a0025.jsВсе данные, которые используются web-приложением, существуют только пока открыта вкладка браузера. Однако, существуют способы сохранить какие-то данные в браузере и воспользоваться ими потом:
Web Storage API — механизм для сохранения key/value значений с возможностью программного управления данными. Предоставляет два host объекта в браузере пользователя с возможностью персистентного сохранения данных (до 10 MB на origin)
window.sessionStorage — сохраняет данные пока открыт браузерwindow.localStorage — сохраняет данные навсегда, пока пользователь вручную не очистит хранилище данных в настройках браузераwindow.localStorage// элементами Storage являются строкиlocalStorage[key]; /* String */localStorage[key] = value; /* String */// работа с объектами Storage синхроннаяlocalStorage.lengthlocalStorage.key(i) /* String */localStorage.getItem(key) /* String */localStorage.setItem(key, value) // может сгенерировать exception,// если нет местаlocalStorage.removeItem(key)localStorage.clear()
Можно написать обёртку, которая позволит сохранять в Storage простые объекты:
function setJSON(key, value) {localStorage[key] = JSON.stringify(value);}function getJSON(key) {const value = localStorage[key];return value ? JSON.parse(value) : null;}
storageСобытие storage происходит при любых изменениях в Storage в других вкладках с того же origin. То есть это событие позволяет общаться между вкладками
// обработчик добавляется на объект windowwindow.addEventListener('storage', function (e) {/* e.key, e.newValue */...});
WebSQL — полноценная SQL база данных, которая позволяет персистентно хранить данные в браузере пользователя и работать с ними посредством SQL-запросов. Максимальный размер сохраняемых данных — 5 MB. Поддержка на caniuse.
// создаём объект базы данных (доступно и в воркерах!)const db = openDatabase('forum', 'v1.0.0', 'Forum', 100000);// создаём транзакциюdb.transaction(function(tx) {tx.executeSql('SELECT COUNT(*) FROM `forum`',[],function (result) { console.log(result) },function (tx, error) { /* some error logic */ });});
IndexedDB — низкоуровневое API для клиентского хранилища большого объема структурированных данных, включая файлы/blobs. Эти API используют индексы для обеспечения высоко-производительного поиска данных. Максимальный размер сохраняемых данных — 50 MB!!! Поддержка на caniuse.
// открываем базу данных Forum (доступно и в воркерах!)const request = window.indexedDB.open('Forum', 3); // 3 - версия бд// обработчик успешного открытия базы данныхrequest.onsuccess = function(event) {const db = event.target.result;const store = db.createObjectStore('users', { keyPath: 'userId' });store.createIndex('age', 'age', { unique: false });store.createIndex('email', 'email', { unique: true });store.add({ age: 21, email: 'a.ostapenko@corp.mail.ru' });};
С помощью FileSystem API и File API веб приложение может создавать, читать, просматривать и записывать файлы находящиеся в области пользовательской «песочницы». Крутой туториал. Поддержка на caniuse.
Service Workers — продвинутая технология, которая позволяет получить полный контроль над жизненным циклом приложения. Сервис воркер — это воркер, который:
- работает в выделенном контексте и отдельном потоке
- имеет доступ к Cache Storage (расширенное хранилище данных)
- имеет возможность перехватывать HTTP-запросы, отправляемые страницей
- а так же может работать даже если само web-приложение или даже браузер не запущены
Подробнее — по ссылке на MDN
Работает в специальном скоупе ServiceWorkerGlobalScope, который не имеет доступа к обычному скоупу с window
Имеет несколько событий, на которые можно навешивать обработчики:
this.addEventListener('install', listener); // SW зарегистрировалиthis.addEventListener('activate', listener); // SW запустилиthis.addEventListener('fetch', listener); // SW перехватил запросthis.addEventListener('message', listener); // SW получил сообщениеthis.addEventListener('push', listener); // SW получил push
navigator.serviceWorker.register('/sw.js', { scope: '/' }).then(function(registration) {// Registration was successfulconsole.log('SW registration OK:', registration);}).catch(function(err) {// registration failed :(console.log('SW registration FAIL:', err);});});
this.addEventListener('install', function (event) {console.log('Service worker установлен')event.waitUntil(// находим Cache-объект с нашим именемcaches.open('MY_CACHE').then(function (cache) {// загружаем в наш cache необходимые файлыreturn cache.addAll(['/index.html']);}););});
Протокол WebSocket — протокол полнодуплексной связи (может передавать и принимать одновременно) поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени. С помощью его API вы можете отправить сообщение на сервер и получить ответ без выполнения отдельного HTTP-запроса, причем этот процесс будет событийно-управляемым
Были созданы, чтобы обойти ограничение HTTP на формат запрос/ответ и дать возможность отправлять сообщения с сервера на клиент
Подробнее — по ссылке на learn.javascript.ru
Именно поэтому WebSocket'ы очень удобно использовать для написания:
WebSocketconst ws = new WebSocket('ws://example.com/ws');// если страница загружена по https://const ws = new WebSocket('wss://example.com/ws');// События WebSocketws.addEventListener('open', listener); // соединение установленоws.addEventListener('message', listener); // пришло новое сообщениеws.addEventListener('error', listener); // ошибкаws.addEventListener('close', listener); // сокет закрылся
После создания объекта WebSocket необходимо дождаться, пока соединение не откроется и не установится:
ws.onopen = function() {console.log('Соединение установлено, можно отправлять сообщения!');// Отправка текстаws.send('Hello!');ws.send(JSON.stringify({ x: 100, y: 150 }));// Отправка бинарных данных (например файлы из формы)ws.send(form.elements[0].file);};
error и closews.onerror = function(error) {// произошла ошибка в отправке/приёме данных или сетевая ошибкаconsole.log('Ошибка ' + error.message);};ws.onclose = function(event) {// 1000 - штатное закрытие сокета (коды WebSocket из 4х цифр)// 1001 - удалённая сторона исчезла// 1002 - ошибка протокола// 1003 - неверный запросconsole.log('Код: ' + event.code);console.log('Причина: ' + event.reason);};
message — обработка ws.onmessage = function(event) {const data = event.data;const message = JSON.parse(data);console.log('Прислали сообщение: ' + message.text);// или, если есть глобальная шина событийbus.emit(message.event, message.payload);};
{"action": "FIRE","payload": { "cell": "b4" }}{"action": "FIRE_RESULT","payload": { "state": "Убил" }}
const webSocketService = new WebSocketService('/ws');webSocketService.send('FIRE', { "cell": "b4" });webSocketService.subscribe('FIRE_RESULT', function (payload) {const state = payload.state;game.reRender(state);});
HTTP/1.1 был спроектирован для сетей с более низкими пропускными способностями (bandwidth) и более высокими задержками (latency), чем сейчас. Поэтому у него есть недостатки:
HTTP/2 создавался с целью улучшить скорость работы web-приложений, за счёт уменьшения сетевых задержек и более удобного управления ресурсами в web. Основные особенности:
Сервера, которые поддерживают HTTP/2:
Поддержка браузерами — caniuse
Stream — двунаправленный поток байтов через установленное соединение, который может состоять из одного или более сообщений
Message — целостная последовательность фреймов, которая составляет полное логическое сообщение: запрос или ответ
Frame — минимальная единица коммуникации в HTTP/2. Каждый фрейм содержит заголовок фрейма, который идентифицирует, к какому сообщению внутри стрима относится это фрейм
404 Slide Not Found
История, больше касающаяся серверсайда/реверс-прокси и прочих сисадминских штучек. Используется для более тщательного контроля над пересылкой данных, буферизацией и всяким таким добром