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