Question

Trouble with specific npm module dependency in Node Function

I’m using Functions and trying to use the @protobuf-ts/runtime npm module as a runtime dependency in my project. This runtime dependency works locally on my machine but when I deploy and then invoke my function I get greeted with Error: Cannot find module '@protobuf-ts/runtime', with the code MODULE_NOT_FOUND. I find this surprising as I installed this dependency the same way I installed another dependency, @aws-sdk/client-s3, which works as expected at runtime.

In an attempt to isolate the issue I have…

  1. Initialised a fresh Functions project with doctl serverless init --language ts test-do-project-0.
  2. Changed the sample/hello function runtime from nodejs:default to nodejs:18 in the project.yml
  3. Run npm install @protobuf-ts/runtime in the sample/hello function directory.
  4. Imported a class from @protobuf-ts/runtime and used it in the main function.

When I deploy and invoke the function it errors with:

2023-05-02T21:57:49.277554254Z stdout: ReferenceError: exports is not defined in ES module scope
2023-05-02T21:57:49.277556929Z stdout:     at file:///tmp/index.mjs:2:23
2023-05-02T21:57:49.277577833Z stdout:     at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

I have tried fiddling with the tsconfig.json module settings to resolve the issue to no avail, only succeeding in matching my original error (Cannot find module '@protobuf-ts/runtime'). The function package.json does not include "type": "module".

If I instead npm install @aws-sdk/client-s3 and import and use it in hello.ts instead of @protobuf-ts/runtime, then everything works as expected. This makes me wonder if it is a problem specific to the @protobuf-ts/runtime npm module, but since it works locally on my machine, it must be something to do with how this dependency is handled in the DO Function Node runtime. Perhaps there is a difference in npm version?

I have read through both the Build Process and Functions Node.js JavaScript Runtime docs but can’t figure out what is going awry.

For reference and reproduction I have pushed the issue isolating Functions project to Github: https://github.com/willjvsmith/test-do-project-0

Thanks!


Submit an answer


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Sign In or Sign Up to Answer

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

Matt Welke
DigitalOcean Employee
DigitalOcean Employee badge
May 7, 2023
Accepted Answer

Sorry to hear you had trouble with TypeScript with Functions. I was able to reproduce the issue you encountered by creating projects with the starter template and changing them in the way you describe. When I used the starter TypeScript template to deploy a function that used just @aws-sdk/client-s3, it worked. And when I used that same template to deploy a function used just @protobuf-ts/runtime, it didn’t work, with the exact same error message you posted here. I was also able to get a function working using @protobuf-ts/runtime by changing a few files.

This is happening because the starter template includes just enough to get you going. In this case, it’s enough for when your function consists of one TypeScript file. This consists of the default TypeScript compiler settings, which output the single built file into the lib directory, the .include file which ensures that doctl includes that directory and only that directory in what’s uploaded to our servers during a deployment, and the line "main": "lib/index.js" in package.json, which tells our runtime to look in that directory for the file it should import to run your function.

But your project has outgrown this starter template. You’re using an NPM library. So you need to update a few things to make this work:

  1. Update the .include file to reflect what now needs to be included in the artifact uploaded to our servers during the deployment. Add the lines node_modules (so that your NPM library is included) and package.json (because function upload artifacts that both consist of more than one file and use NPM libraries must include a package.json file at the top level).

  2. Rename the hello.ts file to index.ts and update package.json to change "main": "lib/hello.js", to "main": "lib/index.js", (because function upload artifacts that both consist of more than one file and use NPM libraries must have an entry point named index.js or index.mjs).

  3. Increase the memory higher than the default 256 MB (I found that for this particular library, 256 MB was not enough for the function to run and import this code successfully).

You did not have to complete these steps when you were trying this with @aws-sdk/client-s3 because that NPM library happens to be included already in our runtime. So doctl was uploading just the hello.js file, which had the line const client_s3_1 = require("@aws-sdk/client-s3");, which had no problem being executed because the library was already on the runtime and could be imported that way.

I understand this could be confusing because this isn’t in our product documentation. We don’t officially support these included libraries. We plan to begin officially supporting libraries included in the runtimes so that customers can use them in their functions without them contributing to the 48 MB function size limit. For Node.js, this could happen when we publish the Node.js 20 runtime. We don’t yet have a planned date for publishing this runtime. Keep an eye on Functions release notes for that.

One thing I want to add is that what helped me troubleshoot this was using the --remote-build flag while I deployed the function. This is an optional way to deploy standalone functions, and it’s the only way to deploy functions when you deploy them using App Platform. When you deploy this way, instead of the build taking place on your computer and then the built code being uploaded to our servers, the source code is first uploaded to our servers and then the build takes place there. This helps you troubleshoot that your .include file is correct, because it needs to be correct in order for all of the required source code files to be included in what is uploaded when the deployment begins.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel