Skip to content

Commit

Permalink
feat: allow to use an existing Chrome instance (fixes #9)
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Jul 13, 2017
1 parent b727be6 commit 6c676b7
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 10 deletions.
33 changes: 32 additions & 1 deletion README.md
Expand Up @@ -95,6 +95,7 @@ export type UserDeviceMetricsOverrideType = {
type FormatStylesType = (styles: string) => Promise<string>;

export type UserConfigurationType = {
+chromePort?: number,
+cookies?: $ReadOnlyArray<CookieType>,
+delay?: number,
+deviceMetricsOverride?: UserDeviceMetricsOverrideType,
Expand All @@ -113,6 +114,7 @@ The default behaviour is to return the HTML.

|Name|Type|Description|Default value|
|---|---|---|---|
|`chromePort`|`number`|Port of an existing Chrome instance. See [Controlling the Chrome instance](#controlling-the-chrome-instance).|N/A|
|`cookies`|`Array<{name: string, value: string}>`|Sets a cookie with the given cookie data.|N/A|
|`delay`|`number`|Defines how many milliseconds to wait after the "load" event has been fired before capturing the styles used to load the page. This is important if resources appearing on the page are being loaded asynchronously.|`number`|`5000`|
|`deviceMetricsOverride`||See [`deviceMetricsOverride` configuration](#devicemetricsoverride-configuration)||
Expand Down Expand Up @@ -173,10 +175,39 @@ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key
&& sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update -y \
&& apt-get install google-chrome-stable -y

```

This assumes that you are extending from the base [`node` image](https://github.com/nodejs/docker-node).

### Controlling the Chrome instance

By default, ūsus creates a new instance of Chrome for every `render` operation and destroys it after completion. However, you can start Chrome independent of ūsus and re-use the same instance for multiple renderings.

```js
import {
launchChrome,
render
} from 'usus';

const chrome = await launchChrome();

await render('https://go2cinema.com/movies/baywatch-2017-1198354', {
chromePort: chrome.port,
inlineStyles: true
});

await render('https://go2cinema.com/movies/baby-driver-2017-2257838', {
chromePort: chrome.port,
inlineStyles: true
});

await chrome.kill();

```

`launchChrome` is a convenience method to launch Chrome using default ūsus configuration. If you need granular control over how Chrome is launched, refer to the [chrome-launcher](https://github.com/GoogleChrome/lighthouse/tree/master/chrome-launcher) program.

### Minifying the CSS

Use the `formatStyles` callback to minify/ format/ optimize/ remove CSS before it is inlined.
Expand All @@ -191,7 +222,7 @@ import {
minify
} from 'csso';

return render(url, {
await render(url, {
formatStyles: (styles: string): Promise<string> => {
return minify(styles).css;
},
Expand Down
4 changes: 4 additions & 0 deletions src/bin/commands/render.js
Expand Up @@ -10,6 +10,10 @@ export const command = 'render';
export const desc = 'Renders page using Chrome Debugging Protocol. Extracts CSS used to render the page. Renders HTML with the blocking CSS made asynchronous. Inlines the critical CSS.';

export const baseConfiguration = {
chromePort: {
description: 'Port of an existing Chrome instance. See "Controlling the Chrome instance" in the ūsus cookbook.',
type: 'number'
},
cookies: {
description: 'Sets a cookie with the given cookie data. Must be provided as key=value pairs, e.g. foo=bar.',
type: 'array'
Expand Down
2 changes: 2 additions & 0 deletions src/factories/createConfiguration.js
Expand Up @@ -16,6 +16,7 @@ export const deviceMetricsOverrideDesktopProfile = {
};

export default (userConfiguration: UserConfigurationType): ConfigurationType => {
const chromePort = userConfiguration.chromePort;
const cookies = userConfiguration.cookies || [];
const delayConfiguration = userConfiguration.delay || 5000;
const extractStyles = userConfiguration.extractStyles || false;
Expand All @@ -37,6 +38,7 @@ export default (userConfiguration: UserConfigurationType): ConfigurationType =>
}

return {
chromePort,
cookies,
delay: delayConfiguration,
deviceMetricsOverride,
Expand Down
2 changes: 2 additions & 0 deletions src/types.js
Expand Up @@ -28,6 +28,7 @@ export type DeviceMetricsOverrideType = {
type FormatStylesType = (styles: string) => Promise<string>;

export type UserConfigurationType = {
+chromePort?: number,
+cookies?: $ReadOnlyArray<CookieType>,
+delay?: number,
+deviceMetricsOverride?: UserDeviceMetricsOverrideType,
Expand All @@ -38,6 +39,7 @@ export type UserConfigurationType = {
};

export type ConfigurationType = {|
+chromePort?: number,
+cookies: $ReadOnlyArray<CookieType>,
+delay: number,
+deviceMetricsOverride: DeviceMetricsOverrideType,
Expand Down
39 changes: 31 additions & 8 deletions src/usus.js
Expand Up @@ -16,7 +16,7 @@ import type {

const debug = createDebug('usus');

const launchChrome = () => {
export const launchChrome = () => {
return launch({
chromeFlags: [
'--disable-gpu',
Expand Down Expand Up @@ -120,12 +120,32 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy

debug('rendering URL %s', JSON.stringify(configuration));

const chrome = await launchChrome();
let chrome;
let chromePort;

if (configuration.chromePort) {
debug('attempting to use the user provided instance of Chrome (port %d)', configuration.chromePort);

chromePort = configuration.chromePort;
} else {
chrome = await launchChrome();
chromePort = chrome.port;
}

const protocol = await CDP({
port: chrome.port
port: chromePort
});

const end = async (): Promise<void> => {
await protocol.close();

if (!chrome) {
return;
}

await chrome.kill();
};

const {
CSS,
DOM,
Expand Down Expand Up @@ -170,7 +190,7 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
}
});

CSS.startRuleUsageTracking();
await CSS.startRuleUsageTracking();

const frame = await Page.navigate({
url
Expand Down Expand Up @@ -224,6 +244,8 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
});
});

await CSS.stopRuleUsageTracking();

if (configuration.formatStyles) {
usedStyles = await configuration.formatStyles(usedStyles);
}
Expand Down Expand Up @@ -269,15 +291,16 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
nodeId: rootDocument.root.nodeId
})).outerHTML;

await chrome.kill();
await end();

return rootOuterHTMLWithInlinedStyles;
}

if (configuration.extractStyles) {
await chrome.kill();
await end();

// @todo Document that `extractStyles` does not return inline stylesheets.
// @todo Document that `extractStyles` does not return the inline stylesheets.
// @todo Document that `extractStyles` does not return the alien stylesheets.

return usedStyles;
}
Expand All @@ -286,7 +309,7 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy
nodeId: rootDocument.root.nodeId
})).outerHTML;

await chrome.kill();
await end();

return rootOuterHTML;
};
1 change: 1 addition & 0 deletions test/createConfiguration.js
Expand Up @@ -7,6 +7,7 @@ import createConfiguration, {

const createDefaultConfiguration = () => {
return {
chromePort: undefined,
cookies: [],
delay: 5000,
deviceMetricsOverride: {
Expand Down
27 changes: 26 additions & 1 deletion test/usus.js
@@ -1,14 +1,32 @@
// @flow

import test from 'ava';
import test, {
after,
before
} from 'ava';
import {
launchChrome,
render
} from '../src/usus';
import {
isHtmlEqual,
serve
} from './helpers';

let chromeInstance;
let chromePort;

before(async () => {
const chrome = await launchChrome();

chromeInstance = chrome;
chromePort = chrome.port;
});

after.always(async () => {
await chromeInstance.kill();
});

test('renders HTML', async (t) => {
const server = await serve(`
<html>
Expand All @@ -19,6 +37,7 @@ test('renders HTML', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500
});

Expand Down Expand Up @@ -53,6 +72,7 @@ test('inlines CSS (preloadStyles=false)', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500,
inlineStyles: true,
preloadStyles: false
Expand Down Expand Up @@ -94,6 +114,7 @@ test('inlines CSS (preloadStyles=true)', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500,
inlineStyles: true
});
Expand Down Expand Up @@ -135,6 +156,7 @@ test('extracts CSS', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500,
extractStyles: true
});
Expand Down Expand Up @@ -162,6 +184,7 @@ test('does not re-inline the inline CSS', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500,
inlineStyles: true,
preloadStyles: false
Expand Down Expand Up @@ -202,6 +225,7 @@ test('does not inline CSS from alien frames', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 2000,
inlineStyles: true,
preloadStyles: false
Expand Down Expand Up @@ -247,6 +271,7 @@ test('extracts only the used CSS', async (t) => {
`);

const result = await render(server.url, {
chromePort,
delay: 500,
extractStyles: true
});
Expand Down

0 comments on commit 6c676b7

Please sign in to comment.