Skip to content

Commit 0fc52e7

Browse files
committed
fix(core): CHECKOUT-4455 Provide fallback for browsers that don't support preload attribute
1 parent d8e6e4b commit 0fc52e7

11 files changed

+286
-76
lines changed

package-lock.json

Lines changed: 58 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"validate-commits": "validate-commits"
3333
},
3434
"dependencies": {
35+
"@bigcommerce/request-sender": "^0.3.0",
3536
"tslib": "^1.10.0"
3637
},
3738
"devDependencies": {

src/browser-support.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import BrowserSupport from './browser-support';
2+
3+
describe('BrowserSupport', () => {
4+
let link: HTMLLinkElement;
5+
let support: BrowserSupport;
6+
7+
beforeEach(() => {
8+
const createElement = document.createElement.bind(document);
9+
10+
link = {
11+
relList: {
12+
supports: jest.fn(() => true),
13+
},
14+
} as unknown as HTMLLinkElement;
15+
16+
jest.spyOn(document, 'createElement')
17+
.mockImplementation(type => {
18+
return type === 'link' ? link : createElement(type);
19+
});
20+
21+
support = new BrowserSupport();
22+
});
23+
24+
it('returns true if able to support rel type', () => {
25+
expect(support.canSupportRel('preload'))
26+
.toEqual(true);
27+
});
28+
29+
it('returns false if unable to support rel type', () => {
30+
jest.spyOn(link.relList, 'supports')
31+
.mockReturnValue(false);
32+
33+
expect(support.canSupportRel('preload'))
34+
.toEqual(false);
35+
});
36+
37+
it('returns false if `relList` is not supported', () => {
38+
link = {} as unknown as HTMLLinkElement;
39+
40+
expect(support.canSupportRel('preload'))
41+
.toEqual(false);
42+
});
43+
});

src/browser-support.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default class BrowserSupport {
2+
canSupportRel(rel: string): boolean {
3+
const link = document.createElement('link');
4+
5+
return !!(
6+
link.relList &&
7+
link.relList.supports &&
8+
link.relList.supports(rel)
9+
);
10+
}
11+
}

src/create-script-loader.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import { createRequestSender } from '@bigcommerce/request-sender';
2+
3+
import BrowserSupport from './browser-support';
14
import ScriptLoader from './script-loader';
25

36
export default function createScriptLoader(): ScriptLoader {
4-
return new ScriptLoader();
7+
return new ScriptLoader(
8+
new BrowserSupport(),
9+
createRequestSender()
10+
);
511
}

src/create-stylesheet-loader.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import { createRequestSender } from '@bigcommerce/request-sender';
2+
3+
import BrowserSupport from './browser-support';
14
import StylesheetLoader from './stylesheet-loader';
25

36
export default function createStylesheetLoader(): StylesheetLoader {
4-
return new StylesheetLoader();
7+
return new StylesheetLoader(
8+
new BrowserSupport(),
9+
createRequestSender()
10+
);
511
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export { default as ScriptLoader } from './script-loader';
1+
export { default as ScriptLoader, LoadScriptOptions, PreloadScriptOptions } from './script-loader';
22
export { default as createScriptLoader } from './create-script-loader';
33
export { default as getScriptLoader } from './get-script-loader';
44

5-
export { default as StylesheetLoader } from './stylesheet-loader';
5+
export { default as StylesheetLoader, LoadStylesheetOptions, PreloadStylesheetOptions } from './stylesheet-loader';
66
export { default as createStylesheetLoader } from './create-stylesheet-loader';
77
export { default as getStylesheetLoader } from './get-stylesheet-loader';

src/script-loader.spec.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
1+
import { createRequestSender, RequestSender } from '@bigcommerce/request-sender';
2+
3+
import BrowserSupport from './browser-support';
14
import ScriptLoader from './script-loader';
25

36
describe('ScriptLoader', () => {
7+
let browserSupport: BrowserSupport;
48
let loader: ScriptLoader;
9+
let requestSender: RequestSender;
510

611
beforeEach(() => {
7-
loader = new ScriptLoader();
12+
browserSupport = new BrowserSupport();
13+
requestSender = createRequestSender();
14+
15+
jest.spyOn(browserSupport, 'canSupportRel')
16+
.mockReturnValue(true);
17+
18+
jest.spyOn(requestSender, 'get')
19+
.mockReturnValue(Promise.resolve({}));
20+
21+
loader = new ScriptLoader(
22+
browserSupport,
23+
requestSender
24+
);
825
});
926

1027
afterEach(() => {
@@ -36,6 +53,13 @@ describe('ScriptLoader', () => {
3653
.toEqual('https://code.jquery.com/jquery-3.2.1.min.js');
3754
});
3855

56+
it('loads script synchronously by default', async () => {
57+
await loader.loadScript('https://code.jquery.com/jquery-3.2.1.min.js');
58+
59+
expect(script.async)
60+
.toEqual(false);
61+
});
62+
3963
it('resolves promise if script is loaded', async () => {
4064
const output = await loader.loadScript('https://code.jquery.com/jquery-3.2.1.min.js');
4165

@@ -111,13 +135,6 @@ describe('ScriptLoader', () => {
111135
];
112136
});
113137

114-
it('preloads scripts in parallel', async () => {
115-
await loader.loadScripts(urls);
116-
117-
expect(loader.preloadScripts)
118-
.toHaveBeenCalledWith(urls);
119-
});
120-
121138
it('loads preloaded scripts in sequence', async () => {
122139
jest.spyOn(loader, 'loadScript')
123140
.mockReturnValue(Promise.resolve(new Event('readystatechange')));
@@ -195,6 +212,19 @@ describe('ScriptLoader', () => {
195212
.toEqual('https://cdn.foobar.com/foo.min.js');
196213
});
197214

215+
it('falls back to using XHR if browser does not support preload', async () => {
216+
jest.spyOn(browserSupport, 'canSupportRel')
217+
.mockImplementation(rel => rel === 'preload' ? false : true);
218+
219+
await loader.preloadScript('https://cdn.foobar.com/foo.min.js');
220+
221+
expect(requestSender.get)
222+
.toHaveBeenCalledWith('https://cdn.foobar.com/foo.min.js', {
223+
credentials: false,
224+
headers: { Accept: 'application/javascript' },
225+
});
226+
});
227+
198228
it('resolves promise if script is preloaded', async () => {
199229
const output = await loader.preloadScript('https://cdn.foobar.com/foo.min.js');
200230

0 commit comments

Comments
 (0)