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::PagesData { .. }
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    let resolve_options_context = ResolveOptionsContext {
158        enable_node_modules: Some(project_path.root().owned().await?),
159        enable_edge_node_externals: true,
160        custom_conditions,
161        import_map: Some(next_edge_import_map),
162        fallback_import_map: Some(next_edge_fallback_import_map),
163        module: true,
164        browser: true,
165        after_resolve_plugins,
166        before_resolve_plugins,
167
168        ..Default::default()
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: next_config
178            .typescript_tsconfig_path()
179            .await?
180            .as_ref()
181            .map(|p| project_path.join(p))
182            .transpose()?,
183        rules: vec![(
184            foreign_code_context_condition(next_config, project_path).await?,
185            resolve_options_context.clone().resolved_cell(),
186        )],
187        ..resolve_options_context
188    }
189    .cell())
190}
191
192#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
193pub struct EdgeChunkingContextOptions {
194    pub mode: Vc<NextMode>,
195    pub root_path: FileSystemPath,
196    pub node_root: FileSystemPath,
197    pub output_root_to_root_path: Vc<RcStr>,
198    pub environment: Vc<Environment>,
199    pub module_id_strategy: Vc<Box<dyn ModuleIdStrategy>>,
200    pub export_usage: Vc<OptionExportUsageInfo>,
201    pub turbo_minify: Vc<bool>,
202    pub turbo_source_maps: Vc<bool>,
203    pub no_mangling: Vc<bool>,
204    pub scope_hoisting: Vc<bool>,
205}
206
207#[turbo_tasks::function]
208pub async fn get_edge_chunking_context_with_client_assets(
209    options: EdgeChunkingContextOptions,
210    client_root: FileSystemPath,
211    asset_prefix: ResolvedVc<Option<RcStr>>,
212) -> Result<Vc<Box<dyn ChunkingContext>>> {
213    let EdgeChunkingContextOptions {
214        mode,
215        root_path,
216        node_root,
217        output_root_to_root_path,
218        environment,
219        module_id_strategy,
220        export_usage,
221        turbo_minify,
222        turbo_source_maps,
223        no_mangling,
224        scope_hoisting,
225    } = options;
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.join("static/media")?,
235        environment.to_resolved().await?,
236        next_mode.runtime_type(),
237    )
238    .asset_base_path(asset_prefix.owned().await?)
239    .minify_type(if *turbo_minify.await? {
240        MinifyType::Minify {
241            // React needs deterministic function names to work correctly.
242            mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
243        }
244    } else {
245        MinifyType::NoMinify
246    })
247    .source_maps(if *turbo_source_maps.await? {
248        SourceMapsType::Full
249    } else {
250        SourceMapsType::None
251    })
252    .module_id_strategy(module_id_strategy.to_resolved().await?)
253    .export_usage(*export_usage.await?);
254
255    if !next_mode.is_development() {
256        builder = builder
257            .chunking_config(
258                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
259                ChunkingConfig {
260                    min_chunk_size: 20_000,
261                    ..Default::default()
262                },
263            )
264            .chunking_config(
265                Vc::<CssChunkType>::default().to_resolved().await?,
266                ChunkingConfig {
267                    max_merge_chunk_size: 100_000,
268                    ..Default::default()
269                },
270            )
271            .module_merging(*scope_hoisting.await?);
272    }
273
274    Ok(Vc::upcast(builder.build()))
275}
276
277#[turbo_tasks::function]
278pub async fn get_edge_chunking_context(
279    options: EdgeChunkingContextOptions,
280) -> Result<Vc<Box<dyn ChunkingContext>>> {
281    let EdgeChunkingContextOptions {
282        mode,
283        root_path,
284        node_root,
285        output_root_to_root_path,
286        environment,
287        module_id_strategy,
288        export_usage,
289        turbo_minify,
290        turbo_source_maps,
291        no_mangling,
292        scope_hoisting,
293    } = options;
294    let output_root = node_root.join("server/edge")?;
295    let next_mode = mode.await?;
296    let mut builder = BrowserChunkingContext::builder(
297        root_path,
298        output_root.clone(),
299        output_root_to_root_path.owned().await?,
300        output_root.clone(),
301        output_root.join("chunks")?,
302        output_root.join("assets")?,
303        environment.to_resolved().await?,
304        next_mode.runtime_type(),
305    )
306    // Since one can't read files in edge directly, any asset need to be fetched
307    // instead. This special blob url is handled by the custom fetch
308    // implementation in the edge sandbox. It will respond with the
309    // asset from the output directory.
310    .asset_base_path(Some(rcstr!("blob:server/edge/")))
311    .minify_type(if *turbo_minify.await? {
312        MinifyType::Minify {
313            mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
314        }
315    } else {
316        MinifyType::NoMinify
317    })
318    .source_maps(if *turbo_source_maps.await? {
319        SourceMapsType::Full
320    } else {
321        SourceMapsType::None
322    })
323    .module_id_strategy(module_id_strategy.to_resolved().await?)
324    .export_usage(*export_usage.await?);
325
326    if !next_mode.is_development() {
327        builder = builder
328            .chunking_config(
329                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
330                ChunkingConfig {
331                    min_chunk_size: 20_000,
332                    ..Default::default()
333                },
334            )
335            .chunking_config(
336                Vc::<CssChunkType>::default().to_resolved().await?,
337                ChunkingConfig {
338                    max_merge_chunk_size: 100_000,
339                    ..Default::default()
340                },
341            )
342            .module_merging(*scope_hoisting.await?);
343    }
344
345    Ok(Vc::upcast(builder.build()))
346}