Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(turbopack): add support for esm externals in app dir #64918

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/next-swc/crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,13 +848,13 @@ impl AppEndpoint {
{
entry_client_chunks.extend(chunks.await?.iter().copied());
}
for chunks in client_references_chunks_ref
for (chunks, _) in client_references_chunks_ref
.client_component_client_chunks
.values()
{
client_assets.extend(chunks.await?.iter().copied());
}
for chunks in client_references_chunks_ref
for (chunks, _) in client_references_chunks_ref
.client_component_ssr_chunks
.values()
{
Expand Down Expand Up @@ -948,7 +948,7 @@ impl AppEndpoint {
// initialization
let client_references_chunks = &*client_references_chunks.await?;

for &ssr_chunks in client_references_chunks
for (ssr_chunks, _) in client_references_chunks
.client_component_ssr_chunks
.values()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ fn client_modules_ssr_modifier() -> Vc<RcStr> {

#[turbo_tasks::value]
pub struct ClientReferencesChunks {
pub client_component_client_chunks: IndexMap<ClientReferenceType, Vc<OutputAssets>>,
pub client_component_ssr_chunks: IndexMap<ClientReferenceType, Vc<OutputAssets>>,
pub client_component_client_chunks:
IndexMap<ClientReferenceType, (Vc<OutputAssets>, AvailabilityInfo)>,
pub client_component_ssr_chunks:
IndexMap<ClientReferenceType, (Vc<OutputAssets>, AvailabilityInfo)>,
pub layout_segment_client_chunks: IndexMap<Vc<NextServerComponentModule>, Vc<OutputAssets>>,
}

Expand Down Expand Up @@ -63,23 +65,47 @@ pub async fn get_app_client_references_chunks(
) => {
let ecmascript_client_reference_ref =
ecmascript_client_reference.await?;
(
client_chunking_context.root_chunk_group_assets(Vc::upcast(

let client_chunk_group = client_chunking_context
.root_chunk_group(Vc::upcast(
ecmascript_client_reference_ref.client_module,
)),
ssr_chunking_context.map(|ssr_chunking_context| {
ssr_chunking_context.root_chunk_group_assets(Vc::upcast(
ecmascript_client_reference_ref.ssr_module,
))
.await?;

(
(
client_chunk_group.assets,
client_chunk_group.availability_info,
),
if let Some(ssr_chunking_context) = ssr_chunking_context {
let ssr_chunk_group = ssr_chunking_context
.root_chunk_group(Vc::upcast(
ecmascript_client_reference_ref.ssr_module,
))
.await?;

Some((
ssr_chunk_group.assets,
ssr_chunk_group.availability_info,
))
}),
} else {
None
},
)
}
ClientReferenceType::CssClientReference(css_client_reference) => {
let css_client_reference_ref = css_client_reference.await?;
(
client_chunking_context.root_chunk_group_assets(Vc::upcast(
let client_chunk_group = client_chunking_context
.root_chunk_group(Vc::upcast(
css_client_reference_ref.client_module,
)),
))
.await?;

(
(
client_chunk_group.assets,
client_chunk_group.availability_info,
),
None,
)
}
Expand Down Expand Up @@ -162,6 +188,7 @@ pub async fn get_app_client_references_chunks(
})
.try_flat_join()
.await?;

let ssr_chunk_group = if !ssr_modules.is_empty() {
ssr_chunking_context.map(|ssr_chunking_context| {
let _span = tracing::info_span!(
Expand All @@ -182,6 +209,7 @@ pub async fn get_app_client_references_chunks(
} else {
None
};

let client_modules = client_reference_types
.iter()
.map(|client_reference_ty| async move {
Expand Down Expand Up @@ -238,8 +266,10 @@ pub async fn get_app_client_references_chunks(
if let ClientReferenceType::EcmascriptClientReference(_) =
client_reference_ty
{
client_component_client_chunks
.insert(client_reference_ty, client_chunks);
client_component_client_chunks.insert(
client_reference_ty,
(client_chunks, client_chunk_group.availability_info),
);
}
}
}
Expand All @@ -259,7 +289,10 @@ pub async fn get_app_client_references_chunks(
if let ClientReferenceType::EcmascriptClientReference(_) =
client_reference_ty
{
client_component_ssr_chunks.insert(client_reference_ty, ssr_chunks);
client_component_ssr_chunks.insert(
client_reference_ty,
(ssr_chunks, ssr_chunk_group.availability_info),
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use turbo_tasks_fs::{File, FileSystemPath};
use turbopack_binding::turbopack::{
core::{
asset::AssetContent,
chunk::{ChunkItemExt, ChunkableModule, ChunkingContext, ModuleId as TurbopackModuleId},
chunk::{
availability_info::AvailabilityInfo, ChunkItem, ChunkItemExt, ChunkableModule,
ChunkingContext, ModuleId as TurbopackModuleId,
},
output::OutputAsset,
virtual_output::VirtualOutputAsset,
},
Expand Down Expand Up @@ -66,62 +69,70 @@ impl ClientReferenceManifest {
.to_string()
.await?;

let client_module_id = ecmascript_client_reference
let client_chunk_item = ecmascript_client_reference
.client_module
.as_chunk_item(Vc::upcast(client_chunking_context))
.id()
.await?;
.as_chunk_item(Vc::upcast(client_chunking_context));

let client_module_id = client_chunk_item.id().await?;

let (client_chunks_paths, client_is_async) =
if let Some((client_chunks, client_availability_info)) =
client_references_chunks
.client_component_client_chunks
.get(&app_client_reference_ty)
{
let client_chunks = client_chunks.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;

let chunk_paths = client_chunks_paths
.iter()
.filter_map(|chunk_path| client_relative_path.get_path_to(chunk_path))
.map(ToString::to_string)
// It's possible that a chunk also emits CSS files, that will
// be handled separatedly.
.filter(|path| path.ends_with(".js"))
.map(RcStr::from)
.collect::<Vec<_>>();

let is_async =
is_item_async(client_availability_info, client_chunk_item).await?;

(chunk_paths, is_async)
} else {
(Vec::new(), false)
};

let client_chunks_paths = if let Some(client_chunks) = client_references_chunks
.client_component_client_chunks
.get(&app_client_reference_ty)
{
let client_chunks = client_chunks.await?;
let client_chunks_paths = client_chunks
.iter()
.map(|chunk| chunk.ident().path())
.try_join()
.await?;

client_chunks_paths
.iter()
.filter_map(|chunk_path| client_relative_path.get_path_to(chunk_path))
.map(ToString::to_string)
// It's possible that a chunk also emits CSS files, that will
// be handled separatedly.
.filter(|path| path.ends_with(".js"))
.map(RcStr::from)
.collect::<Vec<_>>()
} else {
Vec::new()
};
entry_manifest.client_modules.module_exports.insert(
get_client_reference_module_key(&server_path, "*"),
ManifestNodeEntry {
name: "*".into(),
id: (&*client_module_id).into(),
chunks: client_chunks_paths,
// TODO(WEB-434)
r#async: false,
r#async: client_is_async,
},
);

if let Some(ssr_chunking_context) = ssr_chunking_context {
let ssr_module_id = ecmascript_client_reference
let ssr_chunk_item = ecmascript_client_reference
.ssr_module
.as_chunk_item(Vc::upcast(ssr_chunking_context))
.id()
.await?;
.as_chunk_item(Vc::upcast(ssr_chunking_context));

let ssr_module_id = ssr_chunk_item.id().await?;

let ssr_chunks_paths = if runtime == NextRuntime::Edge {
let (ssr_chunks_paths, ssr_is_async) = if runtime == NextRuntime::Edge {
// the chunks get added to the middleware-manifest.json instead
// of this file because the
// edge runtime doesn't support dynamically
// loading chunks.
Vec::new()
} else if let Some(ssr_chunks) = client_references_chunks
.client_component_ssr_chunks
.get(&app_client_reference_ty)
(Vec::new(), false)
} else if let Some((ssr_chunks, ssr_availability_info)) =
client_references_chunks
.client_component_ssr_chunks
.get(&app_client_reference_ty)
{
let ssr_chunks = ssr_chunks.await?;

Expand All @@ -131,24 +142,28 @@ impl ClientReferenceManifest {
.try_join()
.await?;

ssr_chunks_paths
let chunk_paths = ssr_chunks_paths
.iter()
.filter_map(|chunk_path| node_root_ref.get_path_to(chunk_path))
.map(ToString::to_string)
.map(RcStr::from)
.collect::<Vec<_>>()
.collect::<Vec<_>>();

let is_async = is_item_async(ssr_availability_info, ssr_chunk_item).await?;

(chunk_paths, is_async)
} else {
Vec::new()
(Vec::new(), false)
};

let mut ssr_manifest_node = ManifestNode::default();
ssr_manifest_node.module_exports.insert(
"*".into(),
ManifestNodeEntry {
name: "*".into(),
id: (&*ssr_module_id).into(),
chunks: ssr_chunks_paths,
// TODO(WEB-434)
r#async: false,
r#async: ssr_is_async,
},
);

Expand Down Expand Up @@ -253,3 +268,18 @@ pub fn get_client_reference_module_key(server_path: &str, export_name: &str) ->
format!("{}#{}", server_path, export_name).into()
}
}

async fn is_item_async(
availability_info: &AvailabilityInfo,
chunk_item: Vc<Box<dyn ChunkItem>>,
) -> Result<bool> {
let Some(available_chunk_items) = availability_info.available_chunk_items() else {
return Ok(false);
};

let Some(info) = &*available_chunk_items.get(chunk_item).await? else {
return Ok(false);
};

Ok(info.is_async)
}
7 changes: 4 additions & 3 deletions packages/next-swc/crates/next-core/src/next_server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,15 @@ pub async fn get_server_resolve_options_context(

external_packages.retain(|item| !transpile_packages.contains(item));

let ty = ty.into_value();

let server_external_packages_plugin = ExternalCjsModulesResolvePlugin::new(
project_path,
project_path.root(),
ExternalPredicate::Only(Vc::cell(external_packages)).cell(),
// TODO(sokra) esmExternals support
false,
// app-ssr can't have esm externals as that would make the module async on the server only
*next_config.import_externals().await? && !matches!(ty, ServerContextType::AppSSR { .. }),
);
let ty = ty.into_value();

let mut custom_conditions = vec![mode.await?.condition().to_string().into()];
custom_conditions.extend(
Expand Down
7 changes: 2 additions & 5 deletions test/e2e/app-dir/app-external/app-external.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function resolveStreamResponse(response: any, onData?: any) {
}

describe('app dir - external dependency', () => {
const { next, skipped, isTurbopack } = nextTestSetup({
const { next, skipped } = nextTestSetup({
files: __dirname,
dependencies: {
swr: 'latest',
Expand Down Expand Up @@ -286,10 +286,7 @@ describe('app dir - external dependency', () => {
browser.elementByCss('#dual-pkg-outout button').click()
await check(async () => {
const text = await browser.elementByCss('#dual-pkg-outout p').text()
// TODO: enable esm externals for app router in turbopack for actions
expect(text).toBe(
isTurbopack ? 'dual-pkg-optout:cjs' : 'dual-pkg-optout:mjs'
)
expect(text).toBe('dual-pkg-optout:mjs')
return 'success'
}, /success/)
})
Expand Down
Loading
Loading