Skip to main content

next_core/next_edge/
context.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{ResolvedVc, Vc, trace::TraceRawVcs};
5use turbo_tasks_fs::FileSystemPath;
6use turbopack_browser::BrowserChunkingContext;
7use turbopack_core::{
8    chunk::{
9        AssetSuffix, ChunkingConfig, ChunkingContext, CrossOrigin, MangleType, MinifyType,
10        SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
11        chunk_id_strategy::ModuleIdStrategy,
12    },
13    compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
14    environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment, NodeJsVersion},
15    free_var_references,
16    issue::IssueSeverity,
17    module_graph::{
18        binding_usage_info::OptionBindingUsageInfo, style_groups::StyleGroupsAlgorithm,
19    },
20};
21use turbopack_css::chunk::CssChunkType;
22use turbopack_ecmascript::chunk::EcmascriptChunkType;
23use turbopack_node::execution_context::ExecutionContext;
24use turbopack_resolve::resolve_options_context::{ResolveOptionsContext, TsConfigHandling};
25
26use crate::{
27    app_structure::CollectedRootParams,
28    mode::NextMode,
29    next_config::NextConfig,
30    next_font::local::NextFontLocalResolvePlugin,
31    next_import_map::{get_next_edge_and_server_fallback_import_map, get_next_edge_import_map},
32    next_server::context::ServerContextType,
33    next_shared::resolve::NextSharedRuntimeResolvePlugin,
34    util::{
35        NextRuntime, OptionEnvMap, defines, foreign_code_context_condition,
36        free_var_references_with_vercel_system_env_warnings, worker_forwarded_globals,
37    },
38};
39
40#[turbo_tasks::function]
41async fn next_edge_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
42    Ok(defines(&*define_env.await?).cell())
43}
44
45/// Define variables for the edge runtime can be accessibly globally.
46/// See [here](https://github.com/vercel/next.js/blob/160bb99b06e9c049f88e25806fd995f07f4cc7e1/packages/next/src/build/webpack-config.ts#L1715-L1718) how webpack configures it.
47#[turbo_tasks::function]
48async fn next_edge_free_vars(
49    project_path: FileSystemPath,
50    define_env: Vc<OptionEnvMap>,
51    report_system_env_inlining: Vc<IssueSeverity>,
52) -> Result<Vc<FreeVarReferences>> {
53    Ok(free_var_references!(
54        ..free_var_references_with_vercel_system_env_warnings(
55            defines(&*define_env.await?),
56            *report_system_env_inlining.await?
57        ),
58        Buffer = FreeVarReference::EcmaScriptModule {
59            request: rcstr!("buffer"),
60            lookup_path: Some(project_path),
61            export: Some(rcstr!("Buffer")),
62        },
63    )
64    .cell())
65}
66
67#[turbo_tasks::function]
68pub async fn get_edge_compile_time_info(
69    project_path: FileSystemPath,
70    define_env: Vc<OptionEnvMap>,
71    node_version: ResolvedVc<NodeJsVersion>,
72    report_system_env_inlining: Vc<IssueSeverity>,
73) -> Result<Vc<CompileTimeInfo>> {
74    CompileTimeInfo::builder(
75        Environment::new(ExecutionEnvironment::EdgeWorker(
76            EdgeWorkerEnvironment { node_version }.resolved_cell(),
77        ))
78        .to_resolved()
79        .await?,
80    )
81    .defines(next_edge_defines(define_env).to_resolved().await?)
82    .free_var_references(
83        next_edge_free_vars(project_path, define_env, report_system_env_inlining)
84            .to_resolved()
85            .await?,
86    )
87    .cell()
88    .await
89}
90
91#[turbo_tasks::function]
92pub async fn get_edge_resolve_options_context(
93    project_path: FileSystemPath,
94    ty: ServerContextType,
95    mode: Vc<NextMode>,
96    next_config: Vc<NextConfig>,
97    execution_context: Vc<ExecutionContext>,
98    collected_root_params: Option<Vc<CollectedRootParams>>,
99) -> Result<Vc<ResolveOptionsContext>> {
100    let next_edge_import_map = get_next_edge_import_map(
101        project_path.clone(),
102        ty.clone(),
103        next_config,
104        mode,
105        execution_context,
106        collected_root_params,
107    )
108    .to_resolved()
109    .await?;
110    let next_edge_fallback_import_map =
111        get_next_edge_and_server_fallback_import_map(project_path.clone(), NextRuntime::Edge)
112            .to_resolved()
113            .await?;
114
115    let before_resolve_plugins = if matches!(
116        ty,
117        ServerContextType::Pages { .. }
118            | ServerContextType::AppSSR { .. }
119            | ServerContextType::AppRSC { .. }
120    ) {
121        vec![ResolvedVc::upcast(
122            NextFontLocalResolvePlugin::new(project_path.clone())
123                .to_resolved()
124                .await?,
125        )]
126    } else {
127        vec![]
128    };
129
130    let after_resolve_plugins = vec![ResolvedVc::upcast(
131        NextSharedRuntimeResolvePlugin::new(project_path.clone())
132            .to_resolved()
133            .await?,
134    )];
135
136    // https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102
137    let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();
138    custom_conditions.extend(NextRuntime::Edge.custom_resolve_conditions());
139
140    if ty.should_use_react_server_condition() {
141        custom_conditions.push(rcstr!("react-server"));
142    };
143
144    // Edge runtime is disabled for projects with Cache Components enabled except for Middleware
145    // but Middleware doesn't have all Next.js APIs so we omit the "next-js" condition for all edge
146    // entrypoints
147
148    let resolve_options_context = ResolveOptionsContext {
149        enable_node_modules: Some(project_path.root().owned().await?),
150        enable_edge_node_externals: true,
151        custom_conditions,
152        import_map: Some(next_edge_import_map),
153        fallback_import_map: Some(next_edge_fallback_import_map),
154        module: true,
155        browser: true,
156        after_resolve_plugins,
157        before_resolve_plugins,
158
159        ..Default::default()
160    };
161
162    let tsconfig_path = next_config.typescript_tsconfig_path().await?;
163    let tsconfig_path = project_path.join(
164        tsconfig_path
165            .as_ref()
166            // Fall back to tsconfig only for resolving. This is because we don't want Turbopack to
167            // resolve tsconfig.json relative to the file being compiled.
168            .unwrap_or(&rcstr!("tsconfig.json")),
169    )?;
170
171    Ok(ResolveOptionsContext {
172        enable_typescript: true,
173        enable_react: true,
174        enable_mjs_extension: true,
175        enable_edge_node_externals: true,
176        custom_extensions: next_config.resolve_extension().owned().await?,
177        tsconfig_path: TsConfigHandling::Fixed(tsconfig_path),
178        rules: vec![(
179            foreign_code_context_condition(next_config, project_path).await?,
180            resolve_options_context.clone().resolved_cell(),
181        )],
182        ..resolve_options_context
183    }
184    .cell())
185}
186
187#[turbo_tasks::task_input(contains_unresolved_vcs)]
188#[derive(Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
189pub struct EdgeChunkingContextOptions {
190    pub mode: Vc<NextMode>,
191    pub root_path: FileSystemPath,
192    pub node_root: FileSystemPath,
193    pub output_root_to_root_path: Vc<RcStr>,
194    pub environment: Vc<Environment>,
195    pub module_id_strategy: Vc<ModuleIdStrategy>,
196    pub export_usage: Vc<OptionBindingUsageInfo>,
197    pub unused_references: Vc<UnusedReferences>,
198    pub turbo_minify: Vc<bool>,
199    pub turbo_source_maps: Vc<SourceMapsType>,
200    pub no_mangling: Vc<bool>,
201    pub scope_hoisting: Vc<bool>,
202    pub nested_async_chunking: Vc<bool>,
203    pub client_root: FileSystemPath,
204    pub client_static_folder_name: RcStr,
205    pub asset_prefix: RcStr,
206    pub css_url_suffix: Vc<Option<RcStr>>,
207    pub hash_salt: ResolvedVc<RcStr>,
208    pub cross_origin: Vc<CrossOrigin>,
209    pub style_groups_algorithm: StyleGroupsAlgorithm,
210}
211
212/// Like `get_edge_chunking_context` but all assets are emitted as client assets (so `/_next`)
213#[turbo_tasks::function]
214pub async fn get_edge_chunking_context_with_client_assets(
215    options: EdgeChunkingContextOptions,
216) -> Result<Vc<Box<dyn ChunkingContext>>> {
217    let EdgeChunkingContextOptions {
218        mode,
219        root_path,
220        node_root,
221        output_root_to_root_path,
222        environment,
223        module_id_strategy,
224        export_usage,
225        unused_references,
226        turbo_minify,
227        turbo_source_maps,
228        no_mangling,
229        scope_hoisting,
230        nested_async_chunking,
231        client_root,
232        client_static_folder_name,
233        asset_prefix,
234        css_url_suffix,
235        hash_salt,
236        cross_origin,
237        style_groups_algorithm,
238    } = options;
239    let cross_origin_loading = *cross_origin.await?;
240    let output_root = node_root.join("server/edge")?;
241    let next_mode = mode.await?;
242    let mut builder = BrowserChunkingContext::builder(
243        root_path,
244        output_root.clone(),
245        output_root_to_root_path.owned().await?,
246        client_root.clone(),
247        output_root.join("chunks/ssr")?,
248        client_root
249            .join(&client_static_folder_name)?
250            .join("media")?,
251        environment.to_resolved().await?,
252        next_mode.runtime_type(),
253    )
254    .asset_base_path(Some(asset_prefix))
255    .default_url_behavior(UrlBehavior {
256        suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
257        static_suffix: css_url_suffix.to_resolved().await?,
258    })
259    .minify_type(if *turbo_minify.await? {
260        MinifyType::Minify {
261            // React needs deterministic function names to work correctly.
262            mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
263        }
264    } else {
265        MinifyType::NoMinify
266    })
267    .source_maps(*turbo_source_maps.await?)
268    // The edge server runtime is browser-like, so it uses a `BrowserChunkingContext` whose default
269    // source map source type is `TurbopackUri` (sources left as `turbopack:///[project]/...`).
270    // Match the Node.js server context instead so server stack traces get real file paths:
271    // absolute `file://` URIs in dev, relative paths in production.
272    .source_map_source_type(if next_mode.is_development() {
273        SourceMapSourceType::AbsoluteFileUri
274    } else {
275        SourceMapSourceType::RelativeUri
276    })
277    .cross_origin(cross_origin_loading)
278    .module_id_strategy(module_id_strategy.to_resolved().await?)
279    .export_usage(*export_usage.await?)
280    .unused_references(unused_references.to_resolved().await?)
281    .hash_salt(hash_salt)
282    .nested_async_availability(*nested_async_chunking.await?)
283    .worker_forwarded_globals(worker_forwarded_globals());
284
285    if !next_mode.is_development() {
286        builder = builder
287            .chunking_config(
288                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
289                ChunkingConfig {
290                    min_chunk_size: 20_000,
291                    ..Default::default()
292                },
293            )
294            .chunking_config(
295                Vc::<CssChunkType>::default().to_resolved().await?,
296                ChunkingConfig {
297                    max_merge_chunk_size: 100_000,
298                    style_groups_algorithm: style_groups_algorithm.clone(),
299                    ..Default::default()
300                },
301            )
302            .module_merging(*scope_hoisting.await?);
303    }
304
305    Ok(Vc::upcast(builder.build()))
306}
307
308// By default, assets are server assets, but the StructuredImageModuleType ones are on the client
309#[turbo_tasks::function]
310pub async fn get_edge_chunking_context(
311    options: EdgeChunkingContextOptions,
312) -> Result<Vc<Box<dyn ChunkingContext>>> {
313    let EdgeChunkingContextOptions {
314        mode,
315        root_path,
316        node_root,
317        output_root_to_root_path,
318        environment,
319        module_id_strategy,
320        export_usage,
321        unused_references,
322        turbo_minify,
323        turbo_source_maps,
324        no_mangling,
325        scope_hoisting,
326        nested_async_chunking,
327        client_root,
328        client_static_folder_name,
329        asset_prefix,
330        css_url_suffix,
331        hash_salt,
332        cross_origin,
333        style_groups_algorithm,
334    } = options;
335    let cross_origin = *cross_origin.await?;
336    let css_url_suffix = css_url_suffix.to_resolved().await?;
337    let output_root = node_root.join("server/edge")?;
338    let next_mode = mode.await?;
339    let mut builder = BrowserChunkingContext::builder(
340        root_path,
341        output_root.clone(),
342        output_root_to_root_path.owned().await?,
343        output_root.clone(),
344        output_root.join("chunks")?,
345        output_root.join("assets")?,
346        environment.to_resolved().await?,
347        next_mode.runtime_type(),
348    )
349    .client_roots_override(rcstr!("client"), client_root.clone())
350    .asset_root_path_override(
351        rcstr!("client"),
352        client_root
353            .join(&client_static_folder_name)?
354            .join("media")?,
355    )
356    .asset_base_path_override(rcstr!("client"), asset_prefix)
357    .url_behavior_override(
358        rcstr!("client"),
359        UrlBehavior {
360            suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
361            static_suffix: css_url_suffix,
362        },
363    )
364    .default_url_behavior(UrlBehavior {
365        suffix: AssetSuffix::Inferred,
366        static_suffix: ResolvedVc::cell(None),
367    })
368    // Since one can't read files in edge directly, any asset need to be fetched
369    // instead. This special blob url is handled by the custom fetch
370    // implementation in the edge sandbox. It will respond with the
371    // asset from the output directory.
372    .asset_base_path(Some(rcstr!("blob:server/edge/")))
373    .minify_type(if *turbo_minify.await? {
374        MinifyType::Minify {
375            mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
376        }
377    } else {
378        MinifyType::NoMinify
379    })
380    .source_maps(*turbo_source_maps.await?)
381    // The edge server runtime is browser-like, so it uses a `BrowserChunkingContext` whose default
382    // source map source type is `TurbopackUri` (sources left as `turbopack:///[project]/...`).
383    // Match the Node.js server context instead so server stack traces get real file paths:
384    // absolute `file://` URIs in dev, relative paths in production.
385    .source_map_source_type(if next_mode.is_development() {
386        SourceMapSourceType::AbsoluteFileUri
387    } else {
388        SourceMapSourceType::RelativeUri
389    })
390    .cross_origin(cross_origin)
391    .module_id_strategy(module_id_strategy.to_resolved().await?)
392    .export_usage(*export_usage.await?)
393    .unused_references(unused_references.to_resolved().await?)
394    .hash_salt(hash_salt)
395    .nested_async_availability(*nested_async_chunking.await?)
396    .worker_forwarded_globals(worker_forwarded_globals());
397
398    if !next_mode.is_development() {
399        builder = builder
400            .chunking_config(
401                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
402                ChunkingConfig {
403                    min_chunk_size: 20_000,
404                    ..Default::default()
405                },
406            )
407            .chunking_config(
408                Vc::<CssChunkType>::default().to_resolved().await?,
409                ChunkingConfig {
410                    max_merge_chunk_size: 100_000,
411                    style_groups_algorithm: style_groups_algorithm.clone(),
412                    ..Default::default()
413                },
414            )
415            .module_merging(*scope_hoisting.await?);
416    }
417
418    Ok(Vc::upcast(builder.build()))
419}