DevGang
Авторизоваться

Развертывание Angular Universal в облачных функциях Firebase

Вы наконец закончили свою первую готовую к выпуску версию своего универсального приложения Angular и готовы к ее развертыванию! Так где же развернуть? Честно говоря, вариантов много. DigitalOcean, Heroku, Vercel и т. д. Но в этой статье мы рассмотрим развертывание Angular Universal в функциях Firebase.

Дисклеймер

Целью написания этой статьи является обновление уже существующих ресурсов, таких как Angular Universal с помощью Firebase от Fireship или рендеринг на стороне сервера с помощью Angular от Codeible, а также предоставление обновленного пошагового руководства.

Введение

В этом примере я использую монорепозиторий NX. У меня есть Angular Universal как одно приложение, а облачные функции — как другое приложение. Если вы не используете NX или даже прошло некоторое время с момента публикации этого сообщения в блоге, вы можете посетить: интеграция веб-фреймворков с хостингом от Firebase, однако существует проблема: невозможно обнаружить используемую веб-платформу при использовании angular-приложение в nx monorepo.

Кроме того, в моем проекте я не использую какую-либо серверную библиотеку макетирования DOM, такую ​​​​как domino, для решения проблем Angular SSR, скорее я решил ограничить отображение некоторых компонентов только на стороне клиента, таких как диаграммы.

Проверьте замену файла в project.json

Это необязательный шаг, но в моем случае в project.json не было замены рабочего файла для изменения environment.ts на environment.prod.ts, так что это может случиться и с вами. Вы хотите добавить следующие строки в разделы server и build.

"configurations": {
   "production": {
     "fileReplacements": [
        {
         "replace": "apps/<app-name>/src/environments/environment.ts",
          "with": "apps/<app-name>/src/environments/environment.prod.ts"
         }
      ],
      "outputHashing": "media"
    },
  }

Обновить файл server.ts

Нам необходимо обновить файл server.ts (или ssr.server.ts), особенно две его части. Во-первых, вы не хотите выполнять функцию run(), чтобы ваше приложение прослушивало определенный порт. Прослушивание портов и выполнение приложений будут осуществляться облачными функциями. Во-вторых, вы хотите обновить местоположение вашей distFolder.

// used because of firebase functions
// url: https://fireship.io/lessons/angular-universal-firebase/
(global as any).WebSocket = require('ws');
(global as any).XMLHttpRequest = require('xhr2');

// imports

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
    // .....

  const websiteFileLocation = environment.production ? 
            'browser' : 'dist/apps/<app-name>/browser';
  const distFolder = join(process.cwd(), websiteFileLocation);
  // ^^ step 2.

  // ....

  return server;
}

// commented out because of firebase functions
function run(): void {
  const port = process.env['PORT'] || 4200;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// .....

if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  // in production, the server is run via firebase functions
  if (!environment.production) {
    run();
  }
  // ^^ step 1.
}

export * from './main.server';

В приведенном выше фрагменте, когда мы запускаем производственную сборку, мы меняем расположение файла browser в переменной websiteFileLocation. Причина в том, что после создания универсального приложения (my-app) мы создаем сценарий, который копирует папки browser и server в папку облачных функций, которая в конечном итоге развертывается.

Создайте приложение Angular

Возможно, вы захотите зарегистрировать новый script в package.json для создания универсального приложения Angular, а именно:

"mm:build:ssr": "nx build <app-name> --configuration=production && nx build <app-name>:server --configuration=production",

Копирование сборки Angular в облачные функции

В корневом каталоге создайте файл cp-angular.js и добавьте следующий контент:

const fs = require('fs-extra');

// Copy Angular build to functions folder
(async () => {
  const src = './dist/apps/<app-name>';
  const copy = './dist/apps/<cloud-functions>';

  await fs.copy(src, copy);

  console.log('Angular build copied to functions folder');
})();

Вы также можете установить fs-extra. У вас есть скрипт, который скопирует встроенные папки приложений Angular Universal (browser и server) в папку, где расположены ваши развертываемые облачные функции, чтобы в конечном итоге Angular Universal можно было обслуживать через облачные функции. Чтобы выполнить скрипт, запустите node cp-angular.

Сервер Angular Universal с функцией Firebase

Теперь в корне index.ts создайте новую функцию HTTP Firebase, которая будет выполнять серверный код Angular, main.js, и возвращать содержимое страницы.

// function for SSR
const universal = require(`${process.cwd()}/server/main`).app();
export const ssr = onRequest(universal);

Затем, создав облачные функции (nx build <cloud-functions>) и запустив функции firebase эмулятор firebase emulators:start --only functions, вы получите конечную точку ssr http, которая будет обслуживать приложение SSR.

Обновить хостинг Firebase

В firebase.json вы хотите обновить раздел hosting, переписав все HTTP-запросы, чтобы сначала нацелиться на облачную функцию ssr, которая обслуживает SSR, а затем гидратация на стороне клиента позаботится о взаимодействии с пользователем.

"hosting": [
    {
      "public": "./dist/apps/<app-name>/browser",
      "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
      "rewrites": [
        {
          "source": "**",
          "function": "ssr"
        }
      ]
    }
  ],

Раздел rewrites означает, что каждый раз, когда вы впервые пытаетесь получить доступ к определенной странице (например, /dashboard), ваш запрос будет перенаправлен в облачные функции, в функцию ssr, где Angular Universal позаботится о рендеринге первой страницы.

Проверка рендеринга SSR перед развертыванием

Чтобы проверить, правильно ли функции Firebase обслуживают Angular Universal через конечную точку ssr, вам нужно создать облачные функции и запустить файл cp-angular.js. Зарегистрируйте новый script в package.json.

"mm:cloud-function:build": "nx build <cloud-functions> --prod && node cp-angular",

Затем запустите эмулятор Firebase с помощью функций firebase emulators:start --only, и теперь, нажав на конечную точку ssr, ваше приложение должно быть обслужено. Однако есть одна небольшая проблема.

Когда вы получаете доступ к любой странице вашего приложения, например /dashboard, эта страница будет обслуживаться SSR, однако маршрутизация на стороне клиента не будет работать. Angular Universal может обслуживать только определенную страницу, а затем гидратация на стороне клиента должна позаботиться обо всем остальном, чтобы вести себя как SPA. Мы эмулируем только функции Firebase, а не хостинг, поэтому сбой гидратации клиента — это нормально. Однако когда мы развертываем функции Firebase, нам также необходимо где-то разместить наш клиент, например, на хостинге Firebase.

Развертывание функций Firebase и хостинга

После того, как вы пройдете описанную выше настройку, развертывание вашего приложения SSR с помощью функций Firebase будет так же просто, как запуск команды:

firebase deploy --only hosting:<app-name>,functions

Ограничения облачных функций

Преимущество использования облачных функций заключается в том, что если вы не взаимодействуете со своим веб-приложением, ваши облачные функции переходят в состояние «спящего режима». Они потребляют практически ноль ресурсов, и вы не платите за их неиспользование. Однако, как только вы захотите начать их использовать, вы столкнетесь с холодным запуском, пока все не будет инициализировано в первый раз.

Холодный старт зависит от того, сколько зависимостей у вас есть при первой инициализации. Ситуация начинает ухудшаться, если одной из ваших зависимостей является Firestore. Существует закрытая, но не решенная проблема, связанная с неприемлемой производительностью get() при холодном запуске.

Проблема в том, что инициализация Firestore занимает несколько секунд, что добавляет дополнительные секунды к холодному старту. Чтобы увидеть холодный запуск в действии, я измерил свой веб-сайт Angular Universal, развернул функции Firebase и получил доступ к производительности.

На изображении выше вы можете ясно видеть, что когда облачная функция, обслуживающая приложение SSR, испытывает холодный старт, ответ занимает около 13 секунд, а затем около 2 секунд на каждый запрос.

Существует несколько вариантов уменьшения холодного запуска, например настройка минимального количества экземпляров функции. Таким образом, у вас всегда будут несколько теплых экземпляров функции, обслуживающей часть SSR, однако за все придется платить. Настройка всего 3-х минимальных инстансов обойдется примерно в 30$ в месяц.

Другими вариантами исправления холодного запуска могут быть внедрение сервисных работ для кэширования некоторого javascript на стороне клиента, удаление зависимости Firestore или создание планировщика, который будет периодически проверять некоторые облачные функции, чтобы они постоянно нагревались, что в конечном итоге является более дешевым вариантом, чем настройка минимального экземпляры.

Также есть некоторые исследования по развертыванию Angular Universal в службе Cloud Run, однако, когда я лично пытался закрепить то же приложение и развернуть его в облаке, по опыту холодного запуска это составило тот же самый первый ответ ~ 13 секунд, поэтому я остановился на Firebase функции.

Заключение

Мы рассмотрели, как развернуть универсальное приложение Angular внутри монорепозитория NX с помощью функций Firebase. Эта статья представляет собой обновленную версию Firebase и Codeible, поскольку этим руководствам уже несколько лет.

Теперь, если все настроено правильно, вы сможете развернуть свое универсальное приложение Angular в функциях Firebase, выполнив следующие сценарии:

yarn mm:build:ssr
yarn mm:cloud-function:build
firebase deploy --only hosting:market-monitor-prod,functions

// equivalent to
nx build <app-name> --configuration=production && nx build <app-name>:server --configuration=production
nx build <app-name>-cloud-functions --prod && node cp-angular
firebase deploy --only hosting:market-monitor-prod,functions

Источник:

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу