1- use anyhow:: { Result , bail} ;
1+ use std:: path:: Path ;
2+
3+ use anyhow:: Result ;
24use turbo_rcstr:: { RcStr , rcstr} ;
35use turbo_tasks:: { ResolvedVc , Vc , fxindexmap} ;
46use turbo_tasks_fs:: FileSystemPath ;
5- use turbopack_core:: { context:: AssetContext , module:: Module , reference_type:: ReferenceType } ;
7+ use turbopack_core:: {
8+ context:: AssetContext ,
9+ issue:: { Issue , IssueExt , IssueSeverity , IssueStage , OptionStyledString , StyledString } ,
10+ module:: Module ,
11+ reference_type:: ReferenceType ,
12+ } ;
613use turbopack_ecmascript:: chunk:: { EcmascriptChunkPlaceable , EcmascriptExports } ;
714
815use crate :: util:: load_next_js_template;
@@ -33,7 +40,11 @@ pub async fn get_middleware_module(
3340 // Determine if this is a proxy file by checking the module path
3441 let userland_path = userland_module. ident ( ) . path ( ) . await ?;
3542 let is_proxy = userland_path. file_stem ( ) == Some ( "proxy" ) ;
36- let page_path = if is_proxy { "/proxy" } else { "/middleware" } ;
43+ let ( file_type, function_name, page_path) = if is_proxy {
44+ ( "Proxy" , "proxy" , "/proxy" )
45+ } else {
46+ ( "Middleware" , "middleware" , "/middleware" )
47+ } ;
3748
3849 // Validate that the module has the required exports
3950 if let Some ( ecma_module) =
@@ -46,13 +57,9 @@ pub async fn get_middleware_module(
4657 // ESM modules - check for named or default export
4758 EcmascriptExports :: EsmExports ( esm_exports) => {
4859 let esm_exports = esm_exports. await ?;
49- let has_default = esm_exports. exports . contains_key ( & rcstr ! ( "default" ) ) ;
50- let expected_named = if is_proxy {
51- rcstr ! ( "proxy" )
52- } else {
53- rcstr ! ( "middleware" )
54- } ;
55- let has_named = esm_exports. exports . contains_key ( & expected_named) ;
60+ let has_default = esm_exports. exports . contains_key ( "default" ) ;
61+ let expected_named = function_name;
62+ let has_named = esm_exports. exports . contains_key ( expected_named) ;
5663 has_default || has_named
5764 }
5865 // CommonJS modules are valid (they can have module.exports or exports.default)
@@ -66,21 +73,16 @@ pub async fn get_middleware_module(
6673 } ;
6774
6875 if !has_valid_export {
69- let file_type = if is_proxy { "Proxy" } else { "Middleware" } ;
70- let function_name = if is_proxy { "proxy" } else { "middleware" } ;
71- // Extract just the filename for the error message
72- let file_name = userland_path
73- . path
74- . split ( '/' )
75- . next_back ( )
76- . unwrap_or ( & userland_path. path ) ;
77- // Use the same error message format as the runtime check
78- bail ! (
79- "The {} file \" ./{}\" must export a function named `{}` or a default function." ,
80- file_type,
81- file_name,
82- function_name
83- ) ;
76+ MiddlewareMissingExportIssue {
77+ file_type : file_type. into ( ) ,
78+ function_name : function_name. into ( ) ,
79+ file_path : ( * userland_path) . clone ( ) ,
80+ }
81+ . resolved_cell ( )
82+ . emit ( ) ;
83+
84+ // Continue execution instead of bailing - let the module be processed anyway
85+ // The runtime template will still catch this at runtime
8486 }
8587 }
8688 // If we can't cast to EcmascriptChunkPlaceable, continue without validation
@@ -113,3 +115,51 @@ pub async fn get_middleware_module(
113115
114116 Ok ( module)
115117}
118+
119+ #[ turbo_tasks:: value]
120+ struct MiddlewareMissingExportIssue {
121+ file_type : RcStr , // "Proxy" or "Middleware"
122+ function_name : RcStr , // "proxy" or "middleware"
123+ file_path : FileSystemPath ,
124+ }
125+
126+ #[ turbo_tasks:: value_impl]
127+ impl Issue for MiddlewareMissingExportIssue {
128+ #[ turbo_tasks:: function]
129+ fn stage ( & self ) -> Vc < IssueStage > {
130+ IssueStage :: Transform . into ( )
131+ }
132+
133+ fn severity ( & self ) -> IssueSeverity {
134+ IssueSeverity :: Error
135+ }
136+
137+ #[ turbo_tasks:: function]
138+ fn file_path ( & self ) -> Vc < FileSystemPath > {
139+ self . file_path . clone ( ) . cell ( )
140+ }
141+
142+ #[ turbo_tasks:: function]
143+ fn title ( & self ) -> Vc < StyledString > {
144+ let file_name = Path :: new ( & self . file_path . path )
145+ . file_name ( )
146+ . and_then ( |n| n. to_str ( ) )
147+ . unwrap_or ( & self . file_path . path ) ;
148+
149+ StyledString :: Line ( vec ! [
150+ StyledString :: Text ( rcstr!( "The " ) ) ,
151+ StyledString :: Code ( self . file_type. clone( ) ) ,
152+ StyledString :: Text ( rcstr!( " file \" " ) ) ,
153+ StyledString :: Code ( format!( "./{}" , file_name) . into( ) ) ,
154+ StyledString :: Text ( rcstr!( "\" must export a function named " ) ) ,
155+ StyledString :: Code ( format!( "`{}`" , self . function_name) . into( ) ) ,
156+ StyledString :: Text ( rcstr!( " or a default function." ) ) ,
157+ ] )
158+ . cell ( )
159+ }
160+
161+ #[ turbo_tasks:: function]
162+ fn description ( & self ) -> Vc < OptionStyledString > {
163+ Vc :: cell ( None )
164+ }
165+ }
0 commit comments