Using SWC with Firebase Functions

In this article, we will consider using a compiler for js-code of cloud functions.

Problem

We create a project. Adding a Firebase function.

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

We’ll add a few more after a while.

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

After a while, more and more.

And since we write code for people, therefore, in addition to the code, it will have comments and documentation.

How does the increase in the amount of code affect the project?

The Firebase Functions project is a node module. AT index.js all functions are exported.

At the first start of any function, all imported files are loaded and initialized, from all functions. Node.js is loading index.js and all related code via require and/or import. This is a feature of node modules.

The initialization time of the node module is also affected by the expressions that are evaluated at that moment.

The following example will be evaluated each time the node module is initialized:

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

Next, the event is handled by the function.

Therefore, the more code and the more complex it is, the more time it takes to initialize each function.

Solution

If you reduce, simplify the code and load only the modules used for the running function, then you can reduce the time to initialize the functions.

How can you shorten the code? There are several options, among them minification and/or compilation.

Minification and compilation

Roughly speaking, minification will make variable names short, and compilation will remove unused code, pre-compute and simplify expressions, for example: 50 + 50 will turn into 100.

Minification in action

Before:

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

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

After:

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

Compilation in action

Before:

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

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

After:

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

Compilation and minification in action

Before:

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

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

After:

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

How to load only used modules?

The main idea is to load modules at runtime, not function initialization.

All you need to do is put module loading in the body of the function handler.

Before:

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!");
});

After:

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!");
});

global variable app_inited_by_helloWorld needed to firebase-admin the application was initialized, only once.

You can also place variables from imported modules in global variables.

More about this trick You can read the Firebase documentation.

Which minifier and compiler should I choose?

As a compiler and / or minifier, you can use the SWC project or any other.

Using SWC

I chose SWC, it can minify and compile code at the same time.

Installation

You can install SWC by instructions.

To create a config file .swcrc and choose the settings you need, I recommend using playground project. You can get the configuration by clicking the button Edit as JSON.

For Firebase Functions, I recommend immediately setting Env Targets = node 16 and remove jsc.target in the final file.

playground adds an option to the config file "isModule" = trueit will need to be removed if the compiler gives an error in your project.

As a bonus, you can configure the use of es-modules, all the old code using requireand the new one will live together just fine. An excellent opportunity for a smooth transition to es-modules.

Preparing the project code

In order for the code to be compiled and used, it must be somehow separated from each other. A great way is to divide by directories src and lib. If all the code is in the root of the package, then this will greatly complicate the process.

After preparation, you can set up scripts to compile the project.

AT package.json you need to change the entry point and add scripts:

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

build compiles code from a directory src in liba dev watches files for changes and recompiles them. dev may be required for convenient work with the emulator, for it you will need to install an additional package.

Deploy automation

In order not to forget to compile the project before deployment, I recommend installing the predeploy script. This can be done in firebase.json.

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

Now when calling the command firebase deploythe code will be pre-assembled.

Project example

Example already configured Firebase project can be viewed on GitHub.

Underwater rocks

If an error occurs during code execution, then it will be difficult to read its stack trace due to code modifications.

This problem is solved by generating source maps and module connection source-map-support.

useful links

  1. Use TypeScript for Cloud Functions (Firebase Documentation)

  2. Tips & tricks (Firebase Documentation)

The final

Congratulations! Now your project is compilable and you have taken a small step in its optimization.

To be one of the first to read my next article, you can subscribe to my channel in Telegram.

If you have any questions or comments, feel free to write them in the comments.

License

Licensed under Attribution 4.0 International (CC BY 4.0).

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *