@@ -4,12 +4,14 @@ import { dirname, join, relative } from 'path';
4
4
import { parse , Node } from 'acorn' ;
5
5
import { generate } from 'astring' ;
6
6
import {
7
+ formatRoutePath ,
7
8
normalizePath ,
8
9
readJsonFile ,
10
+ stripIndexRoute ,
9
11
validateDir ,
10
12
validateFile ,
11
13
} from '../utils' ;
12
- import { cliError , CliOptions } from '../cli' ;
14
+ import { cliError , CliOptions , cliWarn } from '../cli' ;
13
15
import { tmpdir } from 'os' ;
14
16
15
17
/**
@@ -27,10 +29,9 @@ import { tmpdir } from 'os';
27
29
export async function generateFunctionsMap (
28
30
functionsDir : string ,
29
31
experimentalMinify : CliOptions [ 'experimentalMinify' ]
30
- ) : Promise < {
31
- functionsMap : Map < string , string > ;
32
- invalidFunctions : Set < string > ;
33
- } > {
32
+ ) : Promise <
33
+ Pick < DirectoryProcessingResults , 'functionsMap' | 'invalidFunctions' >
34
+ > {
34
35
const processingSetup = {
35
36
functionsDir,
36
37
tmpFunctionsDir : join ( tmpdir ( ) , Math . random ( ) . toString ( 36 ) . slice ( 2 ) ) ,
@@ -43,6 +44,8 @@ export async function generateFunctionsMap(
43
44
functionsDir
44
45
) ;
45
46
47
+ await tryToFixInvalidFunctions ( processingResults ) ;
48
+
46
49
if ( experimentalMinify ) {
47
50
await buildWebpackChunkFiles (
48
51
processingResults . webpackChunks ,
@@ -53,6 +56,53 @@ export async function generateFunctionsMap(
53
56
return processingResults ;
54
57
}
55
58
59
+ /**
60
+ * Process the invalid functions and check whether and valid function was created in the functions
61
+ * map to override it.
62
+ *
63
+ * The build output sometimes generates invalid functions at the root, while still creating the
64
+ * valid functions. With the base path and route groups, it might create the valid edge function
65
+ * inside a folder for the route group, but create an invalid one that maps to the same path
66
+ * at the root.
67
+ *
68
+ * When we process the directory, we might add the valid function to the map before we process the
69
+ * invalid one, so we need to check if the invalid one was added to the map and remove it from the
70
+ * set if it was.
71
+ *
72
+ * If the invalid function is an RSC function (e.g. `path.rsc`) and doesn't have a valid squashed
73
+ * version, we check if a squashed non-RSC function exists (e.g. `path`) and use this instead. RSC
74
+ * functions are the same as non-RSC functions, per the Vercel source code.
75
+ * https://github.com/vercel/vercel/blob/main/packages/next/src/server-build.ts#L1193
76
+ *
77
+ * @param processingResults Object containing the results of processing the current function directory.
78
+ */
79
+ async function tryToFixInvalidFunctions ( {
80
+ functionsMap,
81
+ invalidFunctions,
82
+ } : DirectoryProcessingResults ) : Promise < void > {
83
+ if ( invalidFunctions . size === 0 ) {
84
+ return ;
85
+ }
86
+
87
+ for ( const rawPath of invalidFunctions ) {
88
+ const formattedPath = formatRoutePath ( rawPath ) ;
89
+
90
+ if (
91
+ functionsMap . has ( formattedPath ) ||
92
+ functionsMap . has ( stripIndexRoute ( formattedPath ) )
93
+ ) {
94
+ invalidFunctions . delete ( rawPath ) ;
95
+ } else if ( formattedPath . endsWith ( '.rsc' ) ) {
96
+ const value = functionsMap . get ( formattedPath . replace ( / \. r s c $ / , '' ) ) ;
97
+
98
+ if ( value ) {
99
+ functionsMap . set ( formattedPath , value ) ;
100
+ invalidFunctions . delete ( rawPath ) ;
101
+ }
102
+ }
103
+ }
104
+ }
105
+
56
106
async function processDirectoryRecursively (
57
107
setup : ProcessingSetup ,
58
108
dir : string
@@ -117,8 +167,26 @@ async function processFuncDirectory(
117
167
} ;
118
168
}
119
169
170
+ // There are instances where the build output will generate an uncompiled `middleware.js` file that is used as the entrypoint.
171
+ // TODO: investigate when and where the file is generated.
172
+ // This file is not able to be used as it is uncompiled, so we try to instead use the compiled `index.js` if it exists.
173
+ let isMiddleware = false ;
174
+ if ( functionConfig . entrypoint === 'middleware.js' ) {
175
+ isMiddleware = true ;
176
+ functionConfig . entrypoint = 'index.js' ;
177
+ }
178
+
120
179
const functionFile = join ( filepath , functionConfig . entrypoint ) ;
121
180
if ( ! ( await validateFile ( functionFile ) ) ) {
181
+ if ( isMiddleware ) {
182
+ // We sometimes encounter an uncompiled `middleware.js` with no compiled `index.js` outside of a base path.
183
+ // Outside the base path, it should not be utilised, so it should be safe to ignore the function.
184
+ cliWarn (
185
+ `Detected an invalid middleware function for ${ relativePath } . Skipping...`
186
+ ) ;
187
+ return { } ;
188
+ }
189
+
122
190
return {
123
191
invalidFunctions : new Set ( [ file ] ) ,
124
192
} ;
@@ -143,12 +211,15 @@ async function processFuncDirectory(
143
211
await mkdir ( dirname ( newFilePath ) , { recursive : true } ) ;
144
212
await writeFile ( newFilePath , contents ) ;
145
213
146
- functionsMap . set (
147
- normalizePath (
148
- relative ( setup . functionsDir , filepath ) . slice ( 0 , - '.func' . length )
149
- ) ,
150
- normalizePath ( newFilePath )
151
- ) ;
214
+ const formattedPathName = formatRoutePath ( relativePath ) ;
215
+ const normalizedFilePath = normalizePath ( newFilePath ) ;
216
+
217
+ functionsMap . set ( formattedPathName , normalizedFilePath ) ;
218
+
219
+ if ( formattedPathName . endsWith ( '/index' ) ) {
220
+ // strip `/index` from the path name as the build output config doesn't rewrite `/index` to `/`
221
+ functionsMap . set ( stripIndexRoute ( formattedPathName ) , normalizedFilePath ) ;
222
+ }
152
223
153
224
return {
154
225
functionsMap,
0 commit comments