UNPKG

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