Skip to content

Commit a717317

Browse files
committed
Page data loading resilience (gatsbyjs#14286)
* fetchPageHtml if page resources aren't found * add page-data to production-runtime/resource-loading-resilience test Also use cypress tasks for blocking resources instead of npm run chunks * fetchPageHtml -> doesPageHtmlExist * remove loadPageOr404Sync
1 parent 76da063 commit a717317

File tree

8 files changed

+296
-295
lines changed

8 files changed

+296
-295
lines changed
Lines changed: 98 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,110 @@
1-
// TODO need to update to work with page-data.json
1+
Cypress.on(`uncaught:exception`, (err, runnable) => {
2+
// returning false here prevents Cypress from
3+
// failing the test
4+
console.log(err)
5+
return false
6+
})
27

3-
// Cypress.on(`uncaught:exception`, (err, runnable) => {
4-
// // returning false here prevents Cypress from
5-
// // failing the test
6-
// console.log(err)
7-
// return false
8-
// })
8+
const waitForAPIOptions = {
9+
timeout: 3000,
10+
}
911

10-
// const waitForAPIOptions = {
11-
// timeout: 3000,
12-
// }
12+
const runTests = () => {
13+
it(`Loads index`, () => {
14+
cy.visit(`/`).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
15+
cy.getTestElement(`dom-marker`).contains(`index`)
16+
})
1317

14-
// const runTests = () => {
15-
// it(`Loads index`, () => {
16-
// cy.visit(`/`).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
17-
// cy.getTestElement(`dom-marker`).contains(`index`)
18-
// })
18+
it(`Navigates to second page`, () => {
19+
cy.getTestElement(`page2`).click()
20+
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
21+
.location(`pathname`)
22+
.should(`equal`, `/page-2/`)
23+
cy.getTestElement(`dom-marker`).contains(`page-2`)
24+
})
1925

20-
// it(`Navigates to second page`, () => {
21-
// cy.getTestElement(`page2`).click()
22-
// cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
23-
// .location(`pathname`)
24-
// .should(`equal`, `/page-2/`)
25-
// cy.getTestElement(`dom-marker`).contains(`page-2`)
26-
// })
26+
it(`Navigates to 404 page`, () => {
27+
cy.getTestElement(`404`).click()
28+
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
29+
.location(`pathname`)
30+
.should(`equal`, `/page-3/`)
31+
cy.getTestElement(`dom-marker`).contains(`404`)
32+
})
2733

28-
// it(`Navigates to 404 page`, () => {
29-
// cy.getTestElement(`404`).click()
30-
// cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
31-
// .location(`pathname`)
32-
// .should(`equal`, `/page-3/`)
33-
// cy.getTestElement(`dom-marker`).contains(`404`)
34-
// })
34+
it(`Loads 404`, () => {
35+
cy.visit(`/page-3/`, {
36+
failOnStatusCode: false,
37+
}).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
38+
cy.getTestElement(`dom-marker`).contains(`404`)
39+
})
3540

36-
// it(`Loads 404`, () => {
37-
// cy.visit(`/page-3/`, {
38-
// failOnStatusCode: false,
39-
// }).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
40-
// cy.getTestElement(`dom-marker`).contains(`404`)
41-
// })
41+
it(`Can navigate from 404 to index`, () => {
42+
cy.getTestElement(`index`).click()
43+
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
44+
.location(`pathname`)
45+
.should(`equal`, `/`)
46+
cy.getTestElement(`dom-marker`).contains(`index`)
47+
})
48+
}
4249

43-
// it(`Can navigate from 404 to index`, () => {
44-
// cy.getTestElement(`index`).click()
45-
// cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
46-
// .location(`pathname`)
47-
// .should(`equal`, `/`)
48-
// cy.getTestElement(`dom-marker`).contains(`index`)
49-
// })
50-
// }
50+
describe(`Every resources available`, () => {
51+
it(`Restore resources`, () => {
52+
cy.task(`restoreAllBlockedResources`)
53+
})
54+
runTests()
55+
})
5156

52-
// describe(`Every resources available`, () => {
53-
// it(`Restore resources`, () => {
54-
// cy.exec(`npm run chunks -- restore`)
55-
// })
56-
// runTests()
57-
// })
57+
const runBlockedScenario = (scenario, args) => {
58+
it(`Block resources`, () => {
59+
cy.task(`restoreAllBlockedResources`).then(() => {
60+
cy.task(scenario, args).then(() => {
61+
runTests()
62+
})
63+
})
64+
})
65+
}
5866

59-
// describe(`Missing top level resources`, () => {
60-
// describe(`Deleted pages manifest`, () => {
61-
// it(`Block resources`, () => {
62-
// cy.exec(`npm run chunks -- restore`)
63-
// cy.exec(`npm run chunks -- block pages-manifest`)
64-
// })
65-
// runTests()
66-
// })
67+
describe(`Missing top level resources`, () => {
68+
describe(`Deleted app chunk assets`, () => {
69+
runBlockedScenario(`blockAssetsForChunk`, { chunk: `app` })
70+
})
71+
})
6772

68-
// describe(`Deleted app chunk assets`, () => {
69-
// it(`Block resources`, () => {
70-
// cy.exec(`npm run chunks -- restore`)
71-
// cy.exec(`npm run chunks -- block app`)
72-
// })
73-
// runTests()
74-
// })
75-
// })
73+
const runSuiteForPage = (label, pagePath) => {
74+
describe(`Missing "${label}" resources`, () => {
75+
describe(`Missing "${label}" page query results`, () => {
76+
runBlockedScenario(`blockAssetsForPage`, {
77+
pagePath,
78+
filter: `page-data`,
79+
})
80+
})
81+
describe(`Missing "${label}" page page-template asset`, () => {
82+
runBlockedScenario(`blockAssetsForPage`, {
83+
pagePath,
84+
filter: `page-template`,
85+
})
86+
})
87+
describe(`Missing "${label}" page extra assets`, () => {
88+
runBlockedScenario(`blockAssetsForPage`, {
89+
pagePath,
90+
filter: `extra`,
91+
})
92+
})
93+
describe(`Missing all "${label}" page assets`, () => {
94+
runBlockedScenario(`blockAssetsForPage`, {
95+
pagePath,
96+
filter: `all`,
97+
})
98+
})
99+
})
100+
}
76101

77-
// const runSuiteForPage = (label, path) => {
78-
// describe(`Missing "${label}" resources`, () => {
79-
// describe(`Missing "${label}" page query results`, () => {
80-
// it(`Block resources`, () => {
81-
// cy.exec(`npm run chunks -- restore`)
82-
// cy.exec(`npm run chunks -- block-page ${path} query-result`)
83-
// })
84-
// runTests()
85-
// })
86-
// describe(`Missing "${label}" page page-template asset`, () => {
87-
// it(`Block resources`, () => {
88-
// cy.exec(`npm run chunks -- restore`)
89-
// cy.exec(`npm run chunks -- block-page ${path} page-template`)
90-
// })
91-
// runTests()
92-
// })
93-
// describe(`Missing "${label}" page extra assets`, () => {
94-
// it(`Block resources`, () => {
95-
// cy.exec(`npm run chunks -- restore`)
96-
// cy.exec(`npm run chunks -- block-page ${path} extra`)
97-
// })
98-
// runTests()
99-
// })
100-
// describe(`Missing all "${label}" page assets`, () => {
101-
// it(`Block resources`, () => {
102-
// cy.exec(`npm run chunks -- restore`)
103-
// cy.exec(`npm run chunks -- block-page ${path} all`)
104-
// })
105-
// runTests()
106-
// })
107-
// })
108-
// }
102+
runSuiteForPage(`Index`, `/`)
103+
runSuiteForPage(`Page-2`, `/page-2/`)
104+
runSuiteForPage(`404`, `/404.html`)
109105

110-
// runSuiteForPage(`Index`, `/`)
111-
// runSuiteForPage(`Page-2`, `/page-2/`)
112-
// runSuiteForPage(`404`, `/404.html`)
113-
114-
// describe(`Cleanup`, () => {
115-
// it(`Restore resources`, () => {
116-
// cy.exec(`npm run chunks -- restore`)
117-
// })
118-
// })
106+
describe(`Cleanup`, () => {
107+
it(`Restore resources`, () => {
108+
cy.task(`restoreAllBlockedResources`)
109+
})
110+
})
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
const fs = require(`fs-extra`)
2+
const path = require(`path`)
3+
const glob = require(`glob`)
4+
5+
const publicDir = path.join(__dirname, `..`, `..`, `public`)
6+
7+
const getAssetManifest = () => {
8+
const { assetsByChunkName } = require(`${publicDir}/webpack.stats.json`)
9+
return assetsByChunkName
10+
}
11+
12+
const moveAsset = (from, to) => {
13+
const fromExists = fs.existsSync(from)
14+
const toExists = fs.existsSync(to)
15+
16+
if (fromExists && !toExists) {
17+
fs.moveSync(from, to, {
18+
overwrite: true,
19+
})
20+
}
21+
}
22+
23+
const getAssetPath = assetFileName => path.join(publicDir, assetFileName)
24+
const getHiddenAssetPath = assetFileName => getAssetPath(`_${assetFileName}`)
25+
26+
const restoreAsset = assetFileName => {
27+
moveAsset(getHiddenAssetPath(assetFileName), getAssetPath(assetFileName))
28+
}
29+
30+
const blockAsset = assetFileName => {
31+
moveAsset(getAssetPath(assetFileName), getHiddenAssetPath(assetFileName))
32+
}
33+
34+
const blockAssetsForChunk = ({ chunk, filter }) => {
35+
const assetManifest = getAssetManifest()
36+
assetManifest[chunk].forEach(blockAsset)
37+
console.log(`Blocked assets for chunk "${chunk}"`)
38+
return null
39+
}
40+
41+
const restorePageData = hiddenPath => {
42+
if (path.basename(hiddenPath).charAt(0) !== `_`) {
43+
throw new Error(`hiddenPath should have _ prefix`)
44+
}
45+
const restoredPath = path.join(
46+
path.dirname(hiddenPath),
47+
path.basename(hiddenPath).slice(1)
48+
)
49+
moveAsset(hiddenPath, restoredPath)
50+
}
51+
52+
const getPageDataPath = pagePath => {
53+
const fixedPagePath = pagePath === `/` ? `index` : pagePath
54+
return path.join(publicDir, `page-data`, fixedPagePath, `page-data.json`)
55+
}
56+
57+
const getHiddenPageDataPath = pagePath => {
58+
const fixedPagePath = pagePath === `/` ? `index` : pagePath
59+
return path.join(publicDir, `page-data`, fixedPagePath, `_page-data.json`)
60+
}
61+
62+
const blockPageData = pagePath =>
63+
moveAsset(getPageDataPath(pagePath), getHiddenPageDataPath(pagePath))
64+
65+
const filterAssets = (assetsForPath, filter) =>
66+
assetsForPath.filter(asset => {
67+
if (filter === `all`) {
68+
return true
69+
} else if (filter === `page-data`) {
70+
return false
71+
}
72+
73+
const isMain = asset.startsWith(`component---`)
74+
if (filter === `page-template`) {
75+
return isMain
76+
} else if (filter === `extra`) {
77+
return !isMain
78+
}
79+
return false
80+
})
81+
82+
const blockAssetsForPage = ({ pagePath, filter }) => {
83+
const assetManifest = getAssetManifest()
84+
85+
const pageData = JSON.parse(fs.readFileSync(getPageDataPath(pagePath)))
86+
const { componentChunkName } = pageData
87+
const assetsForPath = assetManifest[componentChunkName]
88+
89+
const assets = filterAssets(assetsForPath, filter)
90+
assets.forEach(blockAsset)
91+
92+
if (filter === `all` || filter === `page-data`) {
93+
blockPageData(pagePath)
94+
}
95+
96+
console.log(`Blocked assets for path "${pagePath}" [${filter}]`)
97+
return null
98+
}
99+
100+
const restore = () => {
101+
const allAssets = Object.values(getAssetManifest()).reduce((acc, assets) => {
102+
assets.forEach(asset => acc.add(asset))
103+
return acc
104+
}, new Set())
105+
106+
allAssets.forEach(restoreAsset)
107+
108+
const globPattern = path.join(publicDir, `/page-data/**`, `_page-data.json`)
109+
const hiddenPageDatas = glob.sync(globPattern)
110+
hiddenPageDatas.forEach(restorePageData)
111+
112+
console.log(`Restored resources`)
113+
return null
114+
}
115+
116+
module.exports = {
117+
restoreAllBlockedResources: restore,
118+
blockAssetsForChunk,
119+
blockAssetsForPage,
120+
}

e2e-tests/production-runtime/cypress/plugins/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const compilationHash = require(`./compilation-hash`)
2+
const blockResources = require(`./block-resources`)
23

34
module.exports = (on, config) => {
45
// `on` is used to hook into various events Cypress emits
@@ -17,5 +18,5 @@ module.exports = (on, config) => {
1718
})
1819
}
1920

20-
on(`task`, Object.assign({}, compilationHash))
21+
on(`task`, Object.assign({}, compilationHash, blockResources))
2122
}

e2e-tests/production-runtime/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@
3535
"cy:run": "npm run cy:run:normal && npm run cy:run:slow",
3636
"cy:run:offline": "npm run cy:run:normal -- --env TEST_PLUGIN_OFFLINE=y && npm run cy:run:slow -- --env TEST_PLUGIN_OFFLINE=y",
3737
"cy:run:normal": "cypress run --browser chrome",
38-
"cy:run:slow": "CYPRESS_CONNECTION_TYPE=slow cypress run --browser chrome --config testFiles=prefetching.js",
39-
"chunks": "node scripts/chunks.js"
38+
"cy:run:slow": "CYPRESS_CONNECTION_TYPE=slow cypress run --browser chrome --config testFiles=prefetching.js"
4039
},
4140
"devDependencies": {
4241
"fs-extra": "^7.0.1",
4342
"prettier": "^1.14.3",
44-
"start-server-and-test": "^1.7.1",
45-
"yargs": "^12.0.5"
43+
"start-server-and-test": "^1.7.1"
4644
},
4745
"repository": {
4846
"type": "git",

0 commit comments

Comments
 (0)