refactor(relay): Use transform from swc/plugins repository (#47441)

Previously `next-swc` had relay transform inline, but it makes
maintenance harder. So this PR patches next-swc to use relay plugin from
`swc-project/plugins` repository.

Closes WEB-782
Fixes #47239

fix NEXT-883 ([link](https://linear.app/vercel/issue/NEXT-883))
This commit is contained in:
Donny/강동윤 2023-03-27 16:34:09 +09:00 committed by GitHub
parent 53bcdea01c
commit 688d8253da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 29 additions and 201 deletions

View file

@ -118,6 +118,7 @@ module.exports = {
src: './',
artifactDirectory: './__generated__',
language: 'typescript',
eagerEsModules: false;
},
},
}

View file

@ -3205,6 +3205,7 @@ dependencies = [
"serde",
"serde_json",
"sha1 0.10.5",
"swc_relay",
"tracing",
"walkdir",
]
@ -5971,6 +5972,21 @@ dependencies = [
"wasmer-wasi",
]
[[package]]
name = "swc_relay"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1023f06fac2cfb3531b96edfa9e17cb66b1bbd7e91b13c981e72e3486ab310d"
dependencies = [
"once_cell",
"regex",
"serde",
"serde_json",
"swc_common",
"swc_core",
"tracing",
]
[[package]]
name = "swc_timer"
version = "0.17.42"
@ -7112,7 +7128,7 @@ version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
"rand",
"static_assertions",
]

View file

@ -30,6 +30,7 @@ next-binding = { path = "../next-binding", features = [
"__swc_transform_styled_components",
"__swc_transform_modularize_imports",
] }
swc_relay = "0.1.0"
[dev-dependencies]
next-binding = { path = "../next-binding", features = [

View file

@ -37,6 +37,7 @@ use fxhash::FxHashSet;
use next_transform_font::next_font_loaders;
use serde::Deserialize;
use std::cell::RefCell;
use std::env::current_dir;
use std::rc::Rc;
use std::{path::PathBuf, sync::Arc};
@ -56,8 +57,6 @@ pub mod next_ssg;
pub mod page_config;
pub mod react_remove_properties;
pub mod react_server_components;
#[cfg(not(target_arch = "wasm32"))]
pub mod relay;
pub mod remove_console;
pub mod server_actions;
pub mod shake_exports;
@ -107,7 +106,7 @@ pub struct TransformOptions {
#[serde(default)]
#[cfg(not(target_arch = "wasm32"))]
pub relay: Option<relay::Config>,
pub relay: Option<swc_relay::Config>,
#[allow(unused)]
#[serde(default)]
@ -147,9 +146,10 @@ where
#[cfg(not(target_arch = "wasm32"))]
let relay_plugin = {
if let Some(config) = &opts.relay {
Either::Left(relay::relay(
Either::Left(swc_relay::relay(
config,
file.name.clone(),
current_dir().unwrap(),
opts.pages_dir.clone(),
))
} else {

View file

@ -1,191 +0,0 @@
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use std::path::{Path, PathBuf};
use next_binding::swc::core::{
common::{errors::HANDLER, FileName},
ecma::ast::*,
ecma::atoms::JsWord,
ecma::utils::{quote_ident, ExprFactory},
ecma::visit::{Fold, FoldWith},
};
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RelayLanguageConfig {
TypeScript,
JavaScript,
Flow,
}
impl Default for RelayLanguageConfig {
fn default() -> Self {
Self::Flow
}
}
struct Relay<'a> {
root_dir: PathBuf,
pages_dir: Option<PathBuf>,
file_name: FileName,
config: &'a Config,
}
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Config {
pub src: PathBuf,
pub artifact_directory: Option<PathBuf>,
#[serde(default)]
pub language: RelayLanguageConfig,
}
fn pull_first_operation_name_from_tpl(tpl: &TaggedTpl) -> Option<String> {
tpl.tpl.quasis.iter().find_map(|quasis| {
static OPERATION_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(fragment|mutation|query|subscription) (\w+)").unwrap());
let capture_group = OPERATION_REGEX.captures_iter(&quasis.raw).next();
capture_group.map(|capture_group| capture_group[2].to_string())
})
}
fn build_require_expr_from_path(path: &str) -> Expr {
Expr::Call(CallExpr {
span: Default::default(),
callee: quote_ident!("require").as_callee(),
args: vec![Lit::Str(Str {
span: Default::default(),
value: JsWord::from(path),
raw: None,
})
.as_arg()],
type_args: None,
})
}
impl<'a> Fold for Relay<'a> {
fn fold_expr(&mut self, expr: Expr) -> Expr {
let expr = expr.fold_children_with(self);
match &expr {
Expr::TaggedTpl(tpl) => {
if let Some(built_expr) = self.build_call_expr_from_tpl(tpl) {
built_expr
} else {
expr
}
}
_ => expr,
}
}
}
#[derive(Debug)]
enum BuildRequirePathError {
FileNameNotReal,
ArtifactDirectoryExpected { file_name: String },
}
impl<'a> Relay<'a> {
fn path_for_artifact(
&self,
real_file_name: &Path,
definition_name: &str,
) -> Result<PathBuf, BuildRequirePathError> {
let filename = match &self.config.language {
RelayLanguageConfig::Flow => format!("{}.graphql.js", definition_name),
RelayLanguageConfig::TypeScript => {
format!("{}.graphql.ts", definition_name)
}
RelayLanguageConfig::JavaScript => {
format!("{}.graphql.js", definition_name)
}
};
if let Some(artifact_directory) = &self.config.artifact_directory {
Ok(self.root_dir.join(artifact_directory).join(filename))
} else if self
.pages_dir
.as_ref()
.map_or(false, |pages_dir| real_file_name.starts_with(pages_dir))
{
Err(BuildRequirePathError::ArtifactDirectoryExpected {
file_name: real_file_name.display().to_string(),
})
} else {
Ok(real_file_name
.parent()
.unwrap()
.join("__generated__")
.join(filename))
}
}
fn build_require_path(
&mut self,
operation_name: &str,
) -> Result<PathBuf, BuildRequirePathError> {
match &self.file_name {
FileName::Real(real_file_name) => {
self.path_for_artifact(real_file_name, operation_name)
}
_ => Err(BuildRequirePathError::FileNameNotReal),
}
}
fn build_call_expr_from_tpl(&mut self, tpl: &TaggedTpl) -> Option<Expr> {
if let Expr::Ident(ident) = &*tpl.tag {
if &*ident.sym != "graphql" {
return None;
}
}
let operation_name = pull_first_operation_name_from_tpl(tpl);
match operation_name {
None => None,
Some(operation_name) => match self.build_require_path(operation_name.as_str()) {
Ok(final_path) => Some(build_require_expr_from_path(final_path.to_str().unwrap())),
Err(err) => {
let base_error = "Could not transform GraphQL template to a Relay import.";
let error_message = match err {
BuildRequirePathError::FileNameNotReal => "Source file was not a real \
file. This is likely a bug and \
should be reported to Next.js"
.to_string(),
BuildRequirePathError::ArtifactDirectoryExpected { file_name } => {
format!(
"The generated file for `{}` will be created in `pages` \
directory, which will break production builds. Try moving the \
file outside of `pages` or set the `artifactDirectory` in the \
Relay config file.",
file_name
)
}
};
HANDLER.with(|handler| {
handler.span_err(
tpl.span,
format!("{} {}", base_error, error_message).as_str(),
);
});
None
}
},
}
}
}
pub fn relay(config: &Config, file_name: FileName, pages_dir: Option<PathBuf>) -> impl Fold + '_ {
Relay {
root_dir: std::env::current_dir().unwrap(),
file_name,
pages_dir,
config,
}
}

View file

@ -15,13 +15,13 @@ use next_swc::{
page_config::page_config_test,
react_remove_properties::remove_properties,
react_server_components::server_components,
relay::{relay, Config as RelayConfig, RelayLanguageConfig},
remove_console::remove_console,
server_actions::{self, server_actions},
shake_exports::{shake_exports, Config as ShakeExportsConfig},
};
use next_transform_font::{next_font_loaders, Config as FontLoaderConfig};
use std::path::PathBuf;
use std::{env::current_dir, path::PathBuf};
use swc_relay::{relay, RelayLanguageConfig};
fn syntax() -> Syntax {
Syntax::Es(EsConfig {
@ -112,9 +112,8 @@ fn next_ssg_fixture(input: PathBuf) {
pragma_frag: Some("__jsxFrag".into()),
throw_if_namespace: false.into(),
development: false.into(),
use_builtins: true.into(),
use_spread: true.into(),
refresh: Default::default(),
..Default::default()
},
top_level_mark,
);
@ -141,7 +140,7 @@ fn page_config_fixture(input: PathBuf) {
#[fixture("tests/fixture/relay/**/input.ts*")]
fn relay_no_artifact_dir_fixture(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
let config = RelayConfig {
let config = swc_relay::Config {
language: RelayLanguageConfig::TypeScript,
artifact_directory: Some(PathBuf::from("__generated__")),
..Default::default()
@ -152,6 +151,7 @@ fn relay_no_artifact_dir_fixture(input: PathBuf) {
relay(
&config,
FileName::Real(PathBuf::from("input.tsx")),
current_dir().unwrap(),
Some(PathBuf::from("src/pages")),
)
},

View file

@ -519,6 +519,7 @@ export interface NextConfig extends Record<string, any> {
src: string
artifactDirectory?: string
language?: 'typescript' | 'javascript' | 'flow'
eagerEsModules?: boolean
}
removeConsole?:
| boolean