From c9e9178a4882e414a6b9616baa35e8dbf7b2dd75 Mon Sep 17 00:00:00 2001 From: John Richard Chipps-Harding Date: Mon, 7 Oct 2019 12:39:17 +0100 Subject: [PATCH] feat(options): add multiple `openPage` support (#2266) --- examples/cli/open-page-multiple/README.md | 15 ++++ examples/cli/open-page-multiple/app1.js | 11 +++ examples/cli/open-page-multiple/app2.js | 11 +++ .../cli/open-page-multiple/webpack.config.js | 37 ++++++++++ examples/cli/open-page/README.md | 4 +- lib/options.json | 15 +++- lib/utils/createConfig.js | 2 +- lib/utils/runOpen.js | 28 +++++--- .../__snapshots__/createConfig.test.js.snap | 23 ++++++- .../utils/__snapshots__/runOpen.test.js.snap | 68 +++++++++++++++++++ test/server/utils/createConfig.test.js | 15 ++++ test/server/utils/runOpen.test.js | 57 ++++++++++++++-- 12 files changed, 265 insertions(+), 21 deletions(-) create mode 100644 examples/cli/open-page-multiple/README.md create mode 100644 examples/cli/open-page-multiple/app1.js create mode 100644 examples/cli/open-page-multiple/app2.js create mode 100644 examples/cli/open-page-multiple/webpack.config.js create mode 100644 test/server/utils/__snapshots__/runOpen.test.js.snap diff --git a/examples/cli/open-page-multiple/README.md b/examples/cli/open-page-multiple/README.md new file mode 100644 index 0000000000..e5d07ec496 --- /dev/null +++ b/examples/cli/open-page-multiple/README.md @@ -0,0 +1,15 @@ +# CLI: Open Page Option (Multiple) + +```console +npm run webpack-dev-server -- --open-page example1.html,example2.html +``` + +Some applications may consist of multiple pages. During development it may +be useful to directly open multiple pages at the same time. The pages to open +may be specified as the argument to the `open-page` option. + +## What Should Happen + +The script should open `http://localhost:8080/example1.html` and +`http://localhost:8080/example2.html` in your default browser. +You should see the text on the page itself change to read `Success!`. diff --git a/examples/cli/open-page-multiple/app1.js b/examples/cli/open-page-multiple/app1.js new file mode 100644 index 0000000000..45c49c5c7b --- /dev/null +++ b/examples/cli/open-page-multiple/app1.js @@ -0,0 +1,11 @@ +'use strict'; + +const target = document.querySelector('#target'); + +if (window.location.href.endsWith('example1.html')) { + target.classList.add('pass'); + target.innerHTML = 'Success!'; +} else { + target.classList.add('fail'); + target.innerHTML = 'Houston, we have a problem.'; +} diff --git a/examples/cli/open-page-multiple/app2.js b/examples/cli/open-page-multiple/app2.js new file mode 100644 index 0000000000..ca59bf8047 --- /dev/null +++ b/examples/cli/open-page-multiple/app2.js @@ -0,0 +1,11 @@ +'use strict'; + +const target = document.querySelector('#target'); + +if (window.location.href.endsWith('example2.html')) { + target.classList.add('pass'); + target.innerHTML = 'Success!'; +} else { + target.classList.add('fail'); + target.innerHTML = 'Houston, we have a problem.'; +} diff --git a/examples/cli/open-page-multiple/webpack.config.js b/examples/cli/open-page-multiple/webpack.config.js new file mode 100644 index 0000000000..d933139c04 --- /dev/null +++ b/examples/cli/open-page-multiple/webpack.config.js @@ -0,0 +1,37 @@ +'use strict'; + +const HtmlWebpackPlugin = require('html-webpack-plugin'); +// our setup function adds behind-the-scenes bits to the config that all of our +// examples need +const { setup } = require('../../util'); + +module.exports = [ + setup({ + context: __dirname, + entry: './app1.js', + output: { + filename: 'app1.js', + }, + plugins: [ + new HtmlWebpackPlugin({ + filename: 'example1.html', + template: '../../.assets/layout.html', + title: 'Open Page (Multiple) / Example / Page 1', + }), + ], + }), + setup({ + context: __dirname, + entry: './app2.js', + output: { + filename: 'app2.js', + }, + plugins: [ + new HtmlWebpackPlugin({ + filename: 'example2.html', + template: '../../.assets/layout.html', + title: 'Open Page (Multiple) / Example / Page 2', + }), + ], + }), +]; diff --git a/examples/cli/open-page/README.md b/examples/cli/open-page/README.md index f4c02f76cc..df88bfae78 100644 --- a/examples/cli/open-page/README.md +++ b/examples/cli/open-page/README.md @@ -10,5 +10,5 @@ as the argument to the `open-page` option. ## What Should Happen -The script should open `http://localhost:8080/` in your default browser. -You should see the text on the page itself change to read `Success!`. +The script should open `http://localhost:8080/example.html#page1` in your +default browser. You should see the text on the page itself change to read `Success!`. diff --git a/lib/options.json b/lib/options.json index 217ee5a631..2842b7e30d 100644 --- a/lib/options.json +++ b/lib/options.json @@ -209,7 +209,18 @@ ] }, "openPage": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + ] }, "overlay": { "anyOf": [ @@ -436,7 +447,7 @@ "noInfo": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devservernoinfo-)", "onListening": "should be {Function} (https://webpack.js.org/configuration/dev-server/#onlistening)", "open": "should be {String|Boolean} (https://webpack.js.org/configuration/dev-server/#devserveropen)", - "openPage": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserveropenpage)", + "openPage": "should be {String|Array} (https://webpack.js.org/configuration/dev-server/#devserveropenpage)", "overlay": "should be {Boolean|Object} (https://webpack.js.org/configuration/dev-server/#devserveroverlay)", "pfx": "should be {String|Buffer} (https://webpack.js.org/configuration/dev-server/#devserverpfx)", "pfxPassphrase": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserverpfxpassphrase)", diff --git a/lib/utils/createConfig.js b/lib/utils/createConfig.js index 0211561235..5c15b35c39 100644 --- a/lib/utils/createConfig.js +++ b/lib/utils/createConfig.js @@ -203,7 +203,7 @@ function createConfig(config, argv, { port }) { if (argv.openPage) { options.open = true; - options.openPage = argv.openPage; + options.openPage = argv.openPage.split(','); } if (typeof argv.open !== 'undefined') { diff --git a/lib/utils/runOpen.js b/lib/utils/runOpen.js index a2757ab919..0b42eff54d 100644 --- a/lib/utils/runOpen.js +++ b/lib/utils/runOpen.js @@ -6,23 +6,29 @@ const isAbsoluteUrl = require('is-absolute-url'); function runOpen(uri, options, log) { // https://github.com/webpack/webpack-dev-server/issues/1990 let openOptions = { wait: false }; - let openMessage = 'Unable to open browser'; + let openOptionValue = ''; if (typeof options.open === 'string') { openOptions = Object.assign({}, openOptions, { app: options.open }); - openMessage += `: ${options.open}`; + openOptionValue = `: "${options.open}"`; } - const pageUrl = - options.openPage && isAbsoluteUrl(options.openPage) - ? options.openPage - : `${uri}${options.openPage || ''}`; + const pages = + typeof options.openPage === 'string' + ? [options.openPage] + : options.openPage || ['']; - return open(pageUrl, openOptions).catch(() => { - log.warn( - `${openMessage}. If you are running in a headless environment, please do not use the --open flag` - ); - }); + return Promise.all( + pages.map((page) => { + const pageUrl = page && isAbsoluteUrl(page) ? page : `${uri}${page}`; + + return open(pageUrl, openOptions).catch(() => { + log.warn( + `Unable to open "${pageUrl}" in browser${openOptionValue}. If you are running in a headless environment, please do not use the --open flag` + ); + }); + }) + ); } module.exports = runOpen; diff --git a/test/server/utils/__snapshots__/createConfig.test.js.snap b/test/server/utils/__snapshots__/createConfig.test.js.snap index 585d74105c..dd2215151b 100644 --- a/test/server/utils/__snapshots__/createConfig.test.js.snap +++ b/test/server/utils/__snapshots__/createConfig.test.js.snap @@ -818,6 +818,25 @@ Object { } `; +exports[`createConfig openPage multiple option (in devServer config) 1`] = ` +Object { + "hot": true, + "hotOnly": false, + "noInfo": true, + "open": true, + "openPage": Array [ + "/different/page", + "/different/page2", + ], + "port": 8080, + "publicPath": "/", + "stats": Object { + "cached": false, + "cachedAssets": false, + }, +} +`; + exports[`createConfig openPage option (in devServer config) 1`] = ` Object { "hot": true, @@ -840,7 +859,9 @@ Object { "hotOnly": false, "noInfo": true, "open": true, - "openPage": "/different/page", + "openPage": Array [ + "/different/page", + ], "port": 8080, "publicPath": "/", "stats": Object { diff --git a/test/server/utils/__snapshots__/runOpen.test.js.snap b/test/server/utils/__snapshots__/runOpen.test.js.snap new file mode 100644 index 0000000000..af9cecdd6f --- /dev/null +++ b/test/server/utils/__snapshots__/runOpen.test.js.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`runOpen util on specify multiple absolute https URLs with pages in Google Chrome 1`] = ` +Array [ + "https://example2.com", + Object { + "app": "Google Chrome", + "wait": false, + }, +] +`; + +exports[`runOpen util on specify multiple absolute https URLs with pages in Google Chrome 2`] = ` +Array [ + "https://example3.com", + Object { + "app": "Google Chrome", + "wait": false, + }, +] +`; + +exports[`runOpen util on specify one relative URL and one absolute URL with pages in Google Chrome 1`] = ` +Array [ + "https://example.com/index.html", + Object { + "app": "Google Chrome", + "wait": false, + }, +] +`; + +exports[`runOpen util on specify one relative URL and one absolute URL with pages in Google Chrome 2`] = ` +Array [ + "https://example2.com", + Object { + "app": "Google Chrome", + "wait": false, + }, +] +`; + +exports[`runOpen util should open browser on specify URL with multiple pages inside array 1`] = ` +Array [ + "https://example.com/index.html", + Object { + "wait": false, + }, +] +`; + +exports[`runOpen util should open browser on specify URL with multiple pages inside array 2`] = ` +Array [ + "https://example.com/index2.html", + Object { + "wait": false, + }, +] +`; + +exports[`runOpen util should open browser on specify URL with page inside array 1`] = ` +Array [ + "https://example.com/index.html", + Object { + "wait": false, + }, +] +`; diff --git a/test/server/utils/createConfig.test.js b/test/server/utils/createConfig.test.js index 685941ef56..746b7dba64 100644 --- a/test/server/utils/createConfig.test.js +++ b/test/server/utils/createConfig.test.js @@ -887,6 +887,21 @@ describe('createConfig', () => { expect(config).toMatchSnapshot(); }); + it('openPage multiple option (in devServer config)', () => { + const config = createConfig( + Object.assign({}, webpackConfig, { + devServer: { + open: true, + openPage: ['/different/page', '/different/page2'], + }, + }), + argv, + { port: 8080 } + ); + + expect(config).toMatchSnapshot(); + }); + it('useLocalIp option', () => { const config = createConfig( webpackConfig, diff --git a/test/server/utils/runOpen.test.js b/test/server/utils/runOpen.test.js index e6fa3b155a..80fe358512 100644 --- a/test/server/utils/runOpen.test.js +++ b/test/server/utils/runOpen.test.js @@ -45,6 +45,27 @@ describe('runOpen util', () => { }); }); + it('on specify URL with page inside array', () => { + return runOpen( + 'https://example.com', + { openPage: ['/index.html'] }, + console + ).then(() => { + expect(opn.mock.calls[0]).toMatchSnapshot(); + }); + }); + + it('on specify URL with multiple pages inside array', () => { + return runOpen( + 'https://example.com', + { openPage: ['/index.html', '/index2.html'] }, + console + ).then(() => { + expect(opn.mock.calls[0]).toMatchSnapshot(); + expect(opn.mock.calls[1]).toMatchSnapshot(); + }); + }); + it('on specify URL in Google Chrome', () => { return runOpen( 'https://example.com', @@ -118,6 +139,34 @@ describe('runOpen util', () => { }); }); + it('on specify multiple absolute https URLs with pages in Google Chrome ', () => { + return runOpen( + 'https://example.com', + { + open: 'Google Chrome', + openPage: ['https://example2.com', 'https://example3.com'], + }, + console + ).then(() => { + expect(opn.mock.calls[0]).toMatchSnapshot(); + expect(opn.mock.calls[1]).toMatchSnapshot(); + }); + }); + + it('on specify one relative URL and one absolute URL with pages in Google Chrome ', () => { + return runOpen( + 'https://example.com', + { + open: 'Google Chrome', + openPage: ['/index.html', 'https://example2.com'], + }, + console + ).then(() => { + expect(opn.mock.calls[0]).toMatchSnapshot(); + expect(opn.mock.calls[1]).toMatchSnapshot(); + }); + }); + describe('should not open browser', () => { const logMock = { warn: jest.fn() }; @@ -132,7 +181,7 @@ describe('runOpen util', () => { it('on specify URL and log error', () => { return runOpen('https://example.com', {}, logMock).then(() => { expect(logMock.warn.mock.calls[0][0]).toMatchInlineSnapshot( - `"Unable to open browser. If you are running in a headless environment, please do not use the --open flag"` + `"Unable to open \\"https://example.com\\" in browser. If you are running in a headless environment, please do not use the --open flag"` ); expect(opn.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -152,7 +201,7 @@ describe('runOpen util', () => { logMock ).then(() => { expect(logMock.warn.mock.calls[0][0]).toMatchInlineSnapshot( - `"Unable to open browser. If you are running in a headless environment, please do not use the --open flag"` + `"Unable to open \\"https://example.com/index.html\\" in browser. If you are running in a headless environment, please do not use the --open flag"` ); expect(opn.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -172,7 +221,7 @@ describe('runOpen util', () => { logMock ).then(() => { expect(logMock.warn.mock.calls[0][0]).toMatchInlineSnapshot( - `"Unable to open browser: Google Chrome. If you are running in a headless environment, please do not use the --open flag"` + `"Unable to open \\"https://example.com\\" in browser: \\"Google Chrome\\". If you are running in a headless environment, please do not use the --open flag"` ); expect(opn.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -193,7 +242,7 @@ describe('runOpen util', () => { logMock ).then(() => { expect(logMock.warn.mock.calls[0][0]).toMatchInlineSnapshot( - `"Unable to open browser: Google Chrome. If you are running in a headless environment, please do not use the --open flag"` + `"Unable to open \\"https://example.com/index.html\\" in browser: \\"Google Chrome\\". If you are running in a headless environment, please do not use the --open flag"` ); expect(opn.mock.calls[0]).toMatchInlineSnapshot(` Array [