next_core/next_edge/
context.rs

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