From 40fe615ad6f89cb25b720cd0507e8d9862ac013e Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:03:08 -0800 Subject: [PATCH] @preloadable Support (#4515) Summary: This is based on an earlier implementation done by tomgasson in https://github.com/facebook/relay/issues/4110 Companion PR for `types/relay-runtime`: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/68144 An example application using this can be found [here](https://github.com/tobias-tengler/nextjs-relay-entrypoints) Pull Request resolved: https://github.com/facebook/relay/pull/4515 Reviewed By: tyao1 Differential Revision: D52608572 Pulled By: alunyov fbshipit-source-id: d75f904d0f20a89d3542fabf789c6c9d3c8595ce --- compiler/crates/relay-bin/src/main.rs | 3 + .../crates/relay-codegen/src/build_ast.rs | 10 ++ .../crates/relay-codegen/src/constants.rs | 2 + compiler/crates/relay-codegen/src/printer.rs | 21 +++ .../src/artifact_content/content.rs | 163 +++++++++++++++--- .../src/artifact_content/mod.rs | 17 ++ .../src/build_project/generate_artifacts.rs | 25 +++ .../build_project/generate_extra_artifacts.rs | 36 ++++ .../relay-compiler/src/build_project/mod.rs | 1 + .../fixtures/preloadable_query_flow.expected | 129 ++++++++++++++ .../fixtures/preloadable_query_flow.input | 21 +++ .../preloadable_query_javascript.expected | 107 ++++++++++++ .../preloadable_query_javascript.input | 21 +++ .../preloadable_query_typescript.expected | 120 +++++++++++++ .../preloadable_query_typescript.input | 21 +++ .../tests/relay_compiler_integration/mod.rs | 20 +++ .../tests/relay_compiler_integration_test.rs | 23 ++- .../crates/relay-config/src/project_config.rs | 31 +++- 18 files changed, 741 insertions(+), 30 deletions(-) create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.expected create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.input create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.expected create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.input create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.expected create mode 100644 compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.input diff --git a/compiler/crates/relay-bin/src/main.rs b/compiler/crates/relay-bin/src/main.rs index f832244070dd7..51bab9e93822e 100644 --- a/compiler/crates/relay-bin/src/main.rs +++ b/compiler/crates/relay-bin/src/main.rs @@ -18,6 +18,7 @@ use intern::string_key::Intern; use log::error; use log::info; use relay_compiler::build_project::artifact_writer::ArtifactValidationWriter; +use relay_compiler::build_project::generate_extra_artifacts::default_generate_extra_artifacts_fn; use relay_compiler::compiler::Compiler; use relay_compiler::config::Config; use relay_compiler::errors::Error as CompilerError; @@ -296,6 +297,8 @@ async fn handle_compiler_command(command: CompileCommand) -> Result<(), Error> { ); } + config.generate_extra_artifacts = Some(Box::new(default_generate_extra_artifacts_fn)); + let compiler = Compiler::new(Arc::new(config), Arc::new(ConsoleLogger)); if command.watch { diff --git a/compiler/crates/relay-codegen/src/build_ast.rs b/compiler/crates/relay-codegen/src/build_ast.rs index bced88d849888..3d0e162846bc8 100644 --- a/compiler/crates/relay-codegen/src/build_ast.rs +++ b/compiler/crates/relay-codegen/src/build_ast.rs @@ -158,6 +158,16 @@ pub fn build_request( })) } +pub fn build_preloadable_request( + ast_builder: &mut AstBuilder, + request_parameters: AstKey, +) -> AstKey { + ast_builder.intern(Ast::Object(object! { + kind: Primitive::String(CODEGEN_CONSTANTS.preloadable_concrete_request), + params: Primitive::Key(request_parameters), + })) +} + pub fn build_request_params(operation: &OperationDefinition) -> RequestParameters<'_> { RequestParameters { name: operation.name.item.0, diff --git a/compiler/crates/relay-codegen/src/constants.rs b/compiler/crates/relay-codegen/src/constants.rs index 9c398d71e8ec3..48ec1efc25bb6 100644 --- a/compiler/crates/relay-codegen/src/constants.rs +++ b/compiler/crates/relay-codegen/src/constants.rs @@ -91,6 +91,7 @@ pub struct CodegenConstants { pub passing_value: StringKey, pub path: StringKey, pub plural: StringKey, + pub preloadable_concrete_request: StringKey, pub provided_variables: StringKey, pub provider: StringKey, pub query: StringKey, @@ -203,6 +204,7 @@ lazy_static! { passing_value: "passingValue".intern(), path: "path".intern(), plural: "plural".intern(), + preloadable_concrete_request: "PreloadableConcreteRequest".intern(), provided_variables: "providedVariables".intern(), provider: "provider".intern(), query: "query".intern(), diff --git a/compiler/crates/relay-codegen/src/printer.rs b/compiler/crates/relay-codegen/src/printer.rs index 6cc5a82175924..5503e235a4de7 100644 --- a/compiler/crates/relay-codegen/src/printer.rs +++ b/compiler/crates/relay-codegen/src/printer.rs @@ -37,6 +37,7 @@ use crate::ast::RequestParameters; use crate::ast::ResolverModuleReference; use crate::build_ast::build_fragment; use crate::build_ast::build_operation; +use crate::build_ast::build_preloadable_request; use crate::build_ast::build_provided_variables; use crate::build_ast::build_request; use crate::build_ast::build_request_params; @@ -227,6 +228,26 @@ impl<'p> Printer<'p> { printer.print(key, self.dedupe) } + pub fn print_preloadable_request( + &mut self, + schema: &SDLSchema, + request_parameters: RequestParameters<'_>, + operation: &OperationDefinition, + top_level_statements: &mut TopLevelStatements, + ) -> String { + let request_parameters = build_request_params_ast_key( + schema, + request_parameters, + &mut self.builder, + operation, + operation.name.map(|x| x.0), + self.project_config, + ); + let key = build_preloadable_request(&mut self.builder, request_parameters); + let printer = JSONPrinter::new(&self.builder, self.project_config, top_level_statements); + printer.print(key, self.dedupe) + } + pub fn print_operation( &mut self, schema: &SDLSchema, diff --git a/compiler/crates/relay-compiler/src/artifact_content/content.rs b/compiler/crates/relay-compiler/src/artifact_content/content.rs index 73f4d983abeb0..2549c36a20e73 100644 --- a/compiler/crates/relay-compiler/src/artifact_content/content.rs +++ b/compiler/crates/relay-compiler/src/artifact_content/content.rs @@ -39,6 +39,115 @@ use super::content_section::GenericSection; use crate::config::Config; use crate::config::ProjectConfig; +pub fn generate_preloadable_query_parameters( + config: &Config, + project_config: &ProjectConfig, + printer: &mut Printer<'_>, + schema: &SDLSchema, + normalization_operation: &OperationDefinition, + query_id: &QueryID, +) -> Result, FmtError> { + let mut request_parameters = build_request_params(normalization_operation); + let cloned_query_id = Some(query_id.clone()); + request_parameters.id = &cloned_query_id; + + let mut content_sections = ContentSections::default(); + + // -- Begin Docblock Section -- + let extra_annotations = match query_id { + QueryID::Persisted { text_hash, .. } => vec![format!("@relayHash {}", text_hash)], + _ => vec![], + }; + content_sections.push(ContentSection::Docblock(generate_docblock_section( + config, + project_config, + extra_annotations, + )?)); + // -- End Docblock Section -- + + // -- Begin Disable Lint Section -- + content_sections.push(ContentSection::Generic(generate_disable_lint_section( + &project_config.typegen_config.language, + )?)); + // -- End Disable Lint Section -- + + // -- Begin Use Strict Section -- + content_sections.push(ContentSection::Generic(generate_use_strict_section( + &project_config.typegen_config.language, + )?)); + // -- End Use Strict Section -- + + // -- Begin Metadata Annotations Section -- + let mut section = CommentAnnotationsSection::default(); + if let Some(QueryID::Persisted { id, .. }) = &request_parameters.id { + writeln!(section, "@relayRequestID {}", id)?; + } + content_sections.push(ContentSection::CommentAnnotations(section)); + // -- End Metadata Annotations Section -- + + // -- Begin Types Section -- + let mut section = GenericSection::default(); + if project_config.typegen_config.language == TypegenLanguage::Flow { + writeln!(section, "/*::")?; + } + + write_import_type_from( + project_config, + &mut section, + "PreloadableConcreteRequest", + "relay-runtime", + )?; + write_import_type_from( + project_config, + &mut section, + &normalization_operation.name.item.0.to_string(), + &format!("./{}.graphql", normalization_operation.name.item.0), + )?; + + if project_config.typegen_config.language == TypegenLanguage::Flow { + writeln!(section, "*/")?; + } + content_sections.push(ContentSection::Generic(section)); + // -- End Types Section -- + + // -- Begin Query Node Section -- + let preloadable_request = printer.print_preloadable_request( + schema, + request_parameters, + normalization_operation, + &mut Default::default(), + ); + let mut section = GenericSection::default(); + + let node_type = format!( + "PreloadableConcreteRequest<{}>", + normalization_operation.name.item.0 + ); + + write_variable_value_with_type( + &project_config.typegen_config.language, + &mut section, + "node", + &node_type, + &preloadable_request, + )?; + content_sections.push(ContentSection::Generic(section)); + // -- End Query Node Section -- + + // -- Begin Export Section -- + let mut section = GenericSection::default(); + write_export_generated_node( + &project_config.typegen_config, + &mut section, + "node", + Some(node_type), + )?; + content_sections.push(ContentSection::Generic(section)); + // -- End Export Section -- + + content_sections.into_signed_bytes() +} + #[allow(clippy::too_many_arguments)] pub fn generate_updatable_query( config: &Config, @@ -200,14 +309,14 @@ pub fn generate_operation( let mut content_sections = ContentSections::default(); // -- Begin Docblock Section -- - let v = match id_and_text_hash { + let extra_annotations = match id_and_text_hash { Some(QueryID::Persisted { text_hash, .. }) => vec![format!("@relayHash {}", text_hash)], _ => vec![], }; content_sections.push(ContentSection::Docblock(generate_docblock_section( config, project_config, - v, + extra_annotations, )?)); // -- End Docblock Section -- @@ -328,26 +437,38 @@ pub fn generate_operation( if is_operation_preloadable(normalization_operation) && id_and_text_hash.is_some() { match project_config.typegen_config.language { TypegenLanguage::Flow => { - writeln!( - section, - "require('relay-runtime').PreloadableQueryRegistry.set((node.params/*: any*/).id, node);", - )?; + if project_config.typegen_config.eager_es_modules { + writeln!( + section, + "import {{ PreloadableQueryRegistry }} from 'relay-runtime';", + )?; + writeln!( + section, + "PreloadableQueryRegistry.set((node.params/*: any*/).id, node);", + )?; + } else { + writeln!( + section, + "require('relay-runtime').PreloadableQueryRegistry.set((node.params/*: any*/).id, node);", + )?; + } } - TypegenLanguage::JavaScript => { - writeln!( - section, - "require('relay-runtime').PreloadableQueryRegistry.set(node.params.id, node);", - )?; - } - TypegenLanguage::TypeScript => { - writeln!( - section, - "import {{ PreloadableQueryRegistry }} from 'relay-runtime';", - )?; - writeln!( - section, - "PreloadableQueryRegistry.set(node.params.id, node);", - )?; + TypegenLanguage::JavaScript | TypegenLanguage::TypeScript => { + if project_config.typegen_config.eager_es_modules { + writeln!( + section, + "import {{ PreloadableQueryRegistry }} from 'relay-runtime';", + )?; + writeln!( + section, + "PreloadableQueryRegistry.set(node.params.id, node);", + )?; + } else { + writeln!( + section, + "require('relay-runtime').PreloadableQueryRegistry.set(node.params.id, node);", + )?; + } } } } diff --git a/compiler/crates/relay-compiler/src/artifact_content/mod.rs b/compiler/crates/relay-compiler/src/artifact_content/mod.rs index 0b9d6bd457313..3e120665b64f3 100644 --- a/compiler/crates/relay-compiler/src/artifact_content/mod.rs +++ b/compiler/crates/relay-compiler/src/artifact_content/mod.rs @@ -23,6 +23,7 @@ use relay_codegen::QueryID; use relay_typegen::FragmentLocations; use schema::SDLSchema; +use self::content::generate_preloadable_query_parameters; use crate::config::Config; use crate::config::ProjectConfig; @@ -41,6 +42,10 @@ pub enum ArtifactContent { typegen_operation: Arc, source_hash: String, }, + PreloadableQueryParameters { + normalization_operation: Arc, + query_id: QueryID, + }, Fragment { reader_fragment: Arc, typegen_fragment: Arc, @@ -113,6 +118,18 @@ impl ArtifactContent { fragment_locations, ) .unwrap(), + ArtifactContent::PreloadableQueryParameters { + normalization_operation, + query_id, + } => generate_preloadable_query_parameters( + config, + project_config, + printer, + schema, + normalization_operation, + query_id, + ) + .unwrap(), ArtifactContent::SplitOperation { normalization_operation, typegen_operation, diff --git a/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs b/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs index ac3055b63368a..877db399f750d 100644 --- a/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs +++ b/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs @@ -17,6 +17,7 @@ use graphql_text_printer::OperationPrinter; use graphql_text_printer::PrinterOptions; use intern::string_key::StringKey; use intern::Lookup; +use relay_codegen::QueryID; use relay_config::ResolversSchemaModuleConfig; use relay_transforms::ArtifactSourceKeyData; use relay_transforms::ClientEdgeGeneratedQueryMetadataDirective; @@ -228,6 +229,30 @@ fn generate_normalization_artifact( } } +pub fn generate_preloadable_query_parameters_artifact( + project_config: &ProjectConfig, + normalization: &Arc, + id_and_text_hash: &Option, + source_keys: Vec, + source_file: SourceLocationKey, +) -> Artifact { + let query_id = id_and_text_hash + .clone() + .expect("Expected operation artifact to have an `id`. Ensure a `persistConfig` is setup for the current project."); + + let artifact_name = normalization.name.item.0.to_string() + "$parameters"; + + Artifact { + artifact_source_keys: source_keys, + path: project_config.path_for_language_specific_artifact(source_file, artifact_name), + content: ArtifactContent::PreloadableQueryParameters { + normalization_operation: Arc::clone(normalization), + query_id, + }, + source_file, + } +} + fn generate_updatable_query_artifact( artifact_source: ArtifactSourceKey, project_config: &ProjectConfig, diff --git a/compiler/crates/relay-compiler/src/build_project/generate_extra_artifacts.rs b/compiler/crates/relay-compiler/src/build_project/generate_extra_artifacts.rs index 5a7c3d4b2865f..ee3ab893b4134 100644 --- a/compiler/crates/relay-compiler/src/build_project/generate_extra_artifacts.rs +++ b/compiler/crates/relay-compiler/src/build_project/generate_extra_artifacts.rs @@ -5,15 +5,51 @@ * LICENSE file in the root directory of this source tree. */ +use relay_transforms::is_operation_preloadable; use schema::SDLSchema; +use super::generate_preloadable_query_parameters_artifact; use super::Artifact; use super::Config; use super::Programs; use super::ProjectConfig; +use crate::ArtifactContent; pub type GenerateExtraArtifactsFn = Box< dyn Fn(&Config, &ProjectConfig, &SDLSchema, &Programs, &[Artifact]) -> Vec + Send + Sync, >; + +pub fn default_generate_extra_artifacts_fn( + _config: &Config, + project_config: &ProjectConfig, + _schema: &SDLSchema, + _program: &Programs, + artifacts: &[Artifact], +) -> Vec { + artifacts + .iter() + .map(|artifact| match &artifact.content { + ArtifactContent::Operation { + normalization_operation, + id_and_text_hash, + .. + } => { + if !is_operation_preloadable(normalization_operation) { + return None; + } + + Some(generate_preloadable_query_parameters_artifact( + project_config, + normalization_operation, + id_and_text_hash, + artifact.artifact_source_keys.clone(), + artifact.source_file, + )) + } + _ => None, + }) + .flatten() + .collect() +} diff --git a/compiler/crates/relay-compiler/src/build_project/mod.rs b/compiler/crates/relay-compiler/src/build_project/mod.rs index 754ed4df742aa..461e0e173e4be 100644 --- a/compiler/crates/relay-compiler/src/build_project/mod.rs +++ b/compiler/crates/relay-compiler/src/build_project/mod.rs @@ -40,6 +40,7 @@ use fnv::FnvBuildHasher; use fnv::FnvHashMap; use fnv::FnvHashSet; pub use generate_artifacts::generate_artifacts; +pub use generate_artifacts::generate_preloadable_query_parameters_artifact; pub use generate_artifacts::Artifact; pub use generate_artifacts::ArtifactContent; use graphql_ir::FragmentDefinitionNameSet; diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.expected b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.expected new file mode 100644 index 0000000000000..2b08a3cf1f33a --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.expected @@ -0,0 +1,129 @@ +==================================== INPUT ==================================== +//- foo.js +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "flow", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } +==================================== OUTPUT =================================== +//- __generated__/fooQuery$parameters.js +/** + * SignedSource<<56c59d6aa21f9b768bf09e00494f325e>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +/*:: +import type { PreloadableConcreteRequest } from 'relay-runtime'; +import type { fooQuery } from './fooQuery.graphql'; +*/ + +var node/*: PreloadableConcreteRequest*/ = { + "kind": "PreloadableConcreteRequest", + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; + +export default ((node/*: any*/)/*: PreloadableConcreteRequest*/); + +//- __generated__/fooQuery.graphql.js +/** + * SignedSource<<8cc7b8c559f66dd65618447b4688e2e9>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +export type fooQuery$variables = {||}; +export type fooQuery$data = {| + +userName: ?string, +|}; +export type fooQuery = {| + response: fooQuery$data, + variables: fooQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "userName", + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "fooQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "fooQuery", + "selections": (v0/*: any*/) + }, + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; +})(); + +(node/*: any*/).hash = "21bf4f020aaeb6ce67d04911a13d42a3"; + +import { PreloadableQueryRegistry } from 'relay-runtime'; +PreloadableQueryRegistry.set((node.params/*: any*/).id, node); + +export default ((node/*: any*/)/*: Query< + fooQuery$variables, + fooQuery$data, +>*/); diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.input b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.input new file mode 100644 index 0000000000000..4d16ef06ba05b --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_flow.input @@ -0,0 +1,21 @@ +//- foo.js +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "flow", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.expected b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.expected new file mode 100644 index 0000000000000..64b42ed3cbcb9 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.expected @@ -0,0 +1,107 @@ +==================================== INPUT ==================================== +//- foo.js +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "javascript", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } +==================================== OUTPUT =================================== +//- __generated__/fooQuery$parameters.js +/** + * SignedSource<<41a51ef922042b40623a9a01fbb5f5a3>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +var node = { + "kind": "PreloadableConcreteRequest", + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; + +export default node; + +//- __generated__/fooQuery.graphql.js +/** + * SignedSource<<4027cbd9de156c0ba9c35382960d28f0>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +var node = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "userName", + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "fooQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "fooQuery", + "selections": (v0/*: any*/) + }, + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; +})(); + +node.hash = "21bf4f020aaeb6ce67d04911a13d42a3"; + +import { PreloadableQueryRegistry } from 'relay-runtime'; +PreloadableQueryRegistry.set(node.params.id, node); + +export default node; diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.input b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.input new file mode 100644 index 0000000000000..3f9b4d9e869d0 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_javascript.input @@ -0,0 +1,21 @@ +//- foo.js +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "javascript", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.expected b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.expected new file mode 100644 index 0000000000000..e5fe00c999739 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.expected @@ -0,0 +1,120 @@ +==================================== INPUT ==================================== +//- foo.ts +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "typescript", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } +==================================== OUTPUT =================================== +//- __generated__/fooQuery$parameters.ts +/** + * SignedSource<<7de8baf3626a3ece606b72c2ece21848>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +import { PreloadableConcreteRequest } from 'relay-runtime'; +import { fooQuery } from './fooQuery.graphql'; + +const node: PreloadableConcreteRequest = { + "kind": "PreloadableConcreteRequest", + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; + +export default node; + +//- __generated__/fooQuery.graphql.ts +/** + * SignedSource<<461989c03655ebfecf253b741f65ef91>> + * @relayHash ae6874c86ce5db2df8d6b253a6a0ec13 + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +// @relayRequestID ae6874c86ce5db2df8d6b253a6a0ec13 + +import { ConcreteRequest, Query } from 'relay-runtime'; +export type fooQuery$variables = Record; +export type fooQuery$data = { + readonly userName: string | null | undefined; +}; +export type fooQuery = { + response: fooQuery$data; + variables: fooQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "userName", + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "fooQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "fooQuery", + "selections": (v0/*: any*/) + }, + "params": { + "id": "ae6874c86ce5db2df8d6b253a6a0ec13", + "metadata": {}, + "name": "fooQuery", + "operationKind": "query", + "text": null + } +}; +})(); + +(node as any).hash = "21bf4f020aaeb6ce67d04911a13d42a3"; + +import { PreloadableQueryRegistry } from 'relay-runtime'; +PreloadableQueryRegistry.set(node.params.id, node); + +export default node; diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.input b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.input new file mode 100644 index 0000000000000..676812f950e8c --- /dev/null +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/preloadable_query_typescript.input @@ -0,0 +1,21 @@ +//- foo.ts +graphql` + query fooQuery @preloadable { + userName + }`; + +//- relay.config.json +{ + "language": "typescript", + "schema": "./schema.graphql", + "eagerEsModules": true, + "persistConfig": { + "file": "./operations.json" + } +} + +//- operations.json +{} + +//- schema.graphql +type Query { userName: String } diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/mod.rs b/compiler/crates/relay-compiler/tests/relay_compiler_integration/mod.rs index 473ebd80b59cb..ba8b031b6413d 100644 --- a/compiler/crates/relay-compiler/tests/relay_compiler_integration/mod.rs +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/mod.rs @@ -16,6 +16,7 @@ use futures_util::FutureExt; use graphql_cli::DiagnosticPrinter; use graphql_test_helpers::ProjectFixture; use graphql_test_helpers::TestDir; +use relay_compiler::build_project::generate_extra_artifacts::default_generate_extra_artifacts_fn; use relay_compiler::compiler::Compiler; use relay_compiler::config::Config; use relay_compiler::errors::BuildProjectError; @@ -23,7 +24,11 @@ use relay_compiler::errors::Error; use relay_compiler::source_for_location; use relay_compiler::FileSourceKind; use relay_compiler::FsSourceReader; +use relay_compiler::LocalPersister; +use relay_compiler::OperationPersister; +use relay_compiler::RemotePersister; use relay_compiler::SourceReader; +use relay_config::PersistConfig; pub async fn transform_fixture(fixture: &Fixture<'_>) -> Result { let project_fixture = ProjectFixture::deserialize(fixture.content); @@ -41,6 +46,21 @@ pub async fn transform_fixture(fixture: &Fixture<'_>) -> Result Config::search(&PathBuf::from(test_dir.path())).expect("Could not load config"); config.file_source_config = FileSourceKind::WalkDir; + config.create_operation_persister = Some(Box::new(|project_config| { + project_config.persist.as_ref().map( + |persist_config| -> Box { + match persist_config { + PersistConfig::Remote(remote_config) => { + Box::new(RemotePersister::new(remote_config.clone())) + } + PersistConfig::Local(local_config) => { + Box::new(LocalPersister::new(local_config.clone())) + } + } + }, + ) + })); + config.generate_extra_artifacts = Some(Box::new(default_generate_extra_artifacts_fn)); let compiler = Compiler::new(Arc::new(config), Arc::new(ConsoleLogger)); let compiler_result = compiler.compile().await; diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration_test.rs b/compiler/crates/relay-compiler/tests/relay_compiler_integration_test.rs index bde376b43a4fd..ed218f3a3b364 100644 --- a/compiler/crates/relay-compiler/tests/relay_compiler_integration_test.rs +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<60f611a97f09b3caa35c440952f8059b>> + * @generated SignedSource<<0d99ccbe5aade8069b9aa9abad3aba77>> */ mod relay_compiler_integration; @@ -61,6 +61,27 @@ async fn custom_scalar_variable_default_arg_non_strict() { test_fixture(transform_fixture, file!(), "custom_scalar_variable_default_arg_non_strict.input", "relay_compiler_integration/fixtures/custom_scalar_variable_default_arg_non_strict.expected", input, expected).await; } +#[tokio::test] +async fn preloadable_query_flow() { + let input = include_str!("relay_compiler_integration/fixtures/preloadable_query_flow.input"); + let expected = include_str!("relay_compiler_integration/fixtures/preloadable_query_flow.expected"); + test_fixture(transform_fixture, file!(), "preloadable_query_flow.input", "relay_compiler_integration/fixtures/preloadable_query_flow.expected", input, expected).await; +} + +#[tokio::test] +async fn preloadable_query_javascript() { + let input = include_str!("relay_compiler_integration/fixtures/preloadable_query_javascript.input"); + let expected = include_str!("relay_compiler_integration/fixtures/preloadable_query_javascript.expected"); + test_fixture(transform_fixture, file!(), "preloadable_query_javascript.input", "relay_compiler_integration/fixtures/preloadable_query_javascript.expected", input, expected).await; +} + +#[tokio::test] +async fn preloadable_query_typescript() { + let input = include_str!("relay_compiler_integration/fixtures/preloadable_query_typescript.input"); + let expected = include_str!("relay_compiler_integration/fixtures/preloadable_query_typescript.expected"); + test_fixture(transform_fixture, file!(), "preloadable_query_typescript.input", "relay_compiler_integration/fixtures/preloadable_query_typescript.expected", input, expected).await; +} + #[tokio::test] async fn resolvers_schema_module() { let input = include_str!("relay_compiler_integration/fixtures/resolvers_schema_module.input"); diff --git a/compiler/crates/relay-config/src/project_config.rs b/compiler/crates/relay-config/src/project_config.rs index 64fe33009e640..d1293ed63b40c 100644 --- a/compiler/crates/relay-config/src/project_config.rs +++ b/compiler/crates/relay-config/src/project_config.rs @@ -373,17 +373,32 @@ impl ProjectConfig { ) -> PathBuf { let source_location = definition_name.location.source_location(); let artifact_name = definition_name.item.into(); - let filename = if let Some(extra_artifacts_config) = &self.extra_artifacts_config { - (extra_artifacts_config.filename_for_artifact)(source_location, artifact_name) + if let Some(extra_artifacts_config) = &self.extra_artifacts_config { + let filename = + (extra_artifacts_config.filename_for_artifact)(source_location, artifact_name); + + self.create_path_for_artifact(source_location, filename) } else { - match &self.typegen_config.language { - TypegenLanguage::Flow | TypegenLanguage::JavaScript => { - format!("{}.graphql.js", artifact_name) - } - TypegenLanguage::TypeScript => format!("{}.graphql.ts", artifact_name), + self.path_for_language_specific_artifact( + source_location, + format!("{}.graphql", artifact_name), + ) + } + } + + pub fn path_for_language_specific_artifact( + &self, + source_file: SourceLocationKey, + artifact_file_name: String, + ) -> PathBuf { + let filename = match &self.typegen_config.language { + TypegenLanguage::Flow | TypegenLanguage::JavaScript => { + format!("{}.js", artifact_file_name) } + TypegenLanguage::TypeScript => format!("{}.ts", artifact_file_name), }; - self.create_path_for_artifact(source_location, filename) + + self.create_path_for_artifact(source_file, filename) } /// Generates identifier for importing module at `target_module_path` from module at `importing_artifact_path`.