Используем SWC с Firebase функциями
- воскресенье, 12 июня 2022 г. в 00:34:34
В этой статье рассмотрим использование компилятора для js-кода облачных функций.
Создаем проект. Добавляем 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
приложение инициализировалось, только один раз.
В глобальные переменные также можно помещать переменные из импортированных модулей.
Подробнее про этот трюк можно почитать в документации Firebase.
В качестве компилятора и/или минификатора можно использовать проект SWC или любой другой.
Я выбрал SWC, он умеет минифицировать и компилировать код одновременно.
Установить SWC можно по инструкции.
Чтобы создать конфигурационный файл .swcrc
и подобрать нужные вам настройки, я рекомендую использовать playground проекта. Получить конфигурацию можно нажав кнопку 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
может потребоваться для удобной работы с эмулятором, для него потребуется установить дополнительный пакет.
Чтобы не забыть перед деплоем компилировать проект, рекомендую установить predeploy скрипт. Это можно сделать в firebase.json
.
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
}
Теперь при вызове команды firebase deploy
, код предварительно будет собран.
Пример уже настроенного Firebase проекта можно посмотреть на GitHub.
Если во время выполнения кода появится ошибка, то прочитать её стектрейс будет затруднительно из-за модификаций кода.
Эта проблема решается генерацией source maps и подключением модуля source-map-support.
Use TypeScript for Cloud Functions (Firebase Documentation)
Tips & tricks (Firebase Documentation)
Поздравляю! Теперь ваш проект компилируемый и вы сделали небольшой шаг в его оптимизации.
Чтобы одним из первым прочитать мою следующую статью, можно подписаться на мой канал в Телеграм.
Если у вас возникли вопросы или замечания, можно свободно писать их в комментариях.
Лицензировано под Attribution 4.0 International (CC BY 4.0).