turbopack_ecmascript_plugins/transform/
relay.rs

1use std::{path::PathBuf, sync::Arc};
2
3use anyhow::{Context, Result};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use swc_core::{common::FileName, ecma::ast::Program};
7use swc_relay::RelayLanguageConfig;
8use turbo_tasks::{NonLocalValue, OperationValue, trace::TraceRawVcs};
9use turbo_tasks_fs::FileSystemPath;
10use turbopack_ecmascript::{CustomTransformer, TransformContext};
11
12#[derive(
13    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
14)]
15#[serde(rename_all = "camelCase")]
16pub struct RelayConfig {
17    pub src: String,
18    pub artifact_directory: Option<String>,
19    pub language: Option<RelayLanguage>,
20}
21
22#[derive(
23    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
24)]
25#[serde(rename_all = "lowercase")]
26pub enum RelayLanguage {
27    TypeScript,
28    Flow,
29    JavaScript,
30}
31
32#[derive(Debug)]
33pub struct RelayTransformer {
34    config: Arc<swc_relay::Config>,
35    project_path: FileSystemPath,
36}
37
38impl RelayTransformer {
39    pub fn new(config: &RelayConfig, project_path: &FileSystemPath) -> Self {
40        let options = swc_relay::Config {
41            artifact_directory: config.artifact_directory.as_ref().map(PathBuf::from),
42            language: config.language.as_ref().map_or(
43                RelayLanguageConfig::TypeScript,
44                |v| match v {
45                    RelayLanguage::JavaScript => RelayLanguageConfig::JavaScript,
46                    RelayLanguage::TypeScript => RelayLanguageConfig::TypeScript,
47                    RelayLanguage::Flow => RelayLanguageConfig::Flow,
48                },
49            ),
50            ..Default::default()
51        };
52
53        Self {
54            config: options.into(),
55            project_path: project_path.clone(),
56        }
57    }
58}
59
60#[async_trait]
61impl CustomTransformer for RelayTransformer {
62    #[tracing::instrument(level = tracing::Level::TRACE, name = "relay", skip_all)]
63    async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
64        // If user supplied artifact_directory, it should be resolvable already.
65        // Otherwise, supply default relative path (./__generated__)
66        let path_to_proj = PathBuf::from(
67            ctx.file_path
68                .parent()
69                .await?
70                .get_relative_path_to(&self.project_path)
71                .context("Expected relative path to relay artifact")?,
72        );
73
74        program.mutate(swc_relay::relay(
75            self.config.clone(),
76            FileName::Real(PathBuf::from(ctx.file_name_str)),
77            path_to_proj,
78            // [TODO]: pages_dir comes through next-swc-loader
79            // https://github.com/vercel/next.js/blob/ea472e8058faea8ebdab2ef6d3aab257a1f0d11c/packages/next/src/build/webpack-config.ts#L792
80            None,
81            Some(ctx.unresolved_mark),
82        ));
83
84        Ok(())
85    }
86}