4

Sorry in advance for the long question, but project structure issues/bugs are kind of hard to explain.

I have a Next.js project, which is my root project. It's a Typescript project so it has its own tsconfig.json

Here is the basic structure:

> app    // APP COMPONENTS
> pages  // NEXT.JS PAGES
> types  // SOME GLOBAL TYPES FOR THE PROJECT
firebase.json
firestore.rules
storage.rules
tsconfig.json

I need to add cloud functions to this project. So I've followed the docs on:

https://firebase.google.com/docs/functions/typescript

Basically I've typed firebase init functions and followed the instructions from the CLI.

It then created a functions as following (the > sign is denoting a folder):

> app

> functions  // NEW FOLDER FOR THE FUNCTIONS PROJECT
  > src
    index.ts
  package.json
  tsconfig.json

> pages
> types
firebase.json
firestore.rules
package.json
storage.rules
tsconfig.json

See now that the functions folder has its own tsconfig.json file and its own package.json file. In essence, it is a project of its own. I'm fine with that idea.

Here is the tsconfig.json file that was created:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

It also adds a predeploy hook to my firebase.json file.

"functions": {
  "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build"
}

This is necessary to build the .ts file before deployment. It will build the files on the functions/src folder to the functions/lib folder, as per the tsconfig.json.

The basic helloWorld example builds and deploys just fine. I've added some files to the functions folder and its all working.

See my files on functions/src:

enter image description here

See the compiled files on functions/lib:

enter image description here

It's the very same structure and files, just as you'd expect.

THE PROBLEM

The problem begins when I'm importing a type from the "outer" root project. For example:

I go on functions/src/index.ts and do something like the following:

import SomeType from "../../types/whatever"

// USE THE TYPE HERE

Now see what the build result is (i.e: the functions/lib folder):

enter image description here

Now it basically creates another functions under the lib folder. And it also basically copies the files from my types folder. I have no idea why it is doing that.

I wasn't expecting that at all. I'd like to use my types from the outer root project, but without messing with the structure of the resulting functions/lib folder.

What could be happening?

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • Does this solution work for you? [Using a shared node module for common classes](https://stackoverflow.com/a/58877253/3068190). I'm not as inexperienced with TypeScript anymore, but this seems to work for what you are trying to do. – samthecodingman May 27 '21 at 12:20
  • @samthecodingman Thanks. What you are suggesting there is to copy the shared folder content to the `functions` folder and do some kind of path alias mapping to access it? It could work, but is it the best/only way? That feels hacky. Isn't there a better way? – cbdeveloper May 27 '21 at 12:27
  • Ah, I slightly misread the question: The reason you end up with `$PROJECT_DIR/functions/lib/functions/src` is so that your relative imports (`../../types/whatever`) that you are using in your code point to the **compiled** types at `$PROJECT_DIR/functions/lib/types`. When Firebase deploys your code as a Cloud Functions image, it only uploads the contents of `$PROJECT_DIR/functions` and therefore requires that all code and resources used by that function are available in that directory. – samthecodingman May 27 '21 at 12:32
  • 1
    @samthecodingman so it's expected for it to gather every piece of imported code from the outer project and compile it along with the `functions/src` folder? I thought Typescript would respect the `include` property and not compiled anything outside that. Also I'm just importing types. They don't matter to the result code. This is weird. – cbdeveloper May 27 '21 at 12:39
  • If they are purely types (i.e. `interface`, `declare function`, etc. - not actual functions/code) use `import type ... from "../../types/whatever"`. This tells TypeScript that these files don't contain code, just IDE-helper stuff. – samthecodingman May 27 '21 at 12:48
  • I am having this exact same issue and still cannot figure it out. Any luck @cbdeveloper – mattgabor Aug 07 '21 at 15:19

2 Answers2

1

A way around it, that is not too hacky, is to declare your shared type as a namespace and import them with a /// <reference path="..."> command.

That way, you will be able to access your type through the declared namespace and the build will keep the required directory structure.

Assuming the following project

my-project/
  functions/
    src/
      index.ts
  shared/
    type.d.ts
  ...otherFiles

shared.ts

declare namespace shared {
  interface IType {
    name: string
  }
}

index.ts

// eslint-disable-next-line
/// <reference path="../../shared/model.d.ts" /> 
import * as functions from "firebase-functions";

export const helloWorld = functions.https.onRequest((request, response) => {
  const type:shared.IType = {name: "John"};
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send(`Hello ${type.name}!`);
});

The // eslint-disable-next-line comments is required if you use eslint default rules from firebase template because of the @typescript-eslint/triple-slash-reference rules.

mxp-qk
  • 91
  • 4
0

I just solved a similar issue where I wanted to import the types from the project folder into the firebase functions folder.

// Default folder structure for Next.js and firebase functions
project-folder/
├── functions/
│   ├── src/
│   │   ├── index.ts
│   │   └── interfaces.ts // (See explanation for this one)
│   ├── package.json
│   └── tsconfig.json
├── interfaces/
│   └── index.ts
├── lib/
├── pages/
│   ├── _app.tsx
│   └── index.tsx
├── package.json
└── tsconfig.json

The functions/src/interfaces.ts simply exports the types from the project folder, like so:

export * from "../../interfaces";

If you run tsc in the functions-folder now, it will generate a file structure that looks different from before:

// Output when not importing from outside functions src folder
lib/
├── index.js
└── index.js.map

// Output when importing interfaces from outside src folder
lib/
├── functions/
│   └── src/
│       ├── index.js
│       └── index.js.map
└── interfaces/

All you now have to do is change package.json a little bit:

{
  "main": "lib/functions/src/index.js"
}

That's it! Files in your functions folder can import from the project folder.

Note about interfaces.ts: I added this file because it seemed cleaner to have only one file that imports from outside the functions folder. Also, this way importing interfaces in the functions folder is similar to everywhere else in the project.

Marius Brataas
  • 614
  • 1
  • 5
  • 8