-
Notifications
You must be signed in to change notification settings - Fork 951
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
Support ES Modules for Node 14 functions #2994
Comments
In addition to problems with how triggers are parsed in firebase-tools repo, the functions framework (used to load and run user-provided code in google cloud functions) doesn't seem to support ESM yet: GoogleCloudPlatform/functions-framework-nodejs#233. |
@taeold good catch. Seems like even if we finished the work here we'd be blocked on that. Perhaps we should put a pin in this until work has been done in the functions framework. |
I just want to mention to all who come here to upvote: Don't forget to click "subscribe" on the right side to get updates!! |
Does this affect typescript projects? We use this tsconfig for node 14: https://github.com/tsconfig/bases/blob/master/bases/node14.json I'm guessing it doesn't. |
@larssn correct, since TypeScript with default configuration compiles down into CommonJS, it is not affected -- you can continue to use ESM in TypeScript just like you can in Node < 14. |
Although even in a Typescript project, the emulators refuse to use |
@mbleigh Will top level await work if it compiles from TS? |
@Spencer-Easton Yes. As long the compiled JS code that's deployed to Firebase Functions doesn't contain top level await, things will work. |
Do the ESLint rules generated by |
@jthegedus Good idea. We will probably want to update the template for both JS and TS to reflect ES module support in GCF. |
So if we manually add in the latest version of functions framework as well as |
@DibyodyutiMondal Not quite. We'll need to make a new release to Firebase CLI that bring support for deploying Firebase Functions using ES modules. I will mark this issue fixed once we make the release (should be done in next couple of days). Here's a quick sample for those interested: // index.js
import functions from "firebase-functions";
export const helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
}); // package.json
...
"type": "module",
"dependencies": {
"@google-cloud/functions-framework": "^1.9.0", // Explicitly include functions-framework v1.9.0
... |
Does this aforementioned release include support in the emulator as well? |
@DibyodyutiMondal Yes. It should work as long as the local version of node used to run the emulator supports ES modules (think that's v13 and up). |
Hi guys, any progress with this? |
ES module is now supported with the latest firebase-cli version: $ npm install -g firebase-tools # Get the latest firebase-cli
$ firebase deploy --only functions # Deploy as usual Note that:
e.g. // package.json
...
"engines": {
"node": "14"
},
"type": "module",
"dependencies": {
"@google-cloud/functions-framework": "^1.9.0",
...
}, If you have any problems with deploying your function packaged as an ES module, please consider reporting a new github issue or filing a support ticket. |
I am getting the following error:
Is there anything I can do from my side to solve this problem, or am I forced to use commonjs as long as I am developing on windows? (I do not like the idea of adding |
getting the same issue. Appears we need to use the type 'module' for the emulator to start up the functions as using 'commonjs' leads to many other issues. advice would be greatly appreciated to resolve this issue mentioned by DibyodyutiMondal. |
Thanks for filing the issue @paulvanj . Let's carry the discussion on that issue. |
I'm trying to migrate from CommonJS to ES Modules and I'm encountering a difficulty in that the loader algorithm changed from CJS to ESM. In particular, you can't use This poses two problems for me:
Node.js supports customizing the loader via the I'm guessing I'm not the only one this problem. |
Since all firebase functions are google cloud functions, there is the possibility of using the gcloud SDK to add If only firebase allowed us to add it using the firebase cli or environment options... |
We've been experimenting with bringing env var support to CF3 using dotenv format. We are still working out few kinks, but folks interested in the feature can try this:
Then, all the functions deployed will have the specified environment variable. IMPORTANT Dotenv feature will not preserve existing environment variables on a function. Nor does it allow for setting environment variable per-function. Interested in hearing your feedback. |
The above won't work. But the approach is spot on. As of now, this is the only way that typescript-compiled esm can work without the file extensions. Also, in VS Code, depending on how the terminal is launched, the NODE_OPTIONS env variable may already be populated (by the vs code debugging bootloader). In such a scenario, dot-env will ignore the NODE_OPTIONS set in the It would be great if the documentation for cloud functions mentioned all of this information, so that people who are new to all 3 of javascript, typescript and firebase at the same time (like I was) don't trip over one thing or the other. Even experienced developers are stumbling as the community is slowly shifting from commonjs to esm. |
This seems to have stopped working. Still got the error message even after setting "type" to "module". |
@wliumelb Do you mind sharing steps to reproduce? I have a simple function: import * as functions from "firebase-functions";
export const helloWorld = functions.https.onRequest((req, resp) => {
functions.logger.info("Hello logs!", {structuredData: true});
resp.send("Hello from Firebase!");
}); With {
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "14"
},
"type": "module",
"dependencies": {
"firebase-admin": "^9.2.0",
"firebase-functions": "^3.11.0"
},
"devDependencies": {
"eslint": "^7.6.0",
"eslint-config-google": "^0.14.0",
"firebase-functions-test": "^0.2.0"
},
"private": true
} And it deploys fine. |
@taeold here is an exemple: index.js
package.json
Deployment failure only happens when using node 16. Node 14 is working fine. |
Can confirm. I get cryptic build errors when the deploying machine is running Node 16, even if package.json specifies Node 14. Switching to Node 14 (via NVM or the like) fixed me right up. |
I ran into this as well. functions-framework repo has the same issue opened. |
Thanks for sharing more info everyone. It sounds like the underlying issue is with nodejs/node#41189 and - unfortunately - the only known workaround is to use node v14 instead :(. |
Update node version and include 'type' field as advised here: firebase/firebase-tools#2994
@taeold Fix to issue nodejs/node#41198 seems to be part of node v17.4 already It means it will be available (soon? ever?) in node 16, correct? Thanks for clarifying :) |
@maganap You are right - the fix needs to be backported to node v16, and the scheduling of that work is depended on the node core contributors. I believe Google Cloud Functions team (which Firebase Functions is built on) is intending to release a temporary fix for the issue - you can track that work GoogleCloudPlatform/functions-framework-nodejs#407. IIUC, once the patch is backported to a later version of node 16, it should be made available in GCF pretty soon. That's how we first got into this mess anyway. |
Any update on this case, as i am still experiencing the same issue. and i am using nodejs 14. |
I've set up my functions project as suggested in this thread, but I'm getting an error when importing another file from the same project.
I've tried every suggestion here, including using node 14, adding Directory layout
index.tsimport init from './init' package.json "type": "module",
"engines": {
"node": "16"
},
...
"dependencies": {
"firebase-admin": "^9.8.0",
"firebase-functions": "^3.14.1"
}, tsconfig.json{
"compilerOptions": {
"outDir": "dist",
"module": "es6",
"target": "es2017",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"sourceMap": true,
"strict": true
},
"include": ["src"]
} |
@juni0r Can you try replacing
with
See https://www.typescriptlang.org/docs/handbook/2/modules.html#es-module-syntax for docs. |
@taeold While that might work, it would introduce inconstencies in our codebase. You see, this package is part of a monorepo in which we use Typescript throughout and every other package uses imports without the .js extension. We'd also need to change certain imports from say Since this convention isn't enforced by linting rules and the project will build even with a mixed usage pattern, this is very prone to errors that might only surface during ci or deployment. We have instead opted for targetting CommonJS for functions which required building some internal dependencies for CommonJS as well, but at least it works. Gosh, this whole module mess by far the biggest pain with JS. So much time wasted, so much complexity added. 😄 |
@juni0r i agree it's painful and confusing to work with ESmodule in typescript! Unfortunately, I don't think we can help with changes in this repo.. |
I think this link https://zenn.dev/fjsh/articles/82e1b938ef96f4 could help a ton if you're trying to write it in typescript. I somehow got it working following this link |
I dove into the node_modules and changed the code at the locations in the images below. All it does is add the And it works like a charm with nothing more needed. It seems that while parsing the triggers, the environment variables are not picked up, even if they are specified in a If you think that this is a good approach, I'd be happy to work on a pull request that would add an option to do this (maybe via the previews feature?) Speaking of preview features, I have not been able to deploy this on functions v2, but I think that's because of some other issue, not this. (the location of the file I am editing is visible in the left sidebar) |
Just saw that someone has opened an issue #4361 in connection with what I just sent. I shall post the above images there and continue there instead |
Per my experience so far, if we want typescript-based functions code that ultimately targets ESM, there are so many things that a developer has to think about: import endings, typescript settings, typescript versions, firebase tools versions, and even the node runtime itself. In my case, using esbuild to bundle my functions code resulted in a guarantee of sorts that no matter how my codebase, typescript config, runtime or environment variables change, the functions will always run as intended. As a developer it significantly reduced the attention and time I had to give while setting up functions for a new project. However, it's not necessary to use a bundler, Any bundler will do, but esbuild is pretty fast, so I use that. It works on node 18 and 16 runtimes for cloud-functions as well. On the basis of all this, I recommend that a bundler should be included in the scaffolded code for typescript cloud functions, or at least be mentioned in the documentation. |
I agree with you @DibyodyutiMondal and have myself moved to using bundled functions now, since it neatly resolves a number of issues. My setup now uses es modules and rollup with Node 16, and I decided to use one project per function, and make use of the firebase config Firebase do have documents for Using module bundlers with Firebase. Due to the benefits of bundling, I don't believe I will return to using pure typescript packages for function deployment again. |
@simondotm |
Got just this working with Esbuild. I will post the solution here for others to benefit as i tried to solve this for some time. I have the same problem for a long time. But finally got a solution. Build with Esbuild (Follow instructions from Matt Pocock https://www.totaltypescript.com/build-a-node-app-with-typescript-and-esbuild)
1.1 "type": "module" in package.json
1.2 Dependencies pnpm add -D typescript esbuild @types/node npm-run-all 1.3 TypeScript Config
2.1 build script
2.2 dev script
Run pnpm dev And finally you got your Firebase functions emulator running with Typescript, ESM and even wathc mode (watch is guide slow for me, takes 5-8s for functions to handle new code, but still it does it automatically🎉) |
Making this work with * firebase functions * esm modules, esp with "type":"modules" in package.json * typescript is hilariously hard. Firebase functions do not understand esm modules. This causes their preprocessor to blow up if you use them. There is a workaround here: firebase/firebase-tools#2994 (comment) that uses esbuild to bundle all of the code into one huge index.js that papers over all the preprocessing issues. It also does some magic with package.json and the npm-run-all package to ensure all of esbuild, tsc, and firebase run in parallel so that changing a typescript source file results in an actual behavior change to a running function emulator. However, this is still not sufficient because esbuild has decided to bundle everything in devDependencies and dependencies into the index.js. This will pull in old commonJS packages but attempt to run that syntax in an ESM environment which causes a cryptic "Dynamic require of 'something' is not supported" This will send you down rabbit holes of trying to fix tsc config because it looks like you are exporting the wrong module loading type..which will lead you to try setting tsconfig's module to "nodenext"...which confusing *requires* you to put .js after all your relative path imports even if the source file is .ts. This is confusing as heck. But the real problem is you need to exclude packages from esbuild: evanw/esbuild#3324 (comment) And now everything will run. Howeever, it means you have to ensure the deploy picks up your node_modules.... ugh.
Making this work with * firebase functions * esm modules, esp with "type":"modules" in package.json * typescript is hilariously hard. Firebase functions do not understand esm modules. This causes their preprocessor to blow up if you use them. There is a workaround here: firebase/firebase-tools#2994 (comment) that uses esbuild to bundle all of the code into one huge index.js that papers over all the preprocessing issues. It also does some magic with package.json and the npm-run-all package to ensure all of esbuild, tsc, and firebase run in parallel so that changing a typescript source file results in an actual behavior change to a running function emulator. However, this is still not sufficient because esbuild has decided to bundle everything in devDependencies and dependencies into the index.js. This will pull in old commonJS packages but attempt to run that syntax in an ESM environment which causes a cryptic "Dynamic require of 'something' is not supported" This will send you down rabbit holes of trying to fix tsc config because it looks like you are exporting the wrong module loading type..which will lead you to try setting tsconfig's module to "nodenext"...which confusing *requires* you to put .js after all your relative path imports even if the source file is .ts. This is confusing as heck. But the real problem is you need to exclude packages from esbuild: evanw/esbuild#3324 (comment) And now everything will run. Howeever, it means you have to ensure the deploy picks up your node_modules.... ugh. Oh...and jest's setup requires you to NOT use verbatimModuleSyntax otherwise you cannot import typescript types since its config file is a CommonJs setup and the import syntax is ESM.
Making this work with * firebase functions * esm modules, esp with "type":"modules" in package.json * typescript is hilariously hard. Firebase functions do not understand esm modules. This causes their preprocessor to blow up if you use them. There is a workaround here: firebase/firebase-tools#2994 (comment) that uses esbuild to bundle all of the code into one huge index.js that papers over all the preprocessing issues. It also does some magic with package.json and the npm-run-all package to ensure all of esbuild, tsc, and firebase run in parallel so that changing a typescript source file results in an actual behavior change to a running function emulator. However, this is still not sufficient because esbuild has decided to bundle everything in devDependencies and dependencies into the index.js. This will pull in old commonJS packages but attempt to run that syntax in an ESM environment which causes a cryptic "Dynamic require of 'something' is not supported" This will send you down rabbit holes of trying to fix tsc config because it looks like you are exporting the wrong module loading type..which will lead you to try setting tsconfig's module to "nodenext"...which confusing *requires* you to put .js after all your relative path imports even if the source file is .ts. This is confusing as heck. But the real problem is you need to exclude packages from esbuild: evanw/esbuild#3324 (comment) And now everything will run. Howeever, it means you have to ensure the deploy picks up your node_modules.... ugh. Oh...and jest's setup requires you to NOT use verbatimModuleSyntax otherwise you cannot import typescript types since its config file is a CommonJs setup and the import syntax is ESM.
Currently, it is not possible to use native ES Modules with Cloud Functions for Firebase in Node 14 due to how triggers are parsed from the code. Unfortunately, adding support is not trivial (or I would have done it when I added Node 14 support generally), but we'd like to do it in the future.
Please upvote this issue if it's important to you!
The text was updated successfully, but these errors were encountered: