Skip to content

Commit

Permalink
@autoCodesplit
Browse files Browse the repository at this point in the history
  • Loading branch information
zth committed Jul 6, 2024
1 parent acb4a4f commit bf35b49
Show file tree
Hide file tree
Showing 25 changed files with 1,182 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/relay
Submodule relay updated 42 files
+19 −0 compiler/crates/relay-compiler/src/artifact_content/content.rs
+234 −3 compiler/crates/relay-compiler/src/artifact_content/rescript_relay_utils.rs
+1 −1 compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs
+7 −0 compiler/crates/relay-schema/src/relay-extensions.graphql
+8 −3 compiler/crates/relay-transforms/src/apply_transforms.rs
+1 −1 compiler/crates/relay-transforms/src/flatten.rs
+4 −2 compiler/crates/relay-transforms/src/lib.rs
+117 −0 compiler/crates/relay-transforms/src/rescript_relay_inline_auto_codesplit.rs
+0 −121 compiler/crates/relay-transforms/src/rescript_relay_remove_custom_directives.rs
+86 −0 compiler/crates/relay-transforms/src/rescript_relay_transform_auto_codesplit.rs
+2 −2 compiler/crates/relay-transforms/src/util.rs
+3 −3 compiler/crates/relay-typegen/src/rescript_relay_visitor.rs
+7 −5 compiler/crates/relay-typegen/src/rescript_utils.rs
+1 −0 compiler/test-project-res-preloadable/persisted_queries.json
+5 −2 compiler/test-project-res-preloadable/relay.config.js
+10 −0 compiler/test-project-res-preloadable/src/Test_preloadedQuery.res
+6 −0 compiler/test-project-res-preloadable/src/UserAvatar.res
+6 −0 compiler/test-project-res-preloadable/src/UserName.res
+13 −0 compiler/test-project-res-preloadable/src/__generated__/RelaySchemaAssets_graphql.res
+271 −0 compiler/test-project-res-preloadable/src/__generated__/TestPreloadedQueryWithCodesplitQuery_graphql.res
+71 −0 ...test-project-res-preloadable/src/__generated__/TestPreloadedQueryWithCodesplitQuery_preloadable_graphql.res
+7 −5 ...ler/test-project-res-preloadable/src/__generated__/TestPreloadedQueryWithProvidedVariablesQuery_graphql.res
+5 −5 ...ject-res-preloadable/src/__generated__/TestPreloadedQueryWithProvidedVariablesQuery_preloadable_graphql.res
+3 −1 compiler/test-project-res-preloadable/src/__generated__/TestPreloadedQuery_graphql.res
+1 −1 compiler/test-project-res-preloadable/src/__generated__/TestPreloadedQuery_preloadable_graphql.res
+73 −0 compiler/test-project-res-preloadable/src/__generated__/UserAvatar_user_graphql.res
+68 −0 compiler/test-project-res-preloadable/src/__generated__/UserName_user_graphql.res
+5 −0 compiler/test-project-res/schema.graphql
+5 −0 compiler/test-project-res/src/GroupAvatar.res
+5 −0 compiler/test-project-res/src/RichContent.res
+10 −0 compiler/test-project-res/src/Test_aliasedFragments.res
+16 −0 compiler/test-project-res/src/Test_codesplit.res
+6 −0 compiler/test-project-res/src/UserAvatar.res
+6 −0 compiler/test-project-res/src/UserName.res
+5 −0 compiler/test-project-res/src/UserNode.res
+60 −0 compiler/test-project-res/src/__generated__/GroupAvatar_group_graphql.res
+60 −0 compiler/test-project-res/src/__generated__/RichContent_content_graphql.res
+100 −0 compiler/test-project-res/src/__generated__/TestAliasedFragments_required_graphql.res
+386 −0 compiler/test-project-res/src/__generated__/TestCodesplitQuery_graphql.res
+73 −0 compiler/test-project-res/src/__generated__/UserAvatar_user_graphql.res
+68 −0 compiler/test-project-res/src/__generated__/UserName_user_graphql.res
+68 −0 compiler/test-project-res/src/__generated__/UserNode_node_graphql.res
11 changes: 11 additions & 0 deletions packages/rescript-relay/__tests__/GroupAvatar.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Fragment = %relay(`
fragment GroupAvatar_group on Group {
name
}
`)

@react.component
let make = (~group: RescriptRelay.fragmentRefs<[#GroupAvatar_group]>) => {
let group = Fragment.use(group)
React.string("Group name: " ++ group.name)
}
12 changes: 12 additions & 0 deletions packages/rescript-relay/__tests__/HasNameComponent.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Fragment = %relay(`
fragment HasNameComponent_hasName on HasName {
name
}
`)

@react.component
let make = (~hasName: RescriptRelay.fragmentRefs<[#HasNameComponent_hasName]>) => {
let hasName = Fragment.use(hasName)

<div> {React.string("Has name: " ++ hasName.name)} </div>
}
4 changes: 3 additions & 1 deletion packages/rescript-relay/__tests__/RelayEnv.res
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ let fetchQuery: RescriptRelay.Network.fetchFunctionPromise = async (
)

if Response.ok(resp) {
await Response.json(resp)
let json = await Response.json(resp)
RescriptRelay.Network.preloadResources(operation, json)
json
} else {
raise(Graphql_error("Request failed: " ++ Response.statusText(resp)))
}
Expand Down
12 changes: 12 additions & 0 deletions packages/rescript-relay/__tests__/RichContent.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Fragment = %relay(`
fragment RichContent_content on RichContent {
content
}
`)

@react.component
let make = (~content: RescriptRelay.fragmentRefs<[#RichContent_content]>) => {
let content = Fragment.use(content)

<div> {React.string("Rich content: " ++ content.content)} </div>
}
174 changes: 174 additions & 0 deletions packages/rescript-relay/__tests__/Test_codesplit-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
require("@testing-library/jest-dom/extend-expect");
const t = require("@testing-library/react");
const React = require("react");
const queryMock = require("./queryMock");
const ReactTestUtils = require("react-dom/test-utils");
const query = require("./__generated__/TestCodesplitQuery_graphql.bs");

const { test_codesplit } = require("./Test_codesplit.bs");

describe("Autocodesplits", () => {
let preloadUserHasRun = false;
let preloadUserFn;
const targetUserIndex = query.node.params.metadata.codesplits.findIndex(
([p, _]) => p === "member.$$u$$User"
);

let preloadHasNameHasRun = false;
let preloadHasNameFn;
const targetHasNameIndex = query.node.params.metadata.codesplits.findIndex(
([p, _]) => p === "member.$$i$$HasName"
);

let preloadLinkedFieldHasRun = false;
let preloadLinkedFieldFn;
const targetLinkedFieldIndex =
query.node.params.metadata.codesplits.findIndex(
([p, _]) => p === "member.$$u$$User.description"
);

beforeEach(() => {
// Very hacky way to check if the preload functions has run
preloadUserFn = query.node.params.metadata.codesplits[targetUserIndex][1];
query.node.params.metadata.codesplits[targetUserIndex][1] = () => {
preloadUserHasRun = true;
return preloadUserFn();
};

preloadHasNameFn =
query.node.params.metadata.codesplits[targetHasNameIndex][1];
query.node.params.metadata.codesplits[targetHasNameIndex][1] = () => {
preloadHasNameHasRun = true;
return preloadHasNameFn();
};

preloadLinkedFieldFn =
query.node.params.metadata.codesplits[targetLinkedFieldIndex][1];
query.node.params.metadata.codesplits[targetLinkedFieldIndex][1] = () => {
preloadLinkedFieldHasRun = true;
return preloadLinkedFieldFn();
};
});

afterEach(() => {
query.node.params.metadata.codesplits[targetUserIndex][1] = preloadUserFn;
preloadUserHasRun = false;

query.node.params.metadata.codesplits[targetHasNameIndex][1] =
preloadHasNameFn;
preloadHasNameHasRun = false;

query.node.params.metadata.codesplits[targetLinkedFieldIndex][1] =
preloadLinkedFieldFn;
preloadLinkedFieldHasRun = false;
});

test("preload runs when query response matches", async () => {
queryMock.mockQuery({
name: "TestCodesplitQuery",
data: {
member: {
__typename: "User",
id: "user-1",
firstName: "First",
lastName: "Last",
avatarUrl: "avatar-here",
description: {
content: "Rich content!",
},
},
},
});

// No preload yet
expect(preloadUserHasRun).toBe(false);
expect(preloadHasNameHasRun).toBe(false);
expect(preloadLinkedFieldHasRun).toBe(false);

t.render(test_codesplit());

await t.screen.findByText("Render");

// Preloads as soon as data has loaded
expect(preloadUserHasRun).toBe(true);
expect(preloadHasNameHasRun).toBe(false);
expect(preloadLinkedFieldHasRun).toBe(true);

ReactTestUtils.act(() => {
t.fireEvent.click(t.screen.getByText("Render"));
});

await t.screen.findByText("User avatarUrl: avatar-here");
await t.screen.findByText("User name: First Last");
await t.screen.findByText("Rich content: Rich content!");
});

test("preload does not run when linked field does not match", async () => {
queryMock.mockQuery({
name: "TestCodesplitQuery",
data: {
member: {
__typename: "User",
id: "user-1",
firstName: "First",
lastName: "Last",
avatarUrl: "avatar-here",
description: null,
},
},
});

expect(preloadUserHasRun).toBe(false);
expect(preloadHasNameHasRun).toBe(false);
expect(preloadLinkedFieldHasRun).toBe(false);

t.render(test_codesplit());

await t.screen.findByText("Render");

// Preloads as soon as data has loaded
expect(preloadUserHasRun).toBe(true);
expect(preloadHasNameHasRun).toBe(false);
expect(preloadLinkedFieldHasRun).toBe(false);

ReactTestUtils.act(() => {
t.fireEvent.click(t.screen.getByText("Render"));
});

await t.screen.findByText("User avatarUrl: avatar-here");
await t.screen.findByText("User name: First Last");
});

test("preload runs for interface", async () => {
queryMock.mockQuery({
name: "TestCodesplitQuery",
data: {
member: {
__typename: "Group",
id: "group-1",
name: "A Group",
__isHasName: "Group",
},
},
});

// No preload yet
expect(preloadUserHasRun).toBe(false);
expect(preloadHasNameHasRun).toBe(false);

t.render(test_codesplit());

await t.screen.findByText("Render");

// No preloads now either
expect(preloadUserHasRun).toBe(false);
expect(preloadHasNameHasRun).toBe(true);

ReactTestUtils.act(() => {
t.fireEvent.click(t.screen.getByText("Render"));
});

await t.screen.findByText("Group name: A Group");
await t.screen.findByText("Has name: A Group");
});
});
73 changes: 73 additions & 0 deletions packages/rescript-relay/__tests__/Test_codesplit.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module Query = %relay(`
query TestCodesplitQuery {
member(id: "1") {
...HasNameComponent_hasName @autoCodesplit @alias
... on User {
...UserAvatar_user @autoCodesplit @alias
description {
...RichContent_content @autoCodesplit @alias
}
}
... on Group {
...GroupAvatar_group @autoCodesplit @alias
}
}
}
`)

module Test = {
@react.component
let make = () => {
let query = Query.use(~variables=())
let (shouldRender, setShouldRender) = React.useState(() => false)

switch (shouldRender, query.member) {
| (false, _) =>
<button
onClick={_ => {
setShouldRender(_ => true)
}}>
{React.string("Render")}
</button>
| (true, None) => React.string("not found")
| (true, Some(member)) =>
open Query.CodesplitComponents

<div>
{switch member {
| {hasNameComponent_hasName: Some(hasNameComponent_hasName)} =>
<HasNameComponent hasName=hasNameComponent_hasName.fragmentRefs />
| _ => React.null
}}
{switch member {
| {groupAvatar_group: Some(groupAvatar_group)} =>
<GroupAvatar group=groupAvatar_group.fragmentRefs />
| {userAvatar_user: Some(userAvatar_user), description} =>
<>
<UserAvatar user=userAvatar_user.fragmentRefs />
{switch description {
| Some({richContent_content}) =>
<RichContent content=richContent_content.fragmentRefs />
| None => React.null
}}
</>
| _ => React.null
}}
</div>
}
}
}

@live
let test_codesplit = () => {
let network = RescriptRelay.Network.makePromiseBased(~fetchFunction=RelayEnv.fetchQuery)

let environment = RescriptRelay.Environment.make(
~network,
~store=RescriptRelay.Store.make(~source=RescriptRelay.RecordSource.make()),
)

<TestProviders.Wrapper environment>
<Test />
</TestProviders.Wrapper>
}
20 changes: 20 additions & 0 deletions packages/rescript-relay/__tests__/UserAvatar.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Fragment = %relay(`
fragment UserAvatar_user on User {
avatarUrl
...UserName_user @autoCodesplit
}
`)

@react.component
let make = (~user: RescriptRelay.fragmentRefs<[#UserAvatar_user]>) => {
let user = Fragment.use(user)

open Fragment.Operation.CodesplitComponents

<>
<div>
{React.string("User avatarUrl: " ++ user.avatarUrl->Belt.Option.getWithDefault("-"))}
</div>
<UserName user=user.fragmentRefs />
</>
}
12 changes: 12 additions & 0 deletions packages/rescript-relay/__tests__/UserName.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Fragment = %relay(`
fragment UserName_user on User {
firstName
lastName
}
`)

@react.component
let make = (~user: RescriptRelay.fragmentRefs<[#UserName_user]>) => {
let user = Fragment.use(user)
<div> {React.string("User name: " ++ user.firstName ++ " " ++ user.lastName)} </div>
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bf35b49

Please sign in to comment.