Небольшому веб-приложению больше не всегда нужна сложная инфраструктура. 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-принципы можно применить на собственном хостинге.