1
+ /* eslint-disable max-lines-per-function */
1
2
const { promises, existsSync } = require ( 'fs' )
2
3
const { Server } = require ( 'http' )
3
4
const { tmpdir } = require ( 'os' )
@@ -34,12 +35,14 @@ const makeHandler =
34
35
// In most cases these are served from the CDN, but for rewrites Next may try to read them
35
36
// from disk. We need to intercept these and load them from the CDN instead
36
37
// Sadly the only way to do this is to monkey-patch fs.promises. Yeah, I know.
37
- const staticFiles = new Set ( staticManifest )
38
-
38
+ const staticFiles = new Map ( staticManifest )
39
+ const downloadPromises = new Map ( )
40
+ const statsCache = new Map ( )
39
41
// Yes, you can cache stuff locally in a Lambda
40
42
const cacheDir = path . join ( tmpdir ( ) , 'next-static-cache' )
41
43
// Grab the real fs.promises.readFile...
42
44
const readfileOrig = promises . readFile
45
+ const statsOrig = promises . stat
43
46
// ...then money-patch it to see if it's requesting a CDN file
44
47
promises . readFile = async ( file , options ) => {
45
48
// We only care about page files
@@ -51,13 +54,24 @@ const makeHandler =
51
54
if ( staticFiles . has ( filePath ) && ! existsSync ( file ) ) {
52
55
// This name is safe to use, because it's one that was already created by Next
53
56
const cacheFile = path . join ( cacheDir , filePath )
54
- // Have we already cached it? We ignore the cache if running locally to avoid staleness
57
+ const url = `${ base } /${ staticFiles . get ( filePath ) } `
58
+
59
+ // If it's already downloading we can wait for it to finish
60
+ if ( downloadPromises . has ( url ) ) {
61
+ await downloadPromises . get ( url )
62
+ }
63
+ // Have we already cached it? We download every time if running locally to avoid staleness
55
64
if ( ( ! existsSync ( cacheFile ) || process . env . NETLIFY_DEV ) && base ) {
56
65
await promises . mkdir ( path . dirname ( cacheFile ) , { recursive : true } )
57
66
58
- // Append the path to our host and we can load it like a regular page
59
- const url = `${ base } /${ filePath } `
60
- await downloadFile ( url , cacheFile )
67
+ try {
68
+ // Append the path to our host and we can load it like a regular page
69
+ const downloadPromise = downloadFile ( url , cacheFile )
70
+ downloadPromises . set ( url , downloadPromise )
71
+ await downloadPromise
72
+ } finally {
73
+ downloadPromises . delete ( url )
74
+ }
61
75
}
62
76
// Return the cache file
63
77
return readfileOrig ( cacheFile , options )
@@ -66,6 +80,18 @@ const makeHandler =
66
80
67
81
return readfileOrig ( file , options )
68
82
}
83
+
84
+ promises . stat = async ( file , options ) => {
85
+ // We only care about page files
86
+ if ( file . startsWith ( pageRoot ) ) {
87
+ // We only want the part after `pages/`
88
+ const cacheFile = path . join ( cacheDir , file . slice ( pageRoot . length + 1 ) )
89
+ if ( existsSync ( cacheFile ) ) {
90
+ return statsOrig ( cacheFile , options )
91
+ }
92
+ }
93
+ return statsOrig ( file , options )
94
+ }
69
95
}
70
96
let NextServer
71
97
try {
@@ -183,3 +209,4 @@ exports.handler = ${
183
209
`
184
210
185
211
module . exports = getHandler
212
+ /* eslint-enable max-lines-per-function */
0 commit comments