GoogleCloudPlatform / functions-framework-nodejs

FaaS (Function as a service) framework for writing portable Node.js functions
Apache License 2.0
1.29k stars 160 forks source link

Error importing types when moduleResolution is set to nodenext #521

Closed anothermh closed 9 months ago

anothermh commented 1 year ago

Problem

Given a simple Cloud Function written in TypeScript:

import { type Context } from '@google-cloud/functions-framework/build/src/functions'
import { type PubsubMessage } from '@google-cloud/pubsub/build/src/publisher'

export const testFunction = async (message: PubsubMessage, context: Context): Promise<void> => {
  const messageId = context.eventId as string
  console.log(`Processing messageId ${messageId}`)
}

With a tsconfig.json having:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "nodenext",
    "strict": true,
    "target": "es2022",
    "types": ["node"]
  },
  "include": ["src/**/*.ts"],
  "exclude": [".yarn"],
  "baseUrl": "."
}

The following error is generated:

Cannot find module '@google-cloud/functions-framework/build/src/functions' or its corresponding type declarations.ts(2307)

This error can be resolved by changing moduleResolution to node instead of nodenext.


Demonstration

https://user-images.githubusercontent.com/8844992/225422658-61bfd389-dfd3-48cb-a115-978f09275193.mov


Steps to reproduce

  1. Clone this repo
  2. Open in VSCode
  3. Install the recommended extensions
  4. Use the integrated TypeScript SDK at .yarn/sdks/typescript when prompted
  5. Reload or restart VSCode
  6. Open src/index.ts and observe the error
  7. Open tsconfig.json and change moduleResolution to node
  8. Observe the error is gone

Related GitHub issues

Related StackOverflow questions


Questions

  1. Why does this error occur with nodenext and what can be done to resolve it without switching to node?
  2. Why does the import for PubsubMessage from @google-cloud/pubsub/build/src/publisher work without errors?
  3. What is the difference between that and Context in @google-cloud/functions-framework/build/src/functions that would cause one to have an error and not the other?
cblodgett-sauce commented 1 year ago

This is broken on ES6 and above, not just nodeNext.

Package.json

  "dependencies": {
    "@google-cloud/functions-framework": "^3.1.3",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "gts": "^3.1.1",
    "helmet": "^6.0.1",
    "http-errors": "^2.0.0",
    "morgan": "^1.10.0"
  },
  "devDependencies": {
    "@istanbuljs/nyc-config-typescript": "^1.0.2",
    "@types/cookie-parser": "^1.4.3",
    "@types/cors": "^2.8.13",
    "@types/http-errors": "^2.0.1",
    "@types/mocha": "^10.0.1",
    "@types/morgan": "^1.9.4",
    "@types/node": "^18.14.6",
    "@types/supertest": "^2.0.12",
    "gts": "^3.1.1",
    "mocha": "^10.2.0",
    "nyc": "^15.1.0",
    "pre-commit": "^1.2.2",
    "supertest": "^6.3.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.1.2",
    "typescript": "~4.9.5"
  }

.eslintrc.json

{
  "extends": "./node_modules/gts/",
  "rules": {
    "node/no-unpublished-import": 0
  }
}

index.test.js

import getFunction from '@google-cloud/functions-framework/testing';

Error message

Cannot find module '@google-cloud/functions-framework/testing' or its corresponding type declarations.ts(2307)
cblodgett-sauce commented 1 year ago

They need to fix the type mappings. This is ridiculous for a cloud provider to be so far behind like this. If I wasn't forced into GCP I'd go back to AWS.

garethgeorge commented 1 year ago

Hi all, sorry you've been running into some trouble here. I think there are two separate issues that you both have been running into that both stem from our use of an exports declaration in functions-framework-nodejs's package.json.

We declare the exports here as:

  "exports": {
    ".": {
      "types": "./build/src/index.d.ts",
      "default": "./build/src/index.js"
    },
    "./testing": {
      "types": "./build/src/testing.d.ts",
      "default": "./build/src/testing.js"
    }
  },

From the docs on the exports feature https://nodejs.org/api/packages.html#main-entry-point-export when module resolution is respecting this property all other paths in the module should be hidden and only the exported paths are available to import.

This means that when using nodenext or any other module resolution strategy that respects the exports field you should only import functions framework as:

import ... from '@google-cloud/functions-framework'
import ... from '@google-cloud/functions-framework/testing' // for testing

When using older module resolution strategies i.e. node you may still need to import internal parts functions-framework as:

import { ... } from '@google-cloud/functions-framework/build/src/testing';

All of this is to say, the preferred fix should be to update your module resolution strategy to nodenext and use the modern imports. For the issue @anothermh is running into specifically, the types in @google-cloud/functions-framework/build/src/functions that you are trying to access should be reexported from @google-cloud/functions-framework. Try using the import:

import { type Context } from '@google-cloud/functions-framework

Hope this helps!