Веб-приложение без своего сервера: ESM и CDN
Дата публикации: 2024-07-04Небольшому веб-приложению больше не всегда нужна сложная инфраструктура. ES-модули и CDN позволяют запустить код прямо в браузере пользователя: статическая страница размещается отдельно, а библиотеки загружаются по сети. Разберём это на примере.
Демонстрационное приложение — чат, который работает на стороне клиента, берёт библиотеки из jsDelivr и обращается к внешнему API. Его index.html размещён на GitHub Pages.
После публикации пакета в npm его можно доставлять браузеру через CDN, например jsDelivr или unpkg.
Из чего состоит пример
Интерфейс собран с Vue, Vue Router и Quasar. Навигация минимальна: чат, конфигурация и справка.
В конфигурации пользователь выбирает модель и системную инструкцию. Любой секретный ключ, введённый в браузер, следует считать доступным этому устройству и его расширениям: для реальных продуктов лучше использовать свой серверный посредник с ограниченными учётными данными, а не передавать постоянный API-ключ в клиент.
index.html
Точка входа получается очень компактной. В ней подключаются стили, шрифты, importmap и главный ES-модуль приложения:
<script type="importmap">
{ "imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.4.31/dist/vue.esm-browser.prod.js",
"openai/core": "https://cdn.jsdelivr.net/npm/openai@4.52.2/core.mjs"
} }
</script>
<script type="module">
import app from 'https://cdn.jsdelivr.net/npm/@flancer64/demo-wa-esm-openai@0.1.0/src/App.mjs';
app.mount('#app');
</script>
importmap даёт браузеру node-подобные имена импортов. Сам документ также подгружает ресурсы и монтирует приложение в элемент страницы.
Пакет приложения
Код @flancer64/demo-wa-esm-openai состоит из ES-модулей. В Mod/ расположены модели действий, в Ui/ — Vue-компоненты.
В App.mjs ESM-версии Vue и Vue Router импортируются статически. Quasar в показанной версии был UMD-библиотекой, поэтому перед динамическим импортом он получает Vue через глобальный объект:
import * as Vue from 'https://cdn.jsdelivr.net/npm/vue@3.4.31/dist/vue.esm-browser.prod.js';
window.Vue = Vue;
await import('https://cdn.jsdelivr.net/npm/quasar@2.16.4/dist/quasar.umd.prod.js');
const Quasar = window.Quasar;
Маршруты используют ленивую загрузку. Компонент придёт по сети лишь при переходе на соответствующий экран:
const routes = [
{ path: '/', component: () => import('./Ui/Route/Chat.mjs') },
{ path: '/config', component: () => import('./Ui/Route/Config.mjs') },
];
Что видит браузер
На вкладке Sources видно, что статический хостинг отдаёт только index.html и стили, а пакет и сторонние зависимости приходят с CDN. Непосещённые маршруты не загрузят свои компоненты. Шрифты и картинки тоже могут оставаться на общедоступных источниках, а пользовательские настройки — в локальном хранилище.
Вывод
Современный стек позволяет создавать и распространять небольшие приложения без собственных виртуальных серверов: код и зависимости можно публиковать в открытых сервисах. CDN дают хорошую скорость и кэширование при повторном запуске.
Модули можно грузить с точностью до пользовательского действия, не собирая отдельные бандлы и не выполняя tree-shaking заранее. Но открытая цепочка поставки повышает риск вредоносного или неожиданно изменившегося кода. Фиксируйте версии, оценивайте доверие к пакетам и CDN, используйте CSP и не размещайте важные секреты в клиенте. Для небольшого открытого приложения этот подход практичен; для чувствительных данных те же ESM-принципы можно применить на собственном хостинге.
Дополнительные фрагменты исходного кода
});
const app = createApp(Main);
app.use(router);
app.use(Quasar, {
} }); export default app;
{path: …, component: () => import(‘./Ui/Route/About.mjs’)}