Repository: parcel-bundler/parcel
File: crates/parcel_core/src/plugin/transformer_plugin.rs (Rust)

Code before:

pub struct AST {}

pub struct GenerateOutput {
  pub content: File,
  pub map: Option<SourceMap>,
}

pub struct ResolveOptions {
  /// A list of custom conditions to use when resolving package.json "exports" and "imports"
  pub package_conditions: Vec<String>,

  /// How the specifier should be interpreted
  pub specifier_type: SpecifierType,
}

/// A function that enables transformers to resolve a dependency specifier
pub type Resolve = dyn Fn(PathBuf, String, ResolveOptions) -> Result<PathBuf, anyhow::Error>;

/// Compile a single asset, discover dependencies, or convert the asset to a different format
///
/// Many transformers are wrappers around other tools such as compilers and preprocessors, and are
/// designed to integrate with Parcel.
///
pub trait TransformerPlugin: Debug + Send + Sync {
  /// Whether an AST from a previous transformer can be reused to prevent double-parsing
  ///
  /// This function should inspect the type and version of the AST to determine if it can be
  /// reused. When the AST is reused, parsing is skipped. Otherwise the previous transformer
  /// generate function is called, and the next transformer will parse that result.
  ///
  fn can_reuse_ast(&self, ast: AST) -> bool;

  /// Parse the asset code into an AST
  ///
  /// This function is called when an AST is not available and can_reuse_ast returns false.
  ///
  fn parse(
    &mut self,
    config: &PluginConfig,
    asset: &Asset,
    resolve: &Resolve,
  ) -> Result<AST, anyhow::Error>;

  /// Transform the asset and/or add new assets
  fn transform(
    &mut self,
    config: &PluginConfig,
    asset: &mut Asset,
    resolve: &Resolve,
  ) -> Result<Vec<Asset>, anyhow::Error>;

Review comment (suggestion):
I like the idea of separating the actual Asset from the transform inputs as some stuff isn't needed by the plugins. But we're still using the main Asset type here. Maybe we should have 3 asset types: 
- Pre all transforms
- Post a single transform
- A finalised Asset

Code after:
use crate::types::{Dependency, SourceMap};

pub struct GenerateOutput {
  pub content: File,
  pub map: Option<SourceMap>,
}

pub struct ResolveOptions {
  /// A list of custom conditions to use when resolving package.json "exports" and "imports"
  pub package_conditions: Vec<String>,

  /// How the specifier should be interpreted
  pub specifier_type: SpecifierType,
}

/// A function that enables transformers to resolve a dependency specifier
pub type Resolve = dyn Fn(PathBuf, String, ResolveOptions) -> Result<PathBuf, anyhow::Error>;

/// Transformers may run against:
///
/// * File-paths that have just been discovered
/// * Outputs of previous transformation steps, which are in-place modified
/// * These two scenarios are distinguished
pub enum TransformationInput {
  FilePath(PathBuf),
  Asset(Asset),
}

impl TransformationInput {
  pub fn file_path(&self) -> &Path {
    match self {
      TransformationInput::FilePath(path) => path,
      TransformationInput::Asset(asset) => asset.file_path(),
    }
  }

  pub fn read_source_code(&self, fs: FileSystemRef) -> anyhow::Result<Rc<SourceCode>> {
    match self {
      TransformationInput::FilePath(file_path) => {
        let code = fs.read_to_string(&file_path)?;
        let code = SourceCode::from(code);
        Ok(Rc::new(code))
      }
      TransformationInput::Asset(asset) => Ok(asset.source_code.clone()),
    }
  }
}

/// Context parameters for the transformer, other than the input.
pub struct RunTransformContext {
  file_system: FileSystemRef,