8000 feat: add ability to preload fonts (fixes #21) · gajus/usus@d4865a5 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit d4865a5

Browse files
committed
feat: add ability to preload fonts (fixes #21)
1 parent 50f93d3 commit d4865a5

File tree

10 files changed

+161
-4
lines changed

10 files changed

+161
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export type UserConfigurationType = {
102102
+extractStyles?: boolean,
103103
+formatStyles?: FormatStylesType,
104104
+inlineStyles?: boolean,
105+
+preloadFonts?: boolean,
105106
+preloadStyles?: boolean
106107
};
107108

@@ -121,6 +122,7 @@ The default behaviour is to return the HTML.
121122
|`extractStyles`|`boolean`|Extracts CSS used to render the page.|`false`|
122123
|`formatStyles`|`(styles: string) => Promise<string>`|Used to format CSS. Useful with `inlineStyles=true` option to format the CSS before it is inlined.|N/A|
123124
|`inlineStyles`|`boolean`|Inlines the styles required to render the document.|`false`|
125+
|`preloadFonts`|`boolean`|Adds `rel=preload` for all fonts required to render the page.|`true`|
124126
|`preloadStyles`|`boolean`|Adds `rel=preload` for all styles removed from `<head>`. Used with `inlineStyles=true`.|`true`|
125127
|`url`|`string`|The URL to render.|N/A|
126128

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --copy-files --source-maps && flow-copy-source src dist",
7474
"lint": "eslint ./src ./test && flow",
7575
"precommit": "npm run lint && npm run test && npm run build",
76-
"test": "NODE_ENV=development nyc --reporter=text ava --verbose --serial test/createConfiguration.js test/usus.js"
76+
"test": "NODE_ENV=development nyc --reporter=text ava --verbose --serial test/factories/* test/utilities/* test/usus.js"
7777
},
7878
"version": "1.0.1"
7979
}

src/bin/commands/render.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export const baseConfiguration = {
5757
description: 'Inlines the styles required to render the document.',
5858
type: 'boolean'
5959
},
60+
preloadFonts: {
61+
default: true,
62+
description: 'Adds rel=preload for all fonts required to render the page.',
63+
type: 'boolean'
64+
},
6065
preloadStyles: {
6166
default: true,
6267
description: 'Adds rel=preload for all styles removed from <head>. Used with inlineStyles=true.',

src/factories/createConfiguration.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default (userConfiguration: UserConfigurationType): ConfigurationType =>
2222
const extractStyles = userConfiguration.extractStyles || false;
2323
const formatStyles = userConfiguration.formatStyles;
2424
const inlineStyles = userConfiguration.inlineStyles || false;
25+
const preloadFonts = userConfiguration.preloadFonts !== false;
2526
const preloadStyles = userConfiguration.preloadStyles !== false;
2627

2728
if (extractStyles && inlineStyles) {
@@ -45,6 +46,7 @@ export default (userConfiguration: UserConfigurationType): ConfigurationType =>
4546
extractStyles,
4647
formatStyles,
4748
inlineStyles,
49+
preloadFonts,
4850
preloadStyles
4951
};
5052
};

src/types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type UserConfigurationType = {
3535
+extractStyles?: boolean,
3636
+formatStyles?: FormatStylesType,
3737
+inlineStyles?: boolean,
38+
+preloadFonts?: boolean,
3839
+preloadStyles?: boolean
3940
};
4041

@@ -46,5 +47,6 @@ export type ConfigurationType = {|
4647
+extractStyles: boolean,
4748
+formatStyles?: FormatStylesType,
4849
+inlineStyles: boolean,
50+
+preloadFonts: boolean,
4951
+preloadStyles: boolean
5052
|};

src/usus.js

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22

3+
import URL from 'url';
34
import {
45
launch
56
} from 'chrome-launcher';
@@ -10,6 +11,7 @@ import {
1011
delay
1112
} from 'bluefeather';
1213
import createConfiguration from './factories/createConfiguration';
14+
import normalizeNetworkResourceUrl from './utilities/normalizeNetworkResourceUrl';
1315
import type {
1416
UserConfigurationType
1517
} from './types';
@@ -115,6 +117,37 @@ const inlineStylePreload = async (DOM: *, Runtime: *, rootNodeId: number, styleI
115117
});
116118
};
117119

120+
const inlineFontPreload = async (DOM: *, Runtime: *, rootNodeId: number, fontUrls: $ReadOnlyArray<string>) => {
121+
// @todo See note in inlineStyles.
122+
123+
await Runtime.evaluate({
124+
expression: `
125+
{
126+
const scriptElement = document.createElement('div');
127+
scriptElement.setAttribute('id', 'usus-font-preload');
128+
document.head.appendChild(scriptElement);
129+
}
130+
`
131+
});
132+
133+
const nodeId = (await DOM.querySelector({
134+
nodeId: rootNodeId,
135+
selector: '#usus-font-preload'
136+
})).nodeId;
137+
138+
debug('#usus-font-preload nodeId %d', nodeId);
139+
140+
const stylePreloadLinks = fontUrls
141+
.map((fontUrl) => {
142+
return `<link rel="preload" href="${fontUrl}" as="font">`;
143+
});
144+
145+
await DOM.setOuterHTML({
146+
nodeId,
147+
outerHTML: stylePreloadLinks.join('\n')
148+
});
149+
};
150+
118151
export const render = async (url: string, userConfiguration: UserConfigurationType = {}): Promise<string> => {
119152
const configuration = createConfiguration(userConfiguration);
120153

@@ -150,15 +183,16 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
150183
CSS,
151184
DOM,
152185
Emulation,
186+
Network,
153187
Page,
154-
Runtime,
155-
Network
188+
Runtime
156189
} = protocol;
157190

158191
await DOM.enable();
159192
await CSS.enable();
160193
await Page.enable();
161194
await Runtime.enable();
195+
await Network.enable();
162196

163197
Emulation.setDeviceMetricsOverride(configuration.deviceMetricsOverride);
164198

@@ -196,6 +230,34 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
196230
url
197231
});
198232

233+
const downloadedFontUrls = [];
234+
235+
Network.requestWillBeSent((request) => {
236+
if (request.frameId !== frame.frameId) {
237+
debug('ignoring HTTP request; alien frame');
238+
239+
return;
240+
}
241+
242+
const tokens = URL.parse(request.request.url);
243+
244+
const pathname = tokens.pathname;
245+
246+
if (!pathname) {
247+
debug('ignoring HTTP request; URL is missing pathname');
248+
249+
return;
250+
}
251+
252+
if (!pathname.endsWith('.woff') && !pathname.endsWith('.woff2')) {
253+
debug('ignoring HTTP request; network resource is not a supported font');
254+
255+
return;
256+
}
257+
258+
downloadedFontUrls.push(normalizeNetworkResourceUrl(url, tokens.href));
259+
});
260+
199261
const frameId = frame.frameId;
200262

201263
let usedStyles;
@@ -281,6 +343,10 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
281343
await inlineStylePreload(DOM, Runtime, rootDocument.root.nodeId, styleImportLinks);
282344
}
283345

346+
if (configuration.preloadFonts) {
347+
await inlineFontPreload(DOM, Runtime, rootDocument.root.nodeId, downloadedFontUrls);
348+
}
349+
284350
if (usedStyles) {
285351
await inlineStyles(DOM, Runtime, rootDocument.root.nodeId, usedStyles);
286352
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @flow
2+
3+
import URL from 'url';
4+
5+
/**
6+
* It is important to load to use relative URLs when the protocol and host match.
7+
* This is because pre-rendering might be done against a different URL than the
8+
* actual website, e.g. https://raw.gajus.com/ instead of https://gajus.com/.
9+
* Therefore, we need to ensure that resources are loaded relative to the
10+
* current host.
11+
*/
12+
export default (targetUrl: string, resourceUrl: string): string => {
13+
const targetUrlTokens = URL.parse(targetUrl);
14+
const resourceUrlTokens = URL.parse(resourceUrl);
15+
16+
if (targetUrlTokens.protocol !== resourceUrlTokens.protocol || targetUrlTokens.host !== resourceUrlTokens.host) {
17+
return resourceUrl;
18+
}
19+
20+
return String(resourceUrlTokens.path);
21+
};

test/createConfiguration.js renamed to test/factories/createConfiguration.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import test from 'ava';
44
import createConfiguration, {
55
deviceMetricsOverrideDesktopProfile
6-
} from '../src/factories/createConfiguration';
6+
} from '../../src/factories/createConfiguration';
77

88
const createDefaultConfiguration = () => {
99
return {
@@ -16,6 +16,7 @@ const createDefaultConfiguration = () => {
1616
extractStyles: false,
1717
formatStyles: undefined,
1818
inlineStyles: false,
19+
preloadFonts: true,
1920
preloadStyles: true
2021
};
2122
};

test/usus.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,51 @@ test('inlines CSS (preloadStyles=false)', async (t) => {
9595
));
9696
});
9797

98+
test('inlines CSS (preloadFonts=true)', async (t) => {
99+
const server = await serve(`
100+
<html>
101+
<head>
102+
<style>
103+
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
104+
105+
body {
106+
font-family: 'Open Sans', sans-serif;
107+
}
108+
</style>
109+
</head>
110+
<body>
111+
<p>Hello, World!</p>
112+
</body>
113+
</html>
114+
`);
115+
116+
const result = await render(server.url, {
117+
chromePort,
118+
delay: 500,
119+
inlineStyles: true
120+
});
121+
122+
await server.close();
123+
124+
t.true(isHtmlEqual(result, `
125+
<html>
126+
<head>
127+
<style>
128+
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
129+
130+
body {
131+
font-family: 'Open Sans', sans-serif;
132+
}
133+
</style>
134+
<link rel="preload" href="https://fonts.gstatic.com/s/opensans/v14/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920.woff2" as="font">
135+
</head>
136+
<body>
137+
<p>Hello, World!</p>
138+
</body>
139+
</html>`
140+
));
141+
});
142+
98143
test('inlines CSS (preloadStyles=true)', async (t) => {
99144
const styleServer = await serve(`
100145
body {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @flow
2+
3+
import test from 'ava';
4+
import normalizeNetworkResourceUrl from '../../src/utilities/normalizeNetworkResourceUrl';
5+
6+
test('keeps foreign URLs unchanged', (t) => {
7+
t.true(normalizeNetworkResourceUrl('http://foo/bar', 'http://qux/quux') === 'http://qux/quux');
8+
t.true(normalizeNetworkResourceUrl('http://foo/bar', 'https://foo/bar') === 'https://foo/bar');
9+
});
10+
11+
test('rewrites local URLs to relative paths', (t) => {
12+
t.true(normalizeNetworkResourceUrl('http://foo/bar', 'http://foo/baz') === '/baz');
13+
});

0 commit comments

Comments
 (0)
0