Turbopack: respect the module-sync export condition (#93970)

I set it to `::Unknown` because we the runtime may or may not support
module-sync. So let's just trace both for now



There is one problem though:


https://nodejs.org/docs/latest-v26.x/api/packages.html#conditional-exports
> "module-sync" - matches no matter the package is loaded via import,
import() or require(). The format is expected to be ES modules that does
not contain top-level await in its module graph - if it does,
ERR_REQUIRE_ASYNC_MODULE will be thrown when the module is require()-ed.

So we have multiple options:
1. We definitely want to respect module-sync for tracing. We don't
really have to throw ERR_REQUIRE_ASYNC_MODULE here, Node.js will do it
at runtime anyway.
2. ~~What about bundling? Either~~
- ~~Respect module-sync always, but don't throw
ERR_REQUIRE_ASYNC_MODULE.~~
- ~~Respect module-sync always, and do throw a build error the module
requires TLA. This would require a separate validation pass though.~~
   - I'm not enabling it for bundling for now
This commit is contained in:
Niklas Mischkulnig
2026-05-20 21:29:22 +02:00
committed by GitHub
parent f172ba9c41
commit 6e894d0511
12 changed files with 44 additions and 14 deletions
@@ -39,9 +39,12 @@ pub enum ResolveModules {
},
}
#[derive(TraceRawVcs, Hash, PartialEq, Eq, Clone, Copy, Debug, NonLocalValue, Encode, Decode)]
#[derive(
TraceRawVcs, Hash, PartialEq, Eq, Clone, Copy, Debug, NonLocalValue, Encode, Decode, Default,
)]
pub enum ConditionValue {
Set,
#[default]
Unset,
Unknown,
}
@@ -22,6 +22,7 @@ use turbopack_core::{
output::{OutputAsset, OutputAssetsReference},
reference::all_assets_from_entries,
reference_type::ReferenceType,
resolve::options::ConditionValue,
traced_asset::TracedAsset,
};
use turbopack_ecmascript::AnalyzeMode;
@@ -109,6 +110,7 @@ async fn node_file_trace_operation(
enable_node_native_modules: true,
enable_node_modules: Some(input_dir),
custom_conditions: vec![rcstr!("node")],
module_sync: ConditionValue::Unknown,
enable_node_externals: true,
loose_errors: true,
collect_affecting_sources: true,
@@ -66,6 +66,9 @@ async fn apply_esm_specific_options_internal(
for conditions in get_condition_maps(&mut options) {
conditions.insert(rcstr!("import"), ConditionValue::Set);
conditions.insert(rcstr!("require"), ConditionValue::Unset);
// Don't set "module-sync" to ConditionValue::Set here. When tracing, the Node.js runtime
// version might not support it yet, so we still want the "import"/"require"/"default"
// result anyway.
}
if clear_extensions {
@@ -83,6 +86,9 @@ pub async fn apply_cjs_specific_options(options: Vc<ResolveOptions>) -> Result<V
for conditions in get_condition_maps(&mut options) {
conditions.insert(rcstr!("import"), ConditionValue::Unset);
conditions.insert(rcstr!("require"), ConditionValue::Set);
// Don't set "module-sync" to ConditionValue::Set here. When tracing, the Node.js runtime
// version might not support it yet, so we still want the "import"/"require"/"default"
// result anyway.
}
Ok(options.cell())
}
@@ -88,6 +88,9 @@ async fn base_resolve_options(
if opt.module {
conditions.insert(rcstr!("module"), ConditionValue::Set);
}
if opt.module_sync != ConditionValue::Unset {
conditions.insert(rcstr!("module-sync"), opt.module_sync);
}
if let Some(environment) = emulating {
for condition in environment.resolve_conditions().await?.iter() {
conditions.insert(condition.clone(), ConditionValue::Set);
@@ -7,7 +7,7 @@ use turbopack_core::{
condition::ContextCondition,
environment::Environment,
resolve::{
options::{ImportMap, ResolvedMap},
options::{ConditionValue, ImportMap, ResolvedMap},
plugin::{AfterResolvePlugin, BeforeResolvePlugin},
},
};
@@ -53,6 +53,8 @@ pub struct ResolveOptionsContext {
pub browser: bool,
/// Enables the "module" field and export condition in package.json
pub module: bool,
/// Enables the "module-sync" export condition in package.json
pub module_sync: ConditionValue,
pub custom_conditions: Vec<RcStr>,
pub custom_extensions: Option<Vec<RcStr>>,
/// An additional import map to use when resolving modules.
@@ -51,6 +51,7 @@ use turbopack_core::{
rebase::RebasedAsset,
reference::all_assets_from_entry,
reference_type::ReferenceType,
resolve::options::ConditionValue,
};
use turbopack_ecmascript::AnalyzeMode;
use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
@@ -414,6 +415,7 @@ async fn node_file_trace_operation(
enable_node_native_modules: true,
enable_node_modules: Some(input_dir.clone()),
custom_conditions: vec![rcstr!("node")],
module_sync: ConditionValue::Unknown,
collect_affecting_sources: true,
..Default::default()
}
@@ -1,6 +1,7 @@
;[
'test/unit/imports-module-sync/input.js',
'test/unit/imports-module-sync/internal-default.js',
// @vercel/nft outputs internal-default.js here instead, but I think that's wrong?
'test/unit/imports-module-sync/internal-import.js',
'test/unit/imports-module-sync/internal-sync.js',
'test/unit/imports-module-sync/package.json',
]
@@ -1,5 +1,6 @@
;[
'test/unit/module-sync-condition-es-nested/fallback.js',
// @vercel/nft outputs fallback.js here instead, but I think that's wrong?
'test/unit/module-sync-condition-es-nested/import.js',
'test/unit/module-sync-condition-es-nested/input.js',
'test/unit/module-sync-condition-es-nested/module-sync.js',
'test/unit/module-sync-condition-es-nested/package.json',
@@ -1,5 +1,6 @@
;[
'test/unit/module-sync-condition-es/fallback.js',
// @vercel/nft outputs fallback.js here instead, but I think that's wrong?
'test/unit/module-sync-condition-es/import.js',
'test/unit/module-sync-condition-es/input.js',
'test/unit/module-sync-condition-es/module-sync.js',
'test/unit/module-sync-condition-es/package.json',
@@ -1,5 +1,6 @@
;[
'test/unit/self-reference-module-sync/default.js',
// @vercel/nft outputs default.js here instead, but I think that's wrong?
'test/unit/self-reference-module-sync/import.js',
'test/unit/self-reference-module-sync/input.js',
'test/unit/self-reference-module-sync/module-sync.js',
'test/unit/self-reference-module-sync/package.json',
@@ -30,6 +30,7 @@ use turbopack_core::{
output::OutputAsset,
reference::all_assets_from_entries,
reference_type::ReferenceType,
resolve::options::ConditionValue,
traced_asset::TracedAsset,
};
use turbopack_ecmascript::AnalyzeMode;
@@ -108,8 +109,8 @@ static ALLOC: turbo_tasks_malloc::TurboMalloc = turbo_tasks_malloc::TurboMalloc;
// #[case::import_meta_tpl_cnd("import-meta-tpl-cnd")]
#[case::import_meta_url("import-meta-url")]
// #[case::imports("imports")]
// #[case::imports_module_sync("imports-module-sync")]
// #[case::imports_module_sync_cjs("imports-module-sync-cjs")]
#[case::imports_module_sync("imports-module-sync")]
#[case::imports_module_sync_cjs("imports-module-sync-cjs")]
// #[case::jsonc_parser_wrapper("jsonc-parser-wrapper")]
// #[case::jsx_input("jsx-input")]
// #[case::microtime_node_gyp("microtime-node-gyp")]
@@ -123,10 +124,12 @@ static ALLOC: turbo_tasks_malloc::TurboMalloc = turbo_tasks_malloc::TurboMalloc;
#[case::module_create_require_no_mixed("module-create-require-no-mixed")]
// #[case::module_register("module-register")]
// #[case::module_require("module-require")]
// #[case::module_sync_condition_cjs("module-sync-condition-cjs")]
#[case::module_sync_condition_cjs("module-sync-condition-cjs")]
// Turbopack always includes the module-sync version, regardless of the current Node version
// #[case::module_sync_condition_cjs_node20("module-sync-condition-cjs-node20")]
// #[case::module_sync_condition_es("module-sync-condition-es")]
// #[case::module_sync_condition_es_nested("module-sync-condition-es-nested")]
#[case::module_sync_condition_es("module-sync-condition-es")]
#[case::module_sync_condition_es_nested("module-sync-condition-es-nested")]
// Turbopack always includes the module-sync version, regardless of the current Node version
// #[case::module_sync_condition_es_node20("module-sync-condition-es-node20")]
// #[case::mongoose("mongoose")]
// #[case::multi_input("multi-input")]
@@ -165,7 +168,7 @@ static ALLOC: turbo_tasks_malloc::TurboMalloc = turbo_tasks_malloc::TurboMalloc;
// #[case::resolve_from("resolve-from")]
// #[case::resolve_hook("resolve-hook")]
// #[case::return_emission("return-emission")]
// #[case::self_reference_module_sync("self-reference-module-sync")]
#[case::self_reference_module_sync("self-reference-module-sync")]
// #[case::shiki("shiki")]
// #[case::string_concat("string-concat")]
#[case::syntax_err("syntax-err")]
@@ -250,6 +253,7 @@ async fn node_file_trace_operation(package_root: RcStr, input: RcStr) -> Result<
enable_node_native_modules: true,
enable_node_modules: Some(input_dir.clone()),
custom_conditions: vec![rcstr!("node")],
module_sync: ConditionValue::Unknown,
..Default::default()
}
.cell(),
+6 -2
View File
@@ -36,8 +36,11 @@ use turbopack_core::{
},
resolve::{
ExternalTraced, ExternalType, ModulePart, ModuleResolveResult, ModuleResolveResultItem,
ResolveResult, ResolveResultItem, options::ResolveOptions, origin::PlainResolveOrigin,
parse::Request, resolve,
ResolveResult, ResolveResultItem,
options::{ConditionValue, ResolveOptions},
origin::PlainResolveOrigin,
parse::Request,
resolve,
},
source::Source,
source_transform::SourceTransforms,
@@ -926,6 +929,7 @@ pub async fn externals_tracing_module_context(
loose_errors: true,
collect_affecting_sources: true,
custom_conditions: vec![rcstr!("node")],
module_sync: ConditionValue::Unknown,
..Default::default()
};