unjs / unenv

🕊️ Convert javaScript code to be runtime agnostic
MIT License
464 stars 22 forks source link

Incompatible with express.js #328

Open Tofandel opened 1 month ago

Tofandel commented 1 month ago

Environment

Nuxt with default node env

Reproduction

import createApp from 'express';
import {IncomingMessage} from 'unenv/runtime/node/http/_request'
import {ServerResponse} from 'unenv/runtime/node/http/_response'

const req = new IncomingMessage();
const res = new ServerResponse(req);

// Taken from express init middleware implementation
const app = createApp();
Object.setPrototypeOf(res, app.response);

res.setHeader('vary', 'test'); // TypeError: Cannot set properties of undefined (setting 'vary')

Describe the bug

When sending a local request to an express server using fetch, a server error occurs

Additional context

This is very problematic in nuxt with useFetch and a legacy endpoint that uses an express server behind the scenes (This issue took me about 20h to debug because it's such a complex chain of events to follow and the errors hidden behind 3 layers could not even be sent properly because the response object was broken)

It seems the problem is that unenv's fetch always uses its internal node/http/request,response even if build with the node env preset and this is problematic because then local requests behave differently than external requests https://github.com/unjs/unenv/blob/main/src/runtime/fetch/call.ts#L1-L2

unenv's fetch is then used by nitro even if env is set to node https://github.com/unjs/nitro/blob/a399e189576d4c18d7f837bd454e0503e1f53980/src/types/runtime/nitro.ts#L7

Logs

No response

Tofandel commented 1 month ago

What would be the best way to solve this issue? From the top of my head I could see a few solutions

  1. We could export 2 differents unenv/runtime/fetch/index using the package.json exports field and specify one for node and one for default

  2. We could have IncomingMessage and ServerResponse files function as polyfills and if importing node:http fails then we export the unenv implementation, otherwise we export the node implementation

  3. We could import from node:http from the call implementation and let the rollup aliasing do the job