По умолчанию большинство локальных серверов разработки запускаются по протоколу HTTP. Для многих задач этого достаточно, однако современные браузеры и веб-платформы всё чаще требуют защищённое соединение HTTPS даже на этапе локальной разработки.
Использование HTTPS в dev-сервере позволяет максимально приблизить локальное окружение к реальным условиям эксплуатации приложения и получить доступ к функциям браузера, которые работают только в защищённом контексте.
К таким возможностям относятся:
В проектах на Esbuild HTTPS настраивается через собственный сервер разработки либо через интеграцию с дополнительными инструментами.
Esbuild предоставляет встроенный сервер разработки через метод
serve(). Этот сервер предназначен прежде всего для быстрого
локального тестирования результатов сборки.
Простейший запуск сервера выглядит следующим образом:
import * as esbuild from 'esbuild';
await esbuild.serve(
{
servedir: 'dist',
port: 3000
},
{}
);
После запуска приложение будет доступно по адресу:
http://localhost:3000
Однако встроенный сервер Esbuild не предоставляет полноценного набора возможностей для настройки HTTPS, сравнимого с решениями вроде Vite, Webpack Dev Server или специализированных Node.js-серверов.
Поэтому в реальных проектах обычно используются дополнительные способы организации HTTPS.
Современные браузеры рассматривают HTTPS не просто как механизм шифрования, а как показатель доверенного окружения.
Например, Service Worker не зарегистрируется при обычном HTTP-соединении:
navigator.serviceWorker.register('/sw.js');
При запуске через HTTP можно получить ошибку:
Failed to register a ServiceWorker:
The operation is insecure.
Аналогичная ситуация возникает с Web Push:
const subscription =
await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
});
Без HTTPS подписка на push-уведомления работать не будет.
Наиболее распространённый способ организации HTTPS в локальной среде — применение самоподписанных SSL-сертификатов.
Для генерации сертификата можно использовать OpenSSL.
Создание приватного ключа:
openssl genrsa -out localhost-key.pem 2048
Создание сертификата:
openssl req -new -x509 \
-key localhost-key.pem \
-out localhost.pem \
-days 365
После выполнения команд будут получены два файла:
localhost-key.pem
localhost.pem
Первый содержит приватный ключ, второй — сертификат.
Поскольку встроенный сервер Esbuild имеет ограниченные возможности по работе с HTTPS, часто создаётся отдельный сервер на Node.js.
Сначала выполняется сборка:
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
После этого запускается HTTPS-сервер.
Структура проекта:
project/
├─ src/
├─ dist/
├─ localhost.pem
├─ localhost-key.pem
└─ server.js
Пример сервера:
import fs from 'fs';
import https from 'https';
import express from 'express';
const app = express();
app.use(express.static('dist'));
https.createServer(
{
key: fs.readFileSync('localhost-key.pem'),
cert: fs.readFileSync('localhost.pem')
},
app
).listen(3000);
Теперь приложение открывается по адресу:
https://localhost:3000
Обычно HTTPS используется совместно с режимом наблюдения за файлами.
Esbuild предоставляет API Watch Mode.
import * as esbuild from 'esbuild';
const context = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
await context.watch();
После изменения исходного кода пересборка происходит автоматически.
HTTPS-сервер при этом продолжает обслуживать обновлённые файлы.
Для повышения удобства разработки часто используется автоматическое обновление страницы.
Простейший вариант — WebSocket-соединение между браузером и сервером.
Клиентская часть:
const socket = new WebSocket(
'wss://localhost:3001'
);
socket.addEventListener('message', () => {
location.reload();
});
Обратите внимание на использование протокола:
wss://
Это защищённая версия WebSocket.
Если страница открыта через HTTPS, браузер может блокировать обычное соединение:
ws://
из-за политики смешанного контента (Mixed Content).
Одной из наиболее распространённых проблем является смешивание защищённых и незащищённых ресурсов.
Например:
<script src="http://localhost:5000/app.js"></script>
Если сама страница открыта через HTTPS:
https://localhost:3000
браузер может заблокировать загрузку скрипта.
Ошибка выглядит примерно так:
Mixed Content:
The page was loaded over HTTPS,
but requested an insecure resource.
Поэтому все ресурсы должны использовать HTTPS:
<script src="https://localhost:5000/app.js"></script>
или относительные пути:
<script src="/app.js"></script>
Работа с самоподписанными сертификатами нередко вызывает предупреждения браузера.
Для решения этой проблемы широко применяется инструмент mkcert.
Он создаёт локальный центр сертификации и выпускает сертификаты, которым доверяет операционная система.
Установка:
mkcert -install
Создание сертификата:
mkcert localhost
Результат:
localhost.pem
localhost-key.pem
Преимущества такого подхода:
Иногда требуется тестировать приложение не через
localhost, а через отдельный домен.
Например:
app.local
или
frontend.local
Запись добавляется в файл hosts.
Linux и macOS:
/etc/hosts
Windows:
C:\Windows\System32\drivers\etc\hosts
Пример:
127.0.0.1 app.local
После этого можно выпустить сертификат:
mkcert app.local
И запускать сервер:
https://app.local:3000
Такой подход позволяет более точно воспроизводить производственное окружение.
Большинство браузеров требуют HTTPS для работы HTTP/2.
При тестировании производительности может понадобиться именно этот протокол.
Пример запуска сервера:
import fs from 'fs';
import http2 from 'http2';
const server = http2.createSecureServer({
key: fs.readFileSync('localhost-key.pem'),
cert: fs.readFileSync('localhost.pem')
});
server.listen(3000);
Это позволяет исследовать:
Cookie с флагом Secure передаются только через
HTTPS.
Пример установки:
res.cookie('session', token, {
secure: true,
httpOnly: true
});
При запуске приложения по HTTP такой cookie не будет работать корректно.
Локальное HTTPS позволяет полноценно тестировать:
Во многих приложениях используются строгие политики безопасности.
Пример заголовка:
Content-Security-Policy:
default-src 'self';
HTTPS помогает тестировать реальные сценарии:
res.setHeader(
'Content-Security-Policy',
"default-src 'self'"
);
Это особенно важно для:
Во время разработки фронтенд и бэкенд часто работают на разных портах.
Например:
Frontend:
https://localhost:3000
Backend:
https://localhost:5000
Для устранения проблем с CORS применяется проксирование.
Схема работы:
Browser
|
v
HTTPS Frontend
|
v
Proxy
|
v
Backend API
В результате браузер взаимодействует только с одним источником данных, а прокси перенаправляет запросы на сервер API.
cert: fs.readFileSync('./cert.pem')
Ошибка:
ENOENT
Причина — отсутствие файла по указанному пути.
Сертификат выпущен для:
localhost
а приложение открывается через:
app.local
В этом случае браузер сообщает:
NET::ERR_CERT_COMMON_NAME_INVALID
Например:
<img src="http://localhost/image.png">
или:
fetch('http://localhost:5000/api');
Такие запросы могут блокироваться браузером.
Ошибка:
new WebSocket(
'ws://localhost:3001'
);
при HTTPS-сайте.
Правильный вариант:
new WebSocket(
'wss://localhost:3001'
);
Наиболее распространённая схема выглядит следующим образом:
src
│
▼
Esbuild Watch
│
▼
dist
│
▼
HTTPS Server
│
▼
Browser
В этой архитектуре Esbuild отвечает исключительно за сборку и отслеживание изменений файлов, а HTTPS, WebSocket, проксирование запросов, HTTP/2 и прочие сетевые возможности реализуются отдельным сервером на Node.js. Такой подход обеспечивает максимальную гибкость, позволяет воспроизводить условия промышленной эксплуатации приложения и использовать все современные возможности браузеров, требующие защищённого соединения.