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, MangleType, MinifyType, SourceMapsType,
10        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 asset_prefix: RcStr,
204    pub css_url_suffix: Vc<Option<RcStr>>,
205}
206
207/// Like `get_edge_chunking_context` but all assets are emitted as client assets (so `/_next`)
208#[turbo_tasks::function]
209pub async fn get_edge_chunking_context_with_client_assets(
210    options: EdgeChunkingContextOptions,
211) -> Result<Vc<Box<dyn ChunkingContext>>> {
212    let EdgeChunkingContextOptions {
213        mode,
214        root_path,
215        node_root,
216        output_root_to_root_path,
217        environment,
218        module_id_strategy,
219        export_usage,
220        unused_references,
221        turbo_minify,
222        turbo_source_maps,
223        no_mangling,
224        scope_hoisting,
225        nested_async_chunking,
226        client_root,
227        asset_prefix,
228        css_url_suffix,
229    } = options;
230    let output_root = node_root.join("server/edge")?;
231    let next_mode = mode.await?;
232    let mut builder = BrowserChunkingContext::builder(
233        root_path,
234        output_root.clone(),
235        output_root_to_root_path.owned().await?,
236        client_root.clone(),
237        output_root.join("chunks/ssr")?,
238        client_root.join("static/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    .module_id_strategy(module_id_strategy.to_resolved().await?)
257    .export_usage(*export_usage.await?)
258    .unused_references(unused_references.to_resolved().await?)
259    .nested_async_availability(*nested_async_chunking.await?)
260    .worker_forwarded_globals(worker_forwarded_globals());
261
262    if !next_mode.is_development() {
263        builder = builder
264            .chunking_config(
265                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
266                ChunkingConfig {
267                    min_chunk_size: 20_000,
268                    ..Default::default()
269                },
270            )
271            .chunking_config(
272                Vc::<CssChunkType>::default().to_resolved().await?,
273                ChunkingConfig {
274                    max_merge_chunk_size: 100_000,
275                    ..Default::default()
276                },
277            )
278            .module_merging(*scope_hoisting.await?);
279    }
280
281    Ok(Vc::upcast(builder.build()))
282}
283
284// By default, assets are server assets, but the StructuredImageModuleType ones are on the client
285#[turbo_tasks::function]
286pub async fn get_edge_chunking_context(
287    options: EdgeChunkingContextOptions,
288) -> Result<Vc<Box<dyn ChunkingContext>>> {
289    let EdgeChunkingContextOptions {
290        mode,
291        root_path,
292        node_root,
293        output_root_to_root_path,
294        environment,
295        module_id_strategy,
296        export_usage,
297        unused_references,
298        turbo_minify,
299        turbo_source_maps,
300        no_mangling,
301        scope_hoisting,
302        nested_async_chunking,
303        client_root,
304        asset_prefix,
305        css_url_suffix,
306    } = options;
307    let css_url_suffix = css_url_suffix.to_resolved().await?;
308    let output_root = node_root.join("server/edge")?;
309    let next_mode = mode.await?;
310    let mut builder = BrowserChunkingContext::builder(
311        root_path,
312        output_root.clone(),
313        output_root_to_root_path.owned().await?,
314        output_root.clone(),
315        output_root.join("chunks")?,
316        output_root.join("assets")?,
317        environment.to_resolved().await?,
318        next_mode.runtime_type(),
319    )
320    .client_roots_override(rcstr!("client"), client_root.clone())
321    .asset_root_path_override(rcstr!("client"), client_root.join("static/media")?)
322    .asset_base_path_override(rcstr!("client"), asset_prefix)
323    .url_behavior_override(
324        rcstr!("client"),
325        UrlBehavior {
326            suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
327            static_suffix: css_url_suffix,
328        },
329    )
330    .default_url_behavior(UrlBehavior {
331        suffix: AssetSuffix::Inferred,
332        static_suffix: ResolvedVc::cell(None),
333    })
334    // Since one can't read files in edge directly, any asset need to be fetched
335    // instead. This special blob url is handled by the custom fetch
336    // implementation in the edge sandbox. It will respond with the
337    // asset from the output directory.
338    .asset_base_path(Some(rcstr!("blob:server/edge/")))
339    .minify_type(if *turbo_minify.await? {
340        MinifyType::Minify {
341            mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
342        }
343    } else {
344        MinifyType::NoMinify
345    })
346    .source_maps(*turbo_source_maps.await?)
347    .module_id_strategy(module_id_strategy.to_resolved().await?)
348    .export_usage(*export_usage.await?)
349    .unused_references(unused_references.to_resolved().await?)
350    .nested_async_availability(*nested_async_chunking.await?)
351    .worker_forwarded_globals(worker_forwarded_globals());
352
353    if !next_mode.is_development() {
354        builder = builder
355            .chunking_config(
356                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
357                ChunkingConfig {
358                    min_chunk_size: 20_000,
359                    ..Default::default()
360                },
361            )
362            .chunking_config(
363                Vc::<CssChunkType>::default().to_resolved().await?,
364                ChunkingConfig {
365                    max_merge_chunk_size: 100_000,
366                    ..Default::default()
367                },
368            )
369            .module_merging(*scope_hoisting.await?);
370    }
371
372    Ok(Vc::upcast(builder.build()))
373}