diff --git a/README.md b/README.md index 9655f6b..46bc186 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ export type UserDeviceMetricsOverrideType = { type FormatStylesType = (styles: string) => Promise; export type UserConfigurationType = { + +chromePort?: number, +cookies?: $ReadOnlyArray, +delay?: number, +deviceMetricsOverride?: UserDeviceMetricsOverrideType, @@ -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)|| @@ -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. @@ -191,7 +222,7 @@ import { minify } from 'csso'; -return render(url, { +await render(url, { formatStyles: (styles: string): Promise => { return minify(styles).css; }, diff --git a/src/bin/commands/render.js b/src/bin/commands/render.js index 7b04080..a014970 100644 --- a/src/bin/commands/render.js +++ b/src/bin/commands/render.js @@ -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' diff --git a/src/factories/createConfiguration.js b/src/factories/createConfiguration.js index 502b3c3..9155604 100644 --- a/src/factories/createConfiguration.js +++ b/src/factories/createConfiguration.js @@ -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; @@ -37,6 +38,7 @@ export default (userConfiguration: UserConfigurationType): ConfigurationType => } return { + chromePort, cookies, delay: delayConfiguration, deviceMetricsOverride, diff --git a/src/types.js b/src/types.js index 25774f2..3005b68 100644 --- a/src/types.js +++ b/src/types.js @@ -28,6 +28,7 @@ export type DeviceMetricsOverrideType = { type FormatStylesType = (styles: string) => Promise; export type UserConfigurationType = { + +chromePort?: number, +cookies?: $ReadOnlyArray, +delay?: number, +deviceMetricsOverride?: UserDeviceMetricsOverrideType, @@ -38,6 +39,7 @@ export type UserConfigurationType = { }; export type ConfigurationType = {| + +chromePort?: number, +cookies: $ReadOnlyArray, +delay: number, +deviceMetricsOverride: DeviceMetricsOverrideType, diff --git a/src/usus.js b/src/usus.js index eb4c63b..9dff759 100644 --- a/src/usus.js +++ b/src/usus.js @@ -16,7 +16,7 @@ import type { const debug = createDebug('usus'); -const launchChrome = () => { +export const launchChrome = () => { return launch({ chromeFlags: [ '--disable-gpu', @@ -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 => { + await protocol.close(); + + if (!chrome) { + return; + } + + await chrome.kill(); + }; + const { CSS, DOM, @@ -170,7 +190,7 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy } }); - CSS.startRuleUsageTracking(); + await CSS.startRuleUsageTracking(); const frame = await Page.navigate({ url @@ -224,6 +244,8 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy }); }); + await CSS.stopRuleUsageTracking(); + if (configuration.formatStyles) { usedStyles = await configuration.formatStyles(usedStyles); } @@ -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; } @@ -286,7 +309,7 @@ export const render = async (url: string, userConfiguration: UserConfigurationTy nodeId: rootDocument.root.nodeId })).outerHTML; - await chrome.kill(); + await end(); return rootOuterHTML; }; diff --git a/test/createConfiguration.js b/test/createConfiguration.js index 01c6da6..bc9cb5a 100644 --- a/test/createConfiguration.js +++ b/test/createConfiguration.js @@ -7,6 +7,7 @@ import createConfiguration, { const createDefaultConfiguration = () => { return { + chromePort: undefined, cookies: [], delay: 5000, deviceMetricsOverride: { diff --git a/test/usus.js b/test/usus.js index 43668d3..3963f93 100644 --- a/test/usus.js +++ b/test/usus.js @@ -1,7 +1,11 @@ // @flow -import test from 'ava'; +import test, { + after, + before +} from 'ava'; import { + launchChrome, render } from '../src/usus'; import { @@ -9,6 +13,20 @@ import { 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(` @@ -19,6 +37,7 @@ test('renders HTML', async (t) => { `); const result = await render(server.url, { + chromePort, delay: 500 }); @@ -53,6 +72,7 @@ test('inlines CSS (preloadStyles=false)', async (t) => { `); const result = await render(server.url, { + chromePort, delay: 500, inlineStyles: true, preloadStyles: false @@ -94,6 +114,7 @@ test('inlines CSS (preloadStyles=true)', async (t) => { `); const result = await render(server.url, { + chromePort, delay: 500, inlineStyles: true }); @@ -135,6 +156,7 @@ test('extracts CSS', async (t) => { `); const result = await render(server.url, { + chromePort, delay: 500, extractStyles: true }); @@ -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 @@ -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 @@ -247,6 +271,7 @@ test('extracts only the used CSS', async (t) => { `); const result = await render(server.url, { + chromePort, delay: 500, extractStyles: true });