@@ -3,9 +3,10 @@ import bridgeFile from '@vercel/node-bridge'
3
3
import chalk from 'chalk'
4
4
import destr from 'destr'
5
5
import { copyFile , ensureDir , existsSync , readJSON , writeFile , writeJSON , stat } from 'fs-extra'
6
+ import { PrerenderManifest } from 'next/dist/build'
6
7
import type { ImageConfigComplete , RemotePattern } from 'next/dist/shared/lib/image-config'
7
8
import { outdent } from 'outdent'
8
- import { join , relative , resolve , dirname } from 'pathe'
9
+ import { join , relative , resolve , dirname , basename , extname } from 'pathe'
9
10
import glob from 'tiny-glob'
10
11
11
12
import {
@@ -32,27 +33,35 @@ import { pack } from './pack'
32
33
import { ApiRouteType } from './types'
33
34
import { getFunctionNameForPage } from './utils'
34
35
35
- export interface ApiRouteConfig {
36
+ export interface RouteConfig {
36
37
functionName : string
37
38
functionTitle ?: string
38
39
route : string
39
- config : ApiConfig
40
40
compiled : string
41
41
includedFiles : string [ ]
42
42
}
43
43
44
- export interface APILambda {
44
+ export interface ApiRouteConfig extends RouteConfig {
45
+ config : ApiConfig
46
+ }
47
+
48
+ export interface SSRLambda {
45
49
functionName : string
46
50
functionTitle : string
47
- routes : ApiRouteConfig [ ]
51
+ routes : RouteConfig [ ]
48
52
includedFiles : string [ ]
53
+ }
54
+
55
+ export interface APILambda extends SSRLambda {
56
+ routes : ApiRouteConfig [ ]
49
57
type ?: ApiRouteType
50
58
}
51
59
52
60
export const generateFunctions = async (
53
61
{ FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC , INTERNAL_FUNCTIONS_SRC , PUBLISH_DIR } : NetlifyPluginConstants ,
54
62
appDir : string ,
55
63
apiLambdas : APILambda [ ] ,
64
+ ssrLambdas : SSRLambda [ ] ,
56
65
) : Promise < void > => {
57
66
const publish = resolve ( PUBLISH_DIR )
58
67
const functionsDir = resolve ( INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC )
@@ -144,6 +153,12 @@ export const generateFunctions = async (
144
153
join ( functionsDir , functionName , 'handlerUtils.js' ) ,
145
154
)
146
155
await writeFunctionConfiguration ( { functionName, functionTitle, functionsDir } )
156
+
157
+ const nfInternalFiles = await glob ( join ( functionsDir , functionName , '**' ) )
158
+ const lambda = ssrLambdas . find ( ( l ) => l . functionName === functionName )
159
+ if ( lambda ) {
160
+ lambda . includedFiles . push ( ...nfInternalFiles )
161
+ }
147
162
}
148
163
149
164
await writeHandler ( HANDLER_FUNCTION_NAME , HANDLER_FUNCTION_TITLE , false )
@@ -295,13 +310,17 @@ export const traceNPMPackage = async (packageName: string, publish: string) => {
295
310
}
296
311
}
297
312
298
- export const getAPIPRouteCommonDependencies = async ( publish : string ) => {
313
+ export const getCommonDependencies = async ( publish : string ) => {
299
314
const deps = await Promise . all ( [
300
315
traceRequiredServerFiles ( publish ) ,
301
316
traceNextServer ( publish ) ,
302
317
303
318
// used by our own bridge.js
304
319
traceNPMPackage ( 'follow-redirects' , publish ) ,
320
+
321
+ // using package.json because otherwise, we'd find some /dist/... path
322
+ traceNPMPackage ( '@netlify/functions/package.json' , publish ) ,
323
+ traceNPMPackage ( 'is-promise' , publish ) ,
305
324
] )
306
325
307
326
return deps . flat ( 1 )
@@ -329,12 +348,106 @@ const getBundleWeight = async (patterns: string[]) => {
329
348
return sum ( sizes . flat ( 1 ) )
330
349
}
331
350
351
+ const changeExtension = ( file : string , extension : string ) => {
352
+ const base = basename ( file , extname ( file ) )
353
+ return join ( dirname ( file ) , base + extension )
354
+ }
355
+
356
+ const getSSRDependencies = async ( publish : string ) : Promise < string [ ] > => {
357
+ const prerenderManifest : PrerenderManifest = await readJSON ( join ( publish , 'prerender-manifest.json' ) )
358
+
359
+ return [
360
+ ...Object . entries ( prerenderManifest . routes ) . flatMap ( ( [ route , ssgRoute ] ) => {
361
+ if ( ssgRoute . initialRevalidateSeconds === false ) {
362
+ return [ ]
363
+ }
364
+
365
+ if ( ssgRoute . dataRoute . endsWith ( '.rsc' ) ) {
366
+ return [
367
+ join ( publish , 'server' , 'app' , ssgRoute . dataRoute ) ,
368
+ join ( publish , 'server' , 'app' , changeExtension ( ssgRoute . dataRoute , '.html' ) ) ,
369
+ ]
370
+ }
371
+
372
+ const trimmedPath = route === '/' ? 'index' : route . slice ( 1 )
373
+ return [
374
+ join ( publish , 'server' , 'pages' , `${ trimmedPath } .html` ) ,
375
+ join ( publish , 'server' , 'pages' , `${ trimmedPath } .json` ) ,
376
+ ]
377
+ } ) ,
378
+ join ( publish , '**' , '*.html' ) ,
379
+ join ( publish , 'static-manifest.json' ) ,
380
+ ]
381
+ }
382
+
383
+ export const getSSRLambdas = async ( publish : string ) : Promise < SSRLambda [ ] > => {
384
+ const commonDependencies = await getCommonDependencies ( publish )
385
+ const ssrRoutes = await getSSRRoutes ( publish )
386
+
387
+ // TODO: for now, they're the same - but we should separate them
388
+ const nonOdbRoutes = ssrRoutes
389
+ const odbRoutes = ssrRoutes
390
+
391
+ const ssrDependencies = await getSSRDependencies ( publish )
392
+
393
+ return [
394
+ {
395
+ functionName : HANDLER_FUNCTION_NAME ,
396
+ functionTitle : HANDLER_FUNCTION_TITLE ,
397
+ includedFiles : [
398
+ ...commonDependencies ,
399
+ ...ssrDependencies ,
400
+ ...nonOdbRoutes . flatMap ( ( route ) => route . includedFiles ) ,
401
+ ] ,
402
+ routes : nonOdbRoutes ,
403
+ } ,
404
+ {
405
+ functionName : ODB_FUNCTION_NAME ,
406
+ functionTitle : ODB_FUNCTION_TITLE ,
407
+ includedFiles : [ ...commonDependencies , ...ssrDependencies , ...odbRoutes . flatMap ( ( route ) => route . includedFiles ) ] ,
408
+ routes : odbRoutes ,
409
+ } ,
410
+ ]
411
+ }
412
+
413
+ const getSSRRoutes = async ( publish : string ) : Promise < RouteConfig [ ] > => {
414
+ const pageManifest = ( await readJSON ( join ( publish , 'server' , 'pages-manifest.json' ) ) ) as Record < string , string >
415
+ const pageManifestRoutes = Object . entries ( pageManifest ) . filter (
416
+ ( [ page , compiled ] ) => ! page . startsWith ( '/api/' ) && ! compiled . endsWith ( '.html' ) ,
417
+ )
418
+
419
+ const appPathsManifest : Record < string , string > = await readJSON (
420
+ join ( publish , 'server' , 'app-paths-manifest.json' ) ,
421
+ ) . catch ( ( ) => ( { } ) )
422
+ const appRoutes = Object . entries ( appPathsManifest )
423
+
424
+ const routes = [ ...pageManifestRoutes , ...appRoutes ]
425
+
426
+ return await Promise . all (
427
+ routes . map ( async ( [ route , compiled ] ) => {
428
+ const functionName = getFunctionNameForPage ( route )
429
+
430
+ const compiledPath = join ( publish , 'server' , compiled )
431
+
432
+ const routeDependencies = await getDependenciesOfFile ( compiledPath )
433
+ const includedFiles = [ compiledPath , ...routeDependencies ]
434
+
435
+ return {
436
+ functionName,
437
+ route,
438
+ compiled,
439
+ includedFiles,
440
+ }
441
+ } ) ,
442
+ )
443
+ }
444
+
332
445
export const getAPILambdas = async (
333
446
publish : string ,
334
447
baseDir : string ,
335
448
pageExtensions : string [ ] ,
336
449
) : Promise < APILambda [ ] > => {
337
- const commonDependencies = await getAPIPRouteCommonDependencies ( publish )
450
+ const commonDependencies = await getCommonDependencies ( publish )
338
451
339
452
const threshold = LAMBDA_WARNING_SIZE - ( await getBundleWeight ( commonDependencies ) )
340
453
0 commit comments