diff --git a/packages/next-swc/crates/next-core/src/lib.rs b/packages/next-swc/crates/next-core/src/lib.rs index 5e4cc256e8223..b6c3890e0b53d 100644 --- a/packages/next-swc/crates/next-core/src/lib.rs +++ b/packages/next-swc/crates/next-core/src/lib.rs @@ -9,7 +9,6 @@ mod babel; mod embed_js; pub mod env; mod fallback; -mod image; pub mod manifest; mod next_build; pub mod next_client; diff --git a/packages/next-swc/crates/next-core/src/next_image/content_source.rs b/packages/next-swc/crates/next-core/src/next_image/content_source.rs new file mode 100644 index 0000000000000..e9f72694aeac3 --- /dev/null +++ b/packages/next-swc/crates/next-core/src/next_image/content_source.rs @@ -0,0 +1,182 @@ +use std::collections::BTreeSet; + +use anyhow::Result; +use turbo_binding::turbopack::{ + core::{ + asset::AssetContent, + ident::AssetIdentVc, + introspect::{Introspectable, IntrospectableVc}, + server_fs::ServerFileSystemVc, + version::VersionedContent, + }, + dev_server::source::{ + query::QueryValue, + wrapping_source::{ + encode_pathname_to_url, ContentSourceProcessor, ContentSourceProcessorVc, + WrappedContentSourceVc, + }, + ContentSource, ContentSourceContent, ContentSourceContentVc, ContentSourceData, + ContentSourceDataFilter, ContentSourceDataVary, ContentSourceResultVc, ContentSourceVc, + NeededData, ProxyResult, RewriteBuilder, + }, + image::process::optimize, +}; +use turbo_tasks::{primitives::StringVc, Value}; +use turbo_tasks_fs::FileSystem; + +/// Serves, resizes, optimizes, and re-encodes images to be used with +/// next/image. +#[turbo_tasks::value(shared)] +pub struct NextImageContentSource { + asset_source: ContentSourceVc, +} + +#[turbo_tasks::value_impl] +impl NextImageContentSourceVc { + #[turbo_tasks::function] + pub fn new(asset_source: ContentSourceVc) -> NextImageContentSourceVc { + NextImageContentSource { asset_source }.cell() + } +} + +#[turbo_tasks::value_impl] +impl ContentSource for NextImageContentSource { + #[turbo_tasks::function] + async fn get( + self_vc: NextImageContentSourceVc, + path: &str, + data: Value, + ) -> Result { + let this = self_vc.await?; + + let Some(query) = &data.query else { + let queries = ["url".to_string(), "w".to_string(), "q".to_string()] + .into_iter() + .collect::>(); + + return Ok(ContentSourceResultVc::need_data(Value::new(NeededData { + source: self_vc.into(), + path: path.to_string(), + vary: ContentSourceDataVary { + url: true, + query: Some(ContentSourceDataFilter::Subset(queries)), + ..Default::default() + }, + }))); + }; + + let Some(QueryValue::String(url)) = query.get("url") else { + return Ok(ContentSourceResultVc::not_found()); + }; + + let q = match query.get("q") { + None => 75, + Some(QueryValue::String(s)) => { + let Ok(q) = s.parse::() else { + return Ok(ContentSourceResultVc::not_found()); + }; + q + } + _ => return Ok(ContentSourceResultVc::not_found()), + }; + + let w = match query.get("w") { + Some(QueryValue::String(s)) => { + let Ok(w) = s.parse::() else { + return Ok(ContentSourceResultVc::not_found()); + }; + w + } + _ => return Ok(ContentSourceResultVc::not_found()), + }; + + // TODO: re-encode into next-gen formats. + if let Some(path) = url.strip_prefix('/') { + let wrapped = WrappedContentSourceVc::new( + this.asset_source, + NextImageContentSourceProcessorVc::new(path.to_string(), w, q).into(), + ); + return Ok(ContentSourceResultVc::exact( + ContentSourceContent::Rewrite( + RewriteBuilder::new(encode_pathname_to_url(path)) + .content_source(wrapped.as_content_source()) + .build(), + ) + .cell() + .into(), + )); + } + + // TODO: This should be downloaded by the server, and resized, etc. + Ok(ContentSourceResultVc::exact( + ContentSourceContent::HttpProxy( + ProxyResult { + status: 302, + headers: vec![("Location".to_string(), url.clone())], + body: "".into(), + } + .cell(), + ) + .cell() + .into(), + )) + } +} + +#[turbo_tasks::value_impl] +impl Introspectable for NextImageContentSource { + #[turbo_tasks::function] + fn ty(&self) -> StringVc { + StringVc::cell("next image content source".to_string()) + } + + #[turbo_tasks::function] + fn details(&self) -> StringVc { + StringVc::cell("supports dynamic serving of any statically imported image".to_string()) + } +} + +#[turbo_tasks::value] +struct NextImageContentSourceProcessor { + path: String, + width: u32, + quality: u8, +} + +#[turbo_tasks::value_impl] +impl NextImageContentSourceProcessorVc { + #[turbo_tasks::function] + pub fn new(path: String, width: u32, quality: u8) -> NextImageContentSourceProcessorVc { + NextImageContentSourceProcessor { + path, + width, + quality, + } + .cell() + } +} + +#[turbo_tasks::value_impl] +impl ContentSourceProcessor for NextImageContentSourceProcessor { + #[turbo_tasks::function] + async fn process(&self, content: ContentSourceContentVc) -> Result { + let ContentSourceContent::Static(static_content) = *content.await? else { + return Ok(content); + }; + let static_content = static_content.await?; + let asset_content = static_content.content.content().await?; + let AssetContent::File(file_content) = *asset_content else { + return Ok(content); + }; + let optimized_file_content = optimize( + AssetIdentVc::from_path(ServerFileSystemVc::new().root().join(&self.path)), + file_content, + self.width, + u32::MAX, + self.quality, + ); + Ok(ContentSourceContentVc::static_content( + AssetContent::File(optimized_file_content).into(), + )) + } +} diff --git a/packages/next-swc/crates/next-core/src/next_image/mod.rs b/packages/next-swc/crates/next-core/src/next_image/mod.rs index 59b27ffc029e4..14ff96a02cb86 100644 --- a/packages/next-swc/crates/next-core/src/next_image/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_image/mod.rs @@ -1,100 +1,6 @@ -use std::collections::BTreeSet; +pub(crate) mod content_source; +pub(crate) mod module; +pub(crate) mod source_asset; -use anyhow::Result; -use turbo_binding::turbopack::{ - core::introspect::{Introspectable, IntrospectableVc}, - dev_server::source::{ - query::QueryValue, ContentSource, ContentSourceContent, ContentSourceData, - ContentSourceDataFilter, ContentSourceDataVary, ContentSourceResultVc, ContentSourceVc, - NeededData, ProxyResult, - }, -}; -use turbo_tasks::{primitives::StringVc, Value}; - -/// Serves, resizes, optimizes, and re-encodes images to be used with -/// next/image. -#[turbo_tasks::value(shared)] -pub struct NextImageContentSource { - asset_source: ContentSourceVc, -} - -#[turbo_tasks::value_impl] -impl NextImageContentSourceVc { - #[turbo_tasks::function] - pub fn new(asset_source: ContentSourceVc) -> NextImageContentSourceVc { - NextImageContentSource { asset_source }.cell() - } -} - -#[turbo_tasks::value_impl] -impl ContentSource for NextImageContentSource { - #[turbo_tasks::function] - async fn get( - self_vc: NextImageContentSourceVc, - path: &str, - data: Value, - ) -> Result { - let this = self_vc.await?; - - let query = match &data.query { - None => { - let queries = [ - "url".to_string(), - // TODO: support q and w queries. - ] - .iter() - .cloned() - .collect::>(); - - return Ok(ContentSourceResultVc::need_data(Value::new(NeededData { - source: self_vc.into(), - path: path.to_string(), - vary: ContentSourceDataVary { - url: true, - query: Some(ContentSourceDataFilter::Subset(queries)), - ..Default::default() - }, - }))); - } - Some(query) => query, - }; - - let url = match query.get("url") { - Some(QueryValue::String(s)) => s, - _ => return Ok(ContentSourceResultVc::not_found()), - }; - - // TODO: consume the assets, resize and reduce quality, re-encode into next-gen - // formats. - if let Some(path) = url.strip_prefix('/') { - return Ok(this.asset_source.get(path, Default::default())); - } - - // TODO: This should be downloaded by the server, and resized, etc. - Ok(ContentSourceResultVc::exact( - ContentSourceContent::HttpProxy( - ProxyResult { - status: 302, - headers: vec![("Location".to_string(), url.clone())], - body: "".into(), - } - .cell(), - ) - .cell() - .into(), - )) - } -} - -#[turbo_tasks::value_impl] -impl Introspectable for NextImageContentSource { - #[turbo_tasks::function] - fn ty(&self) -> StringVc { - StringVc::cell("next image content source".to_string()) - } - - #[turbo_tasks::function] - fn details(&self) -> StringVc { - StringVc::cell("suports dynamic serving of any statically imported image".to_string()) - } -} +pub use content_source::NextImageContentSourceVc; +pub use module::StructuredImageModuleTypeVc; diff --git a/packages/next-swc/crates/next-core/src/image/mod.rs b/packages/next-swc/crates/next-core/src/next_image/module.rs similarity index 96% rename from packages/next-swc/crates/next-core/src/image/mod.rs rename to packages/next-swc/crates/next-core/src/next_image/module.rs index 34e441d2395a7..9c49905e52370 100644 --- a/packages/next-swc/crates/next-core/src/image/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_image/module.rs @@ -17,9 +17,7 @@ use turbo_binding::{ }, }; -use self::source::StructuredImageSourceAsset; - -pub(crate) mod source; +use super::source_asset::StructuredImageSourceAsset; /// Module type that analyzes images and offers some meta information like /// width, height and blur placeholder as export from the module. diff --git a/packages/next-swc/crates/next-core/src/image/source.rs b/packages/next-swc/crates/next-core/src/next_image/source_asset.rs similarity index 100% rename from packages/next-swc/crates/next-core/src/image/source.rs rename to packages/next-swc/crates/next-core/src/next_image/source_asset.rs diff --git a/packages/next-swc/crates/next-core/src/next_shared/transforms.rs b/packages/next-swc/crates/next-core/src/next_shared/transforms.rs index 97ca9cd451355..021f6dd0dbd92 100644 --- a/packages/next-swc/crates/next-core/src/next_shared/transforms.rs +++ b/packages/next-swc/crates/next-core/src/next_shared/transforms.rs @@ -29,7 +29,7 @@ use turbo_binding::{ }; use turbo_tasks::trace::TraceRawVcs; -use crate::image::StructuredImageModuleTypeVc; +use crate::next_image::StructuredImageModuleTypeVc; /// Returns a rule which applies the Next.js page export stripping transform. pub async fn get_next_pages_transforms_rule(