From 4a6a2ca2580a9e7dc23a8380a1cf2d906d30a84e Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:05:33 -0700 Subject: [PATCH] feat(turbopack): attach type metadata for static metadata item (#55340) ### What? Trying to mimic https://github.com/vercel/next.js/blob/32e066ff6cf593244daf5a9f7c55009c94a4e29e/packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts#L160C7-L160C12, when there's a static metadata turbopack does not includes `type` properties. This partially fulfills existing metadata test cases for checking those values. Closes WEB-1551 --- .../crates/next-core/src/loader_tree.rs | 9 ++++- .../next-core/src/next_app/metadata/mod.rs | 34 +++++++++++++++++++ .../next-core/src/next_app/metadata/route.rs | 33 +----------------- .../app/implicit-metadata/input/app/test.tsx | 1 + .../loaders/next-metadata-image-loader.ts | 2 ++ 5 files changed, 46 insertions(+), 33 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/loader_tree.rs b/packages/next-swc/crates/next-core/src/loader_tree.rs index fc10077995bcf..2aba5241c5d2d 100644 --- a/packages/next-swc/crates/next-core/src/loader_tree.rs +++ b/packages/next-swc/crates/next-core/src/loader_tree.rs @@ -23,7 +23,10 @@ use crate::{ MetadataWithAltItem, }, mode::NextMode, - next_app::{metadata::image::dynamic_image_metadata_source, AppPage}, + next_app::{ + metadata::{get_content_type, image::dynamic_image_metadata_source}, + AppPage, + }, next_image::module::{BlurPlaceholderMode, StructuredImageModuleType}, }; @@ -291,6 +294,7 @@ impl LoaderTreeBuilder { let helper_import = "import { fillMetadataSegment } from \ \"next/dist/lib/metadata/get-metadata-route\"" .to_string(); + if !self.imports.contains(&helper_import) { self.imports.push(helper_import); } @@ -329,6 +333,9 @@ impl LoaderTreeBuilder { )?; } + let content_type = get_content_type(path).await?; + writeln!(self.loader_tree_code, "{s} type: `{content_type}`,")?; + if let Some(alt_path) = alt_path { let identifier = magic_identifier::mangle(&format!("{name} alt text #{i}")); let inner_module_id = format!("METADATA_ALT_{i}"); diff --git a/packages/next-swc/crates/next-core/src/next_app/metadata/mod.rs b/packages/next-swc/crates/next-core/src/next_app/metadata/mod.rs index e0c86c172f543..13f3b6a5d84be 100644 --- a/packages/next-swc/crates/next-core/src/next_app/metadata/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_app/metadata/mod.rs @@ -2,6 +2,8 @@ use std::{collections::HashMap, ops::Deref}; use anyhow::Result; use once_cell::sync::Lazy; +use turbo_tasks::Vc; +use turbo_tasks_fs::FileSystemPath; use crate::next_app::{AppPage, PageSegment, PageType}; @@ -79,6 +81,38 @@ fn match_metadata_file<'a>( }) } +pub(crate) async fn get_content_type(path: Vc) -> Result { + let stem = &*path.file_stem().await?; + let ext = &*path.extension().await?; + + let name = stem.as_deref().unwrap_or_default(); + let mut ext = ext.as_str(); + if ext == "jpg" { + ext = "jpeg" + } + + if name == "favicon" && ext == "ico" { + return Ok("image/x-icon".to_string()); + } + if name == "sitemap" { + return Ok("application/xml".to_string()); + } + if name == "robots" { + return Ok("text/plain".to_string()); + } + if name == "manifest" { + return Ok("application/manifest+json".to_string()); + } + + if ext == "png" || ext == "jpeg" || ext == "ico" || ext == "svg" { + return Ok(mime_guess::from_ext(ext) + .first_or_octet_stream() + .to_string()); + } + + Ok("text/plain".to_string()) +} + pub fn match_local_metadata_file<'a>( basename: &'a str, page_extensions: &[String], diff --git a/packages/next-swc/crates/next-core/src/next_app/metadata/route.rs b/packages/next-swc/crates/next-core/src/next_app/metadata/route.rs index b3782ad60cbc2..b3b952e2e7580 100644 --- a/packages/next-swc/crates/next-core/src/next_app/metadata/route.rs +++ b/packages/next-swc/crates/next-core/src/next_app/metadata/route.rs @@ -15,6 +15,7 @@ use turbopack_binding::{ }, }; +use super::get_content_type; use crate::{ app_structure::MetadataItem, mode::NextMode, @@ -63,38 +64,6 @@ pub fn get_app_metadata_route_entry( ) } -async fn get_content_type(path: Vc) -> Result { - let stem = &*path.file_stem().await?; - let ext = &*path.extension().await?; - - let name = stem.as_deref().unwrap_or_default(); - let mut ext = ext.as_str(); - if ext == "jpg" { - ext = "jpeg" - } - - if name == "favicon" && ext == "ico" { - return Ok("image/x-icon".to_string()); - } - if name == "sitemap" { - return Ok("application/xml".to_string()); - } - if name == "robots" { - return Ok("text/plain".to_string()); - } - if name == "manifest" { - return Ok("application/manifest+json".to_string()); - } - - if ext == "png" || ext == "jpeg" || ext == "ico" || ext == "svg" { - return Ok(mime_guess::from_ext(ext) - .first_or_octet_stream() - .to_string()); - } - - Ok("text/plain".to_string()) -} - const CACHE_HEADER_NONE: &str = "no-cache, no-store"; const CACHE_HEADER_LONG_CACHE: &str = "public, immutable, no-transform, max-age=31536000"; const CACHE_HEADER_REVALIDATE: &str = "public, max-age=0, must-revalidate"; diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/implicit-metadata/input/app/test.tsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/implicit-metadata/input/app/test.tsx index 1b84fceedc0b9..fbe8614d765ce 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/implicit-metadata/input/app/test.tsx +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/implicit-metadata/input/app/test.tsx @@ -49,6 +49,7 @@ export default function Test() { 'og:image': expect.stringMatching(/^.+\/opengraph-image\.png\?.+$/), 'og:image:width': '114', 'og:image:height': '114', + 'og:image:type': 'image/png', 'og:image:alt': 'This is an alt text.', }) }) diff --git a/packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts b/packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts index 889ec14616d32..214b4abf26d94 100644 --- a/packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts @@ -23,6 +23,8 @@ interface Options { basePath: string } +// [NOTE] For turbopack +// refer loader_tree's write_static|dynamic_metadata for corresponding features async function nextMetadataImageLoader(this: any, content: Buffer) { const options: Options = this.getOptions() const { type, segment, pageExtensions, basePath } = options