rsnext/packages/next/build/webpack/loaders/next-middleware-loader.ts
Damien Simonin Feugas 851e9aeba9
fix(edge-runtime): undefined global in edge runtime. (#38769)
## How to reproduce

1. create a next.js app with a middleware (or an edge route) that imports a node.js module:
   ```js
   // middleware.js
   import { NextResponse } from 'next/server'
   import { basename } from 'path'
   
   export default async function middleware() {
     basename()
     return NextResponse.next()
   }
   ```
2. deploy it to vercel with `vc`
3. go to the your function logs in Vercel Front (https://vercel.com/$user/$project/$deployment/functions)
4. in another tab, query your application
   > it results in a 500 page:
   <img width="517" alt="image" src="https://user-images.githubusercontent.com/186268/179557102-72568ca9-bcfd-49e2-9b9c-c51c3064f2d7.png">

    >  in the logs you should see:
   <img width="1220" alt="image" src="https://user-images.githubusercontent.com/186268/179557266-498f3290-b7df-46ac-8816-7bb396821245.png">

## Expected behavior

The route should fail indeed in a 500, because Edge runtime **does not support node.js modules**. However the error in logs should be completely different:
```shell
error - Error: The edge runtime does not support Node.js 'path' module.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
```

## Notes to reviewers

I introduced this issue in #38234.
Prior to the PR above, the app would not even build, as we were checking imported node.js module during the build with AST analysis.
Since #38234, the app would build and should fail at runtime, with an appropriate error.

The mistake was to declare `__import_unsupported` function in the sandbox's context, that is only used in `next dev` and `next start`, but not shipped to Vercel platform.

By loading it inside webpack loaders (both middleware and edge route), we ensure it will be defined on Vercel as well. 

The existing test suite (`pnpm testheadless --testPathPattern=runtime-module-error`) covers them.
2022-07-20 14:53:27 +00:00

49 lines
1.4 KiB
TypeScript

import { getModuleBuildInfo } from './get-module-build-info'
import { stringifyRequest } from '../stringify-request'
import { MIDDLEWARE_LOCATION_REGEXP } from '../../../lib/constants'
export type MiddlewareLoaderOptions = {
absolutePagePath: string
page: string
matcherRegexp?: string
}
export default function middlewareLoader(this: any) {
const {
absolutePagePath,
page,
matcherRegexp: base64MatcherRegex,
}: MiddlewareLoaderOptions = this.getOptions()
const matcherRegexp = Buffer.from(
base64MatcherRegex || '',
'base64'
).toString()
const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextEdgeMiddleware = {
matcherRegexp,
page:
page.replace(new RegExp(`/${MIDDLEWARE_LOCATION_REGEXP}$`), '') || '/',
}
return `
import { adapter, blockUnallowedResponse, enhanceGlobals } from 'next/dist/server/web/adapter'
enhanceGlobals()
var mod = require(${stringifiedPagePath})
var handler = mod.middleware || mod.default;
if (typeof handler !== 'function') {
throw new Error('The Middleware "pages${page}" must export a \`middleware\` or a \`default\` function');
}
export default function (opts) {
return blockUnallowedResponse(adapter({
...opts,
page: ${JSON.stringify(page)},
handler,
}))
}
`
}