@@ -194,11 +194,182 @@ export const clearStaleEdgeHandlers = async (ctx: PluginContext) => {
194194}
195195
196196export const createEdgeHandlers = async ( ctx : PluginContext ) => {
197+ // Edge middleware
197198 const nextManifest = await ctx . getMiddlewareManifest ( )
199+ // Node middleware
200+ const functionsConfigManifest = await ctx . getFunctionsConfigManifest ( )
201+ 198202 const nextDefinitions = [ ...Object . values ( nextManifest . middleware ) ]
199203 await Promise . all ( nextDefinitions . map ( ( def ) => createEdgeHandler ( ctx , def ) ) )
200204
201205 const netlifyDefinitions = nextDefinitions . flatMap ( ( def ) => buildHandlerDefinition ( ctx , def ) )
206+ 207+ if ( functionsConfigManifest ?. functions ?. [ '/_middleware' ] ) {
208+ const middlewareDefinition = functionsConfigManifest ?. functions ?. [ '/_middleware' ]
209+ const entry = 'server/middleware.js'
210+ const nft = `${ entry } .nft.json`
211+ const name = 'node-middleware'
212+ 213+ // await copyHandlerDependencies(ctx, definition)
214+ const srcDir = join ( ctx . standaloneDir , ctx . nextDistDir )
215+ // const destDir = join(ctx.edgeFunctionsDir, getHandlerName({ name }))
216+ 217+ const fakeNodeModuleName = 'fake-module-with-middleware'
218+ 219+ const fakeNodeModulePath = ctx . resolveFromPackagePath ( join ( 'node_modules' , fakeNodeModuleName ) )
220+ 221+ const nftFilesPath = join ( ctx . nextDistDir , nft )
222+ const nftManifest = JSON . parse ( await readFile ( nftFilesPath , 'utf8' ) )
223+ 224+ const files : string [ ] = nftManifest . files . map ( ( file : string ) => join ( 'server' , file ) )
225+ files . push ( entry )
226+ 227+ // files are relative to location of middleware entrypoint
228+ // we need to capture all of them
229+ // they might be going to parent directories, so first we check how many directories we need to go up
230+ const maxDirsUp = files . reduce ( ( max , file ) => {
231+ let dirsUp = 0
232+ for ( const part of file . split ( '/' ) ) {
233+ if ( part === '..' ) {
234+ dirsUp += 1
235+ } else {
236+ break
237+ }
238+ }
239+ return Math . max ( max , dirsUp )
240+ } , 0 )
241+ 242+ let prefixPath = ''
243+ for ( let nestedIndex = 1 ; nestedIndex <= maxDirsUp ; nestedIndex ++ ) {
244+ // TODO: ideally we preserve the original directory structure
245+ // this is just hack to use arbitrary computed names to speed up hooking things up
246+ prefixPath += `nested-${ nestedIndex } /`
247+ }
248+ 249+ for ( const file of files ) {
250+ const srcPath = join ( srcDir , file )
251+ const destPath = join ( fakeNodeModulePath , prefixPath , file )
252+ 253+ await mkdir ( dirname ( destPath ) , { recursive : true } )
254+ 255+ if ( file === entry ) {
256+ const content = await readFile ( srcPath , 'utf8' )
257+ await writeFile (
258+ destPath ,
259+ // Next.js needs to be set on global even if it's possible to just require it
260+ // so somewhat similar to existing shim we have for edge runtime
261+ `globalThis.AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage;\n${ content } ` ,
262+ )
263+ } else {
264+ await cp ( srcPath , destPath , { force : true } )
265+ }
266+ }
267+ 268+ await writeFile ( join ( fakeNodeModulePath , 'package.json' ) , JSON . stringify ( { type : 'commonjs' } ) )
269+ 270+ // there is `/chunks/**/*` require coming from webpack-runtime that fails esbuild due to nothing matching,
271+ // so this ensure something does
272+ const dummyChunkPath = join ( fakeNodeModulePath , prefixPath , 'server' , 'chunks' , 'dummy.js' )
273+ await mkdir ( dirname ( dummyChunkPath ) , { recursive : true } )
274+ await writeFile ( dummyChunkPath , '' )
275+ 276+ // await writeHandlerFile(ctx, definition)
277+ 278+ const nextConfig = ctx . buildConfig
279+ const handlerName = getHandlerName ( { name } )
280+ const handlerDirectory = join ( ctx . edgeFunctionsDir , handlerName )
281+ const handlerRuntimeDirectory = join ( handlerDirectory , 'edge-runtime' )
282+ 283+ // Copying the runtime files. These are the compatibility layer between
284+ // Netlify Edge Functions and the Next.js edge runtime.
285+ await copyRuntime ( ctx , handlerDirectory )
286+ 287+ // Writing a file with the matchers that should trigger this function. We'll
288+ // read this file from the function at runtime.
289+ await writeFile (
290+ join ( handlerRuntimeDirectory , 'matchers.json' ) ,
291+ JSON . stringify ( middlewareDefinition . matchers ?? [ ] ) ,
292+ )
293+ 294+ // The config is needed by the edge function to match and normalize URLs. To
295+ // avoid shipping and parsing a large file at runtime, let's strip it down to
296+ // just the properties that the edge function actually needs.
297+ const minimalNextConfig = {
298+ basePath : nextConfig . basePath ,
299+ i18n : nextConfig . i18n ,
300+ trailingSlash : nextConfig . trailingSlash ,
301+ skipMiddlewareUrlNormalize : nextConfig . skipMiddlewareUrlNormalize ,
302+ }
303+ 304+ await writeFile (
305+ join ( handlerRuntimeDirectory , 'next.config.json' ) ,
306+ JSON . stringify ( minimalNextConfig ) ,
307+ )
308+ 309+ const htmlRewriterWasm = await readFile (
310+ join (
311+ ctx . pluginDir ,
312+ 'edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/pkg/htmlrewriter_bg.wasm' ,
313+ ) ,
314+ )
315+ 316+ // Writing the function entry file. It wraps the middleware code with the
317+ // compatibility layer mentioned above.
318+ await writeFile (
319+ join ( handlerDirectory , `${ handlerName } .js` ) ,
320+ `
321+ import { init as htmlRewriterInit } from './edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/src/index.ts'
322+ import { handleMiddleware } from './edge-runtime/middleware.ts';
323+
324+ import * as handlerMod from '${ fakeNodeModuleName } /${ prefixPath } ${ entry } ';
325+
326+ const handler = handlerMod.default || handlerMod;
327+
328+ await htmlRewriterInit({ module_or_path: Uint8Array.from(${ JSON . stringify ( [
329+ ...htmlRewriterWasm ,
330+ ] ) } ) });
331+
332+ export default (req, context) => {
333+ return handleMiddleware(req, context, handler);
334+ };
335+ ` ,
336+ )
337+ 338+ // buildHandlerDefinition(ctx, def)
339+ const netlifyDefinitions : Manifest [ 'functions' ] = augmentMatchers (
340+ middlewareDefinition . matchers ?? [ ] ,
341+ ctx ,
342+ ) . map ( ( matcher ) => {
343+ return {
344+ function : getHandlerName ( { name } ) ,
345+ name : `Next.js Node Middleware Handler` ,
346+ pattern : matcher . regexp ,
347+ cache : undefined ,
348+ generator : `${ ctx . pluginName } @${ ctx . pluginVersion } ` ,
349+ }
350+ } )
351+ 352+ const netlifyManifest : Manifest = {
353+ version : 1 ,
354+ functions : netlifyDefinitions ,
355+ }
356+ await writeEdgeManifest ( ctx , netlifyManifest )
357+ 358+ return
359+ }
360+ 361+ // if (functionsConfigManifest?.functions?.['/_middleware']) {
362+ // const nextDefinition: Pick<
363+ // (typeof nextManifest.middleware)[''],
364+ // 'name' | 'env' | 'files' | 'wasm' | 'matchers'
365+ // > = {
366+ // name: 'middleware',
367+ // env: {},
368+ // files: [],
369+ // wasm: [],
370+ // }
371+ // }
372+ 202373 const netlifyManifest : Manifest = {
203374 version : 1 ,
204375 functions : netlifyDefinitions ,
0 commit comments