diff --git a/src/JSDOMDomProvider.js b/src/JSDOMDomProvider.js index d56bf4e..2a0d00a 100644 --- a/src/JSDOMDomProvider.js +++ b/src/JSDOMDomProvider.js @@ -1,11 +1,20 @@ import { JSDOM, VirtualConsole } from 'jsdom'; +import Logger from './Logger'; + const { VERBOSE } = process.env; const MAX_ERROR_DETAIL_LENGTH = 200; -export default class JSDOMDomProvider { - constructor(jsdomOptions, { width, height, webpackBundle }) { +// Cache the JSDOM instance because re-loading the webpack bundle for every +// target can be very expensive. This assumes that jsdomOptions and +// webpackBundle do not change. +let dom; +function getCachedDOM(jsdomOptions, webpackBundle) { + if (!dom) { + const logger = new Logger(); + logger.start('Initializing JSDOM with the bundle...'); + const virtualConsole = new VirtualConsole(); virtualConsole.on('jsdomError', (e) => { const { stack, detail = '' } = e; @@ -19,7 +28,7 @@ export default class JSDOMDomProvider { }); virtualConsole.sendTo(console, { omitJSDOMErrors: true }); - this.dom = new JSDOM( + dom = new JSDOM( ` @@ -37,14 +46,6 @@ export default class JSDOMDomProvider { url: 'http://localhost', virtualConsole, beforeParse(win) { - win.outerWidth = win.innerWidth = width; - win.outerHeight = win.innerHeight = height; - Object.defineProperties(win.screen, { - width: { value: width }, - availWidth: { value: width }, - height: { value: height }, - availHeight: { value: height }, - }); win.requestAnimationFrame = (callback) => setTimeout(callback, 0); win.cancelAnimationFrame = clearTimeout; }, @@ -52,6 +53,21 @@ export default class JSDOMDomProvider { jsdomOptions, ), ); + + logger.success(); + } + + return dom; +} + +// Useful for tests +export function clearCachedDOM() { + dom = undefined; +} + +export default class JSDOMDomProvider { + constructor(jsdomOptions, { webpackBundle }) { + this.dom = getCachedDOM(jsdomOptions, webpackBundle); } async init({ targetName }) { @@ -61,6 +77,17 @@ export default class JSDOMDomProvider { return this.dom.window.happoProcessor.init({ targetName }); } + resize({ width, height }) { + this.dom.window.outerWidth = this.dom.window.innerWidth = width; + this.dom.window.outerHeight = this.dom.window.innerHeight = height; + Object.defineProperties(this.dom.window.screen, { + width: { value: width, configurable: true }, + availWidth: { value: width, configurable: true }, + height: { value: height, configurable: true }, + availHeight: { value: height, configurable: true }, + }); + } + next() { return this.dom.window.happoProcessor.next(); } diff --git a/src/processSnapsInBundle.js b/src/processSnapsInBundle.js index 8c3c9c4..2726d48 100644 --- a/src/processSnapsInBundle.js +++ b/src/processSnapsInBundle.js @@ -5,16 +5,21 @@ export default async function processSnapsInBundle( { viewport, DomProvider, targetName }, ) { const [width, height] = viewport.split('x').map((s) => parseInt(s, 10)); - const domProvider = new DomProvider({ - webpackBundle, - width, - height, - }); + + // TODO Remove width and height in next breaking change after puppeteer plugin + // has been updated. + const domProvider = new DomProvider({ webpackBundle, width, height }); const result = { snapPayloads: [], }; try { + // TODO remove resize guard in next breaking change and after puppeteer + // plugin has been updated. + if (typeof domProvider.resize !== 'undefined') { + domProvider.resize({ width, height }); + } + await domProvider.init({ targetName }); // Disabling eslint here because we actually want to run things serially. diff --git a/test/integrations/error-test.js b/test/integrations/error-test.js index 3994a2e..ed431de 100644 --- a/test/integrations/error-test.js +++ b/test/integrations/error-test.js @@ -4,6 +4,7 @@ import MockTarget from './MockTarget'; import * as defaultConfig from '../../src/DEFAULTS'; import makeRequest from '../../src/makeRequest'; import runCommand from '../../src/commands/run'; +import { clearCachedDOM } from '../../src/JSDOMDomProvider'; jest.mock('../../src/makeRequest'); @@ -15,6 +16,7 @@ let config; let sha; beforeEach(() => { + clearCachedDOM(); console.warn = jest.fn(originalConsoleWarn); console.error = jest.fn(originalConsoleErr); console.error.mockReset(); diff --git a/test/integrations/react-test.js b/test/integrations/react-test.js index 7956594..230b911 100644 --- a/test/integrations/react-test.js +++ b/test/integrations/react-test.js @@ -8,6 +8,7 @@ import MockTarget from './MockTarget'; import * as defaultConfig from '../../src/DEFAULTS'; import makeRequest from '../../src/makeRequest'; import runCommand from '../../src/commands/run'; +import { clearCachedDOM } from '../../src/JSDOMDomProvider'; jest.mock('../../src/makeRequest'); @@ -16,6 +17,8 @@ let config; let sha; beforeEach(() => { + clearCachedDOM(); + makeRequest.mockImplementation(() => Promise.resolve({})); sha = 'foobar'; config = Object.assign({}, defaultConfig, {