Технопарк, весна, 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
# пример для nginx
gzip_static on;
# пример для nginx
brotli on;
WebP (4,9 KB) | GIF (85,2 KB) |
Компилируем в jssyntax = "proto3";
message User {
string name = 1;
int32 age = 2;
}
Можно использовать webpack-loader 😏npm install -g protobufjs
pbjs -t static-module -w commonjs -o user.js user.proto
Кэширование — повторное использование ресурсов для работы приложения. Использование кэшей позволяет уменьшить задержку и расходование сетевого трафика и тем самым уменьшить время, необходимое для отображения ресурса. HTTP-кэширование — кэширование ответов на HTTP-запросы
// Запрос обыкновенный
GET /script.js HTTP/1.1
Host: example.com
Accept: */*
// Ответ на запрос
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=UTF-8
Cache-Control: public, max-age=86400
Last-Modified: Sat, 25 Mar 2017 12:00:00 GMT
ETag: W/"7349b-15b075b6d60"
Cache-Control
Возможные значения
no-cache
no-store
must-revalidate
private/public
max-age=31536000
// Ответ на запрос
HTTP/1.1 200 OK
...
Last-Modified: Sat, 25 Mar 2017 12:00:00 GMT
...
GET /script.js HTTP/1.1
Host: example.com
If-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.1
Host: example.com
If-None-Match: W/"7349b-15b075b6d60"
HTTP/1.1 304 Not Modified // Контент не изменился
HTTP/1.1 200 OK // Новый контент
GET /script.js?_=1492172324776
GET /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.length
localStorage.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. То есть это событие позволяет общаться между вкладками
// обработчик добавляется на объект window
window.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 successful
console.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'ы очень удобно использовать для написания:
WebSocket
const ws = new WebSocket('ws://example.com/ws');
// если страница загружена по https://
const ws = new WebSocket('wss://example.com/ws');
// События WebSocket
ws.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
и close
ws.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
История, больше касающаяся серверсайда/реверс-прокси и прочих сисадминских штучек. Используется для более тщательного контроля над пересылкой данных, буферизацией и всяким таким добром