brillout / telefunc

Remote Functions. Instead of API.
https://telefunc.com
MIT License
664 stars 28 forks source link

Vercel serverless function bug? #121

Open jscottsf opened 1 week ago

jscottsf commented 1 week ago

Followed the examples and deployed a Vercel serverless handler. Upon calling a function, a "bug" is logged. Everything builds correctly and it runs locally with a dev server. Using Build Output API v3.

Vercel serverless log:

Calling: {
  method: 'POST',
  url: '/_telefunc',
  rawBody: '{"file":"/src/telefuncs/Cart.telefunc.js","name":"fnLoadCart","args":[true,null,null]}'
}
Error: [telefunc@0.1.78][Bug] You stumbled upon a Telefunc bug. Go to https://github.com/brillout/telefunc/issues/new and copy-paste this error. A maintainer will fix the bug (usually under 24 hours). 
    at isWebpack (file:///var/task/index.js:65:47023)
    at loadTelefuncFiles (file:///var/task/index.js:65:16854)
    at runTelefunc_ (file:///var/task/index.js:65:7443)
    at runTelefunc (file:///var/task/index.js:65:6584)
    at telefunc (file:///var/task/index.js:65:32015)
    at handler (file:///var/task/index.js:71:1612366)
    at async Server.<anonymous> (/opt/rust/nodejs.js:1:10566)

The handler:

import { telefunc } from 'telefunc'
import { text } from 'micro'

export default async function handler(req, res) {
  const { method, url } = req

  if (url === '/_telefunc') {
    const context = {}
    const rawBody = await text(req)

    console.log('Calling:', { method, url, rawBody })

    const httpResponse = await telefunc({
      url,
      method,
      body: rawBody,
      context
    })
    const { body, statusCode, contentType } = httpResponse

    res.statusCode = statusCode
    res.setHeader('content-type', contentType)
    res.end(body)
  }
}

Config.json

{
    "routes":
    [
       {
            "dest": "/_telefunc",
            "methods":
            [
                "POST"
            ],
            "src": "/_telefunc"
        }
    ],
    "version": 3
}

NOTE: I have a Vike serverless renderer as another function and it works perfectly.

Any ideas on what to try?

jscottsf commented 1 week ago

Additional info

The build step is:

# 7. Bundle telefunc function to a single file.
echo "INFO: Bundling SSR render functions"
cd .vercel/output/functions/_telefunc.func
npx ncc build --minify --out . index.js
if [ $? -ne 0 ]
then
  echo "ERRO: Bundling failed, exiting"
  exit 1
fi

The runtime config is:

{
    "handler": "index.js",
    "launcherType": "Nodejs",
    "runtime": "nodejs20.x",
    "shouldAddHelpers": true
}

Seems to be happening at this line inside loadTelefuncFiles:

if (!(0, o.isWebpack)() || (0, o.isVikeApp)()) {
jscottsf commented 1 week ago

Looks like ncc is Webpack-based, and the assertion is failing on the Webpack test.

brillout commented 1 week ago

It seems to be a relatively simple issue: the failing assertion inside isWebpack() means that the test for checking whether the environment is webpack isn't working as expected. Ideas for improving the isWebpack() function? I guess there is a newer/better way to check?

jscottsf commented 1 week ago

Here is the ncc packed output for the current function, if this helps.

;(s, t, a) => {
  Object.defineProperty(t, '__esModule', { value: true })
  t.isWebpack = void 0
  const o = a(80241)
  function isWebpack() {
    const s = typeof require === 'function'
    const t = typeof a === 'function'
    ;(0, o.assert)(s === t)
    return s || t
  }
  t.isWebpack = isWebpack
}

Also looks like they replace __webpack_require__ with some form of __nccwpck_require<n>_

https://github.com/vercel/ncc/blob/b2a325dec1dc54f2168f838dee0b9e48ee701d18/src/index.js#L552

Maybe this could be a config setting or ENV var to guide/override the loading process in challenging installs?

brillout commented 1 week ago

Good to know, thank you!

Fix released as 0.1.79.

jscottsf commented 1 week ago

Well, now Vercel is throwing this. Hmmm.

Calling: {
  method: 'POST',
  url: '/_telefunc',
  rawBody: '{"file":"/src/telefuncs/Cart.telefunc.js","name":"fnLoadCart","args":[true,null,null]}'
}
Error: [telefunc@0.1.79][Wrong Usage] You don't seem to be using Telefunc with a supported stack. Reach out on GitHub.
    at loadTelefuncFiles (file:///var/task/index.js:65:17127)
    at runTelefunc_ (file:///var/task/index.js:65:7443)
    at runTelefunc (file:///var/task/index.js:65:6584)
    at telefunc (file:///var/task/index.js:65:32015)
    at handler (file:///var/task/index.js:71:1612340)
    at async Server.<anonymous> (/opt/rust/nodejs.js:1:10566)
    at async Server.<anonymous> (/opt/rust/nodejs.js:8:4242)
brillout commented 1 week ago

Hm. What does isWebpack() return? Ideas to make the the isWebpack() check work?

jscottsf commented 1 week ago

A bit confused actually. What code path are we trying to go down? The web app is Vike, but the serverless function is built with ncc (webpack). The serverless load is failing on invocation.

Would it help to specify the Telefunc file to load? I only have one with all server side behaviors for a cart system.

Since we reached the assertUsage at the end:

isWebpack() must be true now (the inner assert was removed and one of the tests passed). isVikeApp() must be false since we didn't enter the block.

!isWebpack() || isVikeApp()
false || false

Is the intent to have this block run?

 if (!isWebpack() || isVikeApp()) {
    const { telefuncFilesLoaded, viteProvider, telefuncFilesAll } = await loadTelefuncFilesWithVite(runContext)
    assertUsage(Object.keys(telefuncFilesAll).length > 0, getNothingFoundErr(`Vite [\`${viteProvider}\`]`))
    return { telefuncFilesLoaded, telefuncFilesAll }
  }
brillout commented 1 week ago

You're right.

isVikeApp() must be false since we didn't enter the block.

That seems to be the culprit. Why is it false even though you're using Vike? What Vike version are you using? Vike sets globalThis._isVikeApp at node_modules/vike/dist/esm/node/runtime/index-common.js (or dist/cjs if you aren't using ESM).

(Btw. reproduction welcome if you prefer.)

jscottsf commented 1 week ago
├── telefunc@0.1.79
├── vike@0.4.195

I have two serverless function builds. One for a Vike SSR, and another just for Telefunc. I wanted separate functions. Since Vike is not imported in the Telefunc one, is the global not getting set?

# 6. Bundle render function to a single file.
echo "INFO: Bundling SSR render functions"
cd .vercel/output/functions/ssr_.func
npx ncc build --minify --out . index.js
if [ $? -ne 0 ]
then
  echo "ERRO: Bundling failed, exiting"
  exit 1
fi
cd ../../../..

# 7. Bundle telefunc function to a single file.
echo "INFO: Bundling SSR render functions"
cd .vercel/output/functions/_telefunc.func
npx ncc build --minify --out . index.js
if [ $? -ne 0 ]
then
  echo "ERRO: Bundling failed, exiting"
  exit 1
fi
cd ../../../..
brillout commented 1 week ago

That makes sense. I've an idea to make it work. Let me finish what I'm currently working on then I'll implement it.

jscottsf commented 1 week ago

Solved it.

If you are using a Vike file structure, but not importing it in a serverless function implementation, then just do:

globalThis._isVikeApp = true
import { telefunc } from 'telefunc'
import { text } from 'micro'

export default async function handler(req, res) {
  const { method, url } = req

  if (url === '/_telefunc') {
    const context = {}
    const rawBody = await text(req)

    console.log('Calling:', { method, url, rawBody })

    globalThis._isVikeApp = true  // DO THIS

    const httpResponse = await telefunc({
      url,
      method,
      body: rawBody,
      context
    })
    const { body, statusCode, contentType } = httpResponse

    res.statusCode = statusCode
    res.setHeader('content-type', contentType)
    res.end(body)
  }
}

Also, the Webpack fix you made allows this to work with ncc since it's a bit more relaxed.

Thank you!!!

brillout commented 1 week ago

Indeed, that workaround should work. I'll be implementing a proper fix next week.