Skip to content

Commit e6ab35f

Browse files
authored
Only prefetch in production and in dev with process.env.SERVICE_WORKE… (#83)
* Only prefetch in production and in dev with process.env.SERVICE_WORKER set to true. * bump version * Always send version when fetching * Support Request as the first parameter to patched fetch.
1 parent a382c65 commit e6ab35f

File tree

10 files changed

+232
-79
lines changed

10 files changed

+232
-79
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-storefront",
3-
"version": "8.2.0",
3+
"version": "8.3.0",
44
"description": "Build and deploy e-commerce progressive web apps (PWAs) in record time.",
55
"module": "./index.js",
66
"license": "Apache-2.0",

src/api/addVersion.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* The query param that is added to fetch alls to /api routes to ensure that
3+
* cached results from previous versions of the app are not served to new versions
4+
* of the app.
5+
*/
6+
export const VERSION_PARAM = '__v__'
7+
8+
/**
9+
* Adds the next build id as the __v__ query parameter to the given url
10+
* @param {String} url Any URL
11+
* @return {URL}
12+
*/
13+
export default function addVersion(url) {
14+
/* istanbul ignore next */
15+
if (typeof window === 'undefined') return new URL(url, 'http://throwaway.api')
16+
17+
const parsed = new URL(url, window.location.href)
18+
19+
if (!parsed.searchParams.has(VERSION_PARAM) && typeof __NEXT_DATA__ !== 'undefined') {
20+
parsed.searchParams.append(VERSION_PARAM, __NEXT_DATA__.buildId)
21+
}
22+
23+
return parsed
24+
}

src/api/getAPIURL.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1+
import addVersion from './addVersion'
2+
13
/**
24
* Returns the API URL for the given page
35
* @param {String} pageURI The page URI
46
* @return {String}
57
*/
68
export default function getAPIURL(pageURI) {
7-
const parsed = new URL(pageURI, 'http://throwaway.com')
8-
9-
if (typeof __NEXT_DATA__ !== 'undefined') {
10-
parsed.searchParams.append('__v__', __NEXT_DATA__.buildId)
11-
}
12-
9+
const parsed = addVersion(pageURI)
1310
return '/api' + parsed.pathname.replace(/\/$/, '') + parsed.search
1411
}

src/fetch.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
1-
let fetch = require('isomorphic-unfetch')
1+
import addVersion from './api/addVersion'
2+
3+
// Here we patch fetch and XMLHttpRequest to always add version parameter to api calls so that cached results
4+
// from previous versions of the app aren't served to new versions.
5+
/* istanbul ignore else */
6+
if (typeof window !== 'undefined') {
7+
const originalFetch = window.fetch
8+
9+
window.fetch = function rsfVersionedFetch(url, init) {
10+
if (url.url) {
11+
// the first param can be a request object
12+
url = new Request(addVersion(url.url).toString(), url)
13+
} else {
14+
url = addVersion(url).toString()
15+
}
16+
17+
return originalFetch(url, init)
18+
}
19+
}
20+
21+
/* istanbul ignore else */
22+
if (typeof XMLHttpRequest !== 'undefined') {
23+
const originalOpen = XMLHttpRequest.prototype.open
24+
25+
XMLHttpRequest.prototype.open = function rsfVersionedOpen(method, url, ...others) {
26+
return originalOpen.call(this, method, addVersion(url).toString(), ...others)
27+
}
28+
}
229

330
/**
431
* An isomorphic implementation of the fetch API. You should always use this to fetch data on both the client and server.
32+
* When making requests to /api routes, ?__v__={next_build_id} will always be added to ensure that cached results
33+
* from previous versions of the app aren't served to new versions.
534
*/
6-
export default fetch
35+
export default require('isomorphic-unfetch')

src/plugins/withReactStorefront.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = ({ prefetchQueryParam, ...nextConfig } = {}) => {
3030
config.plugins.push(
3131
new webpack.DefinePlugin({
3232
'process.env.RSF_PREFETCH_QUERY_PARAM': JSON.stringify(prefetchQueryParam),
33+
'process.env.SERVICE_WORKER': JSON.stringify(process.env.SERVICE_WORKER),
3334
}),
3435
)
3536

src/serviceWorker.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import getAPIURL from './api/getAPIURL'
2+
import addVersion from './api/addVersion'
3+
24
const prefetched = new Set()
35

46
/**
@@ -28,6 +30,16 @@ export function waitForServiceWorker() {
2830
* @param {String} url The URL to prefetch
2931
*/
3032
export async function prefetch(url) {
33+
if (process.env.NODE_ENV !== 'production' && process.env.SERVICE_WORKER !== 'true') {
34+
// note that even though we wait for the service worker to be available, during local
35+
// development it is still possible for a service worker to be around from a previous
36+
// build of the app, so we disable prefetching in development unless process.env.SERVICE_WORKER = true
37+
// so that prefetching does not slow bog down the local node server and slow down development
38+
return
39+
}
40+
41+
url = addVersion(url).toString()
42+
3143
if (prefetched.has(url)) {
3244
return
3345
}

test/api/addVersion.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import addVersion from '../../src/api/addVersion'
2+
3+
describe('addVersion', () => {
4+
beforeEach(() => {
5+
window.__NEXT_DATA__ = {
6+
buildId: 'development',
7+
}
8+
})
9+
10+
afterEach(() => {
11+
delete window.__NEXT_DATA__
12+
})
13+
14+
it('should add the version query param', () => {
15+
expect(addVersion('/foo').toString()).toBe('http://localhost/foo?__v__=development')
16+
})
17+
18+
it('should not duplicate the version query param', () => {
19+
expect(addVersion('/foo?__v__=1').toString()).toBe('http://localhost/foo?__v__=1')
20+
})
21+
22+
it('should leave the original hostname intact', () => {
23+
expect(addVersion('http://foo.com/foo').toString()).toBe('http://foo.com/foo?__v__=development')
24+
})
25+
})

test/fetch.test.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
11
describe('fetch', () => {
2-
let f, fetch
2+
let f, fetch, originalOpen, originalFetch
33

44
beforeEach(() => {
5-
f = jest.fn()
6-
jest.doMock('isomorphic-unfetch', () => f)
7-
fetch = require('../src/fetch').default
5+
jest.isolateModules(() => {
6+
f = jest.fn()
7+
jest.doMock('isomorphic-unfetch', () => f)
8+
originalOpen = jest.spyOn(XMLHttpRequest.prototype, 'open')
9+
originalFetch = jest.spyOn(window, 'fetch').mockImplementation()
10+
fetch = require('../src/fetch').default
11+
window.__NEXT_DATA__ = {
12+
buildId: 'development',
13+
}
14+
})
15+
})
16+
17+
afterEach(() => {
18+
delete window.__NEXT_DATA__
819
})
920

1021
it('should export isomorphic-unfetch for backwards compatibility', () => {
1122
fetch('/foo')
1223
expect(f).toHaveBeenCalledWith('/foo')
1324
})
25+
26+
it('should patch window.fetch', () => {
27+
window.fetch('/foo')
28+
expect(originalFetch).toHaveBeenCalledWith('http://localhost/foo?__v__=development', undefined)
29+
})
30+
31+
it('should accept a request object as the first parameter', () => {
32+
window.fetch(new Request('/foo'))
33+
expect(originalFetch.mock.calls[0][0].url).toBe('http://localhost/foo?__v__=development')
34+
})
35+
36+
it('should add the version to XMLHttpRequest.open', () => {
37+
const req = new XMLHttpRequest()
38+
req.open('GET', '/foo')
39+
expect(originalOpen).toHaveBeenCalledWith('GET', 'http://localhost/foo?__v__=development')
40+
})
1441
})

test/plp/SearchResultsProvider.test.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ describe('SearchResultsProvider', () => {
1616
}
1717
let wrapper, context, getStore
1818

19+
beforeEach(() => {
20+
window.__NEXT_DATA__ = {
21+
buildId: 'development',
22+
}
23+
})
24+
1925
afterEach(() => {
2026
wrapper.unmount()
2127
context = undefined
2228
getStore = undefined
29+
delete window.__NEXT_DATA__
2330
})
2431

2532
const Test = () => {
@@ -147,9 +154,12 @@ describe('SearchResultsProvider', () => {
147154
})
148155

149156
it('refresh - should always remove "more" query param if sees it', async () => {
150-
const windowSpy = jest
151-
.spyOn(global.window, 'location', 'get')
152-
.mockReturnValue({ search: '?more=1', hash: '', pathname: '/test' })
157+
const windowSpy = jest.spyOn(global.window, 'location', 'get').mockReturnValue({
158+
search: '?more=1',
159+
hash: '',
160+
pathname: '/test',
161+
href: 'http://localhost',
162+
})
153163
fetchMock.mockResponseOnce(
154164
JSON.stringify({
155165
pageData: {
@@ -164,7 +174,7 @@ describe('SearchResultsProvider', () => {
164174
await wrapper.update()
165175
})
166176

167-
expect(fetch).toHaveBeenCalledWith('/api/test')
177+
expect(fetch).toHaveBeenCalledWith('/api/test?__v__=development')
168178

169179
windowSpy.mockRestore()
170180
})

0 commit comments

Comments
 (0)