@@ -3,9 +3,10 @@ import bridgeFile from '@vercel/node-bridge'
33import chalk from 'chalk'
44import destr from 'destr'
55import { copyFile , ensureDir , existsSync , readJSON , writeFile , writeJSON , stat } from 'fs-extra'
6+ import { PrerenderManifest } from 'next/dist/build'
67import type { ImageConfigComplete , RemotePattern } from 'next/dist/shared/lib/image-config'
78import { outdent } from 'outdent'
8- import { join , relative , resolve , dirname } from 'pathe'
9+ import { join , relative , resolve , dirname , basename , extname } from 'pathe'
910import glob from 'tiny-glob'
1011
1112import {
@@ -32,27 +33,35 @@ import { pack } from './pack'
3233import { ApiRouteType } from './types'
3334import { getFunctionNameForPage } from './utils'
3435
35- export interface ApiRouteConfig {
36+ export interface RouteConfig {
3637 functionName : string
3738 functionTitle ?: string
3839 route : string
39- config : ApiConfig
4040 compiled : string
4141 includedFiles : string [ ]
4242}
4343
44- export interface APILambda {
44+ export interface ApiRouteConfig extends RouteConfig {
45+ config : ApiConfig
46+ }
47+
48+ export interface SSRLambda {
4549 functionName : string
4650 functionTitle : string
47- routes : ApiRouteConfig [ ]
51+ routes : RouteConfig [ ]
4852 includedFiles : string [ ]
53+ }
54+
55+ export interface APILambda extends SSRLambda {
56+ routes : ApiRouteConfig [ ]
4957 type ?: ApiRouteType
5058}
5159
5260export const generateFunctions = async (
5361 { FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC , INTERNAL_FUNCTIONS_SRC , PUBLISH_DIR } : NetlifyPluginConstants ,
5462 appDir : string ,
5563 apiLambdas : APILambda [ ] ,
64+ ssrLambdas : SSRLambda [ ] ,
5665) : Promise < void > => {
5766 const publish = resolve ( PUBLISH_DIR )
5867 const functionsDir = resolve ( INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC )
@@ -144,6 +153,12 @@ export const generateFunctions = async (
144153 join ( functionsDir , functionName , 'handlerUtils.js' ) ,
145154 )
146155 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+ }
147162 }
148163
149164 await writeHandler ( HANDLER_FUNCTION_NAME , HANDLER_FUNCTION_TITLE , false )
@@ -295,13 +310,17 @@ export const traceNPMPackage = async (packageName: string, publish: string) => {
295310 }
296311}
297312
298- export const getAPIPRouteCommonDependencies = async ( publish : string ) => {
313+ export const getCommonDependencies = async ( publish : string ) => {
299314 const deps = await Promise . all ( [
300315 traceRequiredServerFiles ( publish ) ,
301316 traceNextServer ( publish ) ,
302317
303318 // used by our own bridge.js
304319 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 ) ,
305324 ] )
306325
307326 return deps . flat ( 1 )
@@ -329,12 +348,106 @@ const getBundleWeight = async (patterns: string[]) => {
329348 return sum ( sizes . flat ( 1 ) )
330349}
331350
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+
332445export const getAPILambdas = async (
333446 publish : string ,
334447 baseDir : string ,
335448 pageExtensions : string [ ] ,
336449) : Promise < APILambda [ ] > => {
337- const commonDependencies = await getAPIPRouteCommonDependencies ( publish )
450+ const commonDependencies = await getCommonDependencies ( publish )
338451
339452 const threshold = LAMBDA_WARNING_SIZE - ( await getBundleWeight ( commonDependencies ) )
340453
0 commit comments