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, TaskInput, 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        SourceMapsType, UnusedReferences, UrlBehavior, chunk_id_strategy::ModuleIdStrategy,
11    },
12    compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
13    environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment, NodeJsVersion},
14    free_var_references,
15    issue::IssueSeverity,
16    module_graph::binding_usage_info::OptionBindingUsageInfo,
17};
18use turbopack_css::chunk::CssChunkType;
19use turbopack_ecmascript::chunk::EcmascriptChunkType;
20use turbopack_node::execution_context::ExecutionContext;
21use turbopack_resolve::resolve_options_context::{ResolveOptionsContext, TsConfigHandling};
22
23use crate::{
24    app_structure::CollectedRootParams,
25    mode::NextMode,
26    next_config::NextConfig,
27    next_font::local::NextFontLocalResolvePlugin,
28    next_import_map::{get_next_edge_and_server_fallback_import_map, get_next_edge_import_map},
29    next_server::context::ServerContextType,
30    next_shared::resolve::{ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin},
31    util::{
32        NextRuntime, OptionEnvMap, defines, foreign_code_context_condition,
33        free_var_references_with_vercel_system_env_warnings, worker_forwarded_globals,
34    },
35};
36
37#[turbo_tasks::function]
38async fn next_edge_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
39    Ok(defines(&*define_env.await?).cell())
40}
41
42/// Define variables for the edge runtime can be accessibly globally.
43/// See [here](https://github.com/vercel/next.js/blob/160bb99b06e9c049f88e25806fd995f07f4cc7e1/packages/next/src/build/webpack-config.ts#L1715-L1718) how webpack configures it.
44#[turbo_tasks::function]
45async fn next_edge_free_vars(
46    project_path: FileSystemPath,
47    define_env: Vc<OptionEnvMap>,
48    report_system_env_inlining: Vc<IssueSeverity>,
49) -> Result<Vc<FreeVarReferences>> {
50    Ok(free_var_references!(
51        ..free_var_references_with_vercel_system_env_warnings(
52            defines(&*define_env.await?),
53            *report_system_env_inlining.await?
54        ),
55        Buffer = FreeVarReference::EcmaScriptModule {
56            request: rcstr!("buffer"),
57            lookup_path: Some(project_path),
58            export: Some(rcstr!("Buffer")),
59        },
60    )
61    .cell())
62}
63
64#[turbo_tasks::function]
65pub async fn get_edge_compile_time_info(
66    project_path: FileSystemPath,
67    define_env: Vc<OptionEnvMap>,
68    node_version: ResolvedVc<NodeJsVersion>,
69    report_system_env_inlining: Vc<IssueSeverity>,
70) -> Result<Vc<CompileTimeInfo>> {
71    CompileTimeInfo::builder(
72        Environment::new(ExecutionEnvironment::EdgeWorker(
73            EdgeWorkerEnvironment { node_version }.resolved_cell(),
74        ))
75        .to_resolved()
76        .await?,
77    )
78    .defines(next_edge_defines(define_env).to_resolved().await?)
79    .free_var_references(
80        next_edge_free_vars(project_path, define_env, report_system_env_inlining)
81            .to_resolved()
82            .await?,
83    )
84    .cell()
85    .await
86}
87
88#[turbo_tasks::function]
89pub async fn get_edge_resolve_options_context(
90    project_path: FileSystemPath,
91    ty: ServerContextType,
92    mode: Vc<NextMode>,
93    next_config: Vc<NextConfig>,
94    execution_context: Vc<ExecutionContext>,
95    collected_root_params: Option<Vc<CollectedRootParams>>,
96) -> Result<Vc<ResolveOptionsContext>> {
97    let next_edge_import_map = get_next_edge_import_map(
98        project_path.clone(),
99        ty.clone(),
100        next_config,
101        mode,
102        execution_context,
103        collected_root_params,
104    )
105    .to_resolved()
106    .await?;
107    let next_edge_fallback_import_map =
108        get_next_edge_and_server_fallback_import_map(project_path.clone(), NextRuntime::Edge)
109            .to_resolved()
110            .await?;
111
112    let mut before_resolve_plugins = vec![ResolvedVc::upcast(
113        ModuleFeatureReportResolvePlugin::new(project_path.clone())
114            .to_resolved()
115            .await?,
116    )];
117    if matches!(
118        ty,
119        ServerContextType::Pages { .. }
120            | ServerContextType::AppSSR { .. }
121            | ServerContextType::AppRSC { .. }
122    ) {
123        before_resolve_plugins.push(ResolvedVc::upcast(
124            NextFontLocalResolvePlugin::new(project_path.clone())
125                .to_resolved()
126                .await?,
127        ));
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#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
188pub struct EdgeChunkingContextOptions {
189    pub mode: Vc<NextMode>,
190    pub root_path: FileSystemPath,
191    pub node_root: FileSystemPath,
192    pub output_root_to_root_path: Vc<RcStr>,
193    pub environment: Vc<Environment>,
194    pub module_id_strategy: Vc<ModuleIdStrategy>,
195    pub export_usage: Vc<OptionBindingUsageInfo>,
196    pub unused_references: Vc<UnusedReferences>,
197    pub turbo_minify: Vc<bool>,
198    pub turbo_source_maps: Vc<SourceMapsType>,
199    pub no_mangling: Vc<bool>,
200    pub scope_hoisting: Vc<bool>,
201    pub nested_async_chunking: Vc<bool>,
202    pub client_root: FileSystemPath,
203    pub client_static_folder_name: RcStr,
204    pub asset_prefix: RcStr,
205    pub css_url_suffix: Vc<Option<RcStr>>,
206    pub hash_salt: ResolvedVc<RcStr>,
207    pub cross_origin: Vc<CrossOrigin>,
208}
209
210/// Like `get_edge_chunking_context` but all assets are emitted as client assets (so `/_next`)
211#[turbo_tasks::function]
212pub async fn get_edge_chunking_context_with_client_assets(
213    options: EdgeChunkingContextOptions,
214) -> Result<Vc<Box<dyn ChunkingContext>>> {
215    let EdgeChunkingContextOptions {
216        mode,
217        root_path,
218        node_root,
219        output_root_to_root_path,
220        environment,
221        module_id_strategy,
222        export_usage,
223        unused_references,
224        turbo_minify,
225        turbo_source_maps,
226        no_mangling,
227        scope_hoisting,
228        nested_async_chunking,
229        client_root,
230        client_static_folder_name,
231        asset_prefix,
232        css_url_suffix,
233        hash_salt,
234        cross_origin,
235    } = options;
236    let cross_origin_loading = *cross_origin.await?;
237    let output_root = node_root.join("server/edge")?;
238    let next_mode = mode.await?;
239    let mut builder = BrowserChunkingContext::builder(
240        root_path,
241        output_root.clone(),
242        output_root_to_root_path.owned().await?,
243        client_root.clone(),
244        output_root.join("chunks/ssr")?,
245        client_root
246            .join(&client_static_folder_name)?
247            .join("media")?,
248        environment.to_resolved().await?,
249        next_mode.runtime_type(),
250    )
251    .asset_base_path(Some(asset_prefix))
252    .default_url_behavior(UrlBehavior {
253        suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
254        static_suffix: css_url_suffix.to_resolved().await?,
255    })
256    .minify_type(if *turbo_minify.await? {
257        MinifyType::Minify {
258            // React needs deterministic function names to work correctly.
259            mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
260        }
261    } else {
262        MinifyType::NoMinify
263    })
264    .source_maps(*turbo_source_maps.await?)
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                    ..Default::default()
287                },
288            )
289            .module_merging(*scope_hoisting.await?);
290    }
291
292    Ok(Vc::upcast(builder.build()))
293}
294
295// By default, assets are server assets, but the StructuredImageModuleType ones are on the client
296#[turbo_tasks::function]
297pub async fn get_edge_chunking_context(
298    options: EdgeChunkingContextOptions,
299) -> Result<Vc<Box<dyn ChunkingContext>>> {
300    let EdgeChunkingContextOptions {
301        mode,
302        root_path,
303        node_root,
304        output_root_to_root_path,
305        environment,
306        module_id_strategy,
307        export_usage,
308        unused_references,
309        turbo_minify,
310        turbo_source_maps,
311        no_mangling,
312        scope_hoisting,
313        nested_async_chunking,
314        client_root,
315        client_static_folder_name,
316        asset_prefix,
317        css_url_suffix,
318        hash_salt,
319        cross_origin,
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    .cross_origin(cross_origin)
368    .module_id_strategy(module_id_strategy.to_resolved().await?)
369    .export_usage(*export_usage.await?)
370    .unused_references(unused_references.to_resolved().await?)
371    .hash_salt(hash_salt)
372    .nested_async_availability(*nested_async_chunking.await?)
373    .worker_forwarded_globals(worker_forwarded_globals());
374
375    if !next_mode.is_development() {
376        builder = builder
377            .chunking_config(
378                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
379                ChunkingConfig {
380                    min_chunk_size: 20_000,
381                    ..Default::default()
382                },
383            )
384            .chunking_config(
385                Vc::<CssChunkType>::default().to_resolved().await?,
386                ChunkingConfig {
387                    max_merge_chunk_size: 100_000,
388                    ..Default::default()
389                },
390            )
391            .module_merging(*scope_hoisting.await?);
392    }
393
394    Ok(Vc::upcast(builder.build()))
395}