Skip to main content

next_core/
next_import_map.rs

1use std::{collections::BTreeMap, sync::LazyLock};
2
3use anyhow::{Context, Result};
4use async_trait::async_trait;
5use either::Either;
6use next_taskless::{EDGE_NODE_EXTERNALS, NODE_EXTERNALS};
7use rustc_hash::FxHashMap;
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks::{FxIndexMap, ResolvedVc, Vc, fxindexmap};
10use turbo_tasks_fs::{FileContent, FileSystem, FileSystemPath, to_sys_path};
11use turbopack_core::{
12    asset::AssetContent,
13    issue::{Issue, IssueExt, IssueSeverity, IssueStage, StyledString},
14    reference_type::{CommonJsReferenceSubType, ReferenceType},
15    resolve::{
16        AliasPattern, ExternalTraced, ExternalType, ResolveAliasMap, ResolveResult, SubpathValue,
17        node::node_cjs_resolve_options,
18        options::{ConditionValue, ImportMap, ImportMapping, ResolvedMap},
19        parse::Request,
20        pattern::Pattern,
21        resolve,
22    },
23    source::Source,
24    virtual_source::VirtualSource,
25};
26use turbopack_node::execution_context::ExecutionContext;
27
28use crate::{
29    app_structure::CollectedRootParams,
30    embed_js::{VIRTUAL_PACKAGE_NAME, next_js_fs},
31    mode::NextMode,
32    next_client::context::ClientContextType,
33    next_config::{NextConfig, OptionFileSystemPath},
34    next_edge::unsupported::NextEdgeUnsupportedModuleReplacer,
35    next_font::google::{
36        GOOGLE_FONTS_INTERNAL_PREFIX, NextFontGoogleCssModuleReplacer,
37        NextFontGoogleFontFileReplacer, NextFontGoogleReplacer,
38    },
39    next_root_params::insert_next_root_params_mapping,
40    next_server::context::ServerContextType,
41    util::NextRuntime,
42};
43
44// Make sure to not add any external requests here.
45/// Computes the Next-specific client import map.
46#[turbo_tasks::function]
47pub async fn get_next_client_import_map(
48    project_path: FileSystemPath,
49    ty: ClientContextType,
50    next_config: Vc<NextConfig>,
51    next_mode: Vc<NextMode>,
52    execution_context: Vc<ExecutionContext>,
53) -> Result<Vc<ImportMap>> {
54    let mut import_map = ImportMap::empty();
55
56    insert_next_shared_aliases(
57        &mut import_map,
58        project_path.clone(),
59        execution_context,
60        next_config,
61        next_mode,
62        false,
63    )
64    .await?;
65
66    insert_optimized_module_aliases(&mut import_map, project_path.clone()).await?;
67
68    insert_alias_option(
69        &mut import_map,
70        &project_path,
71        next_config.resolve_alias_options(),
72        ["browser"],
73    )
74    .await?;
75
76    match &ty {
77        ClientContextType::Pages { .. } => {
78            // Resolve next/error to the ESM entry point so the bundler can
79            // tree-shake the error-boundary dependency chain from Pages
80            // Router bundles that only use the default Error component.
81            insert_exact_alias_or_js(
82                &mut import_map,
83                rcstr!("next/error"),
84                request_to_import_mapping(project_path.clone(), rcstr!("next/dist/api/error")),
85            );
86        }
87        ClientContextType::App { app_dir } => {
88            // Keep in sync with file:///./../../../packages/next/src/lib/needs-experimental-react.ts
89            let blocking_ssr = *next_config.enable_blocking_ssr().await?;
90            let taint = *next_config.enable_taint().await?;
91            let transition_indicator = *next_config.enable_transition_indicator().await?;
92            let gesture_transition = *next_config.enable_gesture_transition().await?;
93            let react_channel =
94                if blocking_ssr || taint || transition_indicator || gesture_transition {
95                    "-experimental"
96                } else {
97                    ""
98                };
99
100            import_map.insert_exact_alias(
101                rcstr!("react"),
102                request_to_import_mapping(
103                    app_dir.clone(),
104                    format!("next/dist/compiled/react{react_channel}").into(),
105                ),
106            );
107            import_map.insert_wildcard_alias(
108                rcstr!("react/"),
109                request_to_import_mapping(
110                    app_dir.clone(),
111                    format!("next/dist/compiled/react{react_channel}/*").into(),
112                ),
113            );
114            import_map.insert_exact_alias(
115                rcstr!("react-dom"),
116                request_to_import_mapping(
117                    app_dir.clone(),
118                    format!("next/dist/compiled/react-dom{react_channel}").into(),
119                ),
120            );
121            import_map.insert_exact_alias(
122                rcstr!("react-dom/static"),
123                request_to_import_mapping(
124                    app_dir.clone(),
125                    rcstr!("next/dist/compiled/react-dom-experimental/static"),
126                ),
127            );
128            import_map.insert_exact_alias(
129                rcstr!("react-dom/static.edge"),
130                request_to_import_mapping(
131                    app_dir.clone(),
132                    rcstr!("next/dist/compiled/react-dom-experimental/static.edge"),
133                ),
134            );
135            import_map.insert_exact_alias(
136                rcstr!("react-dom/static.browser"),
137                request_to_import_mapping(
138                    app_dir.clone(),
139                    rcstr!("next/dist/compiled/react-dom-experimental/static.browser"),
140                ),
141            );
142            let react_client_package = get_react_client_package(next_config).await?;
143            import_map.insert_exact_alias(
144                rcstr!("react-dom/client"),
145                request_to_import_mapping(
146                    app_dir.clone(),
147                    format!("next/dist/compiled/react-dom{react_channel}/{react_client_package}")
148                        .into(),
149                ),
150            );
151            import_map.insert_wildcard_alias(
152                rcstr!("react-dom/"),
153                request_to_import_mapping(
154                    app_dir.clone(),
155                    format!("next/dist/compiled/react-dom{react_channel}/*").into(),
156                ),
157            );
158            import_map.insert_wildcard_alias(
159                rcstr!("react-server-dom-webpack/"),
160                request_to_import_mapping(app_dir.clone(), rcstr!("react-server-dom-turbopack/*")),
161            );
162            import_map.insert_wildcard_alias(
163                rcstr!("react-server-dom-turbopack/"),
164                request_to_import_mapping(
165                    app_dir.clone(),
166                    format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/*")
167                        .into(),
168                ),
169            );
170            insert_exact_alias_or_js(
171                &mut import_map,
172                rcstr!("next/head"),
173                request_to_import_mapping(
174                    project_path.clone(),
175                    rcstr!("next/dist/client/components/noop-head"),
176                ),
177            );
178            insert_exact_alias_or_js(
179                &mut import_map,
180                rcstr!("next/dynamic"),
181                request_to_import_mapping(
182                    project_path.clone(),
183                    rcstr!("next/dist/shared/lib/app-dynamic"),
184                ),
185            );
186            insert_exact_alias_or_js(
187                &mut import_map,
188                rcstr!("next/link"),
189                request_to_import_mapping(
190                    project_path.clone(),
191                    rcstr!("next/dist/client/app-dir/link"),
192                ),
193            );
194            insert_exact_alias_or_js(
195                &mut import_map,
196                rcstr!("next/form"),
197                request_to_import_mapping(
198                    project_path.clone(),
199                    rcstr!("next/dist/client/app-dir/form"),
200                ),
201            );
202        }
203        ClientContextType::Fallback => {}
204        ClientContextType::Other => {}
205    }
206
207    // see https://github.com/vercel/next.js/blob/8013ef7372fc545d49dbd060461224ceb563b454/packages/next/src/build/webpack-config.ts#L1449-L1531
208    insert_exact_alias_map(
209        &mut import_map,
210        project_path.clone(),
211        fxindexmap! {rcstr!("server-only") => rcstr!("next/dist/compiled/server-only/index"),
212        rcstr!("client-only") => rcstr!("next/dist/compiled/client-only/index"),
213        rcstr!("next/dist/compiled/server-only") => rcstr!("next/dist/compiled/server-only/index"),
214        rcstr!("next/dist/compiled/client-only") => rcstr!("next/dist/compiled/client-only/index"),},
215    );
216    insert_next_root_params_mapping(&mut import_map, Either::Right(ty.clone()), None).await?;
217
218    match ty {
219        ClientContextType::Pages { .. }
220        | ClientContextType::App { .. }
221        | ClientContextType::Fallback => {
222            for (original, alias) in NEXT_ALIASES.iter() {
223                import_map.insert_exact_alias(
224                    format!("node:{original}"),
225                    request_to_import_mapping(project_path.clone(), alias.clone()),
226                );
227            }
228        }
229        ClientContextType::Other => {}
230    }
231
232    insert_instrumentation_client_alias(&mut import_map, project_path, next_config).await?;
233
234    insert_server_only_error_alias(&mut import_map);
235
236    Ok(import_map.cell())
237}
238
239/// Computes the Next-specific client fallback import map, which provides
240/// polyfills to Node.js externals.
241#[turbo_tasks::function]
242pub async fn get_next_client_fallback_import_map(ty: ClientContextType) -> Result<Vc<ImportMap>> {
243    let mut import_map = ImportMap::empty();
244
245    match ty {
246        ClientContextType::Pages {
247            pages_dir: context_dir,
248        }
249        | ClientContextType::App {
250            app_dir: context_dir,
251        } => {
252            for (original, alias) in NEXT_ALIASES.iter() {
253                import_map.insert_exact_alias(
254                    original.clone(),
255                    request_to_import_mapping(context_dir.clone(), alias.clone()),
256                );
257            }
258        }
259        ClientContextType::Fallback => {}
260        ClientContextType::Other => {}
261    }
262
263    Ok(import_map.cell())
264}
265
266/// Computes the Next-specific server-side import map.
267#[turbo_tasks::function]
268pub async fn get_next_server_import_map(
269    project_path: FileSystemPath,
270    ty: ServerContextType,
271    next_config: Vc<NextConfig>,
272    next_mode: Vc<NextMode>,
273    execution_context: Vc<ExecutionContext>,
274    collected_root_params: Option<Vc<CollectedRootParams>>,
275) -> Result<Vc<ImportMap>> {
276    let mut import_map = ImportMap::empty();
277
278    insert_next_shared_aliases(
279        &mut import_map,
280        project_path.clone(),
281        execution_context,
282        next_config,
283        next_mode,
284        false,
285    )
286    .await?;
287
288    insert_alias_option(
289        &mut import_map,
290        &project_path,
291        next_config.resolve_alias_options(),
292        [],
293    )
294    .await?;
295
296    let external = ImportMapping::External(None, ExternalType::CommonJs, ExternalTraced::Traced)
297        .resolved_cell();
298
299    import_map.insert_exact_alias(rcstr!("next/dist/server/require-hook"), external);
300    match ty {
301        ServerContextType::Pages { .. } | ServerContextType::PagesApi { .. } => {
302            import_map.insert_exact_alias(rcstr!("react"), external);
303            import_map.insert_wildcard_alias(rcstr!("react/"), external);
304            import_map.insert_exact_alias(rcstr!("react-dom"), external);
305            import_map.insert_exact_alias(rcstr!("react-dom/client"), external);
306            import_map.insert_wildcard_alias(rcstr!("react-dom/"), external);
307            import_map.insert_exact_alias(rcstr!("styled-jsx"), external);
308            import_map.insert_exact_alias(
309                rcstr!("styled-jsx/style"),
310                ImportMapping::External(
311                    Some(rcstr!("styled-jsx/style.js")),
312                    ExternalType::CommonJs,
313                    ExternalTraced::Traced,
314                )
315                .resolved_cell(),
316            );
317            import_map.insert_wildcard_alias(rcstr!("styled-jsx/"), external);
318            // TODO: we should not bundle next/dist/build/utils in the pages renderer at all
319            import_map.insert_wildcard_alias(rcstr!("next/dist/build/utils"), external);
320        }
321        ServerContextType::AppSSR { .. }
322        | ServerContextType::AppRSC { .. }
323        | ServerContextType::AppRoute { .. } => {
324            insert_exact_alias_or_js(
325                &mut import_map,
326                rcstr!("next/head"),
327                request_to_import_mapping(
328                    project_path.clone(),
329                    rcstr!("next/dist/client/components/noop-head"),
330                ),
331            );
332            insert_exact_alias_or_js(
333                &mut import_map,
334                rcstr!("next/dynamic"),
335                request_to_import_mapping(
336                    project_path.clone(),
337                    rcstr!("next/dist/shared/lib/app-dynamic"),
338                ),
339            );
340            insert_exact_alias_or_js(
341                &mut import_map,
342                rcstr!("next/link"),
343                request_to_import_mapping(
344                    project_path.clone(),
345                    rcstr!("next/dist/client/app-dir/link"),
346                ),
347            );
348            insert_exact_alias_or_js(
349                &mut import_map,
350                rcstr!("next/form"),
351                request_to_import_mapping(
352                    project_path.clone(),
353                    rcstr!("next/dist/client/app-dir/form"),
354                ),
355            );
356        }
357        ServerContextType::Middleware { .. } | ServerContextType::Instrumentation { .. } => {}
358    }
359
360    insert_next_server_special_aliases(
361        &mut import_map,
362        project_path.clone(),
363        ty,
364        NextRuntime::NodeJs,
365        next_config,
366        collected_root_params,
367    )
368    .await?;
369
370    Ok(import_map.cell())
371}
372
373/// Computes the Next-specific edge-side import map.
374#[turbo_tasks::function]
375pub async fn get_next_edge_import_map(
376    project_path: FileSystemPath,
377    ty: ServerContextType,
378    next_config: Vc<NextConfig>,
379    next_mode: Vc<NextMode>,
380    execution_context: Vc<ExecutionContext>,
381    collected_root_params: Option<Vc<CollectedRootParams>>,
382) -> Result<Vc<ImportMap>> {
383    let mut import_map = ImportMap::empty();
384
385    // https://github.com/vercel/next.js/blob/786ef25e529e1fb2dda398aebd02ccbc8d0fb673/packages/next/src/build/webpack-config.ts#L815-L861
386
387    // Alias next/dist imports to next/dist/esm assets
388    insert_wildcard_alias_map(
389        &mut import_map,
390        project_path.clone(),
391        fxindexmap! {rcstr!("next/dist/build/") => rcstr!("next/dist/esm/build/*"),
392        rcstr!("next/dist/client/") => rcstr!("next/dist/esm/client/*"),
393        rcstr!("next/dist/shared/") => rcstr!("next/dist/esm/shared/*"),
394        rcstr!("next/dist/pages/") => rcstr!("next/dist/esm/pages/*"),
395        rcstr!("next/dist/lib/") => rcstr!("next/dist/esm/lib/*"),
396        rcstr!("next/dist/server/") => rcstr!("next/dist/esm/server/*"),
397        rcstr!("next/dist/api/") => rcstr!("next/dist/esm/api/*"),},
398    );
399
400    // Alias the usage of next public APIs
401    insert_exact_alias_map(
402        &mut import_map,
403        project_path.clone(),
404        fxindexmap! {rcstr!("next/app") => rcstr!("next/dist/api/app"),
405        rcstr!("next/document") => rcstr!("next/dist/api/document"),
406        rcstr!("next/dynamic") => rcstr!("next/dist/api/dynamic"),
407        rcstr!("next/error") => rcstr!("next/dist/api/error"),
408        rcstr!("next/form") => rcstr!("next/dist/api/form"),
409        rcstr!("next/head") => rcstr!("next/dist/api/head"),
410        rcstr!("next/headers") => rcstr!("next/dist/api/headers"),
411        rcstr!("next/image") => rcstr!("next/dist/api/image"),
412        rcstr!("next/link") => rcstr!("next/dist/api/link"),
413        rcstr!("next/navigation") => rcstr!("next/dist/api/navigation"),
414        rcstr!("next/router") => rcstr!("next/dist/api/router"),
415        rcstr!("next/script") => rcstr!("next/dist/api/script"),
416        rcstr!("next/server") => rcstr!("next/dist/api/server"),
417        rcstr!("next/og") => rcstr!("next/dist/api/og"),
418
419        // Alias built-in @vercel/og to edge bundle for edge runtime
420        rcstr!("next/dist/compiled/@vercel/og/index.node.js") => rcstr!("next/dist/compiled/@vercel/og/index.edge.js"),},
421    );
422
423    insert_next_shared_aliases(
424        &mut import_map,
425        project_path.clone(),
426        execution_context,
427        next_config,
428        next_mode,
429        true,
430    )
431    .await?;
432
433    insert_optimized_module_aliases(&mut import_map, project_path.clone()).await?;
434
435    insert_alias_option(
436        &mut import_map,
437        &project_path,
438        next_config.resolve_alias_options(),
439        [],
440    )
441    .await?;
442
443    match &ty {
444        ServerContextType::Pages { .. }
445        | ServerContextType::PagesApi { .. }
446        | ServerContextType::Middleware { .. }
447        | ServerContextType::Instrumentation { .. } => {}
448        ServerContextType::AppSSR { .. }
449        | ServerContextType::AppRSC { .. }
450        | ServerContextType::AppRoute { .. } => {
451            insert_exact_alias_or_js(
452                &mut import_map,
453                rcstr!("next/head"),
454                request_to_import_mapping(
455                    project_path.clone(),
456                    rcstr!("next/dist/client/components/noop-head"),
457                ),
458            );
459            insert_exact_alias_or_js(
460                &mut import_map,
461                rcstr!("next/dynamic"),
462                request_to_import_mapping(
463                    project_path.clone(),
464                    rcstr!("next/dist/shared/lib/app-dynamic"),
465                ),
466            );
467            insert_exact_alias_or_js(
468                &mut import_map,
469                rcstr!("next/link"),
470                request_to_import_mapping(
471                    project_path.clone(),
472                    rcstr!("next/dist/client/app-dir/link"),
473                ),
474            );
475        }
476    }
477
478    insert_next_server_special_aliases(
479        &mut import_map,
480        project_path.clone(),
481        ty.clone(),
482        NextRuntime::Edge,
483        next_config,
484        collected_root_params,
485    )
486    .await?;
487
488    // Look for where 'server/web/globals.ts` are imported to find out corresponding
489    // context
490    match ty {
491        ServerContextType::AppSSR { .. }
492        | ServerContextType::AppRSC { .. }
493        | ServerContextType::AppRoute { .. }
494        | ServerContextType::Middleware { .. }
495        | ServerContextType::Instrumentation { .. }
496        | ServerContextType::Pages { .. }
497        | ServerContextType::PagesApi { .. } => {
498            insert_unsupported_node_internal_aliases(&mut import_map).await?;
499        }
500    }
501
502    if matches!(
503        ty,
504        ServerContextType::AppRSC { .. }
505            | ServerContextType::AppRoute { .. }
506            | ServerContextType::Middleware { .. }
507            | ServerContextType::Instrumentation { .. }
508    ) {
509        insert_client_only_error_alias(&mut import_map);
510    }
511
512    Ok(import_map.cell())
513}
514
515/// Computes the Next-specific server-side and edge-side fallback import map.
516#[turbo_tasks::function]
517pub async fn get_next_edge_and_server_fallback_import_map(
518    project_path: FileSystemPath,
519    runtime: NextRuntime,
520) -> Result<Vc<ImportMap>> {
521    let mut fallback_import_map = ImportMap::empty();
522
523    let external_cjs_if_node = move |context_dir: FileSystemPath, request: RcStr| match runtime {
524        NextRuntime::Edge => request_to_import_mapping(context_dir, request),
525        NextRuntime::NodeJs => external_request_to_cjs_import_mapping(context_dir, request),
526    };
527
528    fallback_import_map.insert_exact_alias(
529        rcstr!("@opentelemetry/api"),
530        // It needs to prefer the local version of @opentelemetry/api, so put this in the fallback
531        // import map
532        ImportMapping::Alternatives(vec![external_cjs_if_node(
533            project_path,
534            rcstr!("next/dist/compiled/@opentelemetry/api"),
535        )])
536        .resolved_cell(),
537    );
538    Ok(fallback_import_map.cell())
539}
540
541/// Insert default aliases for the node.js's internal to raise unsupported
542/// runtime errors. User may provide polyfills for their own by setting user
543/// config's alias.
544async fn insert_unsupported_node_internal_aliases(import_map: &mut ImportMap) -> Result<()> {
545    let unsupported_replacer = ImportMapping::Dynamic(ResolvedVc::upcast(
546        NextEdgeUnsupportedModuleReplacer::new()
547            .to_resolved()
548            .await?,
549    ))
550    .resolved_cell();
551
552    for module in NODE_EXTERNALS {
553        if EDGE_NODE_EXTERNALS.binary_search(&module).is_ok() {
554            continue;
555        }
556        import_map.insert_alias(AliasPattern::exact(module), unsupported_replacer);
557    }
558
559    Ok(())
560}
561
562pub fn get_next_client_resolved_map(
563    _context: FileSystemPath,
564    _root: FileSystemPath,
565    _mode: NextMode,
566) -> Vc<ResolvedMap> {
567    let glob_mappings = vec![];
568    ResolvedMap {
569        by_glob: glob_mappings,
570    }
571    .cell()
572}
573
574static NEXT_ALIASES: LazyLock<[(RcStr, RcStr); 23]> = LazyLock::new(|| {
575    [
576        (rcstr!("assert"), rcstr!("next/dist/compiled/assert")),
577        (rcstr!("buffer"), rcstr!("next/dist/compiled/buffer")),
578        (
579            rcstr!("constants"),
580            rcstr!("next/dist/compiled/constants-browserify"),
581        ),
582        (
583            rcstr!("crypto"),
584            rcstr!("next/dist/compiled/crypto-browserify"),
585        ),
586        (
587            rcstr!("domain"),
588            rcstr!("next/dist/compiled/domain-browser"),
589        ),
590        (rcstr!("http"), rcstr!("next/dist/compiled/stream-http")),
591        (
592            rcstr!("https"),
593            rcstr!("next/dist/compiled/https-browserify"),
594        ),
595        (rcstr!("os"), rcstr!("next/dist/compiled/os-browserify")),
596        (rcstr!("path"), rcstr!("next/dist/compiled/path-browserify")),
597        (rcstr!("punycode"), rcstr!("next/dist/compiled/punycode")),
598        (
599            rcstr!("process"),
600            rcstr!("next/dist/build/polyfills/process"),
601        ),
602        (
603            rcstr!("querystring"),
604            rcstr!("next/dist/compiled/querystring-es3"),
605        ),
606        (
607            rcstr!("stream"),
608            rcstr!("next/dist/compiled/stream-browserify"),
609        ),
610        (
611            rcstr!("string_decoder"),
612            rcstr!("next/dist/compiled/string_decoder"),
613        ),
614        (rcstr!("sys"), rcstr!("next/dist/compiled/util")),
615        (
616            rcstr!("timers"),
617            rcstr!("next/dist/compiled/timers-browserify"),
618        ),
619        (rcstr!("tty"), rcstr!("next/dist/compiled/tty-browserify")),
620        (rcstr!("url"), rcstr!("next/dist/compiled/native-url")),
621        (rcstr!("util"), rcstr!("next/dist/compiled/util")),
622        (rcstr!("vm"), rcstr!("next/dist/compiled/vm-browserify")),
623        (rcstr!("zlib"), rcstr!("next/dist/compiled/browserify-zlib")),
624        (rcstr!("events"), rcstr!("next/dist/compiled/events")),
625        (
626            rcstr!("setImmediate"),
627            rcstr!("next/dist/compiled/setimmediate"),
628        ),
629    ]
630});
631
632async fn insert_next_server_special_aliases(
633    import_map: &mut ImportMap,
634    project_path: FileSystemPath,
635    ty: ServerContextType,
636    runtime: NextRuntime,
637    next_config: Vc<NextConfig>,
638    collected_root_params: Option<Vc<CollectedRootParams>>,
639) -> Result<()> {
640    let external_cjs_if_node = move |context_dir: FileSystemPath, request: RcStr| match runtime {
641        NextRuntime::Edge => request_to_import_mapping(context_dir, request),
642        NextRuntime::NodeJs => external_request_to_cjs_import_mapping(context_dir, request),
643    };
644    let external_esm_if_node = move |context_dir: FileSystemPath, request: RcStr| match runtime {
645        NextRuntime::Edge => request_to_import_mapping(context_dir, request),
646        NextRuntime::NodeJs => external_request_to_esm_import_mapping(context_dir, request),
647    };
648
649    import_map.insert_exact_alias(
650        rcstr!("next/dist/compiled/@vercel/og/index.node.js"),
651        external_esm_if_node(
652            project_path.clone(),
653            rcstr!("next/dist/compiled/@vercel/og/index.node.js"),
654        ),
655    );
656
657    import_map.insert_exact_alias(
658        rcstr!("next/dist/server/ReactDOMServerPages"),
659        ImportMapping::Alternatives(vec![
660            request_to_import_mapping(project_path.clone(), rcstr!("react-dom/server.edge")),
661            request_to_import_mapping(project_path.clone(), rcstr!("react-dom/server.browser")),
662        ])
663        .resolved_cell(),
664    );
665
666    match &ty {
667        ServerContextType::Pages { .. } | ServerContextType::PagesApi { .. } => {}
668        // the logic closely follows the one in createRSCAliases in webpack-config.ts
669        ServerContextType::AppSSR { app_dir } => {
670            let next_package = get_next_package(app_dir.clone()).await?;
671            import_map.insert_exact_alias(
672                rcstr!("styled-jsx"),
673                request_to_import_mapping(next_package.clone(), rcstr!("styled-jsx")),
674            );
675            import_map.insert_wildcard_alias(
676                rcstr!("styled-jsx/"),
677                request_to_import_mapping(next_package.clone(), rcstr!("styled-jsx/*")),
678            );
679
680            rsc_aliases(
681                import_map,
682                project_path.clone(),
683                ty.clone(),
684                runtime,
685                next_config,
686            )
687            .await?;
688        }
689        ServerContextType::AppRSC { .. }
690        | ServerContextType::AppRoute { .. }
691        | ServerContextType::Middleware { .. }
692        | ServerContextType::Instrumentation { .. } => {
693            rsc_aliases(
694                import_map,
695                project_path.clone(),
696                ty.clone(),
697                runtime,
698                next_config,
699            )
700            .await?;
701        }
702    }
703
704    // see https://github.com/vercel/next.js/blob/8013ef7372fc545d49dbd060461224ceb563b454/packages/next/src/build/webpack-config.ts#L1449-L1531
705    // Sets runtime aliases for the import to client|server-only. Depends on the
706    // context, it'll resolve to the noop where it's allowed, or aliased into
707    // the error which throws a runtime error. This works with in combination of
708    // build-time error as well, refer https://github.com/vercel/next.js/blob/0060de1c4905593ea875fa7250d4b5d5ce10897d/packages/next-swc/crates/next-core/src/next_server/context.rs#L103
709    match &ty {
710        ServerContextType::Pages { .. } => {
711            insert_exact_alias_map(
712                import_map,
713                project_path.clone(),
714                fxindexmap! {rcstr!("server-only") => rcstr!("next/dist/compiled/server-only/empty"),
715                rcstr!("client-only") => rcstr!("next/dist/compiled/client-only/index"),
716                rcstr!("next/dist/compiled/server-only") => rcstr!("next/dist/compiled/server-only/empty"),
717                rcstr!("next/dist/compiled/client-only") => rcstr!("next/dist/compiled/client-only/index"),},
718            );
719        }
720        ServerContextType::PagesApi { .. }
721        | ServerContextType::AppRSC { .. }
722        | ServerContextType::AppRoute { .. }
723        | ServerContextType::Middleware { .. }
724        | ServerContextType::Instrumentation { .. } => {
725            insert_exact_alias_map(
726                import_map,
727                project_path.clone(),
728                fxindexmap! {rcstr!("server-only") => rcstr!("next/dist/compiled/server-only/empty"),
729                rcstr!("next/dist/compiled/server-only") => rcstr!("next/dist/compiled/server-only/empty"),
730                rcstr!("next/dist/compiled/client-only") => rcstr!("next/dist/compiled/client-only/error"),},
731            );
732            insert_client_only_error_alias(import_map);
733        }
734        ServerContextType::AppSSR { .. } => {
735            insert_exact_alias_map(
736                import_map,
737                project_path.clone(),
738                fxindexmap! {rcstr!("server-only") => rcstr!("next/dist/compiled/server-only/index"),
739                rcstr!("client-only") => rcstr!("next/dist/compiled/client-only/index"),
740                rcstr!("next/dist/compiled/server-only") => rcstr!("next/dist/compiled/server-only/index"),
741                rcstr!("next/dist/compiled/client-only") => rcstr!("next/dist/compiled/client-only/index"),},
742            );
743        }
744    }
745
746    insert_next_root_params_mapping(import_map, Either::Left(ty), collected_root_params).await?;
747
748    import_map.insert_exact_alias(
749        rcstr!("@vercel/og"),
750        external_cjs_if_node(
751            project_path.clone(),
752            rcstr!("next/dist/server/og/image-response"),
753        ),
754    );
755
756    import_map.insert_exact_alias(
757        rcstr!("next/dist/compiled/next-devtools"),
758        request_to_import_mapping(
759            project_path.clone(),
760            rcstr!("next/dist/next-devtools/dev-overlay.shim.js"),
761        ),
762    );
763
764    Ok(())
765}
766
767async fn get_react_client_package(next_config: Vc<NextConfig>) -> Result<&'static str> {
768    let react_production_profiling = *next_config.enable_react_production_profiling().await?;
769    let react_client_package = if react_production_profiling {
770        "profiling"
771    } else {
772        "client"
773    };
774
775    Ok(react_client_package)
776}
777
778// Use createVendoredReactAliases in file:///./../../../packages/next/src/build/create-compiler-aliases.ts
779// as the source of truth.
780async fn apply_vendored_react_aliases_server(
781    import_map: &mut ImportMap,
782    project_path: FileSystemPath,
783    ty: ServerContextType,
784    runtime: NextRuntime,
785    next_config: Vc<NextConfig>,
786) -> Result<()> {
787    let blocking_ssr = *next_config.enable_blocking_ssr().await?;
788    let taint = *next_config.enable_taint().await?;
789    let transition_indicator = *next_config.enable_transition_indicator().await?;
790    let gesture_transition = *next_config.enable_gesture_transition().await?;
791    let react_channel = if blocking_ssr || taint || transition_indicator || gesture_transition {
792        "-experimental"
793    } else {
794        ""
795    };
796    let react_condition = if ty.should_use_react_server_condition() {
797        "server"
798    } else {
799        "client"
800    };
801
802    // ✅ Correct alias
803    // ❌ Incorrect alias i.e. importing this entrypoint should throw an error.
804    // ❔ Alias that may produce correct code in certain conditions.Keep until react-markup is
805    // available.
806
807    let mut react_alias = FxIndexMap::default();
808    if runtime == NextRuntime::NodeJs && react_condition == "client" {
809        react_alias.extend(fxindexmap! {// file:///./../../../packages/next/src/compiled/react/package.json
810            rcstr!("react") =>                                  /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react"),
811            rcstr!("react/compiler-runtime") =>                 /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-compiler-runtime"),
812            rcstr!("react/jsx-dev-runtime") =>                  /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-jsx-dev-runtime"),
813            rcstr!("react/jsx-runtime") =>                      /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-jsx-runtime"),
814            // file:///./../../../packages/next/src/compiled/react-dom/package.json
815            rcstr!("react-dom") =>                              /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-dom"),
816            rcstr!("react-dom/client") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/client").into(),
817            rcstr!("react-dom/server") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.node").into(),
818            rcstr!("react-dom/server.browser") =>               /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.browser").into(),
819            // TODO: Use build without legacy APIs
820            rcstr!("react-dom/server.edge") =>                  /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
821            rcstr!("react-dom/static") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.node").into(),
822            rcstr!("react-dom/static.browser") =>               /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.browser").into(),
823            rcstr!("react-dom/static.edge") =>                  /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
824            // file:///./../../../packages/next/src/compiled/react-server-dom-webpack/package.json
825            rcstr!("react-server-dom-webpack/client") =>        /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-server-dom-turbopack-client"),
826            rcstr!("react-server-dom-webpack/server") =>        /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
827            rcstr!("react-server-dom-webpack/server.node") =>   /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
828            rcstr!("react-server-dom-webpack/static") =>        /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.node").into(),
829            rcstr!("react-server-dom-turbopack/client") =>      /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/ssr/react-server-dom-turbopack-client"),
830            rcstr!("react-server-dom-turbopack/server") =>      /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
831            rcstr!("react-server-dom-turbopack/server.node") => /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
832            rcstr!("react-server-dom-turbopack/static.edge") => /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.edge").into(),})
833    } else if runtime == NextRuntime::NodeJs && react_condition == "server" {
834        react_alias.extend(fxindexmap! {// file:///./../../../packages/next/src/compiled/react/package.json
835            rcstr!("react") =>                                  /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react"),
836            rcstr!("react/compiler-runtime") =>                 /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-compiler-runtime"),
837            rcstr!("react/jsx-dev-runtime") =>                  /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-jsx-dev-runtime"),
838            rcstr!("react/jsx-runtime") =>                      /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-jsx-runtime"),
839            // file:///./../../../packages/next/src/compiled/react-dom/package.json
840            rcstr!("react-dom") =>                              /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-dom"),
841            rcstr!("react-dom/client") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/client").into(),
842            rcstr!("react-dom/server") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.node").into(),
843            rcstr!("react-dom/server.browser") =>               /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.browser").into(),
844            // TODO: Use build without legacy APIs
845            rcstr!("react-dom/server.edge") =>                  /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
846            rcstr!("react-dom/static") =>                       /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.node").into(),
847            rcstr!("react-dom/static.browser") =>               /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.browser").into(),
848            rcstr!("react-dom/static.edge") =>                  /* ❔ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
849            // file:///./../../../packages/next/src/compiled/react-server-dom-webpack/package.json
850            rcstr!("react-server-dom-webpack/client") =>        /* ❔ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.node").into(),
851            rcstr!("react-server-dom-webpack/server") =>        /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-server"),
852            rcstr!("react-server-dom-webpack/server.node") =>   /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-server"),
853            rcstr!("react-server-dom-webpack/static") =>        /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-static"),
854            rcstr!("react-server-dom-turbopack/client") =>      /* ❔ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.node").into(),
855            rcstr!("react-server-dom-turbopack/server") =>      /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-server"),
856            rcstr!("react-server-dom-turbopack/server.node") => /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-server"),
857            rcstr!("react-server-dom-turbopack/static") =>      /* ✅ */ rcstr!("next/dist/server/route-modules/app-page/vendored/rsc/react-server-dom-turbopack-static"),
858
859            // Needed to make `react-dom/server` work.
860            // TODO: really?
861                rcstr!("next/dist/compiled/react") => rcstr!("next/dist/compiled/react/index.js"),})
862    } else if runtime == NextRuntime::Edge && react_condition == "client" {
863        react_alias.extend(fxindexmap! {// file:///./../../../packages/next/src/compiled/react/package.json
864            rcstr!("react") =>                                  /* ✅ */ format!("next/dist/compiled/react{react_channel}").into(),
865            rcstr!("react/compiler-runtime") =>                 /* ✅ */ format!("next/dist/compiled/react{react_channel}/compiler-runtime").into(),
866            rcstr!("react/jsx-dev-runtime") =>                  /* ✅ */ format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime").into(),
867            rcstr!("react/jsx-runtime") =>                      /* ✅ */ format!("next/dist/compiled/react{react_channel}/jsx-runtime").into(),
868            // file:///./../../../packages/next/src/compiled/react-dom/package.json
869            rcstr!("react-dom") =>                              /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}").into(),
870            rcstr!("react-dom/client") =>                       /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/client").into(),
871            rcstr!("react-dom/server") =>                       /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
872            rcstr!("react-dom/server.browser") =>               /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/server.browser").into(),
873            // TODO: Use build without legacy APIs
874            rcstr!("react-dom/server.edge") =>                  /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
875            rcstr!("react-dom/static") =>                       /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
876            rcstr!("react-dom/static.browser") =>               /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/static.browser").into(),
877            rcstr!("react-dom/static.edge") =>                  /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
878            // file:///./../../../packages/next/src/compiled/react-server-dom-webpack/package.json
879            rcstr!("react-server-dom-webpack/client") =>        /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge").into(),
880            rcstr!("react-server-dom-webpack/server") =>        /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge").into(),
881            rcstr!("react-server-dom-webpack/server.node") =>   /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
882            rcstr!("react-server-dom-webpack/static") =>        /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.edge").into(),
883            rcstr!("react-server-dom-turbopack/client") =>      /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge").into(),
884            rcstr!("react-server-dom-turbopack/server") =>      /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge").into(),
885            rcstr!("react-server-dom-turbopack/server.node") => /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
886            rcstr!("react-server-dom-turbopack/static") =>      /* ❌ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.edge").into(),})
887    } else if runtime == NextRuntime::Edge && react_condition == "server" {
888        react_alias.extend(fxindexmap! {// file:///./../../../packages/next/src/compiled/react/package.json
889            rcstr!("react") =>                                  /* ✅ */ format!("next/dist/compiled/react{react_channel}/react.react-server").into(),
890            rcstr!("react/compiler-runtime") =>                 /* ❌ */ format!("next/dist/compiled/react{react_channel}/compiler-runtime").into(),
891            rcstr!("react/jsx-dev-runtime") =>                  /* ✅ */ format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime.react-server").into(),
892            rcstr!("react/jsx-runtime") =>                      /* ✅ */ format!("next/dist/compiled/react{react_channel}/jsx-runtime.react-server").into(),
893            // file:///./../../../packages/next/src/compiled/react-dom/package.json
894            rcstr!("react-dom") =>                              /* ✅ */ format!("next/dist/compiled/react-dom{react_channel}/react-dom.react-server").into(),
895            rcstr!("react-dom/client") =>                       /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/client").into(),
896            rcstr!("react-dom/server") =>                       /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
897            rcstr!("react-dom/server.browser") =>               /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/server.browser").into(),
898            // TODO: Use build without legacy APIs
899            rcstr!("react-dom/server.edge") =>                  /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/server.edge").into(),
900            rcstr!("react-dom/static") =>                       /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
901            rcstr!("react-dom/static.browser") =>               /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/static.browser").into(),
902            rcstr!("react-dom/static.edge") =>                  /* ❌ */ format!("next/dist/compiled/react-dom{react_channel}/static.edge").into(),
903            // file:///./../../../packages/next/src/compiled/react-server-dom-webpack/package.json
904            rcstr!("react-server-dom-webpack/client") =>        /* ❔ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge").into(),
905            rcstr!("react-server-dom-webpack/server") =>        /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge").into(),
906            rcstr!("react-server-dom-webpack/server.node") =>   /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
907            rcstr!("react-server-dom-webpack/static") =>        /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.edge").into(),
908            rcstr!("react-server-dom-turbopack/client") =>      /* ❔ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge").into(),
909            rcstr!("react-server-dom-turbopack/server") =>      /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge").into(),
910            rcstr!("react-server-dom-turbopack/server.node") => /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node").into(),
911            rcstr!("react-server-dom-turbopack/static") =>      /* ✅ */ format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/static.edge").into(),});
912
913        react_alias.extend(fxindexmap! {// This should just be `next/dist/compiled/react${react_channel}` but how to Rust.
914            rcstr!("next/dist/compiled/react")                               => react_alias["react"].clone(),
915            rcstr!("next/dist/compiled/react-experimental")                  => react_alias["react"].clone(),
916            rcstr!("next/dist/compiled/react/compiler-runtime")              => react_alias["react/compiler-runtime"].clone(),
917            rcstr!("next/dist/compiled/react-experimental/compiler-runtime") => react_alias["react/compiler-runtime"].clone(),
918            rcstr!("next/dist/compiled/react/jsx-dev-runtime")               => react_alias["react/jsx-dev-runtime"].clone(),
919            rcstr!("next/dist/compiled/react-experimental/jsx-dev-runtime")  => react_alias["react/jsx-dev-runtime"].clone(),
920            rcstr!("next/dist/compiled/react/jsx-runtime")                   => react_alias["react/jsx-runtime"].clone(),
921            rcstr!("next/dist/compiled/react-experimental/jsx-runtime")      => react_alias["react/jsx-runtime"].clone(),
922            rcstr!("next/dist/compiled/react-dom")                           => react_alias["react-dom"].clone(),
923            rcstr!("next/dist/compiled/react-dom-experimental")              => react_alias["react-dom"].clone(),});
924    }
925
926    let react_client_package = get_react_client_package(next_config).await?;
927    react_alias.extend(fxindexmap! {rcstr!("react-dom/client") => RcStr::from(format!("next/dist/compiled/react-dom{react_channel}/{react_client_package}")),});
928
929    let mut alias = react_alias;
930    if react_condition == "server" {
931        // This is used in the server runtime to import React Server Components.
932        alias.extend(
933            fxindexmap! {rcstr!("next/error") => rcstr!("next/dist/api/error.react-server"),
934            rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"),
935            rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"),},
936        );
937    }
938
939    insert_exact_alias_map(import_map, project_path, alias);
940
941    Ok(())
942}
943
944async fn rsc_aliases(
945    import_map: &mut ImportMap,
946    project_path: FileSystemPath,
947    ty: ServerContextType,
948    runtime: NextRuntime,
949    next_config: Vc<NextConfig>,
950) -> Result<()> {
951    apply_vendored_react_aliases_server(
952        import_map,
953        project_path.clone(),
954        ty.clone(),
955        runtime,
956        next_config,
957    )
958    .await?;
959
960    let mut alias = FxIndexMap::default();
961    if ty.should_use_react_server_condition() {
962        // This is used in the server runtime to import React Server Components.
963        alias.extend(
964            fxindexmap! {rcstr!("next/error") => rcstr!("next/dist/api/error.react-server"),
965            rcstr!("next/navigation") => rcstr!("next/dist/api/navigation.react-server"),
966            rcstr!("next/link") => rcstr!("next/dist/client/app-dir/link.react-server"),},
967        );
968    }
969
970    insert_exact_alias_map(import_map, project_path.clone(), alias);
971
972    Ok(())
973}
974
975pub fn mdx_import_source_file() -> RcStr {
976    format!("{VIRTUAL_PACKAGE_NAME}/mdx-import-source").into()
977}
978
979// Insert aliases for Next.js stubs of fetch, object-assign, and url
980// Keep in sync with getOptimizedModuleAliases in webpack-config.ts
981async fn insert_optimized_module_aliases(
982    import_map: &mut ImportMap,
983    project_path: FileSystemPath,
984) -> Result<()> {
985    insert_exact_alias_map(
986        import_map,
987        project_path,
988        fxindexmap! {rcstr!("unfetch") => rcstr!("next/dist/build/polyfills/fetch/index.js"),
989        rcstr!("isomorphic-unfetch") => rcstr!("next/dist/build/polyfills/fetch/index.js"),
990        rcstr!("whatwg-fetch") => rcstr!("next/dist/build/polyfills/fetch/whatwg-fetch.js"),
991        rcstr!("object-assign") => rcstr!("next/dist/build/polyfills/object-assign.js"),
992        rcstr!("object.assign/auto") => rcstr!("next/dist/build/polyfills/object.assign/auto.js"),
993        rcstr!("object.assign/implementation") => rcstr!("next/dist/build/polyfills/object.assign/implementation.js"),
994        rcstr!("object.assign/polyfill") => rcstr!("next/dist/build/polyfills/object.assign/polyfill.js"),
995        rcstr!("object.assign/shim") => rcstr!("next/dist/build/polyfills/object.assign/shim.js"),
996        rcstr!("url") => rcstr!("next/dist/compiled/native-url"),
997        rcstr!("node:url") => rcstr!("next/dist/compiled/native-url"),},
998    );
999    Ok(())
1000}
1001
1002// Make sure to not add any external requests here.
1003async fn insert_next_shared_aliases(
1004    import_map: &mut ImportMap,
1005    project_path: FileSystemPath,
1006    execution_context: Vc<ExecutionContext>,
1007    next_config: Vc<NextConfig>,
1008    next_mode: Vc<NextMode>,
1009    is_runtime_edge: bool,
1010) -> Result<()> {
1011    let package_root = next_js_fs().root().owned().await?;
1012
1013    insert_alias_to_alternatives(
1014        import_map,
1015        mdx_import_source_file(),
1016        vec![
1017            request_to_import_mapping(project_path.clone(), rcstr!("./mdx-components")),
1018            request_to_import_mapping(project_path.clone(), rcstr!("./src/mdx-components")),
1019            request_to_import_mapping(project_path.clone(), rcstr!("@mdx-js/react")),
1020            request_to_import_mapping(project_path.clone(), rcstr!("@next/mdx/mdx-components.js")),
1021        ],
1022    );
1023
1024    insert_package_alias(
1025        import_map,
1026        &format!("{VIRTUAL_PACKAGE_NAME}/"),
1027        package_root,
1028    );
1029
1030    // NOTE: `@next/font/local` has moved to a BeforeResolve Plugin, so it does not
1031    // have ImportMapping replacers here.
1032    //
1033    // TODO: Add BeforeResolve plugins for `@next/font/google`
1034
1035    let next_font_google_replacer_mapping = ImportMapping::Dynamic(ResolvedVc::upcast(
1036        NextFontGoogleReplacer::new(project_path.clone())
1037            .to_resolved()
1038            .await?,
1039    ))
1040    .resolved_cell();
1041
1042    import_map.insert_alias(
1043        // Request path from js via next-font swc transform
1044        AliasPattern::exact(rcstr!("next/font/google/target.css")),
1045        next_font_google_replacer_mapping,
1046    );
1047
1048    import_map.insert_alias(
1049        // Request path from js via next-font swc transform
1050        AliasPattern::exact(rcstr!("@next/font/google/target.css")),
1051        next_font_google_replacer_mapping,
1052    );
1053
1054    let fetch_client = next_config.fetch_client();
1055    import_map.insert_alias(
1056        AliasPattern::exact(rcstr!(
1057            "@vercel/turbopack-next/internal/font/google/cssmodule.module.css"
1058        )),
1059        ImportMapping::Dynamic(ResolvedVc::upcast(
1060            NextFontGoogleCssModuleReplacer::new(
1061                project_path.clone(),
1062                execution_context,
1063                next_mode,
1064                fetch_client,
1065            )
1066            .to_resolved()
1067            .await?,
1068        ))
1069        .resolved_cell(),
1070    );
1071
1072    import_map.insert_alias(
1073        AliasPattern::exact(GOOGLE_FONTS_INTERNAL_PREFIX),
1074        ImportMapping::Dynamic(ResolvedVc::upcast(
1075            NextFontGoogleFontFileReplacer::new(project_path.clone(), fetch_client)
1076                .to_resolved()
1077                .await?,
1078        ))
1079        .resolved_cell(),
1080    );
1081
1082    let next_package = get_next_package(project_path.clone()).await?;
1083    import_map.insert_singleton_alias(rcstr!("@swc/helpers"), next_package.clone());
1084    import_map.insert_singleton_alias(rcstr!("styled-jsx"), next_package.clone());
1085    import_map.insert_singleton_alias(rcstr!("next"), project_path.clone());
1086    import_map.insert_singleton_alias(rcstr!("react"), project_path.clone());
1087    import_map.insert_singleton_alias(rcstr!("react-dom"), project_path.clone());
1088    let react_client_package = get_react_client_package(next_config).await?;
1089    import_map.insert_exact_alias(
1090        rcstr!("react-dom/client"),
1091        request_to_import_mapping(
1092            project_path.clone(),
1093            format!("react-dom/{react_client_package}").into(),
1094        ),
1095    );
1096
1097    import_map.insert_alias(
1098        // Make sure you can't import custom server as it'll cause all Next.js internals to be
1099        // bundled which doesn't work.
1100        AliasPattern::exact(rcstr!("next")),
1101        ImportMapping::Empty.resolved_cell(),
1102    );
1103
1104    //https://github.com/vercel/next.js/blob/f94d4f93e4802f951063cfa3351dd5a2325724b3/packages/next/src/build/webpack-config.ts#L1196
1105    import_map.insert_exact_alias(
1106        rcstr!("setimmediate"),
1107        request_to_import_mapping(
1108            project_path.clone(),
1109            rcstr!("next/dist/compiled/setimmediate"),
1110        ),
1111    );
1112
1113    import_map.insert_exact_alias(
1114        rcstr!("private-next-rsc-server-reference"),
1115        request_to_import_mapping(
1116            project_path.clone(),
1117            rcstr!("next/dist/build/webpack/loaders/next-flight-loader/server-reference"),
1118        ),
1119    );
1120    import_map.insert_exact_alias(
1121        rcstr!("private-next-rsc-action-client-wrapper"),
1122        request_to_import_mapping(
1123            project_path.clone(),
1124            rcstr!("next/dist/build/webpack/loaders/next-flight-loader/action-client-wrapper"),
1125        ),
1126    );
1127    import_map.insert_exact_alias(
1128        rcstr!("private-next-rsc-action-validate"),
1129        request_to_import_mapping(
1130            project_path.clone(),
1131            rcstr!("next/dist/build/webpack/loaders/next-flight-loader/action-validate"),
1132        ),
1133    );
1134    import_map.insert_exact_alias(
1135        rcstr!("private-next-rsc-action-encryption"),
1136        request_to_import_mapping(
1137            project_path.clone(),
1138            rcstr!("next/dist/server/app-render/encryption"),
1139        ),
1140    );
1141    import_map.insert_exact_alias(
1142        rcstr!("private-next-rsc-cache-wrapper"),
1143        request_to_import_mapping(
1144            project_path.clone(),
1145            rcstr!("next/dist/build/webpack/loaders/next-flight-loader/cache-wrapper"),
1146        ),
1147    );
1148    import_map.insert_exact_alias(
1149        rcstr!("private-next-rsc-track-dynamic-import"),
1150        request_to_import_mapping(
1151            project_path.clone(),
1152            rcstr!("next/dist/build/webpack/loaders/next-flight-loader/track-dynamic-import"),
1153        ),
1154    );
1155
1156    insert_package_alias(
1157        import_map,
1158        "@vercel/turbopack-node/",
1159        turbopack_node::embed_js::embed_fs().root().owned().await?,
1160    );
1161
1162    let image_config = next_config.image_config().await?;
1163    if let Some(loader_file) = image_config.loader_file.as_deref().map(RcStr::from) {
1164        import_map.insert_exact_alias(
1165            rcstr!("next/dist/shared/lib/image-loader"),
1166            request_to_import_mapping(project_path.clone(), loader_file.clone()),
1167        );
1168
1169        if is_runtime_edge {
1170            import_map.insert_exact_alias(
1171                rcstr!("next/dist/esm/shared/lib/image-loader"),
1172                request_to_import_mapping(project_path.clone(), loader_file),
1173            );
1174        }
1175    }
1176
1177    Ok(())
1178}
1179
1180pub async fn get_next_package(context_directory: FileSystemPath) -> Result<FileSystemPath> {
1181    try_get_next_package(context_directory)
1182        .owned()
1183        .await?
1184        .context("Next.js package not found")
1185}
1186
1187#[turbo_tasks::value(shared)]
1188struct MissingNextFolderIssue {
1189    path: FileSystemPath,
1190    root: FileSystemPath,
1191}
1192
1193#[async_trait]
1194#[turbo_tasks::value_impl]
1195impl Issue for MissingNextFolderIssue {
1196    async fn file_path(&self) -> Result<FileSystemPath> {
1197        Ok(self.path.clone())
1198    }
1199
1200    fn severity(&self) -> IssueSeverity {
1201        // In theory this should be fatal (how can we ever recover from next missing when we are
1202        // next), but we actually might be detecting an ephemeral scenario where 'next' is moving
1203        // and we can recover.
1204        IssueSeverity::Error
1205    }
1206
1207    fn stage(&self) -> IssueStage {
1208        IssueStage::Resolve
1209    }
1210
1211    async fn title(&self) -> Result<StyledString> {
1212        Ok(StyledString::Text(rcstr!(
1213            "Could not find the Next.js package (next/package.json)"
1214        )))
1215    }
1216
1217    async fn description(&self) -> Result<Option<StyledString>> {
1218        let context_path: RcStr = match to_sys_path(self.path.clone()).await? {
1219            Some(path) => path.to_str().unwrap_or("{unknown}").into(),
1220            _ => rcstr!("{unknown}"),
1221        };
1222        let root_path: RcStr = match to_sys_path(self.root.clone()).await? {
1223            Some(path) => path.to_str().unwrap_or("{unknown}").into(),
1224            _ => rcstr!("{unknown}"),
1225        };
1226
1227        Ok(Some(StyledString::Stack(vec![
1228            StyledString::Line(vec![
1229                StyledString::Text(rcstr!("Resolved from: ")),
1230                StyledString::Strong(context_path),
1231            ]),
1232            StyledString::Line(vec![
1233                StyledString::Text(rcstr!("Filesystem root used for resolution: ")),
1234                StyledString::Strong(root_path),
1235            ]),
1236            StyledString::Line(vec![StyledString::Text(rcstr!(""))]),
1237            StyledString::Line(vec![StyledString::Text(rcstr!("Possible causes:"))]),
1238            StyledString::Line(vec![StyledString::Text(rcstr!(
1239                "  - node_modules is being reorganized by a concurrent install (e.g. pnpm adding \
1240                 a package with a `next` peer dependency). This is transient and should clear \
1241                 once the install completes."
1242            ))]),
1243            StyledString::Line(vec![StyledString::Text(rcstr!(
1244                "  - node_modules/next was removed, renamed, or has a broken symlink."
1245            ))]),
1246            StyledString::Line(vec![
1247                StyledString::Text(rcstr!("  - The workspace root is incorrect — see ")),
1248                StyledString::Code(rcstr!("turbopack.root")),
1249                StyledString::Text(rcstr!(
1250                    " in the Next.js config docs for how to configure it."
1251                )),
1252            ]),
1253            StyledString::Line(vec![StyledString::Text(rcstr!(
1254                "  - In a monorepo, the Next.js package may only exist in a directory above the \
1255                 closest directory containing a package manager lockfile. The workspace root is \
1256                 detected by locating the nearest package manager lockfile."
1257            ))]),
1258            StyledString::Line(vec![StyledString::Text(rcstr!(
1259                "  - Next.js is installed globally rather than as a project dependency. This is \
1260                 not supported; install it locally."
1261            ))]),
1262            StyledString::Line(vec![StyledString::Text(rcstr!(""))]),
1263            StyledString::Line(vec![StyledString::Text(rcstr!(
1264                "Note: To ensure a hermetic build and a portable cache, files outside of the \
1265                 workspace root are not compiled."
1266            ))]),
1267        ])))
1268    }
1269
1270    fn documentation_link(&self) -> RcStr {
1271        rcstr!(
1272            "https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#root-directory"
1273        )
1274    }
1275}
1276
1277#[turbo_tasks::function]
1278pub async fn try_get_next_package(
1279    context_directory: FileSystemPath,
1280) -> Result<Vc<OptionFileSystemPath>> {
1281    let root = context_directory.root().owned().await?;
1282    let result = resolve(
1283        context_directory.clone(),
1284        ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
1285        Request::parse(Pattern::Constant(rcstr!("next/package.json"))),
1286        node_cjs_resolve_options(root.clone()),
1287    );
1288    if let Some(source) = result.await?.first_source() {
1289        Ok(Vc::cell(Some(source.ident().await?.path.parent())))
1290    } else {
1291        MissingNextFolderIssue {
1292            path: context_directory,
1293            root,
1294        }
1295        .resolved_cell()
1296        .emit();
1297        Ok(Vc::cell(None))
1298    }
1299}
1300
1301pub async fn insert_alias_option<const N: usize>(
1302    import_map: &mut ImportMap,
1303    project_path: &FileSystemPath,
1304    alias_options: Vc<ResolveAliasMap>,
1305    conditions: [&'static str; N],
1306) -> Result<()> {
1307    let conditions = BTreeMap::from(conditions.map(|c| (c.into(), ConditionValue::Set)));
1308    for (alias, value) in &alias_options.await? {
1309        if let Some(mapping) = export_value_to_import_mapping(value, &conditions, project_path) {
1310            import_map.insert_alias(alias, mapping);
1311        }
1312    }
1313    Ok(())
1314}
1315
1316fn export_value_to_import_mapping(
1317    value: &SubpathValue,
1318    conditions: &BTreeMap<RcStr, ConditionValue>,
1319    project_path: &FileSystemPath,
1320) -> Option<ResolvedVc<ImportMapping>> {
1321    let mut result = Vec::new();
1322    value.add_results(
1323        conditions,
1324        &ConditionValue::Unset,
1325        &mut FxHashMap::default(),
1326        &mut result,
1327    );
1328    if result.is_empty() {
1329        None
1330    } else {
1331        Some(if result.len() == 1 {
1332            ImportMapping::PrimaryAlternative(result[0].0.into(), Some(project_path.clone()))
1333                .resolved_cell()
1334        } else {
1335            ImportMapping::Alternatives(
1336                result
1337                    .iter()
1338                    .map(|(m, _)| {
1339                        ImportMapping::PrimaryAlternative((*m).into(), Some(project_path.clone()))
1340                            .resolved_cell()
1341                    })
1342                    .collect(),
1343            )
1344            .resolved_cell()
1345        })
1346    }
1347}
1348
1349fn insert_exact_alias_map(
1350    import_map: &mut ImportMap,
1351    project_path: FileSystemPath,
1352    map: FxIndexMap<RcStr, RcStr>,
1353) {
1354    for (pattern, request) in map {
1355        import_map.insert_exact_alias(
1356            pattern,
1357            request_to_import_mapping(project_path.clone(), request),
1358        );
1359    }
1360}
1361
1362fn insert_wildcard_alias_map(
1363    import_map: &mut ImportMap,
1364    project_path: FileSystemPath,
1365    map: FxIndexMap<RcStr, RcStr>,
1366) {
1367    for (pattern, request) in map {
1368        import_map.insert_wildcard_alias(
1369            pattern,
1370            request_to_import_mapping(project_path.clone(), request),
1371        );
1372    }
1373}
1374
1375/// Inserts an alias to an alternative of import mappings into an import map.
1376fn insert_alias_to_alternatives<'a>(
1377    import_map: &mut ImportMap,
1378    alias: impl Into<RcStr> + 'a,
1379    alternatives: Vec<ResolvedVc<ImportMapping>>,
1380) {
1381    import_map.insert_exact_alias(
1382        alias.into(),
1383        ImportMapping::Alternatives(alternatives).resolved_cell(),
1384    );
1385}
1386
1387/// Inserts an alias to an import mapping into an import map.
1388fn insert_package_alias(import_map: &mut ImportMap, prefix: &str, package_root: FileSystemPath) {
1389    import_map.insert_wildcard_alias(
1390        prefix,
1391        ImportMapping::PrimaryAlternative(rcstr!("./*"), Some(package_root)).resolved_cell(),
1392    );
1393}
1394
1395/// Handles instrumentation-client.ts bundling logic.
1396///
1397/// Resolves the `private-next-instrumentation-client` alias to a virtual module
1398/// that first requires each entry of `instrumentationClientInject` for side
1399/// effects (in array order) and then re-exports the user's
1400/// `instrumentation-client.{pageExt}` file via the
1401/// `private-next-instrumentation-client-user` alias.
1402async fn insert_instrumentation_client_alias(
1403    import_map: &mut ImportMap,
1404    project_path: FileSystemPath,
1405    next_config: Vc<NextConfig>,
1406) -> Result<()> {
1407    let user_file_alternatives = vec![
1408        request_to_import_mapping(project_path.clone(), rcstr!("./src/instrumentation-client")),
1409        request_to_import_mapping(
1410            project_path.clone(),
1411            rcstr!("./src/instrumentation-client.ts"),
1412        ),
1413        request_to_import_mapping(project_path.clone(), rcstr!("./instrumentation-client")),
1414        request_to_import_mapping(project_path.clone(), rcstr!("./instrumentation-client.ts")),
1415        ImportMapping::Ignore.resolved_cell(),
1416    ];
1417
1418    let injects = next_config.instrumentation_client_inject().await?;
1419
1420    if injects.is_empty() {
1421        insert_alias_to_alternatives(
1422            import_map,
1423            rcstr!("private-next-instrumentation-client"),
1424            user_file_alternatives,
1425        );
1426        return Ok(());
1427    }
1428
1429    // The user file is reached through a separate alias so the existing
1430    // alternative resolution stays unchanged.
1431    insert_alias_to_alternatives(
1432        import_map,
1433        rcstr!("private-next-instrumentation-client-user"),
1434        user_file_alternatives,
1435    );
1436
1437    let injects = injects
1438        .iter()
1439        .map(|s| s.as_str())
1440        .chain(std::iter::once("private-next-instrumentation-client-user"));
1441
1442    let mut body = String::new();
1443    for (i, spec) in injects.clone().enumerate() {
1444        body.push_str(&format!(
1445            "var mod_{i} = require({});\n",
1446            serde_json::to_string(spec)?
1447        ));
1448    }
1449    body.push_str("module.exports = { onRouterTransitionStart(url, type) {\n");
1450    for (i, _) in injects.enumerate() {
1451        body.push_str(&format!(
1452            "    mod_{i}?.onRouterTransitionStart?.(url, type);\n"
1453        ));
1454    }
1455    body.push_str("}};\n");
1456
1457    let virtual_source = VirtualSource::new(
1458        // Use cjs here in case the user has type:module in the package.json. We do intentionally
1459        // place this file in the user's folder, so that the `require`s inserted above resolve
1460        // as expected.
1461        project_path.join("__next_instrumentation_client.cjs")?,
1462        AssetContent::file(FileContent::Content(body.into()).cell()),
1463    )
1464    .to_resolved()
1465    .await?;
1466
1467    import_map.insert_exact_alias(
1468        rcstr!("private-next-instrumentation-client"),
1469        ImportMapping::Direct(
1470            ResolveResult::source(ResolvedVc::upcast(virtual_source)).resolved_cell(),
1471        )
1472        .resolved_cell(),
1473    );
1474
1475    Ok(())
1476}
1477
1478fn insert_client_only_error_alias(import_map: &mut ImportMap) {
1479    import_map.insert_exact_alias(
1480        rcstr!("client-only"),
1481        ImportMapping::Error(ResolvedVc::upcast(
1482            InvalidImportIssue {
1483                title: StyledString::Line(vec![
1484                    StyledString::Code(rcstr!("'client-only'")),
1485                    StyledString::Text(rcstr!(
1486                        " cannot be imported from a Server Component module"
1487                    )),
1488                ])
1489                .resolved_cell(),
1490                description: Some(
1491                    StyledString::Line(vec![StyledString::Text(
1492                        "It should only be used from a Client Component.".into(),
1493                    )])
1494                    .resolved_cell(),
1495                ),
1496            }
1497            .resolved_cell(),
1498        ))
1499        .resolved_cell(),
1500    );
1501
1502    // styled-jsx imports client-only. So this is effectively the same as above but produces a nicer
1503    // import trace.
1504    let mapping = ImportMapping::Error(ResolvedVc::upcast(
1505        InvalidImportIssue {
1506            title: StyledString::Line(vec![
1507                StyledString::Code(rcstr!("'styled-jsx'")),
1508                StyledString::Text(rcstr!(" cannot be imported from a Server Component module")),
1509            ])
1510            .resolved_cell(),
1511            description: Some(
1512                StyledString::Line(vec![StyledString::Text(
1513                    "It only works in a Client Component but none of its parents are marked with \
1514                     'use client', so they're Server Components by default."
1515                        .into(),
1516                )])
1517                .resolved_cell(),
1518            ),
1519        }
1520        .resolved_cell(),
1521    ))
1522    .resolved_cell();
1523    import_map.insert_exact_alias(rcstr!("styled-jsx"), mapping);
1524    import_map.insert_wildcard_alias(rcstr!("styled-jsx/"), mapping);
1525}
1526
1527fn insert_server_only_error_alias(import_map: &mut ImportMap) {
1528    import_map.insert_exact_alias(
1529        rcstr!("server-only"),
1530        ImportMapping::Error(ResolvedVc::upcast(
1531            InvalidImportIssue {
1532                title: StyledString::Line(vec![
1533                    StyledString::Code(rcstr!("'server-only'")),
1534                    StyledString::Text(rcstr!(
1535                        " cannot be imported from a Client Component module"
1536                    )),
1537                ])
1538                .resolved_cell(),
1539                description: Some(
1540                    StyledString::Line(vec![StyledString::Text(
1541                        "It should only be used from a Server Component.".into(),
1542                    )])
1543                    .resolved_cell(),
1544                ),
1545            }
1546            .resolved_cell(),
1547        ))
1548        .resolved_cell(),
1549    );
1550}
1551
1552#[turbo_tasks::value(shared)]
1553struct InvalidImportIssue {
1554    title: ResolvedVc<StyledString>,
1555    description: Option<ResolvedVc<StyledString>>,
1556}
1557
1558#[async_trait]
1559#[turbo_tasks::value_impl]
1560impl Issue for InvalidImportIssue {
1561    fn severity(&self) -> IssueSeverity {
1562        IssueSeverity::Error
1563    }
1564
1565    async fn file_path(&self) -> Result<FileSystemPath> {
1566        panic!("InvalidImportIssue::file_path should not be called");
1567    }
1568
1569    fn stage(&self) -> IssueStage {
1570        IssueStage::Resolve
1571    }
1572
1573    async fn title(&self) -> Result<StyledString> {
1574        Ok((*self.title.await?).clone())
1575    }
1576
1577    async fn description(&self) -> Result<Option<StyledString>> {
1578        match self.description {
1579            Some(inner) => Ok(Some((*inner.await?).clone())),
1580            None => Ok(None),
1581        }
1582    }
1583}
1584
1585// To alias e.g. both `import "next/link"` and `import "next/link.js"`
1586fn insert_exact_alias_or_js(
1587    import_map: &mut ImportMap,
1588    pattern: RcStr,
1589    mapping: ResolvedVc<ImportMapping>,
1590) {
1591    import_map.insert_exact_alias(format!("{pattern}.js"), mapping);
1592    import_map.insert_exact_alias(pattern, mapping);
1593}
1594
1595/// Creates a direct import mapping to the result of resolving a request
1596/// in a context.
1597fn request_to_import_mapping(
1598    context_path: FileSystemPath,
1599    request: RcStr,
1600) -> ResolvedVc<ImportMapping> {
1601    ImportMapping::PrimaryAlternative(request, Some(context_path)).resolved_cell()
1602}
1603
1604/// Creates a direct import mapping to the result of resolving an external
1605/// request.
1606fn external_request_to_cjs_import_mapping(
1607    context_dir: FileSystemPath,
1608    request: RcStr,
1609) -> ResolvedVc<ImportMapping> {
1610    ImportMapping::PrimaryAlternativeExternal {
1611        name: Some(request),
1612        ty: ExternalType::CommonJs,
1613        traced: ExternalTraced::Traced,
1614        lookup_dir: context_dir,
1615    }
1616    .resolved_cell()
1617}
1618
1619/// Creates a direct import mapping to the result of resolving an external
1620/// request.
1621fn external_request_to_esm_import_mapping(
1622    context_dir: FileSystemPath,
1623    request: RcStr,
1624) -> ResolvedVc<ImportMapping> {
1625    ImportMapping::PrimaryAlternativeExternal {
1626        name: Some(request),
1627        ty: ExternalType::EcmaScriptModule,
1628        traced: ExternalTraced::Traced,
1629        lookup_dir: context_dir,
1630    }
1631    .resolved_cell()
1632}