next_core/next_edge/
context.rs

1use anyhow::Result;
2use turbo_rcstr::RcStr;
3use turbo_tasks::{FxIndexMap, OptionVcExt, ResolvedVc, Value, Vc};
4use turbo_tasks_env::EnvMap;
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::{
14        CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, DefineableNameSegment,
15        FreeVarReference, FreeVarReferences,
16    },
17    environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment},
18    free_var_references,
19};
20use turbopack_ecmascript::chunk::EcmascriptChunkType;
21use turbopack_node::execution_context::ExecutionContext;
22
23use crate::{
24    mode::NextMode,
25    next_config::NextConfig,
26    next_font::local::NextFontLocalResolvePlugin,
27    next_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, foreign_code_context_condition},
34};
35
36fn defines(define_env: &FxIndexMap<RcStr, RcStr>) -> CompileTimeDefines {
37    let mut defines = FxIndexMap::default();
38
39    for (k, v) in define_env {
40        defines
41            .entry(
42                k.split('.')
43                    .map(|s| DefineableNameSegment::Name(s.into()))
44                    .collect::<Vec<_>>(),
45            )
46            .or_insert_with(|| {
47                let val = serde_json::from_str(v);
48                match val {
49                    Ok(serde_json::Value::Bool(v)) => CompileTimeDefineValue::Bool(v),
50                    Ok(serde_json::Value::String(v)) => CompileTimeDefineValue::String(v.into()),
51                    _ => CompileTimeDefineValue::JSON(v.clone()),
52                }
53            });
54    }
55
56    CompileTimeDefines(defines)
57}
58
59#[turbo_tasks::function]
60async fn next_edge_defines(define_env: Vc<EnvMap>) -> Result<Vc<CompileTimeDefines>> {
61    Ok(defines(&*define_env.await?).cell())
62}
63
64/// Define variables for the edge runtime can be accessibly globally.
65/// See [here](https://github.com/vercel/next.js/blob/160bb99b06e9c049f88e25806fd995f07f4cc7e1/packages/next/src/build/webpack-config.ts#L1715-L1718) how webpack configures it.
66#[turbo_tasks::function]
67async fn next_edge_free_vars(
68    project_path: ResolvedVc<FileSystemPath>,
69    define_env: Vc<EnvMap>,
70) -> Result<Vc<FreeVarReferences>> {
71    Ok(free_var_references!(
72        ..defines(&*define_env.await?).into_iter(),
73        Buffer = FreeVarReference::EcmaScriptModule {
74            request: "buffer".into(),
75            lookup_path: Some(project_path),
76            export: Some("Buffer".into()),
77        },
78    )
79    .cell())
80}
81
82#[turbo_tasks::function]
83pub async fn get_edge_compile_time_info(
84    project_path: Vc<FileSystemPath>,
85    define_env: Vc<EnvMap>,
86) -> Result<Vc<CompileTimeInfo>> {
87    CompileTimeInfo::builder(
88        Environment::new(Value::new(ExecutionEnvironment::EdgeWorker(
89            EdgeWorkerEnvironment {}.resolved_cell(),
90        )))
91        .to_resolved()
92        .await?,
93    )
94    .defines(next_edge_defines(define_env).to_resolved().await?)
95    .free_var_references(
96        next_edge_free_vars(project_path, define_env)
97            .to_resolved()
98            .await?,
99    )
100    .cell()
101    .await
102}
103
104#[turbo_tasks::function]
105pub async fn get_edge_resolve_options_context(
106    project_path: ResolvedVc<FileSystemPath>,
107    ty: Value<ServerContextType>,
108    mode: Vc<NextMode>,
109    next_config: Vc<NextConfig>,
110    execution_context: Vc<ExecutionContext>,
111) -> Result<Vc<ResolveOptionsContext>> {
112    let next_edge_import_map =
113        get_next_edge_import_map(*project_path, ty, next_config, execution_context)
114            .to_resolved()
115            .await?;
116
117    let ty: ServerContextType = ty.into_value();
118
119    let mut before_resolve_plugins = vec![ResolvedVc::upcast(
120        ModuleFeatureReportResolvePlugin::new(*project_path)
121            .to_resolved()
122            .await?,
123    )];
124    if matches!(
125        ty,
126        ServerContextType::Pages { .. }
127            | ServerContextType::AppSSR { .. }
128            | ServerContextType::AppRSC { .. }
129    ) {
130        before_resolve_plugins.push(ResolvedVc::upcast(
131            NextFontLocalResolvePlugin::new(*project_path)
132                .to_resolved()
133                .await?,
134        ));
135    };
136
137    if matches!(
138        ty,
139        ServerContextType::AppRSC { .. }
140            | ServerContextType::AppRoute { .. }
141            | ServerContextType::PagesData { .. }
142            | ServerContextType::Middleware { .. }
143            | ServerContextType::Instrumentation { .. }
144    ) {
145        before_resolve_plugins.push(ResolvedVc::upcast(
146            get_invalid_client_only_resolve_plugin(project_path)
147                .to_resolved()
148                .await?,
149        ));
150        before_resolve_plugins.push(ResolvedVc::upcast(
151            get_invalid_styled_jsx_resolve_plugin(project_path)
152                .to_resolved()
153                .await?,
154        ));
155    }
156
157    let after_resolve_plugins = vec![ResolvedVc::upcast(
158        NextSharedRuntimeResolvePlugin::new(*project_path)
159            .to_resolved()
160            .await?,
161    )];
162
163    // https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102
164    let mut custom_conditions = vec![mode.await?.condition().into()];
165    custom_conditions.extend(
166        NextRuntime::Edge
167            .conditions()
168            .iter()
169            .map(ToString::to_string)
170            .map(RcStr::from),
171    );
172
173    if ty.supports_react_server() {
174        custom_conditions.push("react-server".into());
175    };
176
177    let resolve_options_context = ResolveOptionsContext {
178        enable_node_modules: Some(project_path.root().to_resolved().await?),
179        enable_edge_node_externals: true,
180        custom_conditions,
181        import_map: Some(next_edge_import_map),
182        module: true,
183        browser: true,
184        after_resolve_plugins,
185        before_resolve_plugins,
186
187        ..Default::default()
188    };
189
190    Ok(ResolveOptionsContext {
191        enable_typescript: true,
192        enable_react: true,
193        enable_mjs_extension: true,
194        enable_edge_node_externals: true,
195        custom_extensions: next_config.resolve_extension().owned().await?,
196        tsconfig_path: next_config
197            .typescript_tsconfig_path()
198            .await?
199            .as_ref()
200            .map(|p| project_path.join(p.to_owned()))
201            .to_resolved()
202            .await?,
203        rules: vec![(
204            foreign_code_context_condition(next_config, project_path).await?,
205            resolve_options_context.clone().resolved_cell(),
206        )],
207        ..resolve_options_context
208    }
209    .cell())
210}
211
212#[turbo_tasks::function]
213pub async fn get_edge_chunking_context_with_client_assets(
214    mode: Vc<NextMode>,
215    root_path: ResolvedVc<FileSystemPath>,
216    node_root: ResolvedVc<FileSystemPath>,
217    output_root_to_root_path: ResolvedVc<RcStr>,
218    client_root: ResolvedVc<FileSystemPath>,
219    asset_prefix: ResolvedVc<Option<RcStr>>,
220    environment: ResolvedVc<Environment>,
221    module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
222    turbo_minify: Vc<bool>,
223    turbo_source_maps: Vc<bool>,
224    no_mangling: Vc<bool>,
225) -> Result<Vc<Box<dyn ChunkingContext>>> {
226    let output_root = node_root.join("server/edge".into()).to_resolved().await?;
227    let next_mode = mode.await?;
228    let mut builder = BrowserChunkingContext::builder(
229        root_path,
230        output_root,
231        output_root_to_root_path,
232        client_root,
233        output_root.join("chunks/ssr".into()).to_resolved().await?,
234        client_root
235            .join("static/media".into())
236            .to_resolved()
237            .await?,
238        environment,
239        next_mode.runtime_type(),
240    )
241    .asset_base_path(asset_prefix)
242    .minify_type(if *turbo_minify.await? {
243        MinifyType::Minify {
244            // React needs deterministic function names to work correctly.
245            mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
246        }
247    } else {
248        MinifyType::NoMinify
249    })
250    .source_maps(if *turbo_source_maps.await? {
251        SourceMapsType::Full
252    } else {
253        SourceMapsType::None
254    })
255    .module_id_strategy(module_id_strategy);
256
257    if !next_mode.is_development() {
258        builder = builder.chunking_config(
259            Vc::<EcmascriptChunkType>::default().to_resolved().await?,
260            ChunkingConfig {
261                min_chunk_size: 20_000,
262                ..Default::default()
263            },
264        );
265        builder = builder.chunking_config(
266            Vc::<CssChunkType>::default().to_resolved().await?,
267            ChunkingConfig {
268                max_merge_chunk_size: 100_000,
269                ..Default::default()
270            },
271        );
272    }
273
274    Ok(Vc::upcast(builder.build()))
275}
276
277#[turbo_tasks::function]
278pub async fn get_edge_chunking_context(
279    mode: Vc<NextMode>,
280    root_path: ResolvedVc<FileSystemPath>,
281    node_root: ResolvedVc<FileSystemPath>,
282    node_root_to_root_path: ResolvedVc<RcStr>,
283    environment: ResolvedVc<Environment>,
284    module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
285    turbo_minify: Vc<bool>,
286    turbo_source_maps: Vc<bool>,
287    no_mangling: Vc<bool>,
288) -> Result<Vc<Box<dyn ChunkingContext>>> {
289    let output_root = node_root.join("server/edge".into()).to_resolved().await?;
290    let next_mode = mode.await?;
291    let mut builder = BrowserChunkingContext::builder(
292        root_path,
293        output_root,
294        node_root_to_root_path,
295        output_root,
296        output_root.join("chunks".into()).to_resolved().await?,
297        output_root.join("assets".into()).to_resolved().await?,
298        environment,
299        next_mode.runtime_type(),
300    )
301    // Since one can't read files in edge directly, any asset need to be fetched
302    // instead. This special blob url is handled by the custom fetch
303    // implementation in the edge sandbox. It will respond with the
304    // asset from the output directory.
305    .asset_base_path(ResolvedVc::cell(Some("blob:server/edge/".into())))
306    .minify_type(if *turbo_minify.await? {
307        MinifyType::Minify {
308            mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
309        }
310    } else {
311        MinifyType::NoMinify
312    })
313    .source_maps(if *turbo_source_maps.await? {
314        SourceMapsType::Full
315    } else {
316        SourceMapsType::None
317    })
318    .module_id_strategy(module_id_strategy);
319
320    if !next_mode.is_development() {
321        builder = builder.chunking_config(
322            Vc::<EcmascriptChunkType>::default().to_resolved().await?,
323            ChunkingConfig {
324                min_chunk_size: 20_000,
325                ..Default::default()
326            },
327        );
328        builder = builder.chunking_config(
329            Vc::<CssChunkType>::default().to_resolved().await?,
330            ChunkingConfig {
331                max_merge_chunk_size: 100_000,
332                ..Default::default()
333            },
334        );
335    }
336
337    Ok(Vc::upcast(builder.build()))
338}