next_core/next_root_params/
mod.rs1use std::iter;
2
3use anyhow::{Result, anyhow, ensure};
4use either::Either;
5use indoc::formatdoc;
6use itertools::Itertools;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{EitherTaskInput, ResolvedVc, Vc};
9use turbo_tasks_fs::{FileContent, FileSystemPath};
10use turbopack_core::{
11 asset::AssetContent,
12 issue::IssueExt,
13 resolve::{
14 ResolveResult,
15 options::{
16 ImportMap, ImportMapResult, ImportMapping, ImportMappingReplacement,
17 ReplacedImportMapping,
18 },
19 parse::Request,
20 pattern::Pattern,
21 },
22 virtual_source::VirtualSource,
23};
24
25use crate::{
26 app_structure::CollectedRootParams, embed_js::next_js_file_path,
27 next_client::ClientContextType, next_server::ServerContextType,
28 next_shared::resolve::InvalidImportModuleIssue,
29};
30
31const ROOT_PARAM_GETTER_MODULE: &str = "private-next-root-params/getter";
33
34pub fn root_param_getters_path() -> Vc<FileSystemPath> {
37 next_js_file_path("root-params".into())
38}
39
40pub async fn insert_next_root_params_mapping(
41 import_map: &mut ImportMap,
42 ty: Either<ServerContextType, ClientContextType>,
43 collected_root_params: Option<Vc<CollectedRootParams>>,
44) -> Result<()> {
45 let mapping = get_next_root_params_mapping(EitherTaskInput(ty), collected_root_params)
46 .to_resolved()
47 .await?;
48 import_map.insert_exact_alias("next/root-params", mapping);
49 import_map.insert_exact_alias(ROOT_PARAM_GETTER_MODULE, mapping);
50 Ok(())
51}
52
53#[turbo_tasks::function]
54async fn get_next_root_params_mapping(
55 ty: EitherTaskInput<ServerContextType, ClientContextType>,
56 collected_root_params: Option<Vc<CollectedRootParams>>,
57) -> Result<Vc<ImportMapping>> {
58 let mapping = ImportMapping::Dynamic(ResolvedVc::upcast(
66 NextRootParamsMapper::new(ty, collected_root_params)
67 .to_resolved()
68 .await?,
69 ));
70 Ok(mapping.cell())
71}
72
73#[turbo_tasks::value]
74struct NextRootParamsMapper {
75 #[bincode(with = "turbo_bincode::either")]
76 context_type: Either<ServerContextType, ClientContextType>,
77 collected_root_params: Option<ResolvedVc<CollectedRootParams>>,
78}
79
80#[turbo_tasks::value_impl]
81impl NextRootParamsMapper {
82 #[turbo_tasks::function]
83 pub fn new(
84 context_type: EitherTaskInput<ServerContextType, ClientContextType>,
85 collected_root_params: Option<ResolvedVc<CollectedRootParams>>,
86 ) -> Vc<Self> {
87 NextRootParamsMapper {
88 context_type: context_type.0,
89 collected_root_params,
90 }
91 .cell()
92 }
93
94 #[turbo_tasks::function]
95 async fn import_map_result(
96 self: Vc<Self>,
97 param_name: Option<RcStr>,
98 ) -> Result<Vc<ImportMapResult>> {
99 let this = self.await?;
100 Ok(match &this.context_type {
101 Either::Left(server_ty) => match &server_ty {
102 ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => {
103 let collected_root_params = *this.collected_root_params.ok_or_else(|| {
104 anyhow!(
105 "Invariant: Root params should have been collected for context {:?}. \
106 This is a bug in Next.js.",
107 server_ty.clone()
108 )
109 })?;
110 Self::valid_import_map_result(collected_root_params, param_name)
111 }
112 ServerContextType::PagesApi { .. }
113 | ServerContextType::Instrumentation { .. }
114 | ServerContextType::Middleware { .. } => {
115 Self::invalid_import_map_result(
118 "'next/root-params' can only be used inside the App Directory.".into(),
119 )
120 }
121 _ => {
122 Self::invalid_import_map_result(
126 "'next/root-params' cannot be imported from a Client Component module. It \
127 should only be used from a Server Component."
128 .into(),
129 )
130 }
131 },
132 Either::Right(_) => {
133 Self::invalid_import_map_result(
137 "'next/root-params' cannot be imported from a Client Component module. It \
138 should only be used from a Server Component."
139 .into(),
140 )
141 }
142 })
143 }
144
145 #[turbo_tasks::function]
146 async fn valid_import_map_result(
147 collected_root_params: ResolvedVc<CollectedRootParams>,
148 param_name: Option<RcStr>,
149 ) -> Result<Vc<ImportMapResult>> {
150 let collected_root_params = collected_root_params.await?;
151
152 let (path, module_content) = if let Some(param_name) = ¶m_name {
153 ensure!(
154 collected_root_params.contains(param_name),
155 "Unknown root parameter in generated getter request: {param_name}"
156 );
157 (
158 root_param_getters_path()
159 .await?
160 .join(&format!("{param_name}.js"))?,
161 formatdoc!(
162 r#"
163 import {{ getRootParam }} from 'next/dist/server/request/root-params';
164 export function {param_name}() {{
165 return getRootParam('{param_name}');
166 }}
167 "#,
168 ),
169 )
170 } else {
171 let module_content =
175 iter::once("'use turbopack: no side effects';\nexport {};".to_string())
176 .chain(collected_root_params.iter().map(|param_name| {
177 format!(
178 "export {{ {param_name} }} from \
179 '{ROOT_PARAM_GETTER_MODULE}?{param_name}';"
180 )
181 }))
182 .join("\n");
183 (
184 next_js_file_path("root-params.js".into()).owned().await?,
185 module_content,
186 )
187 };
188
189 let virtual_source = VirtualSource::new(
190 path,
191 AssetContent::file(FileContent::Content(module_content.into()).cell()),
192 )
193 .to_resolved()
194 .await?;
195
196 let import_map_result = ImportMapResult::Result(
197 ResolveResult::source(ResolvedVc::upcast(virtual_source)).resolved_cell(),
198 );
199 Ok(import_map_result.cell())
200 }
201
202 #[turbo_tasks::function]
203 async fn invalid_import_map_result(message: RcStr) -> Result<Vc<ImportMapResult>> {
204 let path: FileSystemPath = next_js_file_path("root-params.js".into()).owned().await?;
205
206 InvalidImportModuleIssue {
208 file_path: path.clone(),
209 messages: vec![message.clone()],
210 skip_context_message: false,
211 }
212 .resolved_cell()
213 .emit();
214
215 let virtual_source = VirtualSource::new(
217 path.clone(),
218 AssetContent::file(
219 FileContent::Content(
220 format!("throw new Error({})", serde_json::to_string(&message)?).into(),
221 )
222 .cell(),
223 ),
224 )
225 .to_resolved()
226 .await?;
227
228 let import_map_result = ImportMapResult::Result(
229 ResolveResult::source(ResolvedVc::upcast(virtual_source)).resolved_cell(),
230 );
231 Ok(import_map_result.cell())
232 }
233}
234
235#[turbo_tasks::value_impl]
236impl ImportMappingReplacement for NextRootParamsMapper {
237 #[turbo_tasks::function]
238 fn replace(&self, _capture: Vc<Pattern>) -> Vc<ReplacedImportMapping> {
239 ReplacedImportMapping::Ignore.cell()
240 }
241
242 #[turbo_tasks::function]
243 async fn result(
244 self: Vc<Self>,
245 _lookup_path: FileSystemPath,
246 request: Vc<Request>,
247 ) -> Result<Vc<ImportMapResult>> {
248 let request = request.await?;
249 let param_name = match &*request {
250 Request::Module { query, .. }
251 if request.request().as_deref() == Some(ROOT_PARAM_GETTER_MODULE) =>
252 {
253 Some(
254 query
255 .strip_prefix('?')
256 .ok_or_else(|| {
257 anyhow!("Missing root parameter in generated getter request")
258 })?
259 .into(),
260 )
261 }
262 _ => None,
263 };
264 Ok(self.import_map_result(param_name))
266 }
267}