Hosting a file along with my function

@voteymcvotefaceuk There is a way to handle this for both path resolution to be the same and get your assets into your function.

  • Put your function into a sub-directory under the folder you specified in your netlify.toml (i.e pdf-read.js would be put to `functions/pdf-read/pdf-read.js).
  • Include the assets in the functions/pdf-read folder
  • Change your path with require.resolve
    const contents = fs.readFileSync(require.resolve('./dummy.pdf'))

Working example in this repository to read a .txt file in the function under functions/read-file

Note: Remember, this is the target functions directory, not a source directory prior to a build. The example does not do a webpack functions build using netlify-lambda. That process would take a little more workflow to get the bundle and assets into the target location. As long as you are using nodejs javascript, this is a valid solution.

2 Likes

Moving the static files my function depended on to a subdirectory along with the function itself worked beautifully, thank you!

Although I didn’t need to use require.resolve to get the filepath. Simply using fs.readFileSync('dummy.pdf') worked fine. Are you sure this step is necessary?

Not needed in most cases. You should be good.

I’m trying to read files as shown in the example repo, but for some reason I keep getting cannot find module './build/__app.html.

/** get-html.js **/
exports.handler = async (event, context) => {
const template = fs.readFileSync(require.resolve('./build/__app.html'), 'utf8')
const script = fs.readFileSync(require.resolve('./build/bundle.js'), 'utf8')
...

The structure:

project/
  functions/
    get-html/
      build/
        __app.html
        bundle.js
      get-html.js //function being called

Am I doing something wrong?

Hi @jakobrosenberg,

Is your repo public? if so can you please share a link? Also are you zipping the function yourself or using zip it and ship it?

Functions of files in subdirectories of the main folder for function are invisible for build. So your solution currently doesn’t work.

Hey @step what does your file structure look like?

Following:
/dist hosting
/functions editing functions
/lambda build functions

To be honest, I just run netlify deploy. I assumed Netlify automatically zipped as necessary. This is not the case?

Also, is it possible to access files outside the respective function’s folder? Ie. Access files from the publish folder.

I created a working example to allow you to discover where your setup might not work:

It will work with netlify deploy.

Hope this helps you discover what is wrong about your setup.
Feel free to ask if anything about that setup is unclear.

2 Likes

Doesn’t work with Lambda. I have to resort to raw-loader to make it work.

hi @patarapolw - can you say a little more about how it doesn’t work? Are you getting an error message? What behaviour are you seeing? Thank you.

Hi, I’m having a similar issue.
I’m developing a lambda locally using this command (through Yarn):
netlify-lambda serve src/functions --config webpack.functions.js
(to add nodeExternals because I’m using Firebase).

I also have a .babelrc in my functions dir to make typescript work.
All is great both locally and on Netlify.

Now I would like to embark PDF files that I want to open with fs.readFileSync.

Initially, I had this error:

ENOENT: no such file or directory

Then I used require.resolve as suggested, but I got:

WARNING in ./pdf/coupon.pdf 1:0
Module parse failed: Unexpected token (1:0)

So I guessed Webpack was trying to load it as a source file, so I updated my webpack.functions.js like this:

const nodeExternals = require("webpack-node-externals")

module.exports = {
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        test: /\.pdf$/i,
        use: [{ loader: "file-loader" }],
      },
    ],
  },
}

But now the error is:

{ Error: ENXIO: no such device or address, read
    at Object.readSync (fs.js:493:3)
    at tryReadSync (fs.js:332:20)
    at Object.readFileSync (fs.js:369:19)
    at Object.b [as handler] ([...]/functions/coupon.js:1:4032)
    at process._tickCallback (internal/process/next_tick.js:68:7) errno: -6, syscall: 'read', code: 'ENXIO' }

Any clue? :confused:

Edit: I realized that using a Babel loader meant the file would actually be read, so I don’t need to read it again, right? So I just imported the PDF:

const pdf = require("./assets/template.pdf")

(import works too)

but now the pdf-lib is failing with an error:

Error: Failed to parse PDF document (line:0 col:54 offset=27): No PDF header found

But if I make a Node script to open that PDF with that lib, it works…

Hmm, I am not sure if that is the best way to include a file but I am not a major functions user. We have a known working example of including a separate file shown here:

… can you try to emulate that pattern a bit to see if it works better?

Are there any solutions or workarounds for accessing files from outside the functions directory, like the publish directory?

Since the ‘publish’ is essentially your site, you could use the env var URL (as mentioned here) to address the file on your deployed site. Note that your lambda function does not live in the same ‘machine’ as your netlify site, so you can’t access any files from your publish directory directly through some filesystem method.

Hope that answers your question.

Thanks for your reply Dennis.

Another solution for me would be accessing files outside the root directory. Is that possible?

Ideally
Root Directory: scripts/now
Output Directory: ../../dist
Build Command npm run build

for a setup that looks like this

src/
dist/
scripts/
  now/
    api/
    build-for-now.js #Now specific build instructions
    now.json
    package.json #{"scripts": {"build": "node ./build-for-now.js"}}
    ...
  dev-server/
  service1/
  service2/

Since lambda functions are self-contained, that would only work if your build pipeline copies each required file to your function folder and explicitly bundles it up. Our buildbot will not natively handle what you described, though if you bundle your function manually things should be doable.

I think I am running into a similar issue. When I run my site locally with netlify dev and upload a file it works fine and I see it pop up in my CMS. I keep seeing errors in the serverless function that it cant find the file I am trying to open and upload to contentful. I put a couple different logging statements to get to the bottom of it

INFO Created file dragonsblood.jpg in directory /var/task

INFO Possible path: /var/task/dragonsblood.jpg

INFO Lambda root: /var/task

INFO Relative path: …/dragonsblood.jpg

ERROR Uncaught Exception {“errorType”:“Error”,“errorMessage”:“ENOENT: no such file or directory, open ‘/var/task/dragonsblood.jpg’”,“code”:“ENOENT”,“errno”:-2,“syscall”:“open”,“path”:"/var/task/dragonsblood.jpg",“stack”:[“Error: ENOENT: no such file or directory, open ‘/var/task/dragonsblood.jpg’”]}

Ive pin pointed in my code the block of code that is causing the issues when run on netlify: https://github.com/L-Town-FC/we-roast/blob/dev/functions/utils/contentful.js#L92-L106

I dont think that I know enough about the netlify servless functions run time but I assumed I could just write a file and upload it to my CMS.

The process.env.LAMBDA_TASK_ROOT method described above had the same issue. When I run it locally the image writes to the root of my project which I capture with path.join(process.cwd(), fileName).

Any thoughts? @Dennis

Also on a side note should I be deleting these images or does the serverless function just spin down and drop any changes?

One thing to consider is that the execution path of the function is not the folder that your function is actually located. Here’s an example of how I find a file I included in my zipped function: https://github.com/depadiernos/function-deploy-test/blob/master/lambda/zipped-function/zipped-function.js#L6-L8