|
1 | | -// @flow |
2 | | - |
3 | 1 | const _ = require(`lodash`) |
4 | | -const Queue = require(`better-queue`) |
5 | | -// const convertHrtime = require(`convert-hrtime`) |
6 | | -const { store, emitter } = require(`../redux`) |
7 | | -const { boundActionCreators } = require(`../redux/actions`) |
8 | | -const report = require(`gatsby-cli/lib/reporter`) |
| 2 | +const { store } = require(`../redux`) |
| 3 | +const { hasFlag, FLAG_ERROR_EXTRACTION } = require(`../redux/reducers/queries`) |
9 | 4 | const queryQueue = require(`./queue`) |
10 | | -const { GraphQLRunner } = require(`./graphql-runner`) |
11 | | -const pageDataUtil = require(`../utils/page-data`) |
12 | | - |
13 | | -const seenIdsWithoutDataDependencies = new Set() |
14 | | -let queuedDirtyActions = [] |
15 | | -const extractedQueryIds = new Set() |
16 | | - |
17 | | -// Remove pages from seenIdsWithoutDataDependencies when they're deleted |
18 | | -// so their query will be run again if they're created again. |
19 | | -emitter.on(`DELETE_PAGE`, action => { |
20 | | - seenIdsWithoutDataDependencies.delete(action.payload.path) |
21 | | -}) |
22 | | - |
23 | | -emitter.on(`CREATE_NODE`, action => { |
24 | | - queuedDirtyActions.push(action) |
25 | | -}) |
26 | | - |
27 | | -emitter.on(`DELETE_NODE`, action => { |
28 | | - queuedDirtyActions.push({ payload: action.payload }) |
29 | | -}) |
30 | 5 |
|
31 | | -// /////////////////////////////////////////////////////////////////// |
32 | | -// Calculate dirty static/page queries |
33 | | - |
34 | | -const popExtractedQueries = () => { |
35 | | - const queries = [...extractedQueryIds] |
36 | | - extractedQueryIds.clear() |
37 | | - return queries |
38 | | -} |
39 | | - |
40 | | -const findIdsWithoutDataDependencies = state => { |
41 | | - const allTrackedIds = new Set() |
42 | | - const boundAddToTrackedIds = allTrackedIds.add.bind(allTrackedIds) |
43 | | - state.componentDataDependencies.nodes.forEach(dependenciesOnNode => { |
44 | | - dependenciesOnNode.forEach(boundAddToTrackedIds) |
45 | | - }) |
46 | | - state.componentDataDependencies.connections.forEach( |
47 | | - dependenciesOnConnection => { |
48 | | - dependenciesOnConnection.forEach(boundAddToTrackedIds) |
| 6 | +/** |
| 7 | + * Calculates the set of dirty query IDs (page.paths, or staticQuery.id's). |
| 8 | + * |
| 9 | + * Dirty state is tracked in `queries` reducer, here we simply filter |
| 10 | + * them from all tracked queries. |
| 11 | + */ |
| 12 | +const calcDirtyQueryIds = state => { |
| 13 | + const { trackedQueries, trackedComponents, deletedQueries } = state.queries |
| 14 | + |
| 15 | + const queriesWithBabelErrors = new Set() |
| 16 | + for (const component of trackedComponents.values()) { |
| 17 | + if (hasFlag(component.errors, FLAG_ERROR_EXTRACTION)) { |
| 18 | + for (const queryId of component.pages) { |
| 19 | + queriesWithBabelErrors.add(queryId) |
| 20 | + } |
49 | 21 | } |
50 | | - ) |
51 | | - |
52 | | - // Get list of paths not already tracked and run the queries for these |
53 | | - // paths. |
54 | | - const notTrackedIds = new Set( |
55 | | - [ |
56 | | - ...Array.from(state.pages.values(), p => p.path), |
57 | | - ...[...state.staticQueryComponents.values()].map(c => c.id), |
58 | | - ].filter( |
59 | | - x => !allTrackedIds.has(x) && !seenIdsWithoutDataDependencies.has(x) |
60 | | - ) |
61 | | - ) |
62 | | - |
63 | | - // Add new IDs to our seen array so we don't keep trying to run queries for them. |
64 | | - // Pages without queries can't be tracked. |
65 | | - for (const notTrackedId of notTrackedIds) { |
66 | | - seenIdsWithoutDataDependencies.add(notTrackedId) |
67 | 22 | } |
68 | | - |
69 | | - return notTrackedIds |
70 | | -} |
71 | | - |
72 | | -const popNodeQueries = state => { |
73 | | - const actions = _.uniq(queuedDirtyActions, a => a.payload.id) |
74 | | - const uniqDirties = actions.reduce((dirtyIds, action) => { |
75 | | - const node = action.payload |
76 | | - |
77 | | - if (!node || !node.id || !node.internal.type) return dirtyIds |
78 | | - |
79 | | - // Find components that depend on this node so are now dirty. |
80 | | - if (state.componentDataDependencies.nodes.has(node.id)) { |
81 | | - state.componentDataDependencies.nodes.get(node.id).forEach(n => { |
82 | | - if (n) { |
83 | | - dirtyIds.add(n) |
84 | | - } |
85 | | - }) |
| 23 | + // Note: trackedQueries contains both - page and static query ids |
| 24 | + const dirtyQueryIds = [] |
| 25 | + for (const [queryId, query] of trackedQueries) { |
| 26 | + if (deletedQueries.has(queryId)) { |
| 27 | + continue |
86 | 28 | } |
87 | | - |
88 | | - // Find connections that depend on this node so are now invalid. |
89 | | - if (state.componentDataDependencies.connections.has(node.internal.type)) { |
90 | | - state.componentDataDependencies.connections |
91 | | - .get(node.internal.type) |
92 | | - .forEach(n => { |
93 | | - if (n) { |
94 | | - dirtyIds.add(n) |
95 | | - } |
96 | | - }) |
| 29 | + if (query.dirty > 0 && !queriesWithBabelErrors.has(queryId)) { |
| 30 | + dirtyQueryIds.push(queryId) |
97 | 31 | } |
98 | | - |
99 | | - return dirtyIds |
100 | | - }, new Set()) |
101 | | - |
102 | | - boundActionCreators.deleteComponentsDependencies([...uniqDirties]) |
103 | | - |
104 | | - queuedDirtyActions = [] |
105 | | - return uniqDirties |
106 | | -} |
107 | | - |
108 | | -const popNodeAndDepQueries = state => { |
109 | | - const nodeQueries = popNodeQueries(state) |
110 | | - |
111 | | - const noDepQueries = findIdsWithoutDataDependencies(state) |
112 | | - |
113 | | - return _.uniq([...nodeQueries, ...noDepQueries]) |
114 | | -} |
115 | | - |
116 | | -/** |
117 | | - * Calculates the set of dirty query IDs (page.paths, or |
118 | | - * staticQuery.hash's). These are queries that: |
119 | | - * |
120 | | - * - depend on nodes or node collections (via |
121 | | - * `actions.createPageDependency`) that have changed. |
122 | | - * - do NOT have node dependencies. Since all queries should return |
123 | | - * data, then this implies that node dependencies have not been |
124 | | - * tracked, and therefore these queries haven't been run before |
125 | | - * - have been recently extracted (see `./query-watcher.js`) |
126 | | - * |
127 | | - * Note, this function pops queries off internal queues, so it's up |
128 | | - * to the caller to reference the results |
129 | | - */ |
130 | | - |
131 | | -const calcDirtyQueryIds = state => |
132 | | - _.union(popNodeAndDepQueries(state), popExtractedQueries()) |
133 | | - |
134 | | -/** |
135 | | - * Same as `calcDirtyQueryIds`, except that we only include extracted |
136 | | - * queries that depend on nodes or haven't been run yet. We do this |
137 | | - * because the page component reducer/machine always enqueues |
138 | | - * extractedQueryIds but during bootstrap we may not want to run those |
139 | | - * page queries if their data hasn't changed since the last time we |
140 | | - * ran Gatsby. |
141 | | - */ |
142 | | -const calcInitialDirtyQueryIds = state => { |
143 | | - const nodeAndNoDepQueries = popNodeAndDepQueries(state) |
144 | | - |
145 | | - const extractedQueriesThatNeedRunning = _.intersection( |
146 | | - popExtractedQueries(), |
147 | | - nodeAndNoDepQueries |
148 | | - ) |
149 | | - return _.union(extractedQueriesThatNeedRunning, nodeAndNoDepQueries) |
| 32 | + } |
| 33 | + return dirtyQueryIds |
150 | 34 | } |
151 | 35 |
|
152 | 36 | /** |
@@ -176,37 +60,14 @@ const createStaticQueryJob = (state, queryId) => { |
176 | 60 | const component = state.staticQueryComponents.get(queryId) |
177 | 61 | const { hash, id, query, componentPath } = component |
178 | 62 | return { |
179 | | - id: hash, |
| 63 | + id: queryId, |
180 | 64 | hash, |
181 | 65 | query, |
182 | 66 | componentPath, |
183 | 67 | context: { path: id }, |
184 | 68 | } |
185 | 69 | } |
186 | 70 |
|
187 | | -/** |
188 | | - * Creates activity object which: |
189 | | - * - creates actual progress activity if there are any queries that need to be run |
190 | | - * - creates activity-like object that just cancels pending activity if there are no queries to run |
191 | | - */ |
192 | | -const createQueryRunningActivity = (queryJobsCount, parentSpan) => { |
193 | | - if (queryJobsCount) { |
194 | | - const activity = report.createProgress(`run queries`, queryJobsCount, 0, { |
195 | | - id: `query-running`, |
196 | | - parentSpan, |
197 | | - }) |
198 | | - activity.start() |
199 | | - return activity |
200 | | - } else { |
201 | | - return { |
202 | | - done: () => { |
203 | | - report.completeActivity(`query-running`) |
204 | | - }, |
205 | | - tick: () => {}, |
206 | | - } |
207 | | - } |
208 | | -} |
209 | | - |
210 | 71 | const processStaticQueries = async ( |
211 | 72 | queryIds, |
212 | 73 | { state, activity, graphqlRunner, graphqlTracing } |
@@ -258,120 +119,10 @@ const createPageQueryJob = (state, page) => { |
258 | 119 | } |
259 | 120 | } |
260 | 121 |
|
261 | | -// /////////////////////////////////////////////////////////////////// |
262 | | -// Listener for gatsby develop |
263 | | - |
264 | | -// Initialized via `startListening` |
265 | | -let listenerQueue |
266 | | - |
267 | | -/** |
268 | | - * Run any dirty queries. See `calcQueries` for what constitutes a |
269 | | - * dirty query |
270 | | - */ |
271 | | -const runQueuedQueries = () => { |
272 | | - if (listenerQueue) { |
273 | | - const state = store.getState() |
274 | | - const { staticQueryIds, pageQueryIds } = groupQueryIds( |
275 | | - calcDirtyQueryIds(state) |
276 | | - ) |
277 | | - const pages = _.filter(pageQueryIds.map(id => state.pages.get(id))) |
278 | | - const queryJobs = [ |
279 | | - ...staticQueryIds.map(id => createStaticQueryJob(state, id)), |
280 | | - ...pages.map(page => createPageQueryJob(state, page)), |
281 | | - ] |
282 | | - listenerQueue.push(queryJobs) |
283 | | - } |
284 | | -} |
285 | | - |
286 | | -/** |
287 | | - * Starts a background process that processes any dirty queries |
288 | | - * whenever one of the following occurs: |
289 | | - * |
290 | | - * 1. A node has changed (but only after the api call has finished |
291 | | - * running) |
292 | | - * 2. A component query (e.g. by editing a React Component) has |
293 | | - * changed |
294 | | - * |
295 | | - * For what constitutes a dirty query, see `calcQueries` |
296 | | - */ |
297 | | - |
298 | | -const startListeningToDevelopQueue = ({ graphqlTracing } = {}) => { |
299 | | - // We use a queue to process batches of queries so that they are |
300 | | - // processed consecutively |
301 | | - let graphqlRunner = null |
302 | | - const developQueue = queryQueue.createDevelopQueue(() => { |
303 | | - if (!graphqlRunner) { |
304 | | - graphqlRunner = new GraphQLRunner(store, { graphqlTracing }) |
305 | | - } |
306 | | - return graphqlRunner |
307 | | - }) |
308 | | - listenerQueue = new Queue((queryJobs, callback) => { |
309 | | - const activity = createQueryRunningActivity(queryJobs.length) |
310 | | - |
311 | | - const onFinish = (...arg) => { |
312 | | - pageDataUtil.enqueueFlush() |
313 | | - activity.done() |
314 | | - return callback(...arg) |
315 | | - } |
316 | | - |
317 | | - return queryQueue |
318 | | - .processBatch(developQueue, queryJobs, activity) |
319 | | - .then(() => onFinish(null)) |
320 | | - .catch(onFinish) |
321 | | - }) |
322 | | - |
323 | | - emitter.on(`API_RUNNING_START`, () => { |
324 | | - report.pendingActivity({ id: `query-running` }) |
325 | | - }) |
326 | | - |
327 | | - emitter.on(`API_RUNNING_QUEUE_EMPTY`, runQueuedQueries) |
328 | | - ;[ |
329 | | - `DELETE_CACHE`, |
330 | | - `CREATE_NODE`, |
331 | | - `DELETE_NODE`, |
332 | | - `DELETE_NODES`, |
333 | | - `SET_SCHEMA_COMPOSER`, |
334 | | - `SET_SCHEMA`, |
335 | | - `ADD_FIELD_TO_NODE`, |
336 | | - `ADD_CHILD_NODE_TO_PARENT_NODE`, |
337 | | - ].forEach(eventType => { |
338 | | - emitter.on(eventType, event => { |
339 | | - graphqlRunner = null |
340 | | - }) |
341 | | - }) |
342 | | -} |
343 | | - |
344 | | -const enqueueExtractedQueryId = pathname => { |
345 | | - extractedQueryIds.add(pathname) |
346 | | -} |
347 | | - |
348 | | -const getPagesForComponent = componentPath => { |
349 | | - const state = store.getState() |
350 | | - return [...state.pages.values()].filter( |
351 | | - p => p.componentPath === componentPath |
352 | | - ) |
353 | | -} |
354 | | - |
355 | | -const enqueueExtractedPageComponent = componentPath => { |
356 | | - const pages = getPagesForComponent(componentPath) |
357 | | - // Remove page data dependencies before re-running queries because |
358 | | - // the changing of the query could have changed the data dependencies. |
359 | | - // Re-running the queries will add back data dependencies. |
360 | | - boundActionCreators.deleteComponentsDependencies( |
361 | | - pages.map(p => p.path || p.id) |
362 | | - ) |
363 | | - pages.forEach(page => enqueueExtractedQueryId(page.path)) |
364 | | - runQueuedQueries() |
365 | | -} |
366 | | - |
367 | 122 | module.exports = { |
368 | | - calcInitialDirtyQueryIds, |
| 123 | + calcInitialDirtyQueryIds: calcDirtyQueryIds, |
369 | 124 | calcDirtyQueryIds, |
370 | 125 | processPageQueries, |
371 | 126 | processStaticQueries, |
372 | 127 | groupQueryIds, |
373 | | - startListeningToDevelopQueue, |
374 | | - runQueuedQueries, |
375 | | - enqueueExtractedQueryId, |
376 | | - enqueueExtractedPageComponent, |
377 | 128 | } |
0 commit comments