273 lines
8.3 KiB
JavaScript
273 lines
8.3 KiB
JavaScript
"use strict";
|
|
|
|
const _ = require(`lodash`);
|
|
|
|
const Queue = require(`better-queue`);
|
|
|
|
const convertHrtime = require(`convert-hrtime`);
|
|
|
|
const {
|
|
store,
|
|
emitter
|
|
} = require(`../redux`);
|
|
|
|
const queryQueue = require(`./queue`);
|
|
|
|
const {
|
|
boundActionCreators
|
|
} = require(`../redux/actions`);
|
|
|
|
let seenIdsWithoutDataDependencies = [];
|
|
let queuedDirtyActions = [];
|
|
const extractedQueryIds = new Set(); // Remove pages from seenIdsWithoutDataDependencies when they're deleted
|
|
// so their query will be run again if they're created again.
|
|
|
|
emitter.on(`DELETE_PAGE`, action => {
|
|
seenIdsWithoutDataDependencies = seenIdsWithoutDataDependencies.filter(p => p !== action.payload.path);
|
|
});
|
|
emitter.on(`CREATE_NODE`, action => {
|
|
queuedDirtyActions.push(action);
|
|
});
|
|
emitter.on(`DELETE_NODE`, action => {
|
|
queuedDirtyActions.push({
|
|
payload: action.payload
|
|
});
|
|
}); /////////////////////////////////////////////////////////////////////
|
|
// Calculate dirty static/page queries
|
|
|
|
const popExtractedQueries = () => {
|
|
const queries = [...extractedQueryIds];
|
|
extractedQueryIds.clear();
|
|
return queries;
|
|
};
|
|
|
|
const findIdsWithoutDataDependencies = state => {
|
|
const allTrackedIds = _.uniq(_.flatten(_.concat(_.values(state.componentDataDependencies.nodes), _.values(state.componentDataDependencies.connections)))); // Get list of paths not already tracked and run the queries for these
|
|
// paths.
|
|
|
|
|
|
const notTrackedIds = _.difference([...Array.from(state.pages.values(), p => p.path), ...[...state.staticQueryComponents.values()].map(c => c.id)], [...allTrackedIds, ...seenIdsWithoutDataDependencies]); // Add new IDs to our seen array so we don't keep trying to run queries for them.
|
|
// Pages without queries can't be tracked.
|
|
|
|
|
|
seenIdsWithoutDataDependencies = _.uniq([...notTrackedIds, ...seenIdsWithoutDataDependencies]);
|
|
return notTrackedIds;
|
|
};
|
|
|
|
const popNodeQueries = state => {
|
|
const actions = _.uniq(queuedDirtyActions, a => a.payload.id);
|
|
|
|
const uniqDirties = _.uniq(actions.reduce((dirtyIds, action) => {
|
|
const node = action.payload;
|
|
if (!node || !node.id || !node.internal.type) return dirtyIds; // Find components that depend on this node so are now dirty.
|
|
|
|
dirtyIds = dirtyIds.concat(state.componentDataDependencies.nodes[node.id]); // Find connections that depend on this node so are now invalid.
|
|
|
|
dirtyIds = dirtyIds.concat(state.componentDataDependencies.connections[node.internal.type]);
|
|
return _.compact(dirtyIds);
|
|
}, []));
|
|
|
|
queuedDirtyActions = [];
|
|
return uniqDirties;
|
|
};
|
|
|
|
const popNodeAndDepQueries = state => {
|
|
const nodeQueries = popNodeQueries(state);
|
|
const noDepQueries = findIdsWithoutDataDependencies(state);
|
|
return _.uniq([...nodeQueries, ...noDepQueries]);
|
|
};
|
|
/**
|
|
* Calculates the set of dirty query IDs (page.paths, or
|
|
* staticQuery.hash's). These are queries that:
|
|
*
|
|
* - depend on nodes or node collections (via
|
|
* `actions.createPageDependency`) that have changed.
|
|
* - do NOT have node dependencies. Since all queries should return
|
|
* data, then this implies that node dependencies have not been
|
|
* tracked, and therefore these queries haven't been run before
|
|
* - have been recently extracted (see `./query-watcher.js`)
|
|
*
|
|
* Note, this function pops queries off internal queues, so it's up
|
|
* to the caller to reference the results
|
|
*/
|
|
|
|
|
|
const calcDirtyQueryIds = state => _.union(popNodeAndDepQueries(state), popExtractedQueries());
|
|
/**
|
|
* Same as `calcDirtyQueryIds`, except that we only include extracted
|
|
* queries that depend on nodes or haven't been run yet. We do this
|
|
* because the page component reducer/machine always enqueues
|
|
* extractedQueryIds but during bootstrap we may not want to run those
|
|
* page queries if their data hasn't changed since the last time we
|
|
* ran Gatsby.
|
|
*/
|
|
|
|
|
|
const calcInitialDirtyQueryIds = state => {
|
|
const nodeAndNoDepQueries = popNodeAndDepQueries(state);
|
|
|
|
const extractedQueriesThatNeedRunning = _.intersection(popExtractedQueries(), nodeAndNoDepQueries);
|
|
|
|
return _.union(extractedQueriesThatNeedRunning, nodeAndNoDepQueries);
|
|
};
|
|
/**
|
|
* groups queryIds by whether they are static or page queries.
|
|
*/
|
|
|
|
|
|
const groupQueryIds = queryIds => {
|
|
const grouped = _.groupBy(queryIds, p => p.slice(0, 4) === `sq--` ? `static` : `page`);
|
|
|
|
return {
|
|
staticQueryIds: grouped.static || [],
|
|
pageQueryIds: grouped.page || []
|
|
};
|
|
};
|
|
|
|
const reportStats = (queue, activity) => {
|
|
const startQueries = process.hrtime();
|
|
queue.on(`task_finish`, () => {
|
|
const stats = queue.getStats();
|
|
activity.setStatus(`${stats.total}/${stats.peak} ${(stats.total / convertHrtime(process.hrtime(startQueries)).seconds).toFixed(2)} queries/second`);
|
|
});
|
|
};
|
|
|
|
const processQueries = async (queryJobs, activity) => {
|
|
const queue = queryQueue.createBuildQueue();
|
|
reportStats(queue, activity);
|
|
await queryQueue.processBatch(queue, queryJobs);
|
|
};
|
|
|
|
const createStaticQueryJob = (state, queryId) => {
|
|
const component = state.staticQueryComponents.get(queryId);
|
|
const {
|
|
hash,
|
|
id,
|
|
query,
|
|
componentPath
|
|
} = component;
|
|
return {
|
|
id: hash,
|
|
hash,
|
|
query,
|
|
componentPath,
|
|
context: {
|
|
path: id
|
|
}
|
|
};
|
|
};
|
|
|
|
const processStaticQueries = async (queryIds, {
|
|
state,
|
|
activity
|
|
}) => {
|
|
state = state || store.getState();
|
|
await processQueries(queryIds.map(id => createStaticQueryJob(state, id)), activity);
|
|
};
|
|
|
|
const createPageQueryJob = (state, page) => {
|
|
const component = state.components.get(page.componentPath);
|
|
const {
|
|
path,
|
|
componentPath,
|
|
context
|
|
} = page;
|
|
const {
|
|
query
|
|
} = component;
|
|
return {
|
|
id: path,
|
|
query,
|
|
isPage: true,
|
|
componentPath,
|
|
context: Object.assign({}, page, context)
|
|
};
|
|
};
|
|
|
|
const processPageQueries = async (queryIds, {
|
|
state,
|
|
activity
|
|
}) => {
|
|
state = state || store.getState(); // Make sure we filter out pages that don't exist. An example is
|
|
// /dev-404-page/, whose SitePage node is created via
|
|
// `internal-data-bridge`, but the actual page object is only
|
|
// created during `gatsby develop`.
|
|
|
|
const pages = _.filter(queryIds.map(id => state.pages.get(id)));
|
|
|
|
await processQueries(pages.map(page => createPageQueryJob(state, page)), activity);
|
|
}; /////////////////////////////////////////////////////////////////////
|
|
// Listener for gatsby develop
|
|
// Initialized via `startListening`
|
|
|
|
|
|
let listenerQueue;
|
|
/**
|
|
* Run any dirty queries. See `calcQueries` for what constitutes a
|
|
* dirty query
|
|
*/
|
|
|
|
const runQueuedQueries = () => {
|
|
if (listenerQueue) {
|
|
const state = store.getState();
|
|
const {
|
|
staticQueryIds,
|
|
pageQueryIds
|
|
} = groupQueryIds(calcDirtyQueryIds(state));
|
|
|
|
const pages = _.filter(pageQueryIds.map(id => state.pages.get(id)));
|
|
|
|
const queryJobs = [...staticQueryIds.map(id => createStaticQueryJob(state, id)), ...pages.map(page => createPageQueryJob(state, page))];
|
|
listenerQueue.push(queryJobs);
|
|
}
|
|
};
|
|
/**
|
|
* Starts a background process that processes any dirty queries
|
|
* whenever one of the following occurs:
|
|
*
|
|
* 1. A node has changed (but only after the api call has finished
|
|
* running)
|
|
* 2. A component query (e.g by editing a React Component) has
|
|
* changed
|
|
*
|
|
* For what constitutes a dirty query, see `calcQueries`
|
|
*/
|
|
|
|
|
|
const startListening = queue => {
|
|
// We use a queue to process batches of queries so that they are
|
|
// processed consecutively
|
|
listenerQueue = new Queue((queryJobs, callback) => queryQueue.processBatch(queue, queryJobs).then(() => callback(null)).catch(callback));
|
|
emitter.on(`API_RUNNING_QUEUE_EMPTY`, runQueuedQueries);
|
|
};
|
|
|
|
const enqueueExtractedQueryId = pathname => {
|
|
extractedQueryIds.add(pathname);
|
|
};
|
|
|
|
const getPagesForComponent = componentPath => {
|
|
const state = store.getState();
|
|
return [...state.pages.values()].filter(p => p.componentPath === componentPath);
|
|
};
|
|
|
|
const enqueueExtractedPageComponent = componentPath => {
|
|
const pages = getPagesForComponent(componentPath); // Remove page data dependencies before re-running queries because
|
|
// the changing of the query could have changed the data dependencies.
|
|
// Re-running the queries will add back data dependencies.
|
|
|
|
boundActionCreators.deleteComponentsDependencies(pages.map(p => p.path || p.id));
|
|
pages.forEach(page => enqueueExtractedQueryId(page.path));
|
|
runQueuedQueries();
|
|
};
|
|
|
|
module.exports = {
|
|
calcInitialDirtyQueryIds,
|
|
groupQueryIds,
|
|
processStaticQueries,
|
|
processPageQueries,
|
|
startListening,
|
|
runQueuedQueries,
|
|
enqueueExtractedQueryId,
|
|
enqueueExtractedPageComponent
|
|
};
|
|
//# sourceMappingURL=index.js.map
|