Технопарк, осень, 2024 г.
<!-- резолвит 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) |
<img src="image.webp" onerror="this.onerror=null; this.src='image.png'">
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg">
</picture>
Компилируем в 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.
Протокол 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);
};
var buffer = new ArrayBuffer(128);
socket.send(buffer);
var intview = new Uint32Array(buffer);
socket.send(intview);
var blob = new Blob([buffer]);
socket.send(blob);
{
"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);
});
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']);
});
);
});
HTTP/1.1 был спроектирован для сетей с более низкими пропускными способностями (bandwidth) и более высокими задержками (latency), чем сейчас. Поэтому у него есть недостатки:
HTTP/2 создавался с целью улучшить скорость работы web-приложений, за счёт уменьшения сетевых задержек и более удобного управления ресурсами в web. Основные особенности:
Сервера, которые поддерживают HTTP/2:
Поддержка браузерами — caniuse
Stream — двунаправленный поток байтов через установленное соединение, который может состоять из одного или более сообщений
Message — целостная последовательность фреймов, которая составляет полное логическое сообщение: запрос или ответ
Frame — минимальная единица коммуникации в HTTP/2. Каждый фрейм содержит заголовок фрейма, который идентифицирует, к какому сообщению внутри стрима относится это фрейм
404 Slide Not Found
История, больше касающаяся серверсайда/реверс-прокси и прочих сисадминских штучек. Используется для более тщательного контроля над пересылкой данных, буферизацией и всяким таким добром
HTTP/2 — абстракция
TCP — стрим из пакетов
packet loss — resend — delay
While it’s true that some of the HTTP/2 features can be mapped on top of QUIC very easily, that’s not true for all of them.
One in particular, HTTP/2’s header compression scheme called HPACK, heavily depends on the order in which different HTTP requests and responses are delivered to the endpoints.
QUIC enforces delivery order of bytes within single streams, but does not guarantee ordering among different streams.
* QUIC -> QPACK