Repository: vercel/next.js
Pull request: Turbopack: set env in tracing context

Part of PACK-4164

~~Set `NODE_ENV` in tracing context~~ For that, see https://github.com/vercel/next.js/pull/75254#issuecomment-2627686346
Set `TURBOPACK` in tracing context

This get rids of some constant build overhead (because we only analyze one instance of react-dom for NFT now, instead of all of the edge/server/client onces).
Otherwise no measureable perf/memory impact



Closes PACK-5175

Changed files:
filename: crates/next-core/src/next_server/context.rs
patch: @@ -8,8 +8,8 @@ use turbo_tasks_fs::FileSystemPath;
 use turbopack::{
     css::chunk::CssChunkType,
     module_options::{
-        CssOptionsContext, EcmascriptOptionsContext, JsxTransformOptions, ModuleOptionsContext,
-        ModuleRule, TypeofWindow, TypescriptTransformOptions,
+        CssOptionsContext, EcmascriptOptionsContext, ExternalsTracingOptions, JsxTransformOptions,
+        ModuleOptionsContext, ModuleRule, TypeofWindow, TypescriptTransformOptions,
     },
     resolve_options_context::ResolveOptionsContext,
     transition::Transition,
@@ -19,6 +19,7 @@ use turbopack_core::{
         ChunkingConfig, MangleType, MinifyType, SourceMapsType,
         module_id_strategies::ModuleIdStrategy,
     },
+    compile_time_defines,
     compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReferences},
     environment::{
         Environment, ExecutionEnvironment, NodeJsEnvironment, NodeJsVersion, RuntimeVersions,
@@ -387,6 +388,49 @@ pub async fn get_server_compile_time_info(
     .await
 }
 
+#[turbo_tasks::function]
+pub async fn get_tracing_compile_time_info() -> Result<Vc<CompileTimeInfo>> {
+    CompileTimeInfo::builder(
+        Environment::new(ExecutionEnvironment::NodeJsLambda(
+            NodeJsEnvironment::default().resolved_cell(),
+        ))
+        .to_resolved()
+        .await?,
+    )
+    /*
+    We'd really like to set `process.env.NODE_ENV = "production"` here, but with that,
+    `react/cjs/react.development.js` won't be copied anymore (as expected).
+    However if you `import` react from native ESM: `import {createContext} from 'react';`, it fails with
+    ```
+    import {createContext} from 'react';
+            ^^^^^^^^^^^^^
+    SyntaxError: Named export 'createContext' not found. The requested module 'react' is a CommonJS module, which may not support all module.exports as named exports.
+    CommonJS modules can always be imported via the default export, for example using:
+    ```
+    This is because Node's import-cjs-from-esm feature can correctly find all named exports in
+    ```
+    // `react/index.js`
+    if (process.env.NODE_ENV === 'production') {
+      module.exports = require('./cjs/react.production.js');
+    } else {
+      module.exports = require('./cjs/react.development.js');
+    }
+    ```
+    if both files exist (which is what's happening so far).
+    If `react.development.js` doesn't exist, then it bails with that error message.
+    Also just removing that second branch works fine, but a `require` to a non-existent file fails.
+    */
+    .defines(
+        compile_time_defines!(
+            process.env.TURBOPACK = true,
+            // process.env.NODE_ENV = "production",
+        )
+        .resolved_cell(),
+    )
+    .cell()
+    .await
+}
+
 #[turbo_tasks::function]
 pub async fn get_server_module_options_context(
     project_path: FileSystemPath,
@@ -544,7 +588,13 @@ pub async fn get_server_module_options_context(
         tree_shaking_mode: tree_shaking_mode_for_user_code,
         side_effect_free_packages: next_config.optimize_package_imports().owned().await?,
         enable_externals_tracing: if next_mode.is_production() {
-            Some(project_path)
+            Some(
+                ExternalsTracingOptions {
+                    tracing_root: project_path,
+                    compile_time_info: get_tracing_compile_time_info().to_resolved().await?,
+                }
+                .resolved_cell(),
+            )
         } else {
             None
         },

filename: turbopack/crates/turbopack/src/lib.rs
patch: @@ -34,7 +34,6 @@ use turbopack_core::{
     chunk::SourceMapsType,
     compile_time_info::CompileTimeInfo,
     context::{AssetContext, ProcessResult},
-    environment::{Environment, ExecutionEnvironment, NodeJsEnvironment},
     ident::Layer,
     issue::{IssueExt, IssueSource, StyledString, module::ModuleIssue},
     module::Module,
@@ -643,15 +642,12 @@ async fn process_default_internal(
 }
 
 #[turbo_tasks::function]
-async fn externals_tracing_module_context(ty: ExternalType) -> Result<Vc<ModuleAssetContext>> {
-    let env = Environment::new(ExecutionEnvironment::NodeJsLambda(
-        NodeJsEnvironment::default().resolved_cell(),
-    ))
-    .to_resolved()
-    .await?;
-
+async fn externals_tracing_module_context(
+    ty: ExternalType,
+    compile_time_info: Vc<CompileTimeInfo>,
+) -> Result<Vc<ModuleAssetContext>> {
     let resolve_options = ResolveOptionsContext {
-        emulate_environment: Some(env),
+        emulate_environment: Some(compile_time_info.await?.environment),
         loose_errors: true,
         custom_conditions: match ty {
             ExternalType::CommonJs => vec![rcstr!("require")],
@@ -663,7 +659,7 @@ async fn externals_tracing_module_context(ty: ExternalType) -> Result<Vc<ModuleA
 
     Ok(ModuleAssetContext::new_without_replace_externals(
         Default::default(),
-        CompileTimeInfo::builder(env).cell().await?,
+        compile_time_info,
         // Keep these options more or less in sync with
         // turbopack/crates/turbopack/tests/node-file-trace.rs to ensure that the NFT unit tests
         // are actually representative of what Turbopack does.
@@ -784,7 +780,7 @@ impl AssetContext for ModuleAssetContext {
                         ResolveResultItem::External { name, ty, traced } => {
                             let replacement = if replace_externals {
                                 let tracing_mode = if traced == ExternalTraced::Traced
-                                    && let Some(tracing_root) = &self
+                                    && let Some(options) = &self
                                         .module_options_context()
                                         .await?
                                         .enable_externals_tracing
@@ -793,13 +789,17 @@ impl AssetContext for ModuleAssetContext {
                                     // request will later be resolved relative to tracing_root
                                     // anyway.
 
+                                    let options = options.await?;
                                     CachedExternalTracingMode::Traced {
                                         externals_context: ResolvedVc::upcast(
-                                            externals_tracing_module_context(ty)
-                                                .to_resolved()
-                                                .await?,
+                                            externals_tracing_module_context(
+                                                ty,
+                                                *options.compile_time_info,
+                                            )
+                                            .to_resolved()
+                                            .await?,
                                         ),
-                                        root_origin: tracing_root.join("_")?,
+                                        root_origin: options.tracing_root.join("_")?,
                                     }
                                 } else {
                                     CachedExternalTracingMode::Untraced

filename: turbopack/crates/turbopack/src/module_options/module_options_context.rs
patch: @@ -6,8 +6,8 @@ use turbo_rcstr::RcStr;
 use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, ValueDefault, Vc, trace::TraceRawVcs};
 use turbo_tasks_fs::FileSystemPath;
 use turbopack_core::{
-    chunk::SourceMapsType, condition::ContextCondition, environment::Environment,
-    resolve::options::ImportMapping,
+    chunk::SourceMapsType, compile_time_info::CompileTimeInfo, condition::ContextCondition,
+    environment::Environment, resolve::options::ImportMapping,
 };
 use turbopack_ecmascript::{TreeShakingMode, references::esm::UrlRewriteBehavior};
 pub use turbopack_mdx::MdxTransformOptions;
@@ -121,7 +121,6 @@ impl ValueDefault for TypescriptTransformOptions {
     }
 }
 
-// [TODO]: should enabled_react_refresh belong to this options?
 #[turbo_tasks::value(shared)]
 #[derive(Default, Clone, Debug)]
 pub struct JsxTransformOptions {
@@ -131,6 +130,14 @@ pub struct JsxTransformOptions {
     pub runtime: Option<RcStr>,
 }
 
+#[turbo_tasks::value(shared)]
+#[derive(Clone, Debug)]
+pub struct ExternalsTracingOptions {
+    /// The directory from which the bundled files will require the externals at runtime.
+    pub tracing_root: FileSystemPath,
+    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
+}
+
 #[turbo_tasks::value(shared)]
 #[derive(Clone, Default)]
 #[serde(default)]
@@ -152,10 +159,7 @@ pub struct ModuleOptionsContext {
 
     /// Generate (non-emitted) output assets for static assets and externals, to facilitate
     /// generating a list of all non-bundled files that will be required at runtime.
-    ///
-    /// The filepath is the directory from which the bundled files will require the externals at
-    /// runtime.
-    pub enable_externals_tracing: Option<FileSystemPath>,
+    pub enable_externals_tracing: Option<ResolvedVc<ExternalsTracingOptions>>,
 
     /// If true, it stores the last successful parse result in state and keeps using it when
     /// parsing fails. This is useful to keep the module graph structure intact when syntax errors