diff --git a/compiler/crates/common/src/feature_flags.rs b/compiler/crates/common/src/feature_flags.rs index 9cc2d8fc4b6e1..7d7e047b8519a 100644 --- a/compiler/crates/common/src/feature_flags.rs +++ b/compiler/crates/common/src/feature_flags.rs @@ -179,6 +179,14 @@ pub struct FeatureFlags { /// across a number of diffs. #[serde(default)] pub legacy_include_path_in_required_reader_nodes: FeatureFlag, + + /// Produce native GraphQL fragment spreads when spreading a fragment with + /// @relay(mask: false) into a query, instead of inlining the contents of + /// that fragment for each use. This significantly reduces the uncompressed + /// size of query text when several mask:false fragment spreads are present, + /// but has no impact on masking behavior. + #[serde(default)] + pub use_native_fragment_spreads_for_unmasked_fragments: bool, } #[derive(Debug, serde::Deserialize, Clone, Serialize, Default, JsonSchema)] diff --git a/compiler/crates/relay-compiler/relay-compiler-config-schema.json b/compiler/crates/relay-compiler/relay-compiler-config-schema.json index 3973a80e78c59..7a17b5f727a3f 100644 --- a/compiler/crates/relay-compiler/relay-compiler-config-schema.json +++ b/compiler/crates/relay-compiler/relay-compiler-config-schema.json @@ -841,6 +841,11 @@ } ] }, + "use_native_fragment_spreads_for_unmasked_fragments": { + "description": "Produce native GraphQL fragment spreads when spreading a fragment with @relay(mask: false) into a query, instead of inlining the contents of that fragment for each use. This significantly reduces the uncompressed size of query text when several mask:false fragment spreads are present, but has no impact on masking behavior.", + "default": false, + "type": "boolean" + }, "use_reader_module_imports": { "description": "Generate the `moduleImports` field in the Reader AST.", "default": { @@ -1082,6 +1087,7 @@ "text_artifacts": { "kind": "disabled" }, + "use_native_fragment_spreads_for_unmasked_fragments": false, "use_reader_module_imports": { "kind": "disabled" } diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.expected b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.expected new file mode 100644 index 0000000000000..ec10c32812ec6 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.expected @@ -0,0 +1,370 @@ +==================================== INPUT ==================================== +query unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery($isRelative: Boolean) { + me { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user @relay(mask: false) + nearest_neighbor { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user @relay(mask: false) + } + } +} + +fragment unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user on User { + url(relative: $isRelative) + canViewerLike + doesViewerLike + lastName + name +} + +%project_config% +{ + "language": "flow", + "jsModuleFormat": "haste", + "featureFlags": { + "use_native_fragment_spreads_for_unmasked_fragments": true + } +} +==================================== OUTPUT =================================== +{ + "fragment": { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "isRelative" + } + ], + "kind": "Fragment", + "metadata": null, + "name": "unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "relative", + "variableName": "isRelative" + } + ], + "kind": "ScalarField", + "name": "url", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "canViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "doesViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "nearest_neighbor", + "plural": false, + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "relative", + "variableName": "isRelative" + } + ], + "kind": "ScalarField", + "name": "url", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "canViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "doesViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "isRelative" + } + ], + "kind": "Operation", + "name": "unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "relative", + "variableName": "isRelative" + } + ], + "kind": "ScalarField", + "name": "url", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "canViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "doesViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "nearest_neighbor", + "plural": false, + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "relative", + "variableName": "isRelative" + } + ], + "kind": "ScalarField", + "name": "url", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "canViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "doesViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "3de23a25a89b722aee3f7748aa98647b", + "id": null, + "metadata": {}, + "name": "unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery", + "operationKind": "query", + "text": null + } +} + +QUERY: + +query unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery( + $isRelative: Boolean +) { + me { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user + nearest_neighbor { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user + id + } + id + } +} + +fragment unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user on User { + url(relative: $isRelative) + canViewerLike + doesViewerLike + lastName + name +} + + +{ + "argumentDefinitions": [ + { + "kind": "RootArgument", + "name": "isRelative" + } + ], + "kind": "Fragment", + "metadata": null, + "name": "unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "relative", + "variableName": "isRelative" + } + ], + "kind": "ScalarField", + "name": "url", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "canViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "doesViewerLike", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +} diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.graphql b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.graphql new file mode 100644 index 0000000000000..b54d742b373c3 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.graphql @@ -0,0 +1,25 @@ +query unmaskedFragmentSpreadsInQueryInlineDisabled_TestQuery($isRelative: Boolean) { + me { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user @relay(mask: false) + nearest_neighbor { + ...unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user @relay(mask: false) + } + } +} + +fragment unmaskedFragmentSpreadsInQueryInlineDisabled_UserUtil_user on User { + url(relative: $isRelative) + canViewerLike + doesViewerLike + lastName + name +} + +%project_config% +{ + "language": "flow", + "jsModuleFormat": "haste", + "featureFlags": { + "use_native_fragment_spreads_for_unmasked_fragments": true + } +} diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs index cda842e6e73d5..8ac840e912a71 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_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<<67aa79681f4c3c1406944dd059174dc7>> + * @generated SignedSource<<642d3b0a3533bbca281898c58d5d62a4>> */ mod compile_relay_artifacts; @@ -2028,6 +2028,13 @@ async fn unmasked_fragment_spreads_global_arguments() { test_fixture(transform_fixture, file!(), "unmasked-fragment-spreads-global-arguments.graphql", "compile_relay_artifacts/fixtures/unmasked-fragment-spreads-global-arguments.expected", input, expected).await; } +#[tokio::test] +async fn unmasked_fragment_spreads_in_query_inline_disabled() { + let input = include_str!("compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.graphql"); + let expected = include_str!("compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.expected"); + test_fixture(transform_fixture, file!(), "unmasked-fragment-spreads-in-query-inline-disabled.graphql", "compile_relay_artifacts/fixtures/unmasked-fragment-spreads-in-query-inline-disabled.expected", input, expected).await; +} + #[tokio::test] async fn unmasked_fragment_spreads_local_arguments_invalid() { let input = include_str!("compile_relay_artifacts/fixtures/unmasked-fragment-spreads-local-arguments.invalid.graphql"); diff --git a/compiler/crates/relay-transforms/src/apply_transforms.rs b/compiler/crates/relay-transforms/src/apply_transforms.rs index b611764033302..f9cb2a88caac8 100644 --- a/compiler/crates/relay-transforms/src/apply_transforms.rs +++ b/compiler/crates/relay-transforms/src/apply_transforms.rs @@ -186,7 +186,13 @@ fn apply_common_transforms( false, ) }); - program = log_event.time("mask", || mask(&program)); + + if !project_config + .feature_flags + .use_native_fragment_spreads_for_unmasked_fragments + { + program = log_event.time("mask", || mask(&program)); + } program = log_event.time("transform_defer_stream", || { transform_defer_stream( &program, @@ -277,6 +283,8 @@ fn apply_reader_transforms( None, )?; + program = log_event.time("mask", || mask(&program)); + program = log_event.time("required_directive", || required_directive(&program))?; program = log_event.time("catch_directive", || catch_directive(&program))?; @@ -574,6 +582,13 @@ fn apply_operation_text_transforms( ) })?; + if !project_config + .feature_flags + .use_native_fragment_spreads_for_unmasked_fragments + { + program = log_event.time("mask", || mask(&program)); + } + log_event.time("validate_global_variables", || { validate_global_variables(&program) })?;