Skip to content

Commit ac44d1d

Browse files
authored
Merge pull request #7 from davidchin/prefetch
CHECKOUT-4400: Add ability to prefetch scripts and stylesheets
2 parents f3356b0 + 072d567 commit ac44d1d

File tree

4 files changed

+119
-4
lines changed

4 files changed

+119
-4
lines changed

src/script-loader.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,24 @@ describe('ScriptLoader', () => {
177177
.toEqual('https://cdn.foobar.com/foo.min.js');
178178
});
179179

180+
it('prefetches script if option is provided', async () => {
181+
await loader.preloadScript('https://cdn.foobar.com/foo.min.js', {
182+
prefetch: true,
183+
});
184+
185+
expect(document.head.appendChild)
186+
.toHaveBeenCalledWith(preloadedScript);
187+
188+
expect(preloadedScript.rel)
189+
.toEqual('prefetch');
190+
191+
expect(preloadedScript.as)
192+
.toEqual('script');
193+
194+
expect(preloadedScript.href)
195+
.toEqual('https://cdn.foobar.com/foo.min.js');
196+
});
197+
180198
it('resolves promise if script is preloaded', async () => {
181199
const output = await loader.preloadScript('https://cdn.foobar.com/foo.min.js');
182200

src/script-loader.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export interface PreloadScriptOptions {
2+
prefetch: boolean;
3+
}
4+
15
export default class ScriptLoader {
26
private _scripts: { [key: string]: Promise<Event> } = {};
37
private _preloadedScripts: { [key: string]: Promise<Event> } = {};
@@ -48,13 +52,14 @@ export default class ScriptLoader {
4852
.then(() => events);
4953
}
5054

51-
preloadScript(url: string): Promise<Event> {
55+
preloadScript(url: string, options?: PreloadScriptOptions): Promise<Event> {
5256
if (!this._preloadedScripts[url]) {
5357
this._preloadedScripts[url] = new Promise((resolve, reject) => {
5458
const preloadedScript = document.createElement('link');
59+
const { prefetch = false } = options || {};
5560

5661
preloadedScript.as = 'script';
57-
preloadedScript.rel = 'preload';
62+
preloadedScript.rel = prefetch ? 'prefetch' : 'preload';
5863
preloadedScript.href = url;
5964

6065
preloadedScript.onload = event => {
@@ -73,8 +78,8 @@ export default class ScriptLoader {
7378
return this._preloadedScripts[url];
7479
}
7580

76-
preloadScripts(urls: string[]): Promise<Event[]> {
77-
return Promise.all(urls.map(url => this.preloadScript(url)));
81+
preloadScripts(urls: string[], options?: PreloadScriptOptions): Promise<Event[]> {
82+
return Promise.all(urls.map(url => this.preloadScript(url, options)));
7883
}
7984
}
8085

src/stylesheet-loader.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,61 @@ describe('StylesheetLoader', () => {
8383
.toHaveBeenCalledTimes(2);
8484
});
8585
});
86+
87+
describe('when preloading stylesheet', () => {
88+
let preloadedStylesheet: HTMLLinkElement;
89+
90+
beforeEach(() => {
91+
preloadedStylesheet = document.createElement('link');
92+
93+
jest.spyOn(document, 'createElement')
94+
.mockImplementation(() => preloadedStylesheet);
95+
96+
jest.spyOn(document.head, 'appendChild')
97+
.mockImplementation(element =>
98+
setTimeout(() => element.onload(new Event('load')), 0)
99+
);
100+
});
101+
102+
it('attaches preload link tag to document', async () => {
103+
await loader.preloadStylesheet('https://foo.bar/hello-world.css');
104+
105+
expect(document.head.appendChild)
106+
.toHaveBeenCalledWith(preloadedStylesheet);
107+
108+
expect(preloadedStylesheet.rel)
109+
.toEqual('preload');
110+
111+
expect(preloadedStylesheet.as)
112+
.toEqual('style');
113+
114+
expect(preloadedStylesheet.href)
115+
.toEqual('https://foo.bar/hello-world.css');
116+
});
117+
118+
it('prefetches stylesheet if option is provided', async () => {
119+
await loader.preloadStylesheet('https://foo.bar/hello-world.css', {
120+
prefetch: true,
121+
});
122+
123+
expect(document.head.appendChild)
124+
.toHaveBeenCalledWith(preloadedStylesheet);
125+
126+
expect(preloadedStylesheet.rel)
127+
.toEqual('prefetch');
128+
129+
expect(preloadedStylesheet.as)
130+
.toEqual('style');
131+
132+
expect(preloadedStylesheet.href)
133+
.toEqual('https://foo.bar/hello-world.css');
134+
});
135+
136+
it('resolves promise if stylesheet is preloaded', async () => {
137+
const output = await loader.preloadStylesheet('https://foo.bar/hello-world.css');
138+
139+
expect(output)
140+
.toBeInstanceOf(Event);
141+
});
142+
});
86143
});

src/stylesheet-loader.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ export interface LoadStylesheetOptions {
22
prepend: boolean;
33
}
44

5+
export interface PreloadStylesheetOptions {
6+
prefetch: boolean;
7+
}
8+
59
export default class StylesheetLoader {
610
private _stylesheets: { [key: string]: Promise<Event> } = {};
11+
private _preloadedStylesheets: { [key: string]: Promise<Event> } = {};
712

813
loadStylesheet(src: string, options?: LoadStylesheetOptions): Promise<Event> {
914
if (!this._stylesheets[src]) {
@@ -33,4 +38,34 @@ export default class StylesheetLoader {
3338
loadStylesheets(urls: string[], options?: LoadStylesheetOptions): Promise<Event[]> {
3439
return Promise.all(urls.map(url => this.loadStylesheet(url, options)));
3540
}
41+
42+
preloadStylesheet(url: string, options?: PreloadStylesheetOptions): Promise<Event> {
43+
if (!this._preloadedStylesheets[url]) {
44+
this._preloadedStylesheets[url] = new Promise((resolve, reject) => {
45+
const preloadedStylesheet = document.createElement('link');
46+
const { prefetch = false } = options || {};
47+
48+
preloadedStylesheet.as = 'style';
49+
preloadedStylesheet.rel = prefetch ? 'prefetch' : 'preload';
50+
preloadedStylesheet.href = url;
51+
52+
preloadedStylesheet.onload = event => {
53+
resolve(event);
54+
};
55+
56+
preloadedStylesheet.onerror = event => {
57+
delete this._preloadedStylesheets[url];
58+
reject(event);
59+
};
60+
61+
document.head.appendChild(preloadedStylesheet);
62+
});
63+
}
64+
65+
return this._preloadedStylesheets[url];
66+
}
67+
68+
preloadStylesheets(urls: string[], options?: PreloadStylesheetOptions): Promise<Event[]> {
69+
return Promise.all(urls.map(url => this.preloadStylesheet(url, options)));
70+
}
3671
}

0 commit comments

Comments
 (0)