Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot find module '@custom-modules/moduleName' when deploy using TypeScript #986

Closed
chen86860 opened this issue Nov 1, 2018 · 13 comments
Closed

Comments

@chen86860
Copy link

Hey, guys~ I can't deploy when I assign a paths arguments in tsconfig.json, like this:

{
  "compilerOptions": {
    "lib": ["es6"],
    "module": "commonjs",
    "outDir": "lib",
    "rootDir": "src",
    "target": "es6",
    "sourceMap": true,
    "noImplicitReturns": true,
    "baseUrl": ".",
    // here, I assign a paths arguments
    "paths": {
      "@custom-modules/*": ["src/modules/*"],
    }
  },
  "compileOnSave": true,
  "include": ["src"]
}

The paths arguments usually using TypeScript and it work perfectly. But firebase deploy throw that error:

Error: Error parsing triggers: Cannot find module '@custom-modules/moduleName'

This question has published in StackOverFlow before How to use TS Path Mapping with Firebase Cloud Functions, and for now, Can anyone help me? Thanks~ 🤣

@bkendall
Copy link
Contributor

bkendall commented Nov 1, 2018

As my understanding works, baseUrl and paths are only used by the compiler and linter to understand the typings and location of modules. Those two properties do not tell the compiler to include those mapped paths into the compilation or change the import statements to work during compile time. Take a look at the compiled lib directory to see that the @custom-modules/* import statement have not changed.

That said, the error is coming from the fact that in the deployment process, the CLI interprets the functions code to understand what functions need to be deployed. Since the functions code is requiring a module that it has no knowledge of (this is in the lib folder, remember), an error is thrown that prints out that message.

If you are trying to save some typing by doing import ... from "@custom-modules/..."; and remapping to elsewhere in your structure, you can fix this by doing relative imports, which is probably the recommended way anyway. I don't believe baseUrl and paths are the answer you are looking for.

@idudinov
Copy link

idudinov commented Jun 13, 2019

Hi there! Want to add more info for this issue, because I've encountered it as well.
In my other Node.js+TypeScript project the issue was in place, because, as it explained above, Typescript doesn't change require paths in output JS. The only working solution I was able to find is https://github.com/dividab/tsconfig-paths#with-node , which changes Node.js paths handling way based on tsconfig.json config file. That works thanks to -r tsconfig-paths/register argument passed to Node, and executing register in runtime (even at the very top of index.ts) doesn't seem like to work in Firebase Functions.

Having relative paths seems very inconvenient for me, because I have kinda common folder that is used also by frontend code, relative path to it would look like ../../../common in index.ts, and even more ugly in subfolders.

So maybe there's a way to add this -r tsconfig-paths/register argument for Firebase Functions, including both local and production environments?

@izakfilmalter
Copy link

izakfilmalter commented Aug 15, 2019

I have found that https://www.npmjs.com/package/module-alias worked for me.

  1. Make a new file /src/fixTsPaths.ts:

    import * as ModuleAlias from 'module-alias'
    
    ModuleAlias.addAliases({
      helpers: __dirname + '/helpers',
    })
  2. Import it in /scr/index.ts.

    import './fixTsPaths' // Note: needs to be first according to module-alias docs
    import * as functions from 'firebase-functions'
    import * as admin from 'firebase-admin'

@lukaselmer
Copy link

lukaselmer commented Feb 11, 2021

Thx @izakfilmalter

If you use the base url as described in https://create-react-app.dev/docs/importing-a-component/#absolute-imports, you can fix the absolute import errors the following way:

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "lib"
  },
  "include": ["src"]
}

/src/fix-ts-paths.ts

import { addPath } from 'module-alias'

addPath('./lib')

/src/index.ts

import './fix-ts-paths'
export * from './functions'

/src/functions.ts

// normal functions code, using absolute imports

Since absolute imports are commonly used, would it make sense to document this in the official firebase functions documentation @bkendall?

@ezzabuzaid
Copy link

Almost two years with no Optimal solution.

for anyone who still struggles with this issue, but the below code at the top of the entry point file (main.ts).
don't forget to adjust the tsconfig.json file path if it is not in default location

const tsConfig = require('../tsconfig.json');
const tsConfigPaths = require('tsconfig-paths');
tsConfigPaths.register({
    baseUrl: __dirname,
    paths: tsConfig.compilerOptions.paths,
});

@rendomnet
Copy link

why this is closed?

@ggirotto
Copy link

Almost two years with no Optimal solution.

for anyone who still struggles with this issue, but the below code at the top of the entry point file (main.ts).
don't forget to adjust the tsconfig.json file path if it is not in default location

const tsConfig = require('../tsconfig.json');
const tsConfigPaths = require('tsconfig-paths');
tsConfigPaths.register({
    baseUrl: __dirname,
    paths: tsConfig.compilerOptions.paths,
});

Here it still output cannot find module error. Does it need another config than this one?

@LucasSolares
Copy link

Thx @izakfilmalter

If you use the base url as described in https://create-react-app.dev/docs/importing-a-component/#absolute-imports, you can fix the absolute import errors the following way:

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "lib"
  },
  "include": ["src"]
}

/src/fix-ts-paths.ts

import { addPath } from 'module-alias'

addPath('./lib')

/src/index.ts

import './fix-ts-paths'
export * from './functions'

/src/functions.ts

// normal functions code, using absolute imports

Since absolute imports are commonly used, would it make sense to document this in the official firebase functions documentation @bkendall?

I try that, but for some reason this not works on GitHub Actions

@khevamann
Copy link

The simplest solution is to just add tsc-alias to your build step. Changing "build": "tsc" in package.json to "build": "tsc && tsc-alias" and installing tsc-alias as a devDependency will tell the build step to resolve the import paths. This is the best solution because it requires no direct code changes, just a change to the build steps

@leonardorb
Copy link

Just to expand on @khevamann's response here, the neatest way to do this is to use tsc-alias.

On tsconfig.json, add baseUrl and add your paths under compilerOptions. Something like:

{
  "compilerOptions": {
    ...
    "baseUrl": "./src",
    "paths": {
      "@constants/*": ["api/constants/*"],
      "@interfaces/*": ["api/interfaces/*"],
      "@middlewares/*": ["api/middlewares/*"],
      "@modules/*": ["api/modules/*"],
      "@services/*": ["api/services/*"]
    },
    ...
}

Then, change your serve and build scripts, under package.json. Like:

...
  "scripts": {
    "build": "tsc && tsc-alias",
    "build:watch": "tsc-alias --watch",
    "serve": "concurrently --kill-others \"firebase emulators:start --only functions\" \"npm run build:watch\"",
    ...
  },
...

☝️ here I'm using concurrently, but feel free to use whatever you like.

And that's it. You can now import stuff using your defined paths, like:

import { messages } from '@constants/responses'
import CandidatesService from '@modules/candidates/candidates.service'
import { IModule } from '@interfaces/module.interface'

etc...

@mateo-m
Copy link

mateo-m commented Dec 29, 2022

Hey @leonardorb, thank you for your guide on how to work around this limitation.

I have been trying to do as you described in your answer, but it seems that I'm not able to make it work.
I've been at it for a few hours now, but I just keep getting the following error:

Failed to load function definition from source: FirebaseError: Failed to load function definition from source: Failed to generate manifest from function source: Error: Cannot find module '@shared/utils'

I really don't know what else to try, would you mind taking a look at my project config?
In case you don't, here's some useful insight.

Here's the structure of my functions folder:
Folder structure

Here's the content of my package.json file:

{
  "name": "functions",
  "main": "lib/functions/src/index.js",
  "scripts": {
    "lint": "eslint --ext .js,.ts .",
    "build": "tsc && tsc-alias",
    "build:watch": "tsc-alias --watch",
    "serve": "concurrently -n 'Firebase - Emulators,Firebase - Functions' -k 'firebase emulators:start' 'npm run build:watch'",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  ...
  "engines": {
    "node": "16"
  },
  ...
  "devDependencies": {
    "@types/uuid": "^9.0.0",
    "@typescript-eslint/eslint-plugin": "^5.12.0",
    "@typescript-eslint/parser": "^5.12.0",
    "concurrently": "^7.6.0",
    "eslint": "^8.9.0",
    "eslint-config-google": "^0.14.0",
    "eslint-plugin-import": "^2.25.4",
    "firebase-functions-test": "^0.2.0",
    "tsc-alias": "^1.8.2",
    "typescript": "^4.5.4"
  },
}

Here's the content of my tsconfig.json file:

{
  "compileOnSave": true,
  "include": [
    "src"
  ],
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "resolveJsonModule": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@shared/*": ["shared/*"]
    }
  }
}

@risalfajar
Copy link

Using @leonardorb answer works, but not with the build:watch, use this instead:

  "scripts": {
    "build": "tsc && tsc-alias",
    "build:watch": "tsc --watch & tsc-alias --watch",
  },

@dalindev
Copy link

these issues took me hours, so I want to share what works for me so you don't have to...

  1. install tsc-alias as a devDependencies
  2. now update your build and build watch like the following:
"build": "tsc && tsc-alias"
"build:watch": "tsc && (concurrently \"tsc -w\" \"tsc-alias -w\")"
"serve": "concurrently --kill-others \"npm run build:watch\" \"firebase emulators:start --only functions\"",
  1. update your jest.config.ts with:
  testPathIgnorePatterns: ["/node_modules/", "/dist/", "/lib/", "/coverage/"],
  modulePathIgnorePatterns: ["/node_modules/", "/dist/", "/lib/", "/coverage/"],
  moduleNameMapper: {
    "^src/(.*)$": "<rootDir>/src/$1",
    "^@src/(.*)$": "<rootDir>/src/$1",
  },
  1. update your tsconfig.json, and make sure the resolveFullPaths is false otherwise it will add .js to the end of some lib like stripe, cors, ...etc
{
"compilerOptions": {
   // your other configs...
  "sourceRoot": ".",
  "baseUrl": ".",
  "paths": {
    "@src/*": ["src/*"],
  },
},
"tsc-alias": {
  "resolveFullPaths": false,
  "baseUrl": ".",
  "paths": {
    "@src/*": ["src/*"],
  }
},
"compileOnSave": true,
"include": ["src"],
"exclude": [
  "**/*.js",
]
}
  1. Now you can use an absolute path like import db from "@src/config/firebase"; but the compiled .js file will use relative path like "../../config/firebase";

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests