From 3e60c232a86d2c3b40a45a427f1d32a85b89daf7 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 1 Feb 2022 12:18:55 -0600 Subject: [PATCH] Relay Support in Rust Compiler (#33702) Reverts vercel/next.js#33699 This re-opens the support for relay in swc, although we need to narrow in the causes for the build failures in https://github.com/vercel/next.js/runs/4950448889?check_suite_focus=true Co-authored-by: Andrey Lunyov <102968+alunyov@users.noreply.github.com> --- docs/advanced-features/compiler.md | 20 +++ package.json | 3 + packages/next-swc/Cargo.lock | 1 + packages/next-swc/crates/core/Cargo.toml | 3 +- packages/next-swc/crates/core/src/lib.rs | 21 ++- packages/next-swc/crates/core/src/relay.rs | 170 ++++++++++++++++++ .../next-swc/crates/core/tests/fixture.rs | 17 ++ .../crates/core/tests/fixture/relay/input.tsx | 42 +++++ .../crates/core/tests/fixture/relay/output.js | 10 ++ packages/next-swc/crates/core/tests/full.rs | 1 + packages/next/build/swc/options.js | 1 + packages/next/build/webpack-config.ts | 1 + packages/next/server/config-shared.ts | 5 + .../__generated__/pagesAQuery.graphql.ts | 63 +++++++ .../project-a/next.config.js | 12 ++ .../project-a/pages/api/query.ts | 12 ++ .../project-a/pages/index.tsx | 63 +++++++ .../project-a/tsconfig.json | 21 +++ .../__generated__/pagesBQuery.graphql.ts | 63 +++++++ .../project-b/next.config.js | 12 ++ .../project-b/pages/api/query.ts | 12 ++ .../project-b/pages/index.tsx | 63 +++++++ .../project-b/tsconfig.json | 21 +++ .../relay.config.js | 19 ++ .../schema.graphql | 3 + .../test/index.test.js | 89 +++++++++ .../__generated__/pagesQuery.graphql.ts | 63 +++++++ .../next.config.js | 7 + .../pages/api/query.ts | 12 ++ .../pages/index.tsx | 63 +++++++ .../relay.config.js | 6 + .../schema.graphql | 3 + .../test/index.test.js | 57 ++++++ .../tsconfig.json | 21 +++ yarn.lock | 74 +++++++- 35 files changed, 1049 insertions(+), 5 deletions(-) create mode 100644 packages/next-swc/crates/core/src/relay.rs create mode 100644 packages/next-swc/crates/core/tests/fixture/relay/input.tsx create mode 100644 packages/next-swc/crates/core/tests/fixture/relay/output.js create mode 100644 test/integration/relay-graphql-swc-multi-project/project-a/__generated__/pagesAQuery.graphql.ts create mode 100644 test/integration/relay-graphql-swc-multi-project/project-a/next.config.js create mode 100644 test/integration/relay-graphql-swc-multi-project/project-a/pages/api/query.ts create mode 100644 test/integration/relay-graphql-swc-multi-project/project-a/pages/index.tsx create mode 100644 test/integration/relay-graphql-swc-multi-project/project-a/tsconfig.json create mode 100644 test/integration/relay-graphql-swc-multi-project/project-b/__generated__/pagesBQuery.graphql.ts create mode 100644 test/integration/relay-graphql-swc-multi-project/project-b/next.config.js create mode 100644 test/integration/relay-graphql-swc-multi-project/project-b/pages/api/query.ts create mode 100644 test/integration/relay-graphql-swc-multi-project/project-b/pages/index.tsx create mode 100644 test/integration/relay-graphql-swc-multi-project/project-b/tsconfig.json create mode 100644 test/integration/relay-graphql-swc-multi-project/relay.config.js create mode 100644 test/integration/relay-graphql-swc-multi-project/schema.graphql create mode 100644 test/integration/relay-graphql-swc-multi-project/test/index.test.js create mode 100644 test/integration/relay-graphql-swc-single-project/__generated__/pagesQuery.graphql.ts create mode 100644 test/integration/relay-graphql-swc-single-project/next.config.js create mode 100644 test/integration/relay-graphql-swc-single-project/pages/api/query.ts create mode 100644 test/integration/relay-graphql-swc-single-project/pages/index.tsx create mode 100644 test/integration/relay-graphql-swc-single-project/relay.config.js create mode 100644 test/integration/relay-graphql-swc-single-project/schema.graphql create mode 100644 test/integration/relay-graphql-swc-single-project/test/index.test.js create mode 100644 test/integration/relay-graphql-swc-single-project/tsconfig.json diff --git a/docs/advanced-features/compiler.md b/docs/advanced-features/compiler.md index ac07f30fe24c..7e192389688e 100644 --- a/docs/advanced-features/compiler.md +++ b/docs/advanced-features/compiler.md @@ -94,6 +94,26 @@ const customJestConfig = { module.exports = createJestConfig(customJestConfig) ``` +### Relay + +To enable [Relay](https://relay.dev/) support: + +```js +// next.config.js +module.exports = { + experimental: { + relay: { + // This should match relay.config.js + src: './', + artifactDirectory: './__generated__' + language: 'typescript', + }, + }, +} +``` + +NOTE: In Next.js all JavaScripts files in `pages` directory are considered routes. So, for `relay-compiler` you'll need to specify `artifactDirectory` configuration settings outside of the `pages`, otherwise `relay-compiler` will generate files next to the source file in the `__generated__` directory, and this file will be considered a route, which will break production build. + ### Remove React Properties Allows to remove JSX properties. This is often used for testing. Similar to `babel-plugin-react-remove-properties`. diff --git a/package.json b/package.json index 088088a500d0..565756b37826 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/http-proxy": "1.17.3", "@types/jest": "24.0.13", "@types/node": "13.11.0", + "@types/relay-runtime": "13.0.0", "@types/selenium-webdriver": "4.0.15", "@types/sharp": "0.29.3", "@types/string-hash": "1.1.1", @@ -147,6 +148,8 @@ "react-dom": "17.0.2", "react-dom-18": "npm:react-dom@18.0.0-rc.0", "react-ssr-prepass": "1.0.8", + "relay-compiler": "13.0.2", + "relay-runtime": "13.0.2", "react-virtualized": "9.22.3", "release": "6.3.0", "request-promise-core": "1.1.2", diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock index 71bbaccd17e5..efc296f06629 100644 --- a/packages/next-swc/Cargo.lock +++ b/packages/next-swc/Cargo.lock @@ -800,6 +800,7 @@ dependencies = [ "easy-error", "either", "fxhash", + "once_cell", "pathdiff", "regex", "serde", diff --git a/packages/next-swc/crates/core/Cargo.toml b/packages/next-swc/crates/core/Cargo.toml index 0ccf231f9148..6efe5f72b6a6 100644 --- a/packages/next-swc/crates/core/Cargo.toml +++ b/packages/next-swc/crates/core/Cargo.toml @@ -8,6 +8,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] chrono = "0.4" +once_cell = "1.8.0" easy-error = "1.0.0" either = "1" fxhash = "0.2.1" @@ -30,5 +31,3 @@ regex = "1.5" swc_ecma_transforms_testing = "0.59.0" testing = "0.18.0" walkdir = "2.3.2" - - diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index 1a36b3b2b569..03b77e8ea85f 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -53,6 +53,8 @@ pub mod next_dynamic; pub mod next_ssg; pub mod page_config; pub mod react_remove_properties; +#[cfg(not(target_arch = "wasm32"))] +pub mod relay; pub mod remove_console; pub mod shake_exports; pub mod styled_jsx; @@ -91,6 +93,10 @@ pub struct TransformOptions { #[serde(default)] pub react_remove_properties: Option, + #[serde(default)] + #[cfg(not(target_arch = "wasm32"))] + pub relay: Option, + #[serde(default)] pub shake_exports: Option, } @@ -99,7 +105,19 @@ pub fn custom_before_pass( cm: Arc, file: Arc, opts: &TransformOptions, -) -> impl Fold { +) -> impl Fold + '_ { + #[cfg(target_arch = "wasm32")] + let relay_plugin = noop(); + + #[cfg(not(target_arch = "wasm32"))] + let relay_plugin = { + if let Some(config) = &opts.relay { + Either::Left(relay::relay(config, file.name.clone())) + } else { + Either::Right(noop()) + } + }; + chain!( disallow_re_export_all_in_page::disallow_re_export_all_in_page(opts.is_page_file), styled_jsx::styled_jsx(cm.clone()), @@ -130,6 +148,7 @@ pub fn custom_before_pass( page_config::page_config(opts.is_development, opts.is_page_file), !opts.disable_page_config ), + relay_plugin, match &opts.remove_console { Some(config) if config.truthy() => Either::Left(remove_console::remove_console(config.clone())), diff --git a/packages/next-swc/crates/core/src/relay.rs b/packages/next-swc/crates/core/src/relay.rs new file mode 100644 index 000000000000..76c7298eee91 --- /dev/null +++ b/packages/next-swc/crates/core/src/relay.rs @@ -0,0 +1,170 @@ +use once_cell::sync::Lazy; +use regex::Regex; +use serde::Deserialize; +use std::path::{Path, PathBuf}; +use swc_atoms::JsWord; +use swc_common::errors::HANDLER; +use swc_common::FileName; +use swc_ecmascript::ast::*; +use swc_ecmascript::utils::{quote_ident, ExprFactory}; +use swc_ecmascript::visit::{Fold, FoldWith}; + +#[derive(Copy, Clone, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum RelayLanguageConfig { + TypeScript, + Flow, +} + +impl Default for RelayLanguageConfig { + fn default() -> Self { + Self::Flow + } +} + +struct Relay<'a> { + root_dir: 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, + #[serde(default)] + pub language: RelayLanguageConfig, +} + +fn pull_first_operation_name_from_tpl(tpl: &TaggedTpl) -> Option { + tpl.tpl.quasis.iter().find_map(|quasis| { + static OPERATION_REGEX: Lazy = + Lazy::new(|| Regex::new(r"(fragment|mutation|query|subscription) (\w+)").unwrap()); + + let capture_group = OPERATION_REGEX.captures_iter(&quasis.raw.value).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), + has_escape: false, + kind: Default::default(), + }) + .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, +} + +fn path_for_artifact( + root_dir: &Path, + config: &Config, + definition_name: &str, +) -> Result { + let filename = match &config.language { + RelayLanguageConfig::Flow => format!("{}.graphql.js", definition_name), + RelayLanguageConfig::TypeScript => { + format!("{}.graphql.ts", definition_name) + } + }; + + if let Some(artifact_directory) = &config.artifact_directory { + Ok(root_dir.join(artifact_directory).join(filename)) + } else { + Err(BuildRequirePathError::ArtifactDirectoryExpected) + } +} + +impl<'a> Relay<'a> { + fn build_require_path( + &mut self, + operation_name: &str, + ) -> Result { + match &self.file_name { + FileName::Real(_real_file_name) => { + path_for_artifact(&self.root_dir, self.config, operation_name) + } + _ => Err(BuildRequirePathError::FileNameNotReal), + } + } + + fn build_call_expr_from_tpl(&mut self, tpl: &TaggedTpl) -> Option { + 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 => { + "The `artifactDirectory` is expected to be set in the Relay config \ + file to work correctly with Next.js." + .to_string() + } + }; + + HANDLER.with(|handler| { + handler.span_err( + tpl.span, + format!("{} {}", base_error, error_message).as_str(), + ); + }); + + None + } + }, + } + } +} + +pub fn relay<'a>(config: &'a Config, file_name: FileName) -> impl Fold + '_ { + Relay { + root_dir: std::env::current_dir().unwrap(), + file_name, + config, + } +} diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index f575c26c3d8c..332f35d15fbf 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -4,6 +4,7 @@ use next_swc::{ next_ssg::next_ssg, page_config::page_config_test, react_remove_properties::remove_properties, + relay::{relay, Config as RelayConfig, RelayLanguageConfig}, remove_console::remove_console, shake_exports::{shake_exports, Config as ShakeExportsConfig}, styled_jsx::styled_jsx, @@ -149,6 +150,22 @@ fn page_config_fixture(input: PathBuf) { test_fixture(syntax(), &|_tr| page_config_test(), &input, &output); } +#[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 { + language: RelayLanguageConfig::TypeScript, + artifact_directory: Some(PathBuf::from("__generated__")), + ..Default::default() + }; + test_fixture( + syntax(), + &|_tr| relay(&config, FileName::Real(PathBuf::from("input.tsx"))), + &input, + &output, + ); +} + #[fixture("tests/fixture/remove-console/**/input.js")] fn remove_console_fixture(input: PathBuf) { let output = input.parent().unwrap().join("output.js"); diff --git a/packages/next-swc/crates/core/tests/fixture/relay/input.tsx b/packages/next-swc/crates/core/tests/fixture/relay/input.tsx new file mode 100644 index 000000000000..f244f2045cc0 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/relay/input.tsx @@ -0,0 +1,42 @@ +const variableQuery = graphql` + query InputVariableQuery { + hello + } +` + +fetchQuery(graphql` + query InputUsedInFunctionCallQuery { + hello + } +`) + +function SomeQueryComponent() { + useLazyLoadQuery(graphql` + query InputInHookQuery { + hello + } + `) +} + +const variableMutation = graphql` + query InputVariableMutation { + someMutation + } +` + +commitMutation( + environment, + graphql` + query InputUsedInFunctionCallMutation { + someMutation + } + ` +) + +function SomeMutationComponent() { + useMutation(graphql` + query InputInHookMutation { + someMutation + } + `) +} diff --git a/packages/next-swc/crates/core/tests/fixture/relay/output.js b/packages/next-swc/crates/core/tests/fixture/relay/output.js new file mode 100644 index 000000000000..16a8af74db12 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/relay/output.js @@ -0,0 +1,10 @@ +const variableQuery = require("$DIR/__generated__/InputVariableQuery.graphql.ts"); +fetchQuery(require("$DIR/__generated__/InputUsedInFunctionCallQuery.graphql.ts")); +function SomeQueryComponent() { + useLazyLoadQuery(require("$DIR/__generated__/InputInHookQuery.graphql.ts")); +} +const variableMutation = require("$DIR/__generated__/InputVariableMutation.graphql.ts"); +commitMutation(environment, require("$DIR/__generated__/InputUsedInFunctionCallMutation.graphql.ts")); +function SomeMutationComponent() { + useMutation(require("$DIR/__generated__/InputInHookMutation.graphql.ts")); +} diff --git a/packages/next-swc/crates/core/tests/full.rs b/packages/next-swc/crates/core/tests/full.rs index 834b14748d92..f915188aace8 100644 --- a/packages/next-swc/crates/core/tests/full.rs +++ b/packages/next-swc/crates/core/tests/full.rs @@ -59,6 +59,7 @@ fn test(input: &Path, minify: bool) { styled_components: Some(assert_json("{}")), remove_console: None, react_remove_properties: None, + relay: None, shake_exports: None, }; diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 707c6098c3a6..8ed8822fbb6b 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -83,6 +83,7 @@ export function getBaseSWCOptions({ : null, removeConsole: nextConfig?.experimental?.removeConsole, reactRemoveProperties: nextConfig?.experimental?.reactRemoveProperties, + relay: nextConfig?.experimental?.relay, } } diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 11a1c834a9ed..647194bfbef8 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1621,6 +1621,7 @@ export default async function getBaseWebpackConfig( removeConsole: config.experimental.removeConsole, reactRemoveProperties: config.experimental.reactRemoveProperties, styledComponents: config.experimental.styledComponents, + relay: config.experimental.relay, }) const cache: any = { diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 7b22fd471a45..26a1374106d4 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -172,6 +172,11 @@ export type NextConfig = { [key: string]: any } & { urlImports?: NonNullable['buildHttp'] outputFileTracingRoot?: string outputStandalone?: boolean + relay?: { + src: string + artifactDirectory?: string + language?: 'typescript' | 'flow' + } } } diff --git a/test/integration/relay-graphql-swc-multi-project/project-a/__generated__/pagesAQuery.graphql.ts b/test/integration/relay-graphql-swc-multi-project/project-a/__generated__/pagesAQuery.graphql.ts new file mode 100644 index 000000000000..b34157d840b2 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-a/__generated__/pagesAQuery.graphql.ts @@ -0,0 +1,63 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime' +export type pagesAQuery$variables = {} +export type pagesAQueryVariables = pagesAQuery$variables +export type pagesAQuery$data = { + readonly greeting: string +} +export type pagesAQueryResponse = pagesAQuery$data +export type pagesAQuery = { + variables: pagesAQueryVariables + response: pagesAQuery$data +} + +const node: ConcreteRequest = (function () { + var v0 = [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'greeting', + storageKey: null, + }, + ] + return { + fragment: { + argumentDefinitions: [], + kind: 'Fragment', + metadata: null, + name: 'pagesAQuery', + selections: v0 /*: any*/, + type: 'Query', + abstractKey: null, + }, + kind: 'Request', + operation: { + argumentDefinitions: [], + kind: 'Operation', + name: 'pagesAQuery', + selections: v0 /*: any*/, + }, + params: { + cacheID: 'bc59dc1b50eecd19488f004d5cd93913', + id: null, + metadata: {}, + name: 'pagesAQuery', + operationKind: 'query', + text: 'query pagesAQuery {\n greeting\n}\n', + }, + } +})() + +;(node as any).hash = '7f699085b71746bb18cb74e3a0776f46' + +export default node diff --git a/test/integration/relay-graphql-swc-multi-project/project-a/next.config.js b/test/integration/relay-graphql-swc-multi-project/project-a/next.config.js new file mode 100644 index 000000000000..79ef26c30f3a --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-a/next.config.js @@ -0,0 +1,12 @@ +const relay = require('../relay.config') + +module.exports = { + experimental: { + relay: { + src: './pages', + artifactDirectory: './__generated__', + language: relay.projects['project-b'].language, + }, + externalDir: true, + }, +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-a/pages/api/query.ts b/test/integration/relay-graphql-swc-multi-project/project-a/pages/api/query.ts new file mode 100644 index 000000000000..5ad51e06bc09 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-a/pages/api/query.ts @@ -0,0 +1,12 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +export default function handler( + req: NextApiRequest, + res: NextApiResponse<{ data: { greeting: string } }> +) { + res.status(200).json({ + data: { + greeting: 'Hello, World!', + }, + }) +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-a/pages/index.tsx b/test/integration/relay-graphql-swc-multi-project/project-a/pages/index.tsx new file mode 100644 index 000000000000..2f185a1bcfdf --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-a/pages/index.tsx @@ -0,0 +1,63 @@ +import { + Environment, + FetchFunction, + fetchQuery, + graphql, + Network, + RecordSource, + Store, +} from 'relay-runtime' +import { GetServerSideProps } from 'next' +import { pagesAQuery } from '../__generated__/pagesAQuery.graphql' + +type Props = { greeting: string } + +export default function Index({ greeting }: Props) { + return

Project A:{greeting}

+} + +function createGraphQLFetcher(host: string | undefined): FetchFunction { + return async function fetchGraphQL(params, variables) { + const url = host ? `http://${host}/api/query` : `/api/query` + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: params.text, + variables, + }), + }) + + return await response.json() + } +} + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const environment = new Environment({ + store: new Store(new RecordSource({}), {}), + network: Network.create(createGraphQLFetcher(req.headers.host)), + }) + + const result = await fetchQuery( + environment, + graphql` + query pagesAQuery { + greeting + } + `, + {} + ).toPromise() + + if (!result) { + throw new Error( + 'Mock GraphQL Server network request finished without a response!' + ) + } + + return { + props: { greeting: result.greeting }, + } +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-a/tsconfig.json b/test/integration/relay-graphql-swc-multi-project/project-a/tsconfig.json new file mode 100644 index 000000000000..a247e53e341e --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-a/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "module": "esnext", + "jsx": "preserve", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true + }, + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-b/__generated__/pagesBQuery.graphql.ts b/test/integration/relay-graphql-swc-multi-project/project-b/__generated__/pagesBQuery.graphql.ts new file mode 100644 index 000000000000..da63ea967850 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-b/__generated__/pagesBQuery.graphql.ts @@ -0,0 +1,63 @@ +/** + * @generated SignedSource<<9f92ea3ccfda1f64fa269e68b912abae>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime' +export type pagesBQuery$variables = {} +export type pagesBQueryVariables = pagesBQuery$variables +export type pagesBQuery$data = { + readonly greeting: string +} +export type pagesBQueryResponse = pagesBQuery$data +export type pagesBQuery = { + variables: pagesBQueryVariables + response: pagesBQuery$data +} + +const node: ConcreteRequest = (function () { + var v0 = [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'greeting', + storageKey: null, + }, + ] + return { + fragment: { + argumentDefinitions: [], + kind: 'Fragment', + metadata: null, + name: 'pagesBQuery', + selections: v0 /*: any*/, + type: 'Query', + abstractKey: null, + }, + kind: 'Request', + operation: { + argumentDefinitions: [], + kind: 'Operation', + name: 'pagesBQuery', + selections: v0 /*: any*/, + }, + params: { + cacheID: 'e7cc6f8c55ef42783faec7a49b72ae71', + id: null, + metadata: {}, + name: 'pagesBQuery', + operationKind: 'query', + text: 'query pagesBQuery {\n greeting\n}\n', + }, + } +})() + +;(node as any).hash = '83bf9452eafa7635d81bdc98603cd75f' + +export default node diff --git a/test/integration/relay-graphql-swc-multi-project/project-b/next.config.js b/test/integration/relay-graphql-swc-multi-project/project-b/next.config.js new file mode 100644 index 000000000000..79ef26c30f3a --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-b/next.config.js @@ -0,0 +1,12 @@ +const relay = require('../relay.config') + +module.exports = { + experimental: { + relay: { + src: './pages', + artifactDirectory: './__generated__', + language: relay.projects['project-b'].language, + }, + externalDir: true, + }, +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-b/pages/api/query.ts b/test/integration/relay-graphql-swc-multi-project/project-b/pages/api/query.ts new file mode 100644 index 000000000000..5ad51e06bc09 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-b/pages/api/query.ts @@ -0,0 +1,12 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +export default function handler( + req: NextApiRequest, + res: NextApiResponse<{ data: { greeting: string } }> +) { + res.status(200).json({ + data: { + greeting: 'Hello, World!', + }, + }) +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-b/pages/index.tsx b/test/integration/relay-graphql-swc-multi-project/project-b/pages/index.tsx new file mode 100644 index 000000000000..e1adebcf42d3 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-b/pages/index.tsx @@ -0,0 +1,63 @@ +import { + Environment, + FetchFunction, + fetchQuery, + graphql, + Network, + RecordSource, + Store, +} from 'relay-runtime' +import { GetServerSideProps } from 'next' +import { pagesBQuery } from '../__generated__/pagesBQuery.graphql' + +type Props = { greeting: string } + +export default function Index({ greeting }: Props) { + return

Project B:{greeting}

+} + +function createGraphQLFetcher(host: string | undefined): FetchFunction { + return async function fetchGraphQL(params, variables) { + const url = host ? `http://${host}/api/query` : `/api/query` + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: params.text, + variables, + }), + }) + + return await response.json() + } +} + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const environment = new Environment({ + store: new Store(new RecordSource({}), {}), + network: Network.create(createGraphQLFetcher(req.headers.host)), + }) + + const result = await fetchQuery( + environment, + graphql` + query pagesBQuery { + greeting + } + `, + {} + ).toPromise() + + if (!result) { + throw new Error( + 'Mock GraphQL Server network request finished without a response!' + ) + } + + return { + props: { greeting: result.greeting }, + } +} diff --git a/test/integration/relay-graphql-swc-multi-project/project-b/tsconfig.json b/test/integration/relay-graphql-swc-multi-project/project-b/tsconfig.json new file mode 100644 index 000000000000..a247e53e341e --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/project-b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "module": "esnext", + "jsx": "preserve", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true + }, + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] +} diff --git a/test/integration/relay-graphql-swc-multi-project/relay.config.js b/test/integration/relay-graphql-swc-multi-project/relay.config.js new file mode 100644 index 000000000000..1a999e5be80a --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/relay.config.js @@ -0,0 +1,19 @@ +module.exports = { + root: './', + sources: { + 'project-a/pages': 'project-a', + 'project-b/pages': 'project-b', + }, + projects: { + 'project-a': { + schema: 'schema.graphql', + language: 'typescript', + output: 'project-a/__generated__', + }, + 'project-b': { + schema: 'schema.graphql', + language: 'typescript', + output: 'project-b/__generated__', + }, + }, +} diff --git a/test/integration/relay-graphql-swc-multi-project/schema.graphql b/test/integration/relay-graphql-swc-multi-project/schema.graphql new file mode 100644 index 000000000000..7428cd8c2058 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/schema.graphql @@ -0,0 +1,3 @@ +type Query { + greeting: String! +} diff --git a/test/integration/relay-graphql-swc-multi-project/test/index.test.js b/test/integration/relay-graphql-swc-multi-project/test/index.test.js new file mode 100644 index 000000000000..605f06c87527 --- /dev/null +++ b/test/integration/relay-graphql-swc-multi-project/test/index.test.js @@ -0,0 +1,89 @@ +/* eslint-env jest */ +import { join } from 'path' +import { execSync } from 'child_process' +import { + findPort, + killApp, + launchApp, + nextBuild, + nextStart, + renderViaHTTP, +} from 'next-test-utils' + +let app +let appPort +const projectAAppDir = join(__dirname, '../project-a') +const projectBAppDir = join(__dirname, '../project-b') + +const runTests = (project) => { + it('should resolve index page correctly', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toContain(project) + expect(html).toContain(`Hello, World!`) + }) +} + +const runRelayCompiler = () => { + // Relay expects the current directory to contain a relay.json + // This ensures the CWD is the one with relay.json since running + // the relay-compiler through yarn would make the root of the repo the CWD. + execSync('../../../node_modules/relay-compiler/cli.js', { + cwd: './test/integration/relay-graphql-swc-multi-project', + }) +} + +describe('Relay Compiler Transform - Multi Project Config', () => { + beforeAll(() => { + runRelayCompiler() + }) + + describe('dev mode', () => { + describe('project-a', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(projectAAppDir, appPort, { cwd: projectAAppDir }) + }) + + afterAll(() => killApp(app)) + + runTests('Project A') + }) + + describe('project-b', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(projectBAppDir, appPort, { cwd: projectBAppDir }) + }) + + afterAll(() => killApp(app)) + + runTests('Project B') + }) + }) + + describe('production mode', () => { + describe('project-a', () => { + beforeAll(async () => { + await nextBuild(projectAAppDir, [], { cwd: projectAAppDir }) + appPort = await findPort() + app = await nextStart(projectAAppDir, appPort) + }) + + afterAll(() => killApp(app)) + + runTests('Project A') + }) + + describe('project-b', () => { + beforeAll(async () => { + await nextBuild(projectBAppDir, [], { cwd: projectBAppDir }) + appPort = await findPort() + app = await nextStart(projectBAppDir, appPort) + }) + + afterAll(() => killApp(app)) + + runTests('Project B') + }) + }) +}) diff --git a/test/integration/relay-graphql-swc-single-project/__generated__/pagesQuery.graphql.ts b/test/integration/relay-graphql-swc-single-project/__generated__/pagesQuery.graphql.ts new file mode 100644 index 000000000000..3963e412c615 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/__generated__/pagesQuery.graphql.ts @@ -0,0 +1,63 @@ +/** + * @generated SignedSource<<187ead9fb6e7b26d71c9161bda6ab902>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime' +export type pagesQuery$variables = {} +export type pagesQueryVariables = pagesQuery$variables +export type pagesQuery$data = { + readonly greeting: string +} +export type pagesQueryResponse = pagesQuery$data +export type pagesQuery = { + variables: pagesQueryVariables + response: pagesQuery$data +} + +const node: ConcreteRequest = (function () { + var v0 = [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'greeting', + storageKey: null, + }, + ] + return { + fragment: { + argumentDefinitions: [], + kind: 'Fragment', + metadata: null, + name: 'pagesQuery', + selections: v0 /*: any*/, + type: 'Query', + abstractKey: null, + }, + kind: 'Request', + operation: { + argumentDefinitions: [], + kind: 'Operation', + name: 'pagesQuery', + selections: v0 /*: any*/, + }, + params: { + cacheID: '167b6de16340efeb876a7787c90e7cec', + id: null, + metadata: {}, + name: 'pagesQuery', + operationKind: 'query', + text: 'query pagesQuery {\n greeting\n}\n', + }, + } +})() + +;(node as any).hash = '4017856344f36f61252354e2eb442d98' + +export default node diff --git a/test/integration/relay-graphql-swc-single-project/next.config.js b/test/integration/relay-graphql-swc-single-project/next.config.js new file mode 100644 index 000000000000..ef214a05ace7 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/next.config.js @@ -0,0 +1,7 @@ +const relay = require('./relay.config') + +module.exports = { + experimental: { + relay, + }, +} diff --git a/test/integration/relay-graphql-swc-single-project/pages/api/query.ts b/test/integration/relay-graphql-swc-single-project/pages/api/query.ts new file mode 100644 index 000000000000..5ad51e06bc09 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/pages/api/query.ts @@ -0,0 +1,12 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +export default function handler( + req: NextApiRequest, + res: NextApiResponse<{ data: { greeting: string } }> +) { + res.status(200).json({ + data: { + greeting: 'Hello, World!', + }, + }) +} diff --git a/test/integration/relay-graphql-swc-single-project/pages/index.tsx b/test/integration/relay-graphql-swc-single-project/pages/index.tsx new file mode 100644 index 000000000000..5b586d43dbd9 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/pages/index.tsx @@ -0,0 +1,63 @@ +import { + Environment, + FetchFunction, + fetchQuery, + graphql, + Network, + RecordSource, + Store, +} from 'relay-runtime' +import { GetServerSideProps } from 'next' +import { pagesQuery } from '../__generated__/pagesQuery.graphql' + +type Props = { greeting: string } + +export default function Index({ greeting }: Props) { + return

{greeting}

+} + +function createGraphQLFetcher(host: string | undefined): FetchFunction { + return async function fetchGraphQL(params, variables) { + const url = host ? `http://${host}/api/query` : `/api/query` + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: params.text, + variables, + }), + }) + + return await response.json() + } +} + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const environment = new Environment({ + store: new Store(new RecordSource({}), {}), + network: Network.create(createGraphQLFetcher(req.headers.host)), + }) + + const result = await fetchQuery( + environment, + graphql` + query pagesQuery { + greeting + } + `, + {} + ).toPromise() + + if (!result) { + throw new Error( + 'Mock GraphQL Server network request finished without a response!' + ) + } + + return { + props: { greeting: result.greeting }, + } +} diff --git a/test/integration/relay-graphql-swc-single-project/relay.config.js b/test/integration/relay-graphql-swc-single-project/relay.config.js new file mode 100644 index 000000000000..a79dff776038 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/relay.config.js @@ -0,0 +1,6 @@ +module.exports = { + src: './pages', + schema: './schema.graphql', + artifactDirectory: './__generated__', + language: 'typescript', +} diff --git a/test/integration/relay-graphql-swc-single-project/schema.graphql b/test/integration/relay-graphql-swc-single-project/schema.graphql new file mode 100644 index 000000000000..7428cd8c2058 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/schema.graphql @@ -0,0 +1,3 @@ +type Query { + greeting: String! +} diff --git a/test/integration/relay-graphql-swc-single-project/test/index.test.js b/test/integration/relay-graphql-swc-single-project/test/index.test.js new file mode 100644 index 000000000000..0b1918304787 --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/test/index.test.js @@ -0,0 +1,57 @@ +/* eslint-env jest */ +import { join } from 'path' +import { execSync } from 'child_process' +import { + findPort, + killApp, + launchApp, + nextBuild, + nextStart, + renderViaHTTP, +} from 'next-test-utils' + +let app +let appPort +const appDir = join(__dirname, '../') + +const runTests = () => { + it('should resolve index page correctly', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toContain('Hello, World!') + }) +} + +const runRelayCompiler = () => { + // Relay expects the current directory to contain a relay.json + // This ensures the CWD is the one with relay.json since running + // the relay-compiler through yarn would make the root of the repo the CWD. + execSync('../../../node_modules/relay-compiler/cli.js', { + cwd: './test/integration/relay-graphql-swc-single-project', + }) +} + +describe('Relay Compiler Transform - Single Project Config', () => { + describe('dev mode', () => { + beforeAll(async () => { + runRelayCompiler() + appPort = await findPort() + app = await launchApp(appDir, appPort, { cwd: appDir }) + }) + afterAll(() => killApp(app)) + + runTests() + }) + + describe('production mode', () => { + beforeAll(async () => { + runRelayCompiler() + await nextBuild(appDir, [], { cwd: appDir }) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + + afterAll(() => killApp(app)) + + runTests() + }) +}) diff --git a/test/integration/relay-graphql-swc-single-project/tsconfig.json b/test/integration/relay-graphql-swc-single-project/tsconfig.json new file mode 100644 index 000000000000..a247e53e341e --- /dev/null +++ b/test/integration/relay-graphql-swc-single-project/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "module": "esnext", + "jsx": "preserve", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true + }, + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] +} diff --git a/yarn.lock b/yarn.lock index 855240d2f8e9..05eed298b03b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2260,6 +2260,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.0.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.10.2": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" @@ -4848,6 +4855,11 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/relay-runtime@13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-13.0.0.tgz#d0009275522ff826f2e4dab40419f2db58417ecf" + integrity sha512-yzv6F8EZPWA2rtfFP2qMluS8tsz1q4lfdYxLegCshdAjX5uqxTR2pAliATj9wrzD6OMZF4fl9aU+Y+zmSfm2EA== + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -7914,6 +7926,13 @@ cross-fetch@3.0.6, cross-fetch@^3.0.6: dependencies: node-fetch "2.6.1" +cross-fetch@^3.0.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn-async@^2.1.1: version "2.2.5" resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" @@ -9848,6 +9867,24 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.2.tgz#dfae08a85c66a58372993ce2caf30863f569ff94" + integrity sha512-qv+boqYndjElAJHNN3NoM8XuwQZ1j2m3kEvTgdle8IDjr6oUbkEpvABWtj/rQl3vq4ew7dnElBxL4YJAwTVqQQ== + dependencies: + cross-fetch "^3.0.4" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -11586,6 +11623,13 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -13566,7 +13610,7 @@ longest-streak@^2.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: @@ -16802,6 +16846,13 @@ promise@8.0.1: dependencies: asap "~2.0.3" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.1.0.tgz#bf90bc71f6065d255ea2bdc0fe6520485c1b45db" @@ -17549,6 +17600,20 @@ rehype-retext@^2.0.1: dependencies: hast-util-to-nlcst "^1.0.0" +relay-compiler@13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-13.0.2.tgz#2ec086bcc55ac8cb94cc08a19be129e037ca8044" + integrity sha512-Gg/5FNj8gz03GRkG5KjMNpDB4nOeApDFZq+8CZehu3KkQQ+vNIurX6RiTY3UNUYvg5SX2A9DS5bq5iHJ8iEV3A== + +relay-runtime@13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-13.0.2.tgz#02adb73ddc159c57b92a23c52984bf73fae0d1fc" + integrity sha512-sP4lNGfFcOm7tQD12qlvwsiOREDgjkw4FQAjtemmtwivKOqI4qHAL22Ar62r5TPVlASn4iVWMk7rIdIJI20KGQ== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^3.0.2" + invariant "^2.2.4" + release@6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/release/-/release-6.3.0.tgz#bbd351d7460948f1ed55ea02b4b2393f98a1637a" @@ -18307,7 +18372,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@1.0.5, setimmediate@^1.0.4: +setimmediate@1.0.5, setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -19975,6 +20040,11 @@ ua-parser-js@0.7.28: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== +ua-parser-js@^0.7.30: + version "0.7.31" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" + integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + uglify-js@^3.1.4: version "3.7.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"