From a6152cda3c2bba8f36254f7d9048cd98c842e0fc Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Mon, 20 May 2024 14:51:14 -0700 Subject: [PATCH] Turbopack: Add `BeforeResolve` plugin type (#8165) This adds a new plugin type for replacing resolution _before_ it occurs. This allows plugin authors to intercept requests for modules in place of the typical resolution strategy. This is used to replace `ImportMapping` replacers for `@next/font/local` in an upcoming Next.js PR --- crates/turbopack-core/src/resolve/mod.rs | 62 +++++++++++++++---- crates/turbopack-core/src/resolve/options.rs | 6 +- crates/turbopack-core/src/resolve/plugin.rs | 48 ++++++++++++-- crates/turbopack-resolve/src/resolve.rs | 3 +- .../src/resolve_options_context.rs | 8 +-- crates/turbopack/src/unsupported_sass.rs | 11 ++-- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/crates/turbopack-core/src/resolve/mod.rs b/crates/turbopack-core/src/resolve/mod.rs index 5eaabc5108a7f..49f8584520bdf 100644 --- a/crates/turbopack-core/src/resolve/mod.rs +++ b/crates/turbopack-core/src/resolve/mod.rs @@ -25,6 +25,7 @@ use self::{ origin::{ResolveOrigin, ResolveOriginExt}, parse::Request, pattern::Pattern, + plugin::BeforeResolvePlugin, remap::{ExportsField, ImportsField}, }; use crate::{ @@ -38,7 +39,7 @@ use crate::{ reference_type::ReferenceType, resolve::{ pattern::{read_matches, PatternMatch}, - plugin::ResolvePlugin, + plugin::AfterResolvePlugin, }, source::{OptionSource, Source, Sources}, }; @@ -66,6 +67,7 @@ pub enum ModuleResolveResultItem { OutputAsset(Vc>), External(String, ExternalType), Ignore, + Error(Vc), Empty, Custom(u8), Unresolveable, @@ -404,6 +406,7 @@ pub enum ResolveResultItem { Source(Vc>), External(String, ExternalType), Ignore, + Error(Vc), Empty, Custom(u8), Unresolveable, @@ -488,6 +491,9 @@ impl ValueToString for ResolveResult { ResolveResultItem::Empty => { result.push_str("empty"); } + ResolveResultItem::Error(_) => { + result.push_str("error"); + } ResolveResultItem::Custom(_) => { result.push_str("custom"); } @@ -674,6 +680,7 @@ impl ResolveResult { } ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore, ResolveResultItem::Empty => ModuleResolveResultItem::Empty, + ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e), ResolveResultItem::Custom(u8) => { ModuleResolveResultItem::Custom(u8) } @@ -1326,17 +1333,23 @@ pub async fn resolve_inline( ) }; async { - let raw_result = resolve_internal(lookup_path, request, options) - .resolve() - .await?; - let result = handle_resolve_plugins( - lookup_path, - Value::new(reference_type), - request, - options, - raw_result, - ) - .await?; + let reference_type = Value::new(reference_type); + let before_plugins_result = + handle_before_resolve_plugins(lookup_path, reference_type.clone(), request, options) + .await?; + + let raw_result = match before_plugins_result { + Some(result) => result, + None => { + resolve_internal(lookup_path, request, options) + .resolve() + .await? + } + }; + + let result = + handle_after_resolve_plugins(lookup_path, reference_type, request, options, raw_result) + .await?; Ok(result) } .instrument(span) @@ -1386,7 +1399,29 @@ pub async fn url_resolve( .await } -async fn handle_resolve_plugins( +async fn handle_before_resolve_plugins( + lookup_path: Vc, + reference_type: Value, + request: Vc, + options: Vc, +) -> Result>> { + for plugin in &options.await?.before_resolve_plugins { + let condition = plugin.before_resolve_condition().resolve().await?; + if !*condition.matches(request).await? { + continue; + } + + if let Some(result) = *plugin + .before_resolve(lookup_path, reference_type.clone(), request) + .await? + { + return Ok(Some(result)); + } + } + Ok(None) +} + +async fn handle_after_resolve_plugins( lookup_path: Vc, reference_type: Value, request: Vc, @@ -2553,6 +2588,7 @@ pub async fn handle_resolve_error( .cell() .emit(); } + result } Err(err) => { diff --git a/crates/turbopack-core/src/resolve/options.rs b/crates/turbopack-core/src/resolve/options.rs index b1ca51ec4eb43..fdcd931ad335b 100644 --- a/crates/turbopack-core/src/resolve/options.rs +++ b/crates/turbopack-core/src/resolve/options.rs @@ -9,9 +9,10 @@ use turbo_tasks_fs::{glob::Glob, FileSystemPath}; use super::{ alias_map::{AliasMap, AliasTemplate}, + plugin::BeforeResolvePlugin, AliasPattern, ExternalType, ResolveResult, ResolveResultItem, }; -use crate::resolve::{parse::Request, plugin::ResolvePlugin}; +use crate::resolve::{parse::Request, plugin::AfterResolvePlugin}; #[turbo_tasks::value(shared)] #[derive(Hash, Debug)] @@ -433,7 +434,8 @@ pub struct ResolveOptions { /// An import map to use when a request is otherwise unresolveable. pub fallback_import_map: Option>, pub resolved_map: Option>, - pub plugins: Vec>>, + pub before_resolve_plugins: Vec>>, + pub plugins: Vec>>, pub placeholder_for_future_extensions: (), } diff --git a/crates/turbopack-core/src/resolve/plugin.rs b/crates/turbopack-core/src/resolve/plugin.rs index 3cbca4592ed30..67becbe71576b 100644 --- a/crates/turbopack-core/src/resolve/plugin.rs +++ b/crates/turbopack-core/src/resolve/plugin.rs @@ -9,16 +9,16 @@ use crate::{ /// A condition which determines if the hooks of a resolve plugin gets called. #[turbo_tasks::value] -pub struct ResolvePluginCondition { +pub struct AfterResolvePluginCondition { root: Vc, glob: Vc, } #[turbo_tasks::value_impl] -impl ResolvePluginCondition { +impl AfterResolvePluginCondition { #[turbo_tasks::function] pub fn new(root: Vc, glob: Vc) -> Vc { - ResolvePluginCondition { root, glob }.cell() + AfterResolvePluginCondition { root, glob }.cell() } #[turbo_tasks::function] @@ -39,10 +39,48 @@ impl ResolvePluginCondition { } } +/// A condition which determines if the hooks of a resolve plugin gets called. +#[turbo_tasks::value] +pub struct BeforeResolvePluginCondition { + glob: Vc, +} + +#[turbo_tasks::value_impl] +impl BeforeResolvePluginCondition { + #[turbo_tasks::function] + pub fn new(glob: Vc) -> Vc { + BeforeResolvePluginCondition { glob }.cell() + } + + #[turbo_tasks::function] + pub async fn matches(self: Vc, request: Vc) -> Result> { + Ok(Vc::cell(match request.await?.request() { + Some(request) => { + let this = self.await?; + let glob = this.glob.await?; + glob.execute(&request) + } + None => false, + })) + } +} + +#[turbo_tasks::value_trait] +pub trait BeforeResolvePlugin { + fn before_resolve_condition(self: Vc) -> Vc; + + fn before_resolve( + self: Vc, + lookup_path: Vc, + reference_type: Value, + request: Vc, + ) -> Vc; +} + #[turbo_tasks::value_trait] -pub trait ResolvePlugin { +pub trait AfterResolvePlugin { /// A condition which determines if the hooks gets called. - fn after_resolve_condition(self: Vc) -> Vc; + fn after_resolve_condition(self: Vc) -> Vc; /// This hook gets called when a full filepath has been resolved and the /// condition matches. If a value is returned it replaces the resolve diff --git a/crates/turbopack-resolve/src/resolve.rs b/crates/turbopack-resolve/src/resolve.rs index 89614b108f963..f4e1c49e8451e 100644 --- a/crates/turbopack-resolve/src/resolve.rs +++ b/crates/turbopack-resolve/src/resolve.rs @@ -122,7 +122,7 @@ async fn base_resolve_options( } let import_map = import_map.cell(); - let plugins = opt.plugins.clone(); + let plugins = opt.after_resolve_plugins.clone(); let conditions = { let mut conditions: ResolutionConditions = [ @@ -251,6 +251,7 @@ async fn base_resolve_options( import_map: Some(import_map), resolved_map: opt.resolved_map, plugins, + before_resolve_plugins: opt.before_resolve_plugins.clone(), ..Default::default() } .into()) diff --git a/crates/turbopack-resolve/src/resolve_options_context.rs b/crates/turbopack-resolve/src/resolve_options_context.rs index 3c44b789dd54a..6b38129b2bc25 100644 --- a/crates/turbopack-resolve/src/resolve_options_context.rs +++ b/crates/turbopack-resolve/src/resolve_options_context.rs @@ -6,7 +6,7 @@ use turbopack_core::{ environment::Environment, resolve::{ options::{ImportMap, ResolvedMap}, - plugin::ResolvePlugin, + plugin::{AfterResolvePlugin, BeforeResolvePlugin}, }, }; @@ -69,9 +69,9 @@ pub struct ResolveOptionsContext { /// context paths. The first matching is used. pub rules: Vec<(ContextCondition, Vc)>, #[serde(default)] - /// A list of plugins which get applied before (in the future) and after - /// resolving. - pub plugins: Vec>>, + /// Plugins which get applied before and after resolving. + pub after_resolve_plugins: Vec>>, + pub before_resolve_plugins: Vec>>, #[serde(default)] pub placeholder_for_future_extensions: (), } diff --git a/crates/turbopack/src/unsupported_sass.rs b/crates/turbopack/src/unsupported_sass.rs index 0f84d38e0973e..71d496cbaed34 100644 --- a/crates/turbopack/src/unsupported_sass.rs +++ b/crates/turbopack/src/unsupported_sass.rs @@ -8,7 +8,7 @@ use turbopack_core::{ reference_type::ReferenceType, resolve::{ parse::Request, - plugin::{ResolvePlugin, ResolvePluginCondition}, + plugin::{AfterResolvePlugin, AfterResolvePluginCondition}, ResolveResultOption, }, }; @@ -28,10 +28,13 @@ impl UnsupportedSassResolvePlugin { } #[turbo_tasks::value_impl] -impl ResolvePlugin for UnsupportedSassResolvePlugin { +impl AfterResolvePlugin for UnsupportedSassResolvePlugin { #[turbo_tasks::function] - fn after_resolve_condition(&self) -> Vc { - ResolvePluginCondition::new(self.root.root(), Glob::new("**/*.{sass,scss}".to_string())) + fn after_resolve_condition(&self) -> Vc { + AfterResolvePluginCondition::new( + self.root.root(), + Glob::new("**/*.{sass,scss}".to_string()), + ) } #[turbo_tasks::function]