Skip to content

Commit 7c7ccf9

Browse files
feat: added <url> pattern for open and allow to use multiple browsers (#3496)
1 parent 4fe9a2e commit 7c7ccf9

33 files changed

+579
-360
lines changed

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,7 @@ Options:
137137
--open [value...] Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser).
138138
https://webpack.js.org/configuration/dev-server/#devserveropen
139139
--no-open Negative 'open' option.
140-
--open-target [value...] Opens specified page in browser.
141-
--no-open-target Negative 'open-target' option.
140+
--open-target <value...> Opens specified page in browser.
142141
--open-app-name <value...> Open specified browser.
143142
--open-app <value...> Open specified browser.
144143
--open-reset Clear all items provided in 'open' configuration. Allows to configure dev server to open the browser(s) and page(s) after server had been started

bin/cli-flags.js

-6
Original file line numberDiff line numberDiff line change
@@ -575,12 +575,6 @@ module.exports = {
575575
},
576576
'open-target': {
577577
configs: [
578-
{
579-
type: 'boolean',
580-
multiple: true,
581-
description: 'Opens specified page in browser.',
582-
path: 'open[].target',
583-
},
584578
{
585579
type: 'string',
586580
multiple: true,

lib/Server.js

+25-73
Original file line numberDiff line numberDiff line change
@@ -640,78 +640,30 @@ class Server {
640640
});
641641
}
642642

643-
openBrowser(uri) {
643+
openBrowser(defaultOpenTarget) {
644644
const isAbsoluteUrl = require('is-absolute-url');
645645
const open = require('open');
646646

647-
// https://github.com/webpack/webpack-dev-server/issues/1990
648-
const defaultOpenOptions = { wait: false };
649-
const openTasks = [];
650-
651-
const getOpenTask = (item) => {
652-
if (typeof item === 'boolean') {
653-
return [{ target: uri, options: defaultOpenOptions }];
654-
}
655-
656-
if (typeof item === 'string') {
657-
return [{ target: item, options: defaultOpenOptions }];
658-
}
659-
660-
let targets;
661-
662-
if (item.target) {
663-
targets = Array.isArray(item.target) ? item.target : [item.target];
664-
} else {
665-
targets = [uri];
666-
}
667-
668-
return targets.map((target) => {
669-
const openOptions = defaultOpenOptions;
670-
671-
if (item.app) {
672-
if (typeof item.app === 'string') {
673-
openOptions.app = { name: item.app };
674-
} else {
675-
openOptions.app = item.app;
676-
}
677-
}
678-
679-
return { target, options: openOptions };
680-
});
681-
};
682-
683-
if (Array.isArray(this.options.open)) {
684-
this.options.open.forEach((item) => {
685-
openTasks.push(...getOpenTask(item));
686-
});
687-
} else {
688-
openTasks.push(...getOpenTask(this.options.open));
689-
}
690-
691647
Promise.all(
692-
openTasks.map((openTask) => {
648+
this.options.open.map((item) => {
693649
let openTarget;
694650

695-
if (openTask.target) {
696-
if (typeof openTask.target === 'boolean') {
697-
openTarget = uri;
698-
} else {
699-
openTarget = isAbsoluteUrl(openTask.target)
700-
? openTask.target
701-
: new URL(openTask.target, uri).toString();
702-
}
651+
if (item.target === '<url>') {
652+
openTarget = defaultOpenTarget;
703653
} else {
704-
openTarget = uri;
654+
openTarget = isAbsoluteUrl(item.target)
655+
? item.target
656+
: new URL(item.target, defaultOpenTarget).toString();
705657
}
706658

707-
return open(openTarget, openTask.options).catch(() => {
659+
return open(openTarget, item.options).catch(() => {
708660
this.logger.warn(
709661
`Unable to open "${openTarget}" page${
710662
// eslint-disable-next-line no-nested-ternary
711-
openTask.options.app
712-
? ` in "${openTask.options.app.name}" app${
713-
openTask.options.app.arguments
714-
? ` with "${openTask.options.app.arguments.join(
663+
item.options.app
664+
? ` in "${item.options.app.name}" app${
665+
item.options.app.arguments
666+
? ` with "${item.options.app.arguments.join(
715667
' '
716668
)}" arguments`
717669
: ''
@@ -763,7 +715,7 @@ class Server {
763715
} else {
764716
const protocol = this.options.https ? 'https' : 'http';
765717
const { address, port } = this.server.address();
766-
const prettyPrintUrl = (newHostname) =>
718+
const prettyPrintURL = (newHostname) =>
767719
url.format({ protocol, hostname: newHostname, port, pathname: '/' });
768720

769721
let server;
@@ -775,7 +727,7 @@ class Server {
775727

776728
if (this.options.host) {
777729
if (this.options.host === 'localhost') {
778-
localhost = prettyPrintUrl('localhost');
730+
localhost = prettyPrintURL('localhost');
779731
} else {
780732
let isIP;
781733

@@ -786,41 +738,41 @@ class Server {
786738
}
787739

788740
if (!isIP) {
789-
server = prettyPrintUrl(this.options.host);
741+
server = prettyPrintURL(this.options.host);
790742
}
791743
}
792744
}
793745

794746
const parsedIP = ipaddr.parse(address);
795747

796748
if (parsedIP.range() === 'unspecified') {
797-
localhost = prettyPrintUrl('localhost');
749+
localhost = prettyPrintURL('localhost');
798750

799751
const networkIPv4 = internalIp.v4.sync();
800752

801753
if (networkIPv4) {
802-
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
754+
networkUrlIPv4 = prettyPrintURL(networkIPv4);
803755
}
804756

805757
const networkIPv6 = internalIp.v6.sync();
806758

807759
if (networkIPv6) {
808-
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
760+
networkUrlIPv6 = prettyPrintURL(networkIPv6);
809761
}
810762
} else if (parsedIP.range() === 'loopback') {
811763
if (parsedIP.kind() === 'ipv4') {
812-
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
764+
loopbackIPv4 = prettyPrintURL(parsedIP.toString());
813765
} else if (parsedIP.kind() === 'ipv6') {
814-
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
766+
loopbackIPv6 = prettyPrintURL(parsedIP.toString());
815767
}
816768
} else {
817769
networkUrlIPv4 =
818770
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
819-
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
820-
: prettyPrintUrl(address);
771+
? prettyPrintURL(parsedIP.toIPv4Address().toString())
772+
: prettyPrintURL(address);
821773

822774
if (parsedIP.kind() === 'ipv6') {
823-
networkUrlIPv6 = prettyPrintUrl(address);
775+
networkUrlIPv6 = prettyPrintURL(address);
824776
}
825777
}
826778

@@ -851,8 +803,8 @@ class Server {
851803
);
852804
}
853805

854-
if (this.options.open) {
855-
const openTarget = prettyPrintUrl(this.options.host || 'localhost');
806+
if (this.options.open.length > 0) {
807+
const openTarget = prettyPrintURL(this.options.host || 'localhost');
856808

857809
this.openBrowser(openTarget);
858810
}

lib/options.json

-3
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,6 @@
379379
"type": "string"
380380
}
381381
},
382-
{
383-
"type": "boolean"
384-
},
385382
{
386383
"type": "string"
387384
}

lib/utils/normalizeOptions.js

+48
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,54 @@ function normalizeOptions(compiler, options, logger, cacheDir) {
230230
options.liveReload =
231231
typeof options.liveReload !== 'undefined' ? options.liveReload : true;
232232

233+
// https://github.com/webpack/webpack-dev-server/issues/1990
234+
const defaultOpenOptions = { wait: false };
235+
const getOpenItemsFromObject = ({ target, ...rest }) => {
236+
const normalizedOptions = { ...defaultOpenOptions, ...rest };
237+
238+
if (typeof normalizedOptions.app === 'string') {
239+
normalizedOptions.app = {
240+
name: normalizedOptions.app,
241+
};
242+
}
243+
244+
const normalizedTarget = typeof target === 'undefined' ? '<url>' : target;
245+
246+
if (Array.isArray(normalizedTarget)) {
247+
return normalizedTarget.map((singleTarget) => {
248+
return { target: singleTarget, options: normalizedOptions };
249+
});
250+
}
251+
252+
return [{ target: normalizedTarget, options: normalizedOptions }];
253+
};
254+
255+
if (typeof options.open === 'undefined') {
256+
options.open = [];
257+
} else if (typeof options.open === 'boolean') {
258+
options.open = options.open
259+
? [{ target: '<url>', options: defaultOpenOptions }]
260+
: [];
261+
} else if (typeof options.open === 'string') {
262+
options.open = [{ target: options.open, options: defaultOpenOptions }];
263+
} else if (Array.isArray(options.open)) {
264+
const result = [];
265+
266+
options.open.forEach((item) => {
267+
if (typeof item === 'string') {
268+
result.push({ target: item, options: defaultOpenOptions });
269+
270+
return;
271+
}
272+
273+
result.push(...getOpenItemsFromObject(item));
274+
});
275+
276+
options.open = result;
277+
} else {
278+
options.open = [...getOpenItemsFromObject(options.open)];
279+
}
280+
233281
if (typeof options.port === 'string' && options.port !== 'auto') {
234282
options.port = Number(options.port);
235283
}

test/__snapshots__/validate-options.test.js.snap.webpack4

+1-2
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,11 @@ exports[`options validate should throw an error on the "open" option with '{"tar
450450
-> Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen
451451
Details:
452452
* options.open.target should be one of these:
453-
[string, ...] | boolean | string
453+
[string, ...] | string
454454
-> Opens specified page in browser.
455455
Details:
456456
* options.open.target should be an array:
457457
[string, ...]
458-
* options.open.target should be a boolean.
459458
* options.open.target should be a string."
460459
`;
461460

test/__snapshots__/validate-options.test.js.snap.webpack5

+1-2
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,11 @@ exports[`options validate should throw an error on the "open" option with '{"tar
450450
-> Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen
451451
Details:
452452
* options.open.target should be one of these:
453-
[string, ...] | boolean | string
453+
[string, ...] | string
454454
-> Opens specified page in browser.
455455
Details:
456456
* options.open.target should be an array:
457457
[string, ...]
458-
* options.open.target should be a boolean.
459458
* options.open.target should be a string."
460459
`;
461460

test/cli/__snapshots__/basic.test.js.snap.webpack4

+1-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ Options:
8787
--no-live-reload Disables reload/refresh the page(s) when file changes are detected (enabled by default)
8888
--open [value...] Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen
8989
--no-open Does not open the default browser.
90-
--open-target [value...] Opens specified page in browser.
91-
--no-open-target Does not open specified page in browser.
90+
--open-target <value...> Opens specified page in browser.
9291
--open-app-name <value...> Open specified browser.
9392
--open-app <value...> Open specified browser.
9493
--open-reset Clear all items provided in 'open' configuration. Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen

test/cli/__snapshots__/basic.test.js.snap.webpack5

+1-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ Options:
8787
--no-live-reload Negative 'live-reload' option.
8888
--open [value...] Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen
8989
--no-open Negative 'open' option.
90-
--open-target [value...] Opens specified page in browser.
91-
--no-open-target Negative 'open-target' option.
90+
--open-target <value...> Opens specified page in browser.
9291
--open-app-name <value...> Open specified browser.
9392
--open-app <value...> Open specified browser.
9493
--open-reset Clear all items provided in 'open' configuration. Allows to configure dev server to open the browser(s) and page(s) after server had been started (set it to true to open your default browser). https://webpack.js.org/configuration/dev-server/#devserveropen

test/cli/__snapshots__/host-option.test.js.snap.webpack4

+6-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ exports[`"host" CLI option should work using "--host ::1" (IPv6): stderr 1`] = `
1414
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
1515
`;
1616

17+
exports[`"host" CLI option should work using "--host ::1" (IPv6): stderr 2`] = `
18+
"<i> [webpack-dev-server] Project is running at:
19+
<i> [webpack-dev-server] Loopback: http://[::1]:<port>/
20+
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
21+
`;
22+
1723
exports[`"host" CLI option should work using "--host <IPv4>": stderr 1`] = `
1824
"<i> [webpack-dev-server] Project is running at:
1925
<i> [webpack-dev-server] On Your Network (IPv4): http://<network-ip-v4>:<port>/
@@ -28,13 +34,6 @@ exports[`"host" CLI option should work using "--host 0.0.0.0" (IPv4): stderr 1`]
2834
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
2935
`;
3036

31-
exports[`"host" CLI option should work using "--host 0:0:0:0:0:FFFF:7F00:0001" (IPv6): stderr 1`] = `
32-
"<i> [webpack-dev-server] Project is running at:
33-
<i> [webpack-dev-server] On Your Network (IPv4): http://127.0.0.1:<port>/
34-
<i> [webpack-dev-server] On Your Network (IPv6): http://[::ffff:127.0.0.1]:<port>/
35-
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
36-
`;
37-
3837
exports[`"host" CLI option should work using "--host 127.0.0.1" (IPv4): stderr 1`] = `
3938
"<i> [webpack-dev-server] Project is running at:
4039
<i> [webpack-dev-server] Loopback: http://127.0.0.1:<port>/

test/cli/__snapshots__/host-option.test.js.snap.webpack5

+6-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ exports[`"host" CLI option should work using "--host ::1" (IPv6): stderr 1`] = `
1414
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
1515
`;
1616

17+
exports[`"host" CLI option should work using "--host ::1" (IPv6): stderr 2`] = `
18+
"<i> [webpack-dev-server] Project is running at:
19+
<i> [webpack-dev-server] Loopback: http://[::1]:<port>/
20+
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
21+
`;
22+
1723
exports[`"host" CLI option should work using "--host <IPv4>": stderr 1`] = `
1824
"<i> [webpack-dev-server] Project is running at:
1925
<i> [webpack-dev-server] On Your Network (IPv4): http://<network-ip-v4>:<port>/
@@ -28,13 +34,6 @@ exports[`"host" CLI option should work using "--host 0.0.0.0" (IPv4): stderr 1`]
2834
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
2935
`;
3036

31-
exports[`"host" CLI option should work using "--host 0:0:0:0:0:FFFF:7F00:0001" (IPv6): stderr 1`] = `
32-
"<i> [webpack-dev-server] Project is running at:
33-
<i> [webpack-dev-server] On Your Network (IPv4): http://127.0.0.1:<port>/
34-
<i> [webpack-dev-server] On Your Network (IPv6): http://[::ffff:127.0.0.1]:<port>/
35-
<i> [webpack-dev-server] Content not from webpack is served from '<cwd>/public' directory"
36-
`;
37-
3837
exports[`"host" CLI option should work using "--host 127.0.0.1" (IPv4): stderr 1`] = `
3938
"<i> [webpack-dev-server] Project is running at:
4039
<i> [webpack-dev-server] Loopback: http://127.0.0.1:<port>/

0 commit comments

Comments
 (0)