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