Skip to content

Commit 41d8aef

Browse files
authored
perf: don't load all shopify nodes into memory at once and avoid creating many temp objects (#39138)
* perf: don't load all shopify nodes into memory at once and avoid creating many temp objects * chore: remove debug activities
1 parent 67668c9 commit 41d8aef

File tree

3 files changed

+53
-42
lines changed

3 files changed

+53
-42
lines changed

packages/gatsby-source-shopify/__tests__/fixtures/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ export function mockGatsbyApi(): NodePluginArgs {
3939
getNodesByType: jest.fn((type: string) =>
4040
require(`../fixtures/shopify-nodes/${type}.json`)
4141
),
42+
getNode: jest.fn((nodeId: string) => {
43+
const fixtureFiles = fs.readdirSync(path.join(__dirname, `../fixtures/shopify-nodes`))
44+
for (const fixtureFile of fixtureFiles) {
45+
const nodes = require(`../fixtures/shopify-nodes/${fixtureFile}`)
46+
const node = nodes.find((n: any) => n.id === nodeId)
47+
if (node) {
48+
return node
49+
}
50+
}
51+
52+
return null
53+
})
4254
} as unknown as NodePluginArgs
4355
}
4456

packages/gatsby-source-shopify/src/update-cache.ts

+39-36
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const deletionCounter: { [key: string]: number } = {}
1010
* invalidateNode - Recursive function that returns an array of node ids that are invalidated
1111
* @param gatsbyApi - Gatsby Helpers
1212
* @param pluginOptions - Plugin Options Object
13-
* @param nodeMap - Map Object of all nodes that haven't been deleted
1413
* @param id - The root node to invalidate
1514
*
1615
* Note: This function is designed to receive a single top-level node on the first pass
@@ -19,24 +18,26 @@ const deletionCounter: { [key: string]: number } = {}
1918
function invalidateNode(
2019
gatsbyApi: SourceNodesArgs,
2120
pluginOptions: IShopifyPluginOptions,
22-
nodeMap: IShopifyNodeMap,
23-
id: string
24-
): Array<string> {
21+
id: string,
22+
invalidatedNodeIds = new Set<string>()
23+
): Set<string> {
2524
const { typePrefix } = pluginOptions
2625

27-
const node = nodeMap[id]
28-
let invalidatedNodeIds: Array<string> = []
26+
const node = gatsbyApi.getNode(id)
2927

3028
if (node) {
31-
invalidatedNodeIds.push(node.id)
29+
invalidatedNodeIds.add(node.id)
3230
const type = node.internal.type.replace(`${typePrefix}Shopify`, ``)
3331
const { coupledNodeFields } = shopifyTypes[type]
3432

3533
if (coupledNodeFields) {
3634
for (const field of coupledNodeFields) {
3735
for (const coupledNodeId of node[field] as Array<string>) {
38-
invalidatedNodeIds = invalidatedNodeIds.concat(
39-
invalidateNode(gatsbyApi, pluginOptions, nodeMap, coupledNodeId)
36+
invalidateNode(
37+
gatsbyApi,
38+
pluginOptions,
39+
coupledNodeId,
40+
invalidatedNodeIds
4041
)
4142
}
4243
}
@@ -73,39 +74,41 @@ export async function updateCache(
7374
pluginOptions: IShopifyPluginOptions,
7475
lastBuildTime: Date
7576
): Promise<void> {
76-
const { typePrefix } = pluginOptions
77-
78-
const nodeMap: IShopifyNodeMap = Object.keys(shopifyTypes)
79-
.map(type => gatsbyApi.getNodesByType(`${typePrefix}Shopify${type}`))
80-
.reduce((acc, value) => acc.concat(value), [])
81-
.reduce((acc, value) => {
82-
return { ...acc, [value.id]: value }
83-
}, {})
84-
8577
const { fetchDestroyEventsSince } = eventsApi(pluginOptions)
8678
const destroyEvents = await fetchDestroyEventsSince(lastBuildTime)
8779

88-
const invalidatedNodeIds = destroyEvents.reduce<Array<string>>(
89-
(acc, value) => {
90-
const shopifyId = `gid://shopify/${value.subject_type}/${value.subject_id}`
91-
const nodeId = createNodeId(shopifyId, gatsbyApi, pluginOptions)
92-
return acc.concat(
93-
invalidateNode(gatsbyApi, pluginOptions, nodeMap, nodeId)
94-
)
95-
},
96-
[]
97-
)
98-
99-
for (const node of Object.values(nodeMap)) {
100-
if (invalidatedNodeIds.includes(node.id)) {
101-
gatsbyApi.actions.deleteNode(node)
102-
reportNodeDeletion(gatsbyApi, node)
103-
} else {
104-
gatsbyApi.actions.touchNode(node)
80+
const invalidatedNodeIds = new Set<string>()
81+
for (const value of destroyEvents) {
82+
const shopifyId = `gid://shopify/${value.subject_type}/${value.subject_id}`
83+
const nodeId = createNodeId(shopifyId, gatsbyApi, pluginOptions)
84+
invalidateNode(gatsbyApi, pluginOptions, nodeId, invalidatedNodeIds)
85+
}
86+
87+
// don't block event loop for too long
88+
await new Promise(resolve => setImmediate(resolve))
89+
90+
for (const shopifyType of Object.keys(shopifyTypes)) {
91+
{
92+
// closure so we can let Node GC `nodes` (if needed) before next iteration
93+
const nodes = gatsbyApi.getNodesByType(
94+
`${pluginOptions.typePrefix}Shopify${shopifyType}`
95+
) as Array<IShopifyNode>
96+
97+
for (const node of nodes) {
98+
if (invalidatedNodeIds.has(node.id)) {
99+
gatsbyApi.actions.deleteNode(node)
100+
reportNodeDeletion(gatsbyApi, node)
101+
} else {
102+
gatsbyApi.actions.touchNode(node)
103+
}
104+
}
105105
}
106+
107+
// don't block event loop for too long
108+
await new Promise(resolve => setImmediate(resolve))
106109
}
107110

108-
if (invalidatedNodeIds.length > 0) {
111+
if (invalidatedNodeIds.size > 0) {
109112
reportDeletionSummary(gatsbyApi)
110113
}
111114
}

packages/gatsby-source-shopify/types/interface.d.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ interface IBulkOperationNode {
3333
query: string
3434
}
3535

36-
interface IShopifyNodeMap {
37-
[key: string]: IShopifyNode
38-
}
39-
4036
interface ICurrentBulkOperationResponse {
4137
currentBulkOperation: {
4238
id: string
@@ -129,7 +125,7 @@ interface IShopifyImage extends IShopifyNode {
129125

130126
interface IShopifyNode {
131127
id: string
132-
shopifyId: string
128+
shopifyId?: string
133129
internal: {
134130
type: string
135131
mediaType?: string
@@ -147,7 +143,7 @@ interface IShopifyPluginOptions {
147143
shopifyConnections: Array<string>
148144
typePrefix: string
149145
salesChannel: string
150-
prioritize?: boolean,
146+
prioritize?: boolean
151147
apiVersion: string
152148
}
153149

0 commit comments

Comments
 (0)