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:
parent
53bcdea01c
commit
688d8253da
7 changed files with 29 additions and 201 deletions
|
@ -118,6 +118,7 @@ module.exports = {
|
|||
src: './',
|
||||
artifactDirectory: './__generated__',
|
||||
language: 'typescript',
|
||||
eagerEsModules: false;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
18
packages/next-swc/Cargo.lock
generated
18
packages/next-swc/Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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")),
|
||||
)
|
||||
},
|
||||
|
|
|
@ -519,6 +519,7 @@ export interface NextConfig extends Record<string, any> {
|
|||
src: string
|
||||
artifactDirectory?: string
|
||||
language?: 'typescript' | 'javascript' | 'flow'
|
||||
eagerEsModules?: boolean
|
||||
}
|
||||
removeConsole?:
|
||||
| boolean
|
||||
|
|
Loading…
Reference in a new issue