gatsby
Version:
Blazing fast modern site generator for React
403 lines (398 loc) • 14 kB
JavaScript
exports.__esModule = true;
exports.FLAG_RUNNING_INFLIGHT = exports.FLAG_ERROR_EXTRACTION = exports.FLAG_DIRTY_TEXT = exports.FLAG_DIRTY_PAGE_CONTEXT = exports.FLAG_DIRTY_NEW_PAGE = exports.FLAG_DIRTY_DATA = void 0;
exports.hasFlag = hasFlag;
exports.queriesReducer = queriesReducer;
// page query path or static query id
const FLAG_DIRTY_NEW_PAGE = 0b0001;
exports.FLAG_DIRTY_NEW_PAGE = FLAG_DIRTY_NEW_PAGE;
const FLAG_DIRTY_TEXT = 0b0010;
exports.FLAG_DIRTY_TEXT = FLAG_DIRTY_TEXT;
const FLAG_DIRTY_DATA = 0b0100;
exports.FLAG_DIRTY_DATA = FLAG_DIRTY_DATA;
const FLAG_DIRTY_PAGE_CONTEXT = 0b1000;
exports.FLAG_DIRTY_PAGE_CONTEXT = FLAG_DIRTY_PAGE_CONTEXT;
const FLAG_ERROR_EXTRACTION = 0b0001;
exports.FLAG_ERROR_EXTRACTION = FLAG_ERROR_EXTRACTION;
const FLAG_RUNNING_INFLIGHT = 0b0001;
exports.FLAG_RUNNING_INFLIGHT = FLAG_RUNNING_INFLIGHT;
const initialState = () => {
return {
byNode: new Map(),
byConnection: new Map(),
queryNodes: new Map(),
trackedQueries: new Map(),
trackedComponents: new Map(),
deletedQueries: new Set(),
dirtyQueriesListToEmitViaWebsocket: []
};
};
const initialQueryState = () => {
return {
dirty: -1,
// unknown, must be set right after init
running: 0
};
};
const initialComponentState = () => {
return {
componentPath: ``,
query: ``,
pages: new Set(),
errors: 0
// TODO: staticQueries: new Set<QueryId>()
};
};
/**
* Tracks query dirtiness. Dirty queries are queries that:
*
* - depend on nodes or node collections (via `actions.createPageDependency`) that have changed.
* - have been recently extracted (or their query text has changed)
* - belong to newly created pages (or pages with modified context)
*
* Dirty queries must be re-ran.
*/
function queriesReducer(state = initialState(), action) {
switch (action.type) {
case `DELETE_CACHE`:
return initialState();
case `CREATE_PAGE`:
{
const {
path,
componentPath
} = action.payload;
let query = state.trackedQueries.get(path);
if (!query || action.contextModified) {
query = registerQuery(state, path);
query.dirty = setFlag(query.dirty, action.contextModified ? FLAG_DIRTY_PAGE_CONTEXT : FLAG_DIRTY_NEW_PAGE);
state = trackDirtyQuery(state, path);
}
registerComponent(state, componentPath).pages.add(path);
state.deletedQueries.delete(path);
return state;
}
case `CREATE_SLICE`:
{
const {
name,
componentPath
} = action.payload;
const path = `slice--${name}`;
let query = state.trackedQueries.get(path);
if (!query || action.contextModified) {
query = registerQuery(state, path);
query.dirty = setFlag(query.dirty, action.contextModified ? FLAG_DIRTY_PAGE_CONTEXT : FLAG_DIRTY_NEW_PAGE);
state = trackDirtyQuery(state, path);
}
registerComponent(state, componentPath).pages.add(path);
state.deletedQueries.delete(path);
return state;
}
case `DELETE_PAGE`:
{
// Don't actually remove the page query from trackedQueries, just mark it as "deleted". Why?
// We promote a technique of a consecutive deletePage/createPage calls in onCreatePage hook,
// see https://www.gatsbyjs.com/docs/creating-and-modifying-pages/#pass-context-to-pages
// If we remove a query and then re-add, it will be marked as dirty.
// This is OK for cold cache but with warm cache we will re-run all of those queries (unnecessarily).
// We will reconcile the state after createPages API call and actually delete those queries.
state.deletedQueries.add(action.payload.path);
return state;
}
case `DELETE_SLICE`:
{
const {
name
} = action.payload;
const path = `slice--${name}`;
state.deletedQueries.add(path);
return state;
}
case `API_FINISHED`:
{
if (action.payload.apiName !== `createPages`) {
return state;
}
for (const queryId of state.deletedQueries) {
for (const component of state.trackedComponents.values()) {
component.pages.delete(queryId);
}
state = clearNodeDependencies(state, queryId);
state = clearConnectionDependencies(state, queryId);
state.trackedQueries.delete(queryId);
}
state.deletedQueries.clear();
return state;
}
case `QUERY_EXTRACTED`:
{
// Note: this action is called even in case of
// extraction error or missing query (with query === ``)
// TODO: use hash instead of a query text
const {
componentPath,
query
} = action.payload;
const component = registerComponent(state, componentPath);
if (hasFlag(component.errors, FLAG_ERROR_EXTRACTION)) {
return state;
}
if (component.query !== query) {
// Invalidate all pages associated with a component when query text changes
for (const queryId of component.pages) {
const query = state.trackedQueries.get(queryId);
if (query) {
query.dirty = setFlag(query.dirty, FLAG_DIRTY_TEXT);
state = trackDirtyQuery(state, queryId);
}
}
component.query = query;
}
return state;
}
case `QUERY_EXTRACTION_GRAPHQL_ERROR`:
case `QUERY_EXTRACTION_BABEL_ERROR`:
case `QUERY_EXTRACTION_BABEL_SUCCESS`:
{
const {
componentPath
} = action.payload;
const component = registerComponent(state, componentPath);
const set = action.type !== `QUERY_EXTRACTION_BABEL_SUCCESS`;
component.errors = setFlag(component.errors, FLAG_ERROR_EXTRACTION, set);
return state;
}
case `REPLACE_STATIC_QUERY`:
{
// Only called when static query text has changed, so no need to compare
// TODO: unify the behavior?
const query = registerQuery(state, action.payload.id);
query.dirty = setFlag(query.dirty, FLAG_DIRTY_TEXT);
// static queries are not on demand, so skipping tracking which
// queries were marked dirty recently
// state = trackDirtyQuery(state, action.payload.id)
state.deletedQueries.delete(action.payload.id);
return state;
}
case `REMOVE_STATIC_QUERY`:
{
state.deletedQueries.add(action.payload);
return state;
}
case `CREATE_COMPONENT_DEPENDENCY`:
{
action.payload.forEach(dep => {
const {
path: queryId,
nodeId,
connection
} = dep;
if (nodeId) {
state = addNodeDependency(state, queryId, nodeId);
}
if (connection) {
state = addConnectionDependency(state, queryId, connection);
}
});
return state;
}
case `QUERY_START`:
{
// Reset data dependencies as they will be updated when running the query
const {
path
} = action.payload;
state = clearNodeDependencies(state, path);
state = clearConnectionDependencies(state, path);
const query = state.trackedQueries.get(path);
if (query) {
query.running = setFlag(query.running, FLAG_RUNNING_INFLIGHT);
}
return state;
}
case `CREATE_NODE`:
case `DELETE_NODE`:
{
var _state$byNode$get, _state$byConnection$g;
const node = action.payload;
if (!node) {
return state;
}
const queriesByNode = (_state$byNode$get = state.byNode.get(node.id)) !== null && _state$byNode$get !== void 0 ? _state$byNode$get : [];
const queriesByConnection = (_state$byConnection$g = state.byConnection.get(node.internal.type)) !== null && _state$byConnection$g !== void 0 ? _state$byConnection$g : [];
for (const queryId of queriesByNode) {
const query = state.trackedQueries.get(queryId);
if (query) {
query.dirty = setFlag(query.dirty, FLAG_DIRTY_DATA);
state = trackDirtyQuery(state, queryId);
}
}
for (const queryId of queriesByConnection) {
const query = state.trackedQueries.get(queryId);
if (query) {
query.dirty = setFlag(query.dirty, FLAG_DIRTY_DATA);
state = trackDirtyQuery(state, queryId);
}
}
return state;
}
case `PAGE_QUERY_RUN`:
{
const {
path
} = action.payload;
const query = registerQuery(state, path);
query.dirty = 0;
query.running = 0; // TODO: also
return state;
}
case `SET_PROGRAM_STATUS`:
{
if (action.payload === `BOOTSTRAP_FINISHED`) {
// Reset the running state (as it could've been persisted)
for (const [, query] of state.trackedQueries) {
query.running = 0;
}
// Reset list of dirty queries (this is used only to notify runtime and it could've been persisted)
state.dirtyQueriesListToEmitViaWebsocket = [];
}
return state;
}
case `QUERY_CLEAR_DIRTY_QUERIES_LIST_TO_EMIT_VIA_WEBSOCKET`:
{
state.dirtyQueriesListToEmitViaWebsocket = [];
return state;
}
case `MERGE_WORKER_QUERY_STATE`:
{
// This action may be dispatched in cases where queries might not be included in the merge data
if (!action.payload.queryStateChunk) {
return state;
}
assertCorrectWorkerState(action.payload);
state = mergeWorkerDataDependencies(state, action.payload);
return state;
}
default:
return state;
}
}
function setFlag(allFlags, flag, set = true) {
if (allFlags < 0) {
allFlags = 0;
}
return set ? allFlags | flag : allFlags & ~flag;
}
function hasFlag(allFlags, flag) {
return allFlags >= 0 && (allFlags & flag) > 0;
}
function addNodeDependency(state, queryId, nodeId) {
// Perf: using two-side maps.
// Without additional `queryNodes` map we would have to loop through
// all existing nodes in `clearNodeDependencies` to delete node <-> query dependency
let nodeQueries = state.byNode.get(nodeId);
if (!nodeQueries) {
nodeQueries = new Set();
state.byNode.set(nodeId, nodeQueries);
}
let queryNodes = state.queryNodes.get(queryId);
if (!queryNodes) {
queryNodes = new Set();
state.queryNodes.set(queryId, queryNodes);
}
nodeQueries.add(queryId);
queryNodes.add(nodeId);
return state;
}
function addConnectionDependency(state, queryId, connection) {
// Note: not using two-side maps for connections as associated overhead
// for small number of elements is greater then benefits, so no perf. gains
let queryIds = state.byConnection.get(connection);
if (!queryIds) {
queryIds = new Set();
state.byConnection.set(connection, queryIds);
}
queryIds.add(queryId);
return state;
}
function clearNodeDependencies(state, queryId) {
var _state$queryNodes$get;
const queryNodeIds = (_state$queryNodes$get = state.queryNodes.get(queryId)) !== null && _state$queryNodes$get !== void 0 ? _state$queryNodes$get : new Set();
for (const nodeId of queryNodeIds) {
const nodeQueries = state.byNode.get(nodeId);
if (nodeQueries) {
nodeQueries.delete(queryId);
}
}
return state;
}
function clearConnectionDependencies(state, queryId) {
for (const [, queryIds] of state.byConnection) {
queryIds.delete(queryId);
}
return state;
}
function registerQuery(state, queryId) {
let query = state.trackedQueries.get(queryId);
if (!query) {
query = initialQueryState();
state.trackedQueries.set(queryId, query);
}
return query;
}
function registerComponent(state, componentPath) {
let component = state.trackedComponents.get(componentPath);
if (!component) {
component = initialComponentState();
component.componentPath = componentPath;
state.trackedComponents.set(componentPath, component);
}
return component;
}
function trackDirtyQuery(state, queryId) {
if (process.env.GATSBY_QUERY_ON_DEMAND) {
state.dirtyQueriesListToEmitViaWebsocket.push(queryId);
}
return state;
}
function mergeWorkerDataDependencies(state, workerStateChunk) {
const queryState = workerStateChunk.queryStateChunk;
// First clear data dependencies for all queries tracked by worker
for (const queryId of queryState.trackedQueries.keys()) {
state = clearNodeDependencies(state, queryId);
state = clearConnectionDependencies(state, queryId);
}
// Now re-add all data deps from worker
for (const [nodeId, queries] of queryState.byNode) {
for (const queryId of queries) {
state = addNodeDependency(state, queryId, nodeId);
}
}
for (const [connectionName, queries] of queryState.byConnection) {
for (const queryId of queries) {
state = addConnectionDependency(state, queryId, connectionName);
}
}
return state;
}
function assertCorrectWorkerState({
queryStateChunk,
workerId
}) {
if (queryStateChunk.deletedQueries.size !== 0) {
throw new Error(`Assertion failed: workerState.deletedQueries.size === 0 (worker #${workerId})`);
}
if (queryStateChunk.trackedComponents.size !== 0) {
throw new Error(`Assertion failed: queryStateChunk.trackedComponents.size === 0 (worker #${workerId})`);
}
for (const query of queryStateChunk.trackedQueries.values()) {
if (query.dirty) {
throw new Error(`Assertion failed: all worker queries are not dirty (worker #${workerId})`);
}
if (query.running) {
throw new Error(`Assertion failed: all worker queries are not running (worker #${workerId})`);
}
}
}
//# sourceMappingURL=queries.js.map
;