Skip to content

Commit

Permalink
feat: support custom image loaders in turbopack
Browse files Browse the repository at this point in the history
  • Loading branch information
ForsakenHarmony committed Jan 18, 2024
1 parent 8d4e5be commit ef1e16e
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ pub async fn get_client_resolve_options_context(
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ResolveOptionsContext>> {
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 {
Expand Down
13 changes: 12 additions & 1 deletion packages/next-swc/crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -310,6 +310,8 @@ pub struct ImageConfig {
pub image_sizes: Vec<u16>,
pub path: String,
pub loader: ImageLoader,
#[serde(deserialize_with = "empty_string_is_none")]
pub loader_file: Option<String>,
pub domains: Vec<String>,
pub disable_static_images: bool,
#[serde(rename(deserialize = "minimumCacheTTL"))]
Expand All @@ -322,6 +324,14 @@ pub struct ImageConfig {
pub unoptimized: bool,
}

fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::<String>::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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub async fn get_edge_resolve_options_context(
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ResolveOptionsContext>> {
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();

Expand Down
31 changes: 15 additions & 16 deletions packages/next-swc/crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ use crate::{
pub async fn get_next_client_import_map(
project_path: Vc<FileSystemPath>,
ty: Value<ClientContextType>,
mode: NextMode,
next_config: Vc<NextConfig>,
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ImportMap>> {
Expand All @@ -51,7 +50,6 @@ pub async fn get_next_client_import_map(
project_path,
execution_context,
next_config,
mode,
)
.await?;

Expand Down Expand Up @@ -261,7 +259,6 @@ pub fn get_next_client_fallback_import_map(ty: Value<ClientContextType>) -> Vc<I
pub async fn get_next_server_import_map(
project_path: Vc<FileSystemPath>,
ty: Value<ServerContextType>,
mode: NextMode,
next_config: Vc<NextConfig>,
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ImportMap>> {
Expand All @@ -272,7 +269,6 @@ pub async fn get_next_server_import_map(
project_path,
execution_context,
next_config,
mode,
)
.await?;

Expand Down Expand Up @@ -338,7 +334,6 @@ pub async fn get_next_server_import_map(
pub async fn get_next_edge_import_map(
project_path: Vc<FileSystemPath>,
ty: Value<ServerContextType>,
mode: NextMode,
next_config: Vc<NextConfig>,
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ImportMap>> {
Expand Down Expand Up @@ -399,7 +394,6 @@ pub async fn get_next_edge_import_map(
project_path,
execution_context,
next_config,
mode,
)
.await?;

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -745,7 +754,6 @@ async fn insert_next_shared_aliases(
project_path: Vc<FileSystemPath>,
execution_context: Vc<ExecutionContext>,
next_config: Vc<NextConfig>,
mode: NextMode,
) -> Result<()> {
let package_root = next_js_fs().root();

Expand All @@ -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}/"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub async fn get_server_resolve_options_context(
execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ResolveOptionsContext>> {
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?;
Expand Down
14 changes: 11 additions & 3 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -810,7 +811,7 @@ function bindingToApi(binding: any, _wasm: boolean) {
this._nativeProject = nativeProject
}

async update(options: ProjectOptions) {
async update(options: Partial<ProjectOptions>) {
await withErrorCause(async () =>
binding.projectUpdate(
this._nativeProject,
Expand Down Expand Up @@ -1037,7 +1038,8 @@ function bindingToApi(binding: any, _wasm: boolean) {
}

async function serializeNextConfig(
nextConfig: NextConfigComplete
nextConfig: NextConfigComplete,
projectPath: string
): Promise<string> {
let nextConfigSerializable = nextConfig as any

Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit ef1e16e

Please sign in to comment.