diff --git a/packages/next-swc/crates/next-core/src/lib.rs b/packages/next-swc/crates/next-core/src/lib.rs index 206a8eeb0dd6db..279d3689cfdcfa 100644 --- a/packages/next-swc/crates/next-core/src/lib.rs +++ b/packages/next-swc/crates/next-core/src/lib.rs @@ -38,6 +38,7 @@ mod transform_options; pub mod url_node; mod util; mod web_entry_source; +mod next_telemetry; pub use app_source::create_app_source; pub use page_loader::create_page_loader_entry_asset; 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 2edfe961f39685..e18804f2dc525e 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 @@ -53,7 +53,7 @@ use crate::{ get_next_client_resolved_map, mdx_import_source_file, }, next_shared::{ - resolve::UnsupportedModulesResolvePluginVc, + resolve::{ModuleFeatureReportResolvePluginVc, UnsupportedModulesResolvePluginVc}, transforms::{ emotion::get_emotion_transform_plugin, get_relay_transform_plugin, styled_components::get_styled_components_transform_plugin, @@ -150,7 +150,14 @@ pub async fn get_client_resolve_options_context( resolved_map: Some(next_client_resolved_map), browser: true, module: true, - plugins: vec![UnsupportedModulesResolvePluginVc::new(project_path).into()], + plugins: vec![ + ModuleFeatureReportResolvePluginVc::new( + project_path, + StringVc::cell("next_client".to_string()), + ) + .into(), + UnsupportedModulesResolvePluginVc::new(project_path).into(), + ], ..Default::default() }; Ok(ResolveOptionsContext { 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 01ca5661799182..c77c2b58fc691c 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 @@ -1,5 +1,5 @@ use anyhow::Result; -use turbo_tasks::Value; +use turbo_tasks::{primitives::StringVc, Value}; use turbopack_binding::{ turbo::tasks_fs::FileSystemPathVc, turbopack::{ @@ -20,9 +20,11 @@ use turbopack_binding::{ }; use crate::{ - next_config::NextConfigVc, next_import_map::get_next_edge_import_map, + next_config::NextConfigVc, + next_import_map::get_next_edge_import_map, next_server::context::ServerContextType, - next_shared::resolve::UnsupportedModulesResolvePluginVc, util::foreign_code_context_condition, + next_shared::resolve::{ModuleFeatureReportResolvePluginVc, UnsupportedModulesResolvePluginVc}, + util::foreign_code_context_condition, }; fn defines() -> CompileTimeDefines { @@ -93,7 +95,14 @@ pub async fn get_edge_resolve_options_context( import_map: Some(next_edge_import_map), module: true, browser: true, - plugins: vec![UnsupportedModulesResolvePluginVc::new(project_path).into()], + plugins: vec![ + ModuleFeatureReportResolvePluginVc::new( + project_path, + StringVc::cell("next_edge".to_string()), + ) + .into(), + UnsupportedModulesResolvePluginVc::new(project_path).into(), + ], ..Default::default() }; 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 93ac6ae40bc556..f990a528dd4d55 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 @@ -43,7 +43,7 @@ use crate::{ next_import_map::{get_next_server_import_map, mdx_import_source_file}, next_server::resolve::ExternalPredicate, next_shared::{ - resolve::UnsupportedModulesResolvePluginVc, + resolve::{ModuleFeatureReportResolvePluginVc, UnsupportedModulesResolvePluginVc}, transforms::{ emotion::get_emotion_transform_plugin, get_relay_transform_plugin, styled_components::get_styled_components_transform_plugin, @@ -81,6 +81,10 @@ pub async fn get_server_resolve_options_context( get_next_server_import_map(project_path, ty, next_config, execution_context); let foreign_code_context_condition = foreign_code_context_condition(next_config).await?; let root_dir = project_path.root().resolve().await?; + let module_feature_report_resolve_plugin = ModuleFeatureReportResolvePluginVc::new( + project_path, + StringVc::cell("next_server".to_string()), + ); let unsupported_modules_resolve_plugin = UnsupportedModulesResolvePluginVc::new(project_path); let server_component_externals_plugin = ExternalCjsModulesResolvePluginVc::new( project_path, @@ -103,6 +107,7 @@ pub async fn get_server_resolve_options_context( import_map: Some(next_server_import_map), plugins: vec![ external_cjs_modules_plugin.into(), + module_feature_report_resolve_plugin.into(), unsupported_modules_resolve_plugin.into(), ], ..Default::default() @@ -131,6 +136,7 @@ pub async fn get_server_resolve_options_context( import_map: Some(next_server_import_map), plugins: vec![ server_component_externals_plugin.into(), + module_feature_report_resolve_plugin.into(), unsupported_modules_resolve_plugin.into(), ], ..Default::default() @@ -160,6 +166,7 @@ pub async fn get_server_resolve_options_context( import_map: Some(next_server_import_map), plugins: vec![ server_component_externals_plugin.into(), + module_feature_report_resolve_plugin.into(), unsupported_modules_resolve_plugin.into(), ], ..Default::default() @@ -182,6 +189,7 @@ pub async fn get_server_resolve_options_context( import_map: Some(next_server_import_map), plugins: vec![ server_component_externals_plugin.into(), + module_feature_report_resolve_plugin.into(), unsupported_modules_resolve_plugin.into(), ], ..Default::default() @@ -202,7 +210,10 @@ pub async fn get_server_resolve_options_context( enable_node_externals: true, module: true, custom_conditions: vec![mode.node_env().to_string()], - plugins: vec![unsupported_modules_resolve_plugin.into()], + plugins: vec![ + module_feature_report_resolve_plugin.into(), + unsupported_modules_resolve_plugin.into(), + ], ..Default::default() }; ResolveOptionsContext { diff --git a/packages/next-swc/crates/next-core/src/next_shared/resolve.rs b/packages/next-swc/crates/next-core/src/next_shared/resolve.rs index 4afb31a1f0458a..ca788ad2d518af 100644 --- a/packages/next-swc/crates/next-core/src/next_shared/resolve.rs +++ b/packages/next-swc/crates/next-core/src/next_shared/resolve.rs @@ -1,7 +1,8 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use anyhow::Result; use lazy_static::lazy_static; +use turbo_tasks::primitives::StringVc; use turbo_tasks_fs::glob::GlobVc; use turbopack_binding::{ turbo::tasks_fs::FileSystemPathVc, @@ -16,9 +17,28 @@ use turbopack_binding::{ }, }; +use crate::next_telemetry::ModuleFeatureTelemetry; + lazy_static! { static ref UNSUPPORTED_PACKAGES: HashSet<&'static str> = ["@vercel/og"].into(); static ref UNSUPPORTED_PACKAGE_PATHS: HashSet<(&'static str, &'static str)> = [].into(); + // Set of the features we want to track, following existing references in webpack/plugins/telemetry-plugin. + static ref FEATURE_MODULES: HashMap<&'static str, Vec<&'static str>> = HashMap::from([ + ( + "next", + vec![ + "/image", + "future/image", + "legacy/image", + "/script", + "/dynamic", + "/font/google", + "/font/local" + ] + ), + ("@next", vec!["/font/google", "/font/local"]) + ]) + .into(); } #[turbo_tasks::value] @@ -83,3 +103,62 @@ impl ResolvePlugin for UnsupportedModulesResolvePlugin { Ok(ResolveResultOptionVc::none()) } } + +/// A resolver plugin trackes the usage of certain import paths, emit a +/// telemetry event if there is a match. +#[turbo_tasks::value] +pub(crate) struct ModuleFeatureReportResolvePlugin { + root: FileSystemPathVc, + event_name: StringVc, +} + +#[turbo_tasks::value_impl] +impl ModuleFeatureReportResolvePluginVc { + #[turbo_tasks::function] + pub fn new(root: FileSystemPathVc, event_name: StringVc) -> Self { + ModuleFeatureReportResolvePlugin { root, event_name }.cell() + } +} + +#[turbo_tasks::value_impl] +impl ResolvePlugin for ModuleFeatureReportResolvePlugin { + #[turbo_tasks::function] + fn after_resolve_condition(&self) -> ResolvePluginConditionVc { + ResolvePluginConditionVc::new(self.root.root(), GlobVc::new("**")) + } + + #[turbo_tasks::function] + async fn after_resolve( + &self, + _fs_path: FileSystemPathVc, + _context: FileSystemPathVc, + request: RequestVc, + ) -> Result { + if let Request::Module { + module, + path, + query: _, + } = &*request.await? + { + let feature_module = FEATURE_MODULES.get(module.as_str()); + if let Some(feature_module) = feature_module { + let sub_path = feature_module + .iter() + .find(|sub_path| path.is_match(sub_path)); + + if let Some(sub_path) = sub_path { + ModuleFeatureTelemetry { + event_name: self.event_name.await?.to_string(), + feature_name: format!("{}{}", module, sub_path), + invocation_count: 1, + } + .cell() + .as_next_telemetry() + .emit(); + } + } + } + + Ok(ResolveResultOptionVc::none()) + } +} diff --git a/packages/next-swc/crates/next-core/src/next_telemetry.rs b/packages/next-swc/crates/next-core/src/next_telemetry.rs new file mode 100644 index 00000000000000..fccc4bc58d56a8 --- /dev/null +++ b/packages/next-swc/crates/next-core/src/next_telemetry.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use turbo_tasks::{emit, primitives::StringVc, ValueToString, ValueToStringVc}; + +#[turbo_tasks::value_trait] +pub trait NextTelemetry { + fn event_name(&self) -> StringVc; +} + +impl NextTelemetryVc { + pub fn emit(self) { + emit(self); + } +} + +/// A struct represent telemetry event for feature usage, +/// referred as `importing` a certain module. +#[turbo_tasks::value(shared)] +pub struct ModuleFeatureTelemetry { + pub event_name: String, + pub feature_name: String, + pub invocation_count: usize, +} + +impl ModuleFeatureTelemetryVc { + pub fn new(name: String, feature: String, invocation_count: usize) -> Self { + Self::cell(ModuleFeatureTelemetry { + event_name: name, + feature_name: feature, + invocation_count, + }) + } +} + +#[turbo_tasks::value_impl] +impl ValueToString for ModuleFeatureTelemetry { + #[turbo_tasks::function] + fn to_string(&self) -> StringVc { + StringVc::cell(format!("{},{}", self.event_name, self.feature_name)) + } +} + +#[turbo_tasks::value_impl] +impl NextTelemetry for ModuleFeatureTelemetry { + #[turbo_tasks::function] + async fn event_name(&self) -> Result { + Ok(StringVc::cell(self.event_name.clone())) + } +}