1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, Vc, fxindexmap};
4use turbo_tasks_fs::FileSystemPath;
5use turbopack_core::{
6 context::AssetContext,
7 issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
8 module::Module,
9 reference_type::ReferenceType,
10};
11use turbopack_ecmascript::chunk::{EcmascriptChunkPlaceable, EcmascriptExports};
12
13use crate::util::load_next_js_template;
14
15#[turbo_tasks::function]
16pub async fn middleware_files(page_extensions: Vc<Vec<RcStr>>) -> Result<Vc<Vec<RcStr>>> {
17 let extensions = page_extensions.await?;
18 let files = ["middleware.", "src/middleware.", "proxy.", "src/proxy."]
19 .into_iter()
20 .flat_map(|f| {
21 extensions
22 .iter()
23 .map(move |ext| String::from(f) + ext.as_str())
24 .map(RcStr::from)
25 })
26 .collect();
27 Ok(Vc::cell(files))
28}
29
30#[turbo_tasks::function]
31pub async fn get_middleware_module(
32 asset_context: Vc<Box<dyn AssetContext>>,
33 project_root: FileSystemPath,
34 userland_module: ResolvedVc<Box<dyn Module>>,
35 is_proxy: bool,
36) -> Result<Vc<Box<dyn Module>>> {
37 const INNER: &str = "INNER_MIDDLEWARE_MODULE";
38
39 let userland_path = userland_module.ident().path().await?;
41 let (file_type, function_name, page_path) = if is_proxy {
42 ("Proxy", "proxy", "/proxy")
43 } else {
44 ("Middleware", "middleware", "/middleware")
45 };
46
47 if let Some(ecma_module) =
49 Vc::try_resolve_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(*userland_module).await?
50 {
51 let exports = ecma_module.get_exports().await?;
52
53 let has_valid_export = match &*exports {
55 EcmascriptExports::EsmExports(esm_exports) => {
57 let esm_exports = esm_exports.await?;
58 let has_default = esm_exports.exports.contains_key("default");
59 let expected_named = function_name;
60 let has_named = esm_exports.exports.contains_key(expected_named);
61 has_default || has_named
62 }
63 EcmascriptExports::CommonJs | EcmascriptExports::Value => true,
65 EcmascriptExports::DynamicNamespace => true,
67 EcmascriptExports::None | EcmascriptExports::Unknown => true,
70 EcmascriptExports::EmptyCommonJs => false,
72 };
73
74 if !has_valid_export {
75 MiddlewareMissingExportIssue {
76 file_type: file_type.into(),
77 function_name: function_name.into(),
78 file_path: (*userland_path).clone(),
79 }
80 .resolved_cell()
81 .emit();
82
83 }
86 }
87 let source = load_next_js_template(
92 "middleware.js",
93 project_root,
94 &[("VAR_USERLAND", INNER), ("VAR_DEFINITION_PAGE", page_path)],
95 &[],
96 &[],
97 )
98 .await?;
99
100 let inner_assets = fxindexmap! {
101 rcstr!(INNER) => userland_module
102 };
103
104 let module = asset_context
105 .process(
106 source,
107 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
108 )
109 .module();
110
111 Ok(module)
112}
113
114#[turbo_tasks::value]
115struct MiddlewareMissingExportIssue {
116 file_type: RcStr, function_name: RcStr, file_path: FileSystemPath,
119}
120
121#[turbo_tasks::value_impl]
122impl Issue for MiddlewareMissingExportIssue {
123 #[turbo_tasks::function]
124 fn stage(&self) -> Vc<IssueStage> {
125 IssueStage::Transform.into()
126 }
127
128 fn severity(&self) -> IssueSeverity {
129 IssueSeverity::Error
130 }
131
132 #[turbo_tasks::function]
133 fn file_path(&self) -> Vc<FileSystemPath> {
134 self.file_path.clone().cell()
135 }
136
137 #[turbo_tasks::function]
138 async fn title(&self) -> Result<Vc<StyledString>> {
139 let title_text = format!(
140 "{} is missing expected function export name",
141 self.file_type
142 );
143
144 Ok(StyledString::Text(title_text.into()).cell())
145 }
146
147 #[turbo_tasks::function]
148 async fn description(&self) -> Result<Vc<OptionStyledString>> {
149 let type_description = if self.file_type == "Proxy" {
150 "proxy (previously called middleware)"
151 } else {
152 "middleware"
153 };
154
155 let migration_bullet = if self.file_type == "Proxy" {
156 "- You are migrating from `middleware` to `proxy`, but haven't updated the exported \
157 function.\n"
158 } else {
159 ""
160 };
161
162 let description_text = format!(
164 "This function is what Next.js runs for every request handled by this {}.\n\n\
165 Why this happens:\n\
166 {}\
167 - The file exists but doesn't export a function.\n\
168 - The export is not a function (e.g., an object or constant).\n\
169 - There's a syntax error preventing the export from being recognized.\n\n\
170 To fix it:\n\
171 - Ensure this file has either a default or \"{}\" function export.\n\n\
172 Learn more: https://nextjs.org/docs/messages/middleware-to-proxy",
173 type_description,
174 migration_bullet,
175 self.function_name
176 );
177
178 Ok(Vc::cell(Some(
179 StyledString::Text(description_text.into()).resolved_cell(),
180 )))
181 }
182}