Skip to content

Commit 4cc93ba

Browse files
prateekbhdevelopitForsakenHarmony
authored
Adding blank page for fallback (#1441)
* Adding blank page for fallback * switching fallback to 200.html * adding tests * bug fix * fixing tests * Apply suggestions from code review Co-authored-by: Leah <[email protected]> Co-authored-by: Jason Miller <[email protected]> Co-authored-by: Leah <[email protected]>
1 parent 2c53b0a commit 4cc93ba

File tree

7 files changed

+94
-34
lines changed

7 files changed

+94
-34
lines changed

packages/cli/lib/lib/webpack/render-html-plugin.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const { warn } = require('../../util');
99
const { info } = require('../../util');
1010
const { PRERENDER_DATA_FILE_NAME } = require('../constants');
1111

12+
const PREACT_FALLBACK_URL = '/200.html';
13+
1214
let defaultTemplate = resolve(__dirname, '../../resources/template.html');
1315

1416
function read(path) {
@@ -53,10 +55,14 @@ module.exports = async function (config) {
5355
writeFileSync(template, content);
5456
}
5557

56-
const htmlWebpackConfig = values => {
58+
const htmlWebpackConfig = (values) => {
5759
const { url, title, ...routeData } = values;
60+
// Do not create a folder if the url is for a specific file.
61+
const filename = url.endsWith('.html')
62+
? resolve(dest, url.substring(1))
63+
: resolve(dest, url.substring(1), 'index.html');
5864
return Object.assign(values, {
59-
filename: resolve(dest, url.substring(1), 'index.html'),
65+
filename,
6066
template: `!!ejs-loader?esModule=false!${template}`,
6167
minify: isProd && {
6268
collapseWhitespace: true,
@@ -90,7 +96,9 @@ module.exports = async function (config) {
9096
config,
9197
url,
9298
ssr() {
93-
return config.prerender ? prerender({ cwd, dest, src }, values) : '';
99+
return config.prerender && url !== PREACT_FALLBACK_URL
100+
? prerender({ cwd, dest, src }, values)
101+
: '';
94102
},
95103
scriptLoading: 'defer',
96104
CLI_DATA: { preRenderData: { url, ...routeData } },
@@ -137,12 +145,21 @@ module.exports = async function (config) {
137145
);
138146
}
139147
}
148+
/**
149+
* We cache a non SSRed page in service worker so that there is
150+
* no flash of content when user lands on routes other than `/`.
151+
* And we dont have to cache every single html file.
152+
* Go easy on network usage of clients.
153+
*/
154+
!pages.find((page) => page.url === PREACT_FALLBACK_URL) &&
155+
pages.push({ url: PREACT_FALLBACK_URL });
140156

141-
return pages
157+
const resultPages = pages
142158
.map(htmlWebpackConfig)
143-
.map(conf => new HtmlWebpackPlugin(conf))
159+
.map((conf) => new HtmlWebpackPlugin(conf))
144160
.concat([new HtmlWebpackExcludeAssetsPlugin()])
145-
.concat([...pages.map(page => new PrerenderDataExtractPlugin(page))]);
161+
.concat([...pages.map((page) => new PrerenderDataExtractPlugin(page))]);
162+
return resultPages;
146163
};
147164

148165
// Adds a preact_prerender_data in every folder so that the data could be fetched separately.
@@ -154,7 +171,11 @@ class PrerenderDataExtractPlugin {
154171
this.data_ = JSON.stringify(cliData.preRenderData || {});
155172
}
156173
apply(compiler) {
157-
compiler.hooks.emit.tap('PrerenderDataExtractPlugin', compilation => {
174+
compiler.hooks.emit.tap('PrerenderDataExtractPlugin', (compilation) => {
175+
if (this.location_ === `${PREACT_FALLBACK_URL}/`) {
176+
// We dont build prerender data for `200.html`. It can re-use the one for homepage.
177+
return;
178+
}
158179
let path = this.location_ + PRERENDER_DATA_FILE_NAME;
159180
if (path.startsWith('/')) {
160181
path = path.substr(1);
@@ -166,3 +187,5 @@ class PrerenderDataExtractPlugin {
166187
});
167188
}
168189
}
190+
191+
exports.PREACT_FALLBACK_URL = PREACT_FALLBACK_URL;

packages/cli/lib/lib/webpack/webpack-client-config.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ function isProd(config) {
228228
swSrc: swPath,
229229
swDest: 'sw-esm.js',
230230
include: [
231-
/^\/?index\.html$/,
231+
/200\.html$/,
232232
/\.esm.js$/,
233233
/\.css$/,
234234
/\.(png|jpg|svg|gif|webp)$/,
@@ -246,12 +246,7 @@ function isProd(config) {
246246
prodConfig.plugins.push(
247247
new InjectManifest({
248248
swSrc: swPath,
249-
include: [
250-
/index\.html$/,
251-
/\.js$/,
252-
/\.css$/,
253-
/\.(png|jpg|svg|gif|webp)$/,
254-
],
249+
include: [/200\.html$/, /\.js$/, /\.css$/, /\.(png|jpg|svg|gif|webp)$/],
255250
exclude: [/\.esm\.js$/],
256251
})
257252
);

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"preact": "^10.0.0",
5959
"preact-render-to-string": "^5.0.6",
6060
"preact-router": "^3.0.1",
61-
"puppeteer": "^5.0.0",
61+
"puppeteer": "^5.3.1",
6262
"sass-loader": "^10.0.4",
6363
"shelljs": "^0.8.3",
6464
"sirv": "^1.0.0-next.2"

packages/cli/sw/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function setupRouting() {
2020

2121
setCatchHandler(({ event }) => {
2222
if (isNav(event)) {
23-
return caches.match(getCacheKeyForURL('/index.html'));
23+
return caches.match(getCacheKeyForURL('/200.html'));
2424
}
2525
return Response.error();
2626
});

packages/cli/tests/images/build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ exports.default = exports.full = Object.assign({}, common, {
1616
'bundle.7e56a.css': 901,
1717
'favicon.ico': 15086,
1818
'index.html': 2034,
19+
'200.html': 613,
1920
'manifest.json': 455,
2021
'preact_prerender_data.json': 11,
2122
'push-manifest.json': 812,

packages/cli/tests/service-worker.test.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@ const { sleep } = require('./lib/utils');
55
const { getServer } = require('./server');
66
const startChrome = require('./lib/chrome');
77

8+
async function enableOfflineMode(page, browser) {
9+
await sleep(2000); // wait for service worker installation.
10+
await page.setOfflineMode(true);
11+
const targets = await browser.targets();
12+
const serviceWorker = targets.find((t) => t.type() === 'service_worker');
13+
const serviceWorkerConnection = await serviceWorker.createCDPSession();
14+
await serviceWorkerConnection.send('Network.enable');
15+
await serviceWorkerConnection.send('Network.emulateNetworkConditions', {
16+
offline: true,
17+
latency: 0,
18+
downloadThroughput: 0,
19+
uploadThroughput: 0,
20+
});
21+
}
22+
823
describe('preact service worker tests', () => {
924
let server, browser, dir;
1025

1126
beforeAll(async () => {
1227
dir = await create('default');
13-
browser = await startChrome();
1428
await build(dir, {
1529
sw: true,
1630
esm: true,
@@ -19,9 +33,16 @@ describe('preact service worker tests', () => {
1933
server = getServer(dir);
2034
});
2135

36+
beforeEach(async () => {
37+
browser = await startChrome();
38+
});
39+
40+
afterEach(async () => {
41+
await browser.close();
42+
});
43+
2244
afterAll(async () => {
2345
await server.server.stop();
24-
await browser.close();
2546
});
2647

2748
it('works offline', async () => {
@@ -31,15 +52,14 @@ describe('preact service worker tests', () => {
3152
waitUntil: 'networkidle0',
3253
});
3354
const initialContent = await page.content();
34-
await sleep(2000); // wait for service worker installation.
35-
await page.setOfflineMode(true);
36-
await page.reload();
55+
await enableOfflineMode(page, browser);
56+
await page.reload({ waitUntil: 'networkidle0' });
3757
const offlineContent = await page.content();
3858
await page.waitForSelector('h1');
3959
expect(
40-
await page.$$eval('h1', nodes => nodes.map(n => n.innerText))
60+
await page.$$eval('h1', (nodes) => nodes.map((n) => n.innerText))
4161
).toEqual(['Preact App', 'Home']);
42-
expect(offlineContent).toEqual(initialContent);
62+
expect(offlineContent).not.toEqual(initialContent);
4363
});
4464

4565
it('should fetch navigation requests with networkFirst', async () => {
@@ -64,4 +84,26 @@ describe('preact service worker tests', () => {
6484
expect(initialContent).not.toEqual(refreshedContent);
6585
expect(refreshedContent.includes(NEW_TITLE)).toEqual(true);
6686
});
87+
88+
it('should respond with 200.html when offline', async () => {
89+
const swText = await fetch('http://localhost:3000/sw-esm.js').then((res) =>
90+
res.text()
91+
);
92+
// eslint-disable-next-line no-useless-escape
93+
expect(swText).toContain(
94+
'caches.match((t="/200.html",ce().getCacheKeyForURL(t)))'
95+
);
96+
const page = await browser.newPage();
97+
await page.setCacheEnabled(false);
98+
await page.goto('http://localhost:3000', {
99+
waitUntil: 'networkidle0',
100+
});
101+
await enableOfflineMode(page, browser);
102+
await page.reload({ waitUntil: 'networkidle0' });
103+
expect(
104+
await page.$$eval('script[type=__PREACT_CLI_DATA__]', (nodes) =>
105+
nodes.map((n) => n.innerText)
106+
)
107+
).toEqual(['%7B%22preRenderData%22:%7B%22url%22:%22/200.html%22%7D%7D']);
108+
});
67109
});

yarn.lock

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5343,10 +5343,10 @@ detect-node@^2.0.4:
53435343
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
53445344
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
53455345

5346-
5347-
version "0.0.781568"
5348-
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.781568.tgz#4cdca90a952d2c77831096ff6cd32695d8715a04"
5349-
integrity sha512-9Uqnzy6m6zEStluH9iyJ3iHyaQziFnMnLeC8vK0eN6smiJmIx7+yB64d67C2lH/LZra+5cGscJAJsNXO+MdPMg==
5346+
5347+
version "0.0.799653"
5348+
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.799653.tgz#86fc95ce5bf4fdf4b77a58047ba9d2301078f119"
5349+
integrity sha512-t1CcaZbvm8pOlikqrsIM9GOa7Ipp07+4h/q9u0JXBWjPCjHdBl9KkddX87Vv9vBHoBGtwV79sYQNGnQM6iS5gg==
53505350

53515351
dezalgo@^1.0.0, dezalgo@~1.0.3:
53525352
version "1.0.3"
@@ -9618,7 +9618,7 @@ [email protected]:
96189618
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
96199619
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
96209620

9621-
mime@^2.0.3, mime@^2.3.1, mime@^2.4.4:
9621+
mime@^2.3.1, mime@^2.4.4:
96229622
version "2.4.6"
96239623
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
96249624
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
@@ -11801,16 +11801,15 @@ pupa@^2.0.1:
1180111801
dependencies:
1180211802
escape-goat "^2.0.0"
1180311803

11804-
puppeteer@^5.0.0:
11805-
version "5.2.1"
11806-
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.2.1.tgz#7f0564f0a5384f352a38c8cc42af875cd87f4ea6"
11807-
integrity sha512-PZoZG7u+T6N1GFWBQmGVG162Ak5MAy8nYSVpeeQrwJK2oYUlDWpHEJPcd/zopyuEMTv7DiztS1blgny1txR2qw==
11804+
puppeteer@^5.3.1:
11805+
version "5.3.1"
11806+
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.3.1.tgz#324e190d89f25ac33dba539f57b82a18553f8646"
11807+
integrity sha512-YTM1RaBeYrj6n7IlRXRYLqJHF+GM7tasbvrNFx6w1S16G76NrPq7oYFKLDO+BQsXNtS8kW2GxWCXjIMPvfDyaQ==
1180811808
dependencies:
1180911809
debug "^4.1.0"
11810-
devtools-protocol "0.0.781568"
11810+
devtools-protocol "0.0.799653"
1181111811
extract-zip "^2.0.0"
1181211812
https-proxy-agent "^4.0.0"
11813-
mime "^2.0.3"
1181411813
pkg-dir "^4.2.0"
1181511814
progress "^2.0.1"
1181611815
proxy-from-env "^1.0.0"

0 commit comments

Comments
 (0)