@@ -194,11 +194,182 @@ export const clearStaleEdgeHandlers = async (ctx: PluginContext) => {
194
194
}
195
195
196
196
export const createEdgeHandlers = async ( ctx : PluginContext ) => {
197
+ // Edge middleware
197
198
const nextManifest = await ctx . getMiddlewareManifest ( )
199
+ // Node middleware
200
+ const functionsConfigManifest = await ctx . getFunctionsConfigManifest ( )
201
+
198
202
const nextDefinitions = [ ...Object . values ( nextManifest . middleware ) ]
199
203
await Promise . all ( nextDefinitions . map ( ( def ) => createEdgeHandler ( ctx , def ) ) )
200
204
201
205
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/[email protected] /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/[email protected] /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
+
202
373
const netlifyManifest : Manifest = {
203
374
version : 1 ,
204
375
functions : netlifyDefinitions ,
0 commit comments