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