diff --git a/e2e-tests/development-runtime/content/query-data-caches/adding-static-query-A-to-B-to-A-link.json b/e2e-tests/development-runtime/content/query-data-caches/adding-static-query-A-to-B-to-A-link.json new file mode 100644 index 0000000000000..4edede06ada1c --- /dev/null +++ b/e2e-tests/development-runtime/content/query-data-caches/adding-static-query-A-to-B-to-A-link.json @@ -0,0 +1,4 @@ +{ + "selector": "adding-static-query-A-to-B-to-A-link", + "status": "from-static-query-results" +} diff --git a/e2e-tests/development-runtime/cypress/integration/functionality/query-data-caches.js b/e2e-tests/development-runtime/cypress/integration/functionality/query-data-caches.js index ad057c5cf5a08..0c580f56e6e2a 100644 --- a/e2e-tests/development-runtime/cypress/integration/functionality/query-data-caches.js +++ b/e2e-tests/development-runtime/cypress/integration/functionality/query-data-caches.js @@ -87,19 +87,24 @@ function pageTitleAndDataAssertion(config) { cy.findByText(`Preview custom 404 page`).click() } - cy.findByTestId(`${config.prefix || ``}page-path`) - .should(`have.text`, getExpectedCanonicalPath(config)) - - cy.findByTestId(`${config.prefix || ``}query-data-caches-page-title`) - .should(`have.text`, `This is page ${config.page}`) - - cy.findByTestId(`${config.prefix || ``}${config.queryType}-query-result`) - .should( - `have.text`, - `${config.slug} / ${ - config.page === config.initialPage ? `initial-page` : `second-page` - }: ${config.data}` - ) + cy.findByTestId(`${config.prefix || ``}page-path`).should( + `have.text`, + getExpectedCanonicalPath(config) + ) + + cy.findByTestId(`${config.prefix || ``}query-data-caches-page-title`).should( + `have.text`, + `This is page ${config.page}` + ) + + cy.findByTestId( + `${config.prefix || ``}${config.queryType}-query-result` + ).should( + `have.text`, + `${config.slug} / ${ + config.page === config.initialPage ? `initial-page` : `second-page` + }: ${config.data}` + ) } function runTests(config) { @@ -145,204 +150,267 @@ function runTests(config) { assertNotReloading() } -describe(`Navigate from static page A to page B, invalidate some data resources for static page A, navigate back to static page A`, () => { - describe(`Navigating back with gatsby-link`, () => { - it(`page query (page has trailing slash)`, () => { - const config = { - slug: `page-query-with-trailing-slash-A-to-B-to-A-link`, - queryType: `page`, - navigateBack: `link`, - initialPage: `A`, - } - - runTests(config) - }) - - it(`page query (page doesn't have trailing slash)`, () => { - const config = { - slug: `page-query-no-trailing-slash-A-to-B-to-A-link`, - trailingSlash: false, - queryType: `page`, - navigateBack: `link`, - initialPage: `A`, - } - - runTests(config) - }) - - it(`static query (page has trailing slash)`, () => { - const config = { - slug: `static-query-with-trailing-slash-A-to-B-to-A-link`, - queryType: `static`, - navigateBack: `link`, - initialPage: `A`, - } - - runTests(config) +describe(`Keeping caches up-to-date when updating data`, () => { + describe(`Navigate from static page A to page B, invalidate some data resources for static page A, navigate back to static page A`, () => { + describe(`Navigating back with gatsby-link`, () => { + it(`page query (page has trailing slash)`, () => { + const config = { + slug: `page-query-with-trailing-slash-A-to-B-to-A-link`, + queryType: `page`, + navigateBack: `link`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`page query (page doesn't have trailing slash)`, () => { + const config = { + slug: `page-query-no-trailing-slash-A-to-B-to-A-link`, + trailingSlash: false, + queryType: `page`, + navigateBack: `link`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`static query (page has trailing slash)`, () => { + const config = { + slug: `static-query-with-trailing-slash-A-to-B-to-A-link`, + queryType: `static`, + navigateBack: `link`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`static query (page doesn't have trailing slash)`, () => { + const config = { + slug: `static-query-no-trailing-slash-A-to-B-to-A-link`, + trailingSlash: false, + queryType: `static`, + navigateBack: `link`, + initialPage: `A`, + } + + runTests(config) + }) }) - it(`static query (page doesn't have trailing slash)`, () => { - const config = { - slug: `static-query-no-trailing-slash-A-to-B-to-A-link`, - trailingSlash: false, - queryType: `static`, - navigateBack: `link`, - initialPage: `A`, - } - - runTests(config) + describe(`Navigating back with history.back()`, () => { + it(`page query (page has trailing slash)`, () => { + const config = { + slug: `page-query-with-trailing-slash-A-to-B-to-A-history`, + queryType: `page`, + navigateBack: `history`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`page query (page doesn't have trailing slash)`, () => { + const config = { + slug: `page-query-no-trailing-slash-A-to-B-to-A-history`, + trailingSlash: false, + queryType: `page`, + navigateBack: `history`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`static query (page has trailing slash)`, () => { + const config = { + slug: `static-query-with-trailing-slash-A-to-B-to-A-history`, + queryType: `static`, + navigateBack: `history`, + initialPage: `A`, + } + + runTests(config) + }) + + it(`static query (page doesn't have trailing slash)`, () => { + const config = { + slug: `static-query-no-trailing-slash-A-to-B-to-A-history`, + trailingSlash: false, + queryType: `static`, + navigateBack: `history`, + initialPage: `A`, + } + + runTests(config) + }) }) }) - describe(`Navigating back with history.back()`, () => { - it(`page query (page has trailing slash)`, () => { - const config = { - slug: `page-query-with-trailing-slash-A-to-B-to-A-history`, - queryType: `page`, - navigateBack: `history`, - initialPage: `A`, - } - - runTests(config) + describe(`Navigate from client-only page A to page B, invalidate some data resources for client-only page A, navigate back to client-only page A`, () => { + describe(`Navigating back with gatsby-link`, () => { + it(`page query`, () => { + const config = { + slug: `page-query-CO-to-B-to-CO-link`, + queryType: `page`, + navigateBack: `link`, + initialPage: `client-only`, + } + + runTests(config) + }) + + it(`static query`, () => { + const config = { + slug: `static-query-CO-to-B-to-CO-link`, + queryType: `static`, + navigateBack: `link`, + initialPage: `client-only`, + } + + runTests(config) + }) }) - it(`page query (page doesn't have trailing slash)`, () => { - const config = { - slug: `page-query-no-trailing-slash-A-to-B-to-A-history`, - trailingSlash: false, - queryType: `page`, - navigateBack: `history`, - initialPage: `A`, - } - - runTests(config) - }) - - it(`static query (page has trailing slash)`, () => { - const config = { - slug: `static-query-with-trailing-slash-A-to-B-to-A-history`, - queryType: `static`, - navigateBack: `history`, - initialPage: `A`, - } - - runTests(config) - }) - - it(`static query (page doesn't have trailing slash)`, () => { - const config = { - slug: `static-query-no-trailing-slash-A-to-B-to-A-history`, - trailingSlash: false, - queryType: `static`, - navigateBack: `history`, - initialPage: `A`, - } - - runTests(config) + describe(`Navigating back with history.back()`, () => { + it(`page query`, () => { + const config = { + slug: `page-query-CO-to-B-to-CO-history`, + queryType: `page`, + navigateBack: `history`, + initialPage: `client-only`, + } + + runTests(config) + }) + + it(`static query`, () => { + const config = { + slug: `static-query-CO-to-B-to-CO-history`, + queryType: `static`, + navigateBack: `history`, + initialPage: `client-only`, + } + + runTests(config) + }) }) }) -}) - -describe(`Navigate from client-only page A to page B, invalidate some data resources for client-only page A, navigate back to client-only page A`, () => { - describe(`Navigating back with gatsby-link`, () => { - it(`page query`, () => { - const config = { - slug: `page-query-CO-to-B-to-CO-link`, - queryType: `page`, - navigateBack: `link`, - initialPage: `client-only`, - } - runTests(config) + describe(`Navigate from 404 page A to page B, invalidate some data resources for 404 page A, navigate back to 404 page A`, () => { + describe(`Navigating back with gatsby-link`, () => { + it(`page query`, () => { + const config = { + slug: `page-query-404-to-B-to-404-link`, + queryType: `page`, + navigateBack: `link`, + initialPage: `404`, + prefix: `page-link-`, + } + + runTests(config) + }) + + it(`static query`, () => { + const config = { + slug: `static-query-404-to-B-to-404-link`, + queryType: `static`, + navigateBack: `link`, + initialPage: `404`, + prefix: `static-link-`, + } + + runTests(config) + }) }) - it(`static query`, () => { - const config = { - slug: `static-query-CO-to-B-to-CO-link`, - queryType: `static`, - navigateBack: `link`, - initialPage: `client-only`, - } - - runTests(config) - }) - }) - - describe(`Navigating back with history.back()`, () => { - it(`page query`, () => { - const config = { - slug: `page-query-CO-to-B-to-CO-history`, - queryType: `page`, - navigateBack: `history`, - initialPage: `client-only`, - } - - runTests(config) - }) - - it(`static query`, () => { - const config = { - slug: `static-query-CO-to-B-to-CO-history`, - queryType: `static`, - navigateBack: `history`, - initialPage: `client-only`, - } - - runTests(config) + describe(`Navigating back with history.back()`, () => { + it(`page query`, () => { + const config = { + slug: `page-query-404-to-B-to-404-history`, + queryType: `page`, + navigateBack: `history`, + initialPage: `404`, + prefix: `page-history-`, + } + + runTests(config) + }) + + it(`static query`, () => { + const config = { + slug: `static-query-404-to-B-to-404-history`, + queryType: `static`, + navigateBack: `history`, + initialPage: `404`, + prefix: `static-history-`, + } + + runTests(config) + }) }) }) }) -describe(`Navigate from 404 page A to page B, invalidate some data resources for 404 page A, navigate back to 404 page A`, () => { - describe(`Navigating back with gatsby-link`, () => { - it(`page query`, () => { - const config = { - slug: `page-query-404-to-B-to-404-link`, - queryType: `page`, - navigateBack: `link`, - initialPage: `404`, - prefix: `page-link-`, - } - - runTests(config) - }) - - it(`static query`, () => { +describe(`Keeping caches up to date when modifying list of static query hashes assigned to a template`, () => { + describe(`using gatsby-link`, () => { + it.only(`Navigate from page A to page B, add static query to page A, navigate back to page A`, () => { const config = { - slug: `static-query-404-to-B-to-404-link`, + slug: `adding-static-query-A-to-B-to-A-link`, queryType: `static`, navigateBack: `link`, - initialPage: `404`, - prefix: `static-link-`, + initialPage: `A`, } - runTests(config) - }) - }) - - describe(`Navigating back with history.back()`, () => { - it(`page query`, () => { - const config = { - slug: `page-query-404-to-B-to-404-history`, - queryType: `page`, - navigateBack: `history`, - initialPage: `404`, - prefix: `page-history-`, + cy.visit(`/query-data-caches/${config.slug}/page-A/`).waitForRouteChange() + + setupForAssertingNotReloading() + + // baseline assertions + pageTitleAndDataAssertion({ + ...config, + page: config.initialPage, + data: `from-hardcoded-data`, + }) + + cy.getTestElement(`page-b-link`).click().waitForRouteChange() + + // assert we navigated + pageTitleAndDataAssertion({ + ...config, + page: `B`, + data: `from-static-query-results`, + }) + + cy.exec( + `npm run update -- --file src/pages/query-data-caches/${config.slug}/page-A.js --replacements "adding-static-query-blank:adding-static-query-with-data" --exact` + ) + + // TODO: get rid of this wait + // We currently have timing issue when emitting both webpack's HMR and page-data. + // Problem is we need to potentially wait for webpack recompilation before we emit page-data (due to dependency graph traversal). + // Even if we could delay emitting data on the "server" side - this doesn't guarantee that messages are received + // and handled in correct order (ideally they are applied at the exact same time actually, because ordering might still cause issues if we change query text and component render function) + cy.wait(10000) + + if (config.navigateBack === `link`) { + cy.getTestElement(`page-a-link`).click().waitForRouteChange() + } else if (config.navigateBack === `history`) { + // this is just making sure page components don't have link to navigate back (asserting correct setup) + cy.getTestElement(`page-a-link`).should(`not.exist`) + cy.go(`back`).waitForRouteChange() } - runTests(config) - }) - - it(`static query`, () => { - const config = { - slug: `static-query-404-to-B-to-404-history`, - queryType: `static`, - navigateBack: `history`, - initialPage: `404`, - prefix: `static-history-`, - } + // assert data on page we previously visited is updated + pageTitleAndDataAssertion({ + ...config, + page: config.initialPage, + data: `from-static-query-results`, + }) - runTests(config) + assertNotReloading() }) }) }) diff --git a/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-blank.js b/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-blank.js new file mode 100644 index 0000000000000..5102ea2ccbbfa --- /dev/null +++ b/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-blank.js @@ -0,0 +1,9 @@ +export function useDataForAddingStaticQueryTest() { + return { + queryDataCachesJson: { + selector: `adding-static-query-A-to-B-to-A-link`, + status: `from-hardcoded-data`, + initialOrSecond: `initial-page`, + }, + } +} diff --git a/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-with-data.js b/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-with-data.js new file mode 100644 index 0000000000000..3e5b1645da7b0 --- /dev/null +++ b/e2e-tests/development-runtime/src/components/query-data-caches/adding-static-query-with-data.js @@ -0,0 +1,13 @@ +import { graphql, useStaticQuery } from "gatsby" + +export function useDataForAddingStaticQueryTest() { + return useStaticQuery(graphql` + { + queryDataCachesJson( + selector: { eq: "adding-static-query-A-to-B-to-A-link" } + ) { + ...QueryDataCachesFragmentInitialPage + } + } + `) +} diff --git a/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-A.js b/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-A.js new file mode 100644 index 0000000000000..6e4a2703c0a51 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-A.js @@ -0,0 +1,21 @@ +import React from "react" +import { Link } from "gatsby" +import { QueryDataCachesView } from "../../../components/query-data-caches/view" +import { useDataForAddingStaticQueryTest } from "../../../components/query-data-caches/adding-static-query-blank" + +export default function AddingStaticQueryToPageTemplatePageA({ path }) { + const data = useDataForAddingStaticQueryTest() + return ( + <> + + + Go to page B + + + ) +} diff --git a/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-B.js b/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-B.js new file mode 100644 index 0000000000000..18a209c901662 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/query-data-caches/adding-static-query-A-to-B-to-A-link/page-B.js @@ -0,0 +1,28 @@ +import React from "react" +import { Link, graphql, useStaticQuery } from "gatsby" +import { QueryDataCachesView } from "../../../components/query-data-caches/view" + +export default function AddingStaticQueryToPageTemplatePageB({ path }) { + const data = useStaticQuery(graphql` + { + queryDataCachesJson( + selector: { eq: "adding-static-query-A-to-B-to-A-link" } + ) { + ...QueryDataCachesFragmentSecondPage + } + } + `) + return ( + <> + + + Go back to page A + + + ) +} diff --git a/packages/gatsby/cache-dir/dev-loader.js b/packages/gatsby/cache-dir/dev-loader.js index 7d6169a981b32..25bbdf38d2ff9 100644 --- a/packages/gatsby/cache-dir/dev-loader.js +++ b/packages/gatsby/cache-dir/dev-loader.js @@ -38,8 +38,8 @@ class DevLoader extends BaseLoader { this.handleStaticQueryResultHotUpdate(msg) } else if (msg.type === `pageQueryResult`) { this.handlePageQueryResultHotUpdate(msg) - } else if (msg.type === `dirtyQueries`) { - this.handleDirtyPageQueryMessage(msg) + } else if (msg.type === `stalePageData`) { + this.handleStalePageDataMessage(msg) } }) } else if (process.env.NODE_ENV !== `test`) { @@ -103,6 +103,8 @@ class DevLoader extends BaseLoader { const cachedPageData = this.pageDataDb.get(pageDataDbCacheKey)?.payload if (!isEqual(newPageData, cachedPageData)) { + // TODO: if this is update for current page and there are any new static queries added + // that are not yet cached, there is currently no trigger to fetch them (yikes) // always update canonical key for pageDataDb this.pageDataDb.set(pageDataDbCacheKey, { pagePath: pageDataDbCacheKey, @@ -146,8 +148,8 @@ class DevLoader extends BaseLoader { } } - handleDirtyPageQueryMessage(msg) { - msg.payload.dirtyQueries.forEach(dirtyQueryId => { + handleStalePageDataMessage(msg) { + msg.payload.stalePageDataPaths.forEach(dirtyQueryId => { if (dirtyQueryId === `/dev-404-page/` || dirtyQueryId === `/404.html`) { // those pages are not on demand so skipping return diff --git a/packages/gatsby/cache-dir/ensure-resources.js b/packages/gatsby/cache-dir/ensure-resources.js index b548e7a8d5572..c3232c1fab076 100644 --- a/packages/gatsby/cache-dir/ensure-resources.js +++ b/packages/gatsby/cache-dir/ensure-resources.js @@ -48,7 +48,7 @@ class EnsureResources extends React.Component { } if ( - process.env.GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND && + process.env.BUILD_STAGE === `develop` && nextState.pageResources.stale ) { this.loadResources(nextProps.location.pathname) diff --git a/packages/gatsby/cache-dir/loader.js b/packages/gatsby/cache-dir/loader.js index 80b1a2720e54b..de73b9938d49b 100644 --- a/packages/gatsby/cache-dir/loader.js +++ b/packages/gatsby/cache-dir/loader.js @@ -191,7 +191,7 @@ export class BaseLoader { const pagePath = findPath(rawPath) if (this.pageDataDb.has(pagePath)) { const pageData = this.pageDataDb.get(pagePath) - if (!process.env.GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND || !pageData.stale) { + if (process.env.BUILD_STAGE !== `develop` || !pageData.stale) { return Promise.resolve(pageData) } } @@ -212,10 +212,7 @@ export class BaseLoader { const pagePath = findPath(rawPath) if (this.pageDb.has(pagePath)) { const page = this.pageDb.get(pagePath) - if ( - !process.env.GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND || - !page.payload.stale - ) { + if (process.env.BUILD_STAGE !== `develop` || !page.payload.stale) { return Promise.resolve(page.payload) } } diff --git a/packages/gatsby/src/utils/websocket-manager.ts b/packages/gatsby/src/utils/websocket-manager.ts index b4902e129e7cc..760f8735ac8ce 100644 --- a/packages/gatsby/src/utils/websocket-manager.ts +++ b/packages/gatsby/src/utils/websocket-manager.ts @@ -1,6 +1,7 @@ /* eslint-disable no-invalid-this */ import { store, emitter } from "../redux" +import { IAddPendingTemplateDataWriteAction } from "../redux/types" import { clearDirtyQueriesListToEmitViaWebsocket } from "../redux/actions/internal" import { Server as HTTPSServer } from "https" import { Server as HTTPServer } from "http" @@ -119,12 +120,34 @@ export class WebsocketManager { }) if (process.env.GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND) { - emitter.on(`CREATE_PAGE`, this.emitDirtyQueriesIds) - emitter.on(`CREATE_NODE`, this.emitDirtyQueriesIds) - emitter.on(`DELETE_NODE`, this.emitDirtyQueriesIds) - emitter.on(`QUERY_EXTRACTED`, this.emitDirtyQueriesIds) + // page-data marked stale due to dirty query tracking + const boundEmitStalePageDataPathsFromDirtyQueryTracking = this.emitStalePageDataPathsFromDirtyQueryTracking.bind( + this + ) + emitter.on( + `CREATE_PAGE`, + boundEmitStalePageDataPathsFromDirtyQueryTracking + ) + emitter.on( + `CREATE_NODE`, + boundEmitStalePageDataPathsFromDirtyQueryTracking + ) + emitter.on( + `DELETE_NODE`, + boundEmitStalePageDataPathsFromDirtyQueryTracking + ) + emitter.on( + `QUERY_EXTRACTED`, + boundEmitStalePageDataPathsFromDirtyQueryTracking + ) } + // page-data marked stale due to static query hashes change + emitter.on( + `ADD_PENDING_TEMPLATE_DATA_WRITE`, + this.emitStalePageDataPathsFromStaticQueriesAssignment.bind(this) + ) + return this.websocket } @@ -187,20 +210,35 @@ export class WebsocketManager { } } - emitDirtyQueriesIds = (): void => { + emitStalePageDataPathsFromDirtyQueryTracking(): void { const dirtyQueries = store.getState().queries .dirtyQueriesListToEmitViaWebsocket - if (dirtyQueries.length > 0) { + if (this.emitStalePageDataPaths(dirtyQueries)) { + store.dispatch(clearDirtyQueriesListToEmitViaWebsocket()) + } + } + + emitStalePageDataPathsFromStaticQueriesAssignment( + pendingTemplateDataWrite: IAddPendingTemplateDataWriteAction + ): void { + this.emitStalePageDataPaths( + Array.from(pendingTemplateDataWrite.payload.pages) + ) + } + + emitStalePageDataPaths(stalePageDataPaths: Array): boolean { + if (stalePageDataPaths.length > 0) { if (this.websocket) { this.websocket.send({ - type: `dirtyQueries`, - payload: { dirtyQueries }, + type: `stalePageData`, + payload: { stalePageDataPaths }, }) - store.dispatch(clearDirtyQueriesListToEmitViaWebsocket()) + return true } } + return false } }