diff --git a/packages/next-swc/crates/next-core/js/src/entry/config/next.js b/packages/next-swc/crates/next-core/js/src/entry/config/next.js index 4a734bb34f905..021a6e04a3e01 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/config/next.js +++ b/packages/next-swc/crates/next-core/js/src/entry/config/next.js @@ -2,6 +2,7 @@ import loadConfig from 'next/dist/server/config' import loadCustomRoutes from 'next/dist/lib/load-custom-routes' import { PHASE_DEVELOPMENT_SERVER } from 'next/dist/shared/lib/constants' import assert from 'node:assert' +import * as path from 'node:path' const loadNextConfig = async (silent) => { const nextConfig = await loadConfig( @@ -45,6 +46,12 @@ const loadNextConfig = async (silent) => { ) } + // loaderFile is an absolute path, we need it to be relative for turbopack. + if (nextConfig.images.loaderFile) { + nextConfig.images.loaderFile = + './' + path.relative(process.cwd(), nextConfig.images.loaderFile) + } + return { customRoutes: customRoutes, config: nextConfig, diff --git a/packages/next-swc/crates/next-core/src/next_client/context.rs b/packages/next-swc/crates/next-core/src/next_client/context.rs index 96012a4b0c3b6..409cbe0adb652 100644 --- a/packages/next-swc/crates/next-core/src/next_client/context.rs +++ b/packages/next-swc/crates/next-core/src/next_client/context.rs @@ -145,7 +145,7 @@ pub async fn get_client_resolve_options_context( execution_context: Vc, ) -> Result> { let next_client_import_map = - get_next_client_import_map(project_path, ty, mode, next_config, execution_context); + get_next_client_import_map(project_path, ty, next_config, execution_context); let next_client_fallback_import_map = get_next_client_fallback_import_map(ty); let next_client_resolved_map = get_next_client_resolved_map(project_path, project_path, mode); let module_options_context = ResolveOptionsContext { diff --git a/packages/next-swc/crates/next-core/src/next_config.rs b/packages/next-swc/crates/next-core/src/next_config.rs index 63a202b74af25..50e1adf3acf67 100644 --- a/packages/next-swc/crates/next-core/src/next_config.rs +++ b/packages/next-swc/crates/next-core/src/next_config.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use indexmap::IndexMap; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value as JsonValue; use turbo_tasks::{trace::TraceRawVcs, Completion, Value, Vc}; use turbo_tasks_fs::json::parse_json_with_source_context; @@ -310,6 +310,8 @@ pub struct ImageConfig { pub image_sizes: Vec, pub path: String, pub loader: ImageLoader, + #[serde(deserialize_with = "empty_string_is_none")] + pub loader_file: Option, pub domains: Vec, pub disable_static_images: bool, #[serde(rename(deserialize = "minimumCacheTTL"))] @@ -322,6 +324,14 @@ pub struct ImageConfig { pub unoptimized: bool, } +fn empty_string_is_none<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let o = Option::::deserialize(deserializer)?; + Ok(o.filter(|s| !s.is_empty())) +} + impl Default for ImageConfig { fn default() -> Self { // https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114 @@ -330,6 +340,7 @@ impl Default for ImageConfig { image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384], path: "/_next/image".to_string(), loader: ImageLoader::Default, + loader_file: None, domains: vec![], disable_static_images: false, minimum_cache_ttl: 60, diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index 5d3b4845a9921..27c587c0cf9c0 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -99,7 +99,7 @@ pub async fn get_edge_resolve_options_context( execution_context: Vc, ) -> Result> { let next_edge_import_map = - get_next_edge_import_map(project_path, ty, mode, next_config, execution_context); + get_next_edge_import_map(project_path, ty, next_config, execution_context); let ty = ty.into_value(); diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index d77d154aebd92..0891e132b9df6 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -40,7 +40,6 @@ use crate::{ pub async fn get_next_client_import_map( project_path: Vc, ty: Value, - mode: NextMode, next_config: Vc, execution_context: Vc, ) -> Result> { @@ -51,7 +50,6 @@ pub async fn get_next_client_import_map( project_path, execution_context, next_config, - mode, ) .await?; @@ -261,7 +259,6 @@ pub fn get_next_client_fallback_import_map(ty: Value) -> Vc, ty: Value, - mode: NextMode, next_config: Vc, execution_context: Vc, ) -> Result> { @@ -272,7 +269,6 @@ pub async fn get_next_server_import_map( project_path, execution_context, next_config, - mode, ) .await?; @@ -338,7 +334,6 @@ pub async fn get_next_server_import_map( pub async fn get_next_edge_import_map( project_path: Vc, ty: Value, - mode: NextMode, next_config: Vc, execution_context: Vc, ) -> Result> { @@ -399,7 +394,6 @@ pub async fn get_next_edge_import_map( project_path, execution_context, next_config, - mode, ) .await?; @@ -521,6 +515,21 @@ async fn insert_next_server_special_aliases( external_if_node(project_path, "next/dist/compiled/@opentelemetry/api"), ); + let image_config = next_config.image_config().await?; + if let Some(loader_file) = image_config.loader_file.as_deref() { + import_map.insert_exact_alias( + "next/dist/shared/lib/image-loader", + request_to_import_mapping(project_path, loader_file), + ); + + if runtime == NextRuntime::Edge { + import_map.insert_exact_alias( + "next/dist/esm/shared/lib/image-loader", + request_to_import_mapping(project_path, loader_file), + ); + } + } + match ty { ServerContextType::Pages { pages_dir } | ServerContextType::PagesApi { pages_dir } => { insert_alias_to_alternatives( @@ -745,7 +754,6 @@ async fn insert_next_shared_aliases( project_path: Vc, execution_context: Vc, next_config: Vc, - mode: NextMode, ) -> Result<()> { let package_root = next_js_fs().root(); @@ -761,15 +769,6 @@ async fn insert_next_shared_aliases( ); } - if mode != NextMode::Development { - // we use the next.js hydration code, so we replace the error overlay with our - // own - import_map.insert_exact_alias( - "next/dist/compiled/@next/react-dev-overlay/dist/client", - request_to_import_mapping(package_root, "./overlay/client.ts"), - ); - } - insert_package_alias( import_map, &format!("{VIRTUAL_PACKAGE_NAME}/"), diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 32db4cec20d14..0f33039aa5cda 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -104,7 +104,7 @@ pub async fn get_server_resolve_options_context( execution_context: Vc, ) -> Result> { let next_server_import_map = - get_next_server_import_map(project_path, ty, mode, next_config, execution_context); + get_next_server_import_map(project_path, ty, next_config, execution_context); let foreign_code_context_condition = foreign_code_context_condition(next_config, project_path).await?; let root_dir = project_path.root().resolve().await?; diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts index cd154b87f1fbe..3f0e53193c68b 100644 --- a/packages/next/src/build/swc/index.ts +++ b/packages/next/src/build/swc/index.ts @@ -796,7 +796,8 @@ function bindingToApi(binding: any, _wasm: boolean) { return { ...options, nextConfig: - options.nextConfig && (await serializeNextConfig(options.nextConfig)), + options.nextConfig && + (await serializeNextConfig(options.nextConfig, options.projectPath!)), jsConfig: options.jsConfig && JSON.stringify(options.jsConfig), env: options.env && rustifyEnv(options.env), defineEnv: options.defineEnv, @@ -810,7 +811,7 @@ function bindingToApi(binding: any, _wasm: boolean) { this._nativeProject = nativeProject } - async update(options: ProjectOptions) { + async update(options: Partial) { await withErrorCause(async () => binding.projectUpdate( this._nativeProject, @@ -1037,7 +1038,8 @@ function bindingToApi(binding: any, _wasm: boolean) { } async function serializeNextConfig( - nextConfig: NextConfigComplete + nextConfig: NextConfigComplete, + projectPath: string ): Promise { let nextConfigSerializable = nextConfig as any @@ -1073,6 +1075,12 @@ function bindingToApi(binding: any, _wasm: boolean) { ) : undefined + // loaderFile is an absolute path, we need it to be relative for turbopack. + if (nextConfig.images.loaderFile) { + nextConfig.images.loaderFile = + './' + path.relative(projectPath, nextConfig.images.loaderFile) + } + return JSON.stringify(nextConfigSerializable, null, 2) } diff --git a/test/turbopack-tests-manifest.json b/test/turbopack-tests-manifest.json index 1ca47d47e37ee..366699c430028 100644 --- a/test/turbopack-tests-manifest.json +++ b/test/turbopack-tests-manifest.json @@ -13495,12 +13495,11 @@ "test/integration/next-image-new/loader-config-default-loader-with-file/test/index.test.ts": { "passed": [ "Image Loader Config dev mode - component should work with loader prop", - "Image Loader Config dev mode - getImageProps should work with loader prop" - ], - "failed": [ "Image Loader Config dev mode - component should work with loaderFile config, leaving default image optimization enabled", + "Image Loader Config dev mode - getImageProps should work with loader prop", "Image Loader Config dev mode - getImageProps should work with loaderFile config, leaving default image optimization enabled" ], + "failed": [], "pending": [ "Image Loader Config production mode - component should work with loader prop", "Image Loader Config production mode - component should work with loaderFile config, leaving default image optimization enabled",