- PVSM.RU - https://www.pvsm.ru -

Используем SWC с Firebase функциями

В этой статье рассмотрим использование компилятора для js-кода облачных функций.

Используем SWC с Firebase функциями - 1

Проблема

Создаем проект. Добавляем Firebase функцию.

// index.js
export const helloWorld = https.onRequest(/** … */);

Через некоторое время добавляем ещё несколько.

// index.js
export const helloWorld = https.onRequest(/** … */);
export const lol = https.onRequest(/** … */);
export const pirojok = https.onRequest(/** … */);

Ещё через некоторое время ещё и ещё.

А так как мы пишем код для людей, поэтому помимо кода, в нём будут комментарии и документация.

Как увеличение количества кода влияет на проект?

Проект Firebase Functions — это node модуль. В index.js экспортируются все функции.

При первом запуске любой функции загружаются и инициализируются все импортированные файлы, от всех функций. Node.js загружает index.js и весь связанный код через require и/или import. Это особенность node модулей.

На время инициализации node модуля также влияют выражения, вычисляемые в этот момент.

Следующий пример будет вычисляться при каждой инициализации node модуля:

// constants.js
const ONE_MINUTES_IN_MS = 60 * 1000;

Далее происходит обработка события функцией.

Поэтому, чем больше кода и чем он сложнее, тем больше времени нужно для инициализации каждой функции.

Решение

Если сократить, упростить код и загружать только используемые модули для запущенной функции, то можно сократить время инициализации функций.

Как можно сократить код? Существуют несколько вариантов, среди них минификация и/или компиляция.

Минификация и компиляция

Грубо говоря, минификация сделает имена переменных короткими, а компиляция — уберёт неиспользуемый код, пред вычислит и упростит выражения, например: 50 + 50 превратит в 100.

Минификация в действии

До:

const hello = sayHello("Pirojok");
console.log(hello, 40 + 2);

function sayHello(name) {
  return `Hi, ${name}!`;
}

После:

"use strict";
const a = b("Pirojok");
console.log(a, 40 + 2);
function b(a) {
  return `Hi, ${a}!`;
}

Компиляция в действии

До:

const hello = sayHello("Pirojok");
console.log(hello, 40 + 2);

function sayHello(name) {
  return `Hi, ${name}!`;
}

После:

"use strict";
var name;
const hello = `Hi, ${(name = "Pirojok")}!`;
console.log(hello, 42);

Компиляция и минификация в действии

До:

const hello = sayHello("Pirojok");
console.log(hello, 40 + 2);

function sayHello(name) {
  return `Hi, ${name}!`;
}

После:

"use strict";
var a;
const b = `Hi, ${(a = "Pirojok")}!`;
console.log(b, 42);

Как загружать только используемые модули?

Основная идея — загружать модули в момент выполнения, а не инициализации функций.

Всё что нужно сделать — поместить загрузку модулей в тело обработчика функции.

До:

import {https, logger} from "firebase-functions";
import {initializeApp} from "firebase-admin";

initializeApp();

export const helloWorld = https.onRequest((request, response) => {
  logger.info("Hello logs!", {structuredData: true, computed: 50 + 50});
  response.send("Hello from Firebase!");
});

После:

import {https, logger} from "firebase-functions";

let app_inited_by_helloWorld = false;

export const helloWorld = https.onRequest(async (request, response) => {
  if (!app_inited_by_helloWorld) {
    const {initializeApp} = await import("firebase-admin");
    initializeApp();
    app_inited_by_helloWorld = true;
  }

  logger.info("Hello logs!", {structuredData: true, computed: 50 + 50});
  response.send("Hello from Firebase!");
});

Глобальная переменная app_inited_by_helloWorld нужна, чтобы firebase-admin приложение инициализировалось, только один раз.

В глобальные переменные также можно помещать переменные из импортированных модулей.

Подробнее про этот трюк [1] можно почитать в документации Firebase.

Какой минификатор и компилятор выбрать?

В качестве компилятора и/или минификатора можно использовать проект SWC или любой другой.

Используем SWC

Я выбрал SWC, он умеет минифицировать и компилировать код одновременно.

Установка

Установить SWC можно по инструкции [2].

Чтобы создать конфигурационный файл .swcrc и подобрать нужные вам настройки, я рекомендую использовать playground [3] проекта. Получить конфигурацию можно нажав кнопку Edit as JSON.

Для Firebase Functions рекомендую сразу задать Env Targets = node 16 и убрать jsc.target в конечном файле.

Playground добавляет в конфигурационный файл опцию "isModule" = true, её потребуется удалить, если компилятор, будет выдавать ошибку в вашем проекте.

Бонусом можно настроить использование es-модулей, весь старый код, использующий require, и новый будут прекрасно жить вместе. Отличная возможность для плавного перехода на es-модули.

Подготовка кода проекта

Для того, чтобы код можно было компилировать и использовать, его нужно как-то разделять между собой. Отличным способом является разделение по директориям src и lib. Если весь код лежит в корне пакета, то это сильно осложнит процесс.

После подготовки можно настроить скрипты для компиляции проекта.

В package.json нужно изменить точку входа и добавить скрипты:

{
  "main": "lib/index.js",
  "scripts": {
    "dev": "swc src -w -d lib",
    "build": "swc src -d lib"
  }
}

build компилирует код из директории src в lib, а dev следит за изменениями файлов и перекомпилирует их. dev может потребоваться для удобной работы с эмулятором, для него потребуется установить дополнительный пакет [4].

Автоматизация деплоя

Чтобы не забыть перед деплоем компилировать проект, рекомендую установить predeploy скрипт. Это можно сделать в firebase.json.

{
  "functions": {
    "predeploy": [
      "npm --prefix "$RESOURCE_DIR" run lint",
      "npm --prefix "$RESOURCE_DIR" run build"
    ]
  }
}

Теперь при вызове команды firebase deploy, код предварительно будет собран.

Пример проекта

Пример [5] уже настроенного Firebase проекта можно посмотреть на GitHub.

Подводные камни

Если во время выполнения кода появится ошибка, то прочитать её стектрейс будет затруднительно из-за модификаций кода.

Эта проблема решается генерацией source maps и подключением модуля source-map-support [6].

Полезные ссылки

  1. Use TypeScript for Cloud Functions [7] (Firebase Documentation)

  2. Tips & tricks [8] (Firebase Documentation)

Финал

Поздравляю! Теперь ваш проект компилируемый и вы сделали небольшой шаг в его оптимизации.

Чтобы одним из первых прочитать мою следующую статью, можно подписаться на мой канал [9] в Телеграм.

Если у вас возникли вопросы или замечания, можно свободно писать их в комментариях.

Лицензия

Лицензировано под Attribution 4.0 International (CC BY 4.0) [10].

Автор: Артур

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/375961

Ссылки в тексте:

[1] этот трюк: https://firebase.google.com/docs/functions/tips#use_global_variables_to_reuse_objects_in_future_invocations

[2] инструкции: https://swc.rs/#overview

[3] playground: https://swc.rs/playground

[4] пакет: https://swc.rs/docs/usage/cli#--watch--w

[5] Пример: https://github.com/arthurgubaidullin/using-swc-with-firebase-functions

[6] source-map-support: https://github.com/evanw/node-source-map-support

[7] Use TypeScript for Cloud Functions: https://firebase.google.com/docs/functions/typescript

[8] Tips & tricks: https://firebase.google.com/docs/functions/tips

[9] канал: https://t.me/arthur_g_wrotes

[10] Attribution 4.0 International (CC BY 4.0): https://creativecommons.org/licenses/by/4.0/

[11] Источник: https://habr.com/ru/post/670954/?utm_source=habrahabr&utm_medium=rss&utm_campaign=670954