From 5006cabf3834a38f06ebc9a54158721f48cbea79 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 2 May 2023 07:07:50 +0000 Subject: [PATCH 1/2] allow to use different blur placeholder modes improve performance for static metadata images in app dir by avoiding computing blur placeholder --- .../crates/next-core/src/app_source.rs | 3 +- .../crates/next-core/src/next_image/module.rs | 36 ++++++-- .../next-core/src/next_image/source_asset.rs | 84 +++++++++++++------ .../next-core/src/next_shared/transforms.rs | 6 +- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/app_source.rs b/packages/next-swc/crates/next-core/src/app_source.rs index cc79c10b2475e..94763fdeb12bb 100644 --- a/packages/next-swc/crates/next-core/src/app_source.rs +++ b/packages/next-swc/crates/next-core/src/app_source.rs @@ -92,7 +92,7 @@ use crate::{ context::{get_edge_compile_time_info, get_edge_resolve_options_context}, transition::NextEdgeTransition, }, - next_image::module::StructuredImageModuleType, + next_image::module::{BlurPlaceholderMode, StructuredImageModuleType}, next_route_matcher::NextParamsMatcherVc, next_server::context::{ get_server_compile_time_info, get_server_module_options_context, @@ -783,6 +783,7 @@ import {}, {{ chunks as {} }} from "COMPONENT_{}"; inner_module_id, StructuredImageModuleType::create_module( SourceAssetVc::new(*path).into(), + BlurPlaceholderMode::None, state.context, ) .into(), diff --git a/packages/next-swc/crates/next-core/src/next_image/module.rs b/packages/next-swc/crates/next-core/src/next_image/module.rs index 61a8672f5e197..65fe5598b730b 100644 --- a/packages/next-swc/crates/next-core/src/next_image/module.rs +++ b/packages/next-swc/crates/next-core/src/next_image/module.rs @@ -19,19 +19,43 @@ use turbo_binding::{ use super::source_asset::StructuredImageSourceAsset; +#[turbo_tasks::value(serialization = "auto_for_input")] +#[derive(Clone, Copy, Debug, PartialOrd, Ord, Hash)] +pub enum BlurPlaceholderMode { + /// Do not generate a blur placeholder at all. + None, + /// Generate a blur placeholder as data url and embed it directly into the + /// javascript code. This need to compute the blur placeholder eagerly and + /// has a higher computation overhead. + DataUrl, + /// Avoid generating a blur placeholder eagerly and uses `/_next/image` + /// instead to compute one on demand. This changes the UX slightly (blur + /// placeholder is shown later than it should be) and should + /// only be used for development. + NextImage, +} + /// Module type that analyzes images and offers some meta information like /// width, height and blur placeholder as export from the module. #[turbo_tasks::value] -pub struct StructuredImageModuleType {} +pub struct StructuredImageModuleType { + pub blur_placeholder_mode: BlurPlaceholderMode, +} impl StructuredImageModuleType { pub(crate) fn create_module( source: AssetVc, + blur_placeholder_mode: BlurPlaceholderMode, context: AssetContextVc, ) -> EcmascriptModuleAssetVc { let static_asset = StaticModuleAssetVc::new(source, context); EcmascriptModuleAssetVc::new_with_inner_assets( - StructuredImageSourceAsset { image: source }.cell().into(), + StructuredImageSourceAsset { + image: source, + blur_placeholder_mode, + } + .cell() + .into(), context, Value::new(EcmascriptModuleAssetType::Ecmascript), EcmascriptInputTransformsVc::empty(), @@ -49,8 +73,10 @@ impl StructuredImageModuleType { #[turbo_tasks::value_impl] impl StructuredImageModuleTypeVc { #[turbo_tasks::function] - pub fn new() -> Self { - StructuredImageModuleTypeVc::cell(StructuredImageModuleType {}) + pub fn new(blur_placeholder_mode: Value) -> Self { + StructuredImageModuleTypeVc::cell(StructuredImageModuleType { + blur_placeholder_mode: blur_placeholder_mode.into_value(), + }) } } @@ -63,6 +89,6 @@ impl CustomModuleType for StructuredImageModuleType { context: AssetContextVc, _part: Option, ) -> AssetVc { - StructuredImageModuleType::create_module(source, context).into() + StructuredImageModuleType::create_module(source, self.blur_placeholder_mode, context).into() } } diff --git a/packages/next-swc/crates/next-core/src/next_image/source_asset.rs b/packages/next-swc/crates/next-core/src/next_image/source_asset.rs index 5ffa0df16169d..b59a7931b42d2 100644 --- a/packages/next-swc/crates/next-core/src/next_image/source_asset.rs +++ b/packages/next-swc/crates/next-core/src/next_image/source_asset.rs @@ -1,7 +1,6 @@ use std::io::Write; use anyhow::{bail, Result}; -use serde::Serialize; use turbo_binding::{ turbo::{ tasks::primitives::StringVc, @@ -17,6 +16,8 @@ use turbo_binding::{ }, }; +use super::module::BlurPlaceholderMode; + fn modifier() -> StringVc { StringVc::cell("structured image object".to_string()) } @@ -30,22 +31,12 @@ fn blur_options() -> BlurPlaceholderOptionsVc { .cell() } -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ImageExport<'a> { - width: u32, - height: u32, - #[serde(rename = "blurDataURL")] - blur_data_url: Option<&'a str>, - blur_width: u32, - blur_height: u32, -} - /// An source asset that transforms an image into javascript code which exports /// an object with meta information like width, height and a blur placeholder. #[turbo_tasks::value(shared)] pub struct StructuredImageSourceAsset { pub image: AssetVc, + pub blur_placeholder_mode: BlurPlaceholderMode, } #[turbo_tasks::value_impl] @@ -62,20 +53,63 @@ impl Asset for StructuredImageSourceAsset { bail!("Input source is not a file and can't be transformed into image information"); }; let mut result = RopeBuilder::from(""); - let info = get_meta_data(self.image.ident(), content, Some(blur_options())).await?; - let info = ImageExport { - width: info.width, - height: info.height, - blur_width: info.blur_placeholder.as_ref().map_or(0, |p| p.width), - blur_height: info.blur_placeholder.as_ref().map_or(0, |p| p.height), - blur_data_url: info.blur_placeholder.as_ref().map(|p| p.data_url.as_str()), - }; writeln!(result, "import src from \"IMAGE\";",)?; - writeln!( - result, - "export default {{ src, ...{} }}", - StringifyJs(&info) - )?; + let blur_options = blur_options(); + match self.blur_placeholder_mode { + BlurPlaceholderMode::NextImage => { + let info = get_meta_data(self.image.ident(), content, None).await?; + let width = info.width; + let height = info.height; + let blur_options = blur_options.await?; + let (blur_width, blur_height) = if width > height { + ( + blur_options.size, + (blur_options.size as f32 * height as f32 / width as f32).ceil() as u32, + ) + } else { + ( + (blur_options.size as f32 * width as f32 / height as f32).ceil() as u32, + blur_options.size, + ) + }; + writeln!( + result, + "export default {{ src, width: {width}, height: {height}, blurDataURL: \ + `/_next/image?w={blur_width}&q={quality}&url=${{encodeURIComponent(src)}}`, \ + blurWidth: {blur_width}, blurHeight: {blur_height} }}", + width = StringifyJs(&info.width), + height = StringifyJs(&info.height), + quality = StringifyJs(&blur_options.quality), + blur_width = StringifyJs(&blur_width), + blur_height = StringifyJs(&blur_height), + )?; + } + BlurPlaceholderMode::DataUrl => { + let info = get_meta_data(self.image.ident(), content, Some(blur_options)).await?; + writeln!( + result, + "export default {{ src, width: {width}, height: {height}, blurDataURL: \ + {blur_data_url}, blurWidth: {blur_width}, blurHeight: {blur_height} }}", + width = StringifyJs(&info.width), + height = StringifyJs(&info.height), + blur_data_url = + StringifyJs(&info.blur_placeholder.as_ref().map(|p| p.data_url.as_str())), + blur_width = + StringifyJs(&info.blur_placeholder.as_ref().map_or(0, |p| p.width)), + blur_height = + StringifyJs(&info.blur_placeholder.as_ref().map_or(0, |p| p.height),), + )?; + } + BlurPlaceholderMode::None => { + let info = get_meta_data(self.image.ident(), content, None).await?; + writeln!( + result, + "export default {{ src, width: {width}, height: {height} }}", + width = StringifyJs(&info.width), + height = StringifyJs(&info.height), + )?; + } + }; Ok(AssetContent::File(FileContent::Content(result.build().into()).cell()).cell()) } } 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 f7b6ab2bb1285..1746f76de2ad7 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 @@ -27,9 +27,9 @@ use turbo_binding::{ }, }, }; -use turbo_tasks::trace::TraceRawVcs; +use turbo_tasks::{trace::TraceRawVcs, Value}; -use crate::next_image::StructuredImageModuleTypeVc; +use crate::next_image::{module::BlurPlaceholderMode, StructuredImageModuleTypeVc}; /// Returns a rule which applies the Next.js page export stripping transform. pub async fn get_next_pages_transforms_rule( @@ -98,7 +98,7 @@ pub fn get_next_image_rule() -> ModuleRule { ModuleRuleCondition::ResourcePathEndsWith(".svg".to_string()), ]), vec![ModuleRuleEffect::ModuleType(ModuleType::Custom( - StructuredImageModuleTypeVc::new().into(), + StructuredImageModuleTypeVc::new(Value::new(BlurPlaceholderMode::DataUrl)).into(), ))], ) } From 47aaa4fecee709fb11e87a78d71b198635df6239 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 2 May 2023 09:08:10 +0000 Subject: [PATCH 2/2] review --- packages/next-swc/crates/next-core/src/next_image/module.rs | 4 ++-- .../next-swc/crates/next-core/src/next_image/source_asset.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_image/module.rs b/packages/next-swc/crates/next-core/src/next_image/module.rs index 65fe5598b730b..a43d5f1da3998 100644 --- a/packages/next-swc/crates/next-core/src/next_image/module.rs +++ b/packages/next-swc/crates/next-core/src/next_image/module.rs @@ -25,14 +25,14 @@ pub enum BlurPlaceholderMode { /// Do not generate a blur placeholder at all. None, /// Generate a blur placeholder as data url and embed it directly into the - /// javascript code. This need to compute the blur placeholder eagerly and + /// JavaScript code. This needs to compute the blur placeholder eagerly and /// has a higher computation overhead. DataUrl, /// Avoid generating a blur placeholder eagerly and uses `/_next/image` /// instead to compute one on demand. This changes the UX slightly (blur /// placeholder is shown later than it should be) and should /// only be used for development. - NextImage, + NextImageUrl, } /// Module type that analyzes images and offers some meta information like diff --git a/packages/next-swc/crates/next-core/src/next_image/source_asset.rs b/packages/next-swc/crates/next-core/src/next_image/source_asset.rs index b59a7931b42d2..6f1f73d16fc2a 100644 --- a/packages/next-swc/crates/next-core/src/next_image/source_asset.rs +++ b/packages/next-swc/crates/next-core/src/next_image/source_asset.rs @@ -56,7 +56,7 @@ impl Asset for StructuredImageSourceAsset { writeln!(result, "import src from \"IMAGE\";",)?; let blur_options = blur_options(); match self.blur_placeholder_mode { - BlurPlaceholderMode::NextImage => { + BlurPlaceholderMode::NextImageUrl => { let info = get_meta_data(self.image.ident(), content, None).await?; let width = info.width; let height = info.height;