Skip to content

Commit b559738

Browse files
feat: ipc (#3479)
1 parent 9aeee40 commit b559738

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1302
-237
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Options:
130130
--https-key <value> Path to an SSL key.
131131
--https-pfx <value> Path to an SSL pfx file.
132132
--https-cert <value> Path to an SSL certificate.
133+
--ipc [value] Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc
133134
--live-reload Enables reload/refresh the page(s) when file changes are detected (enabled by default).
134135
https://webpack.js.org/configuration/dev-server/#devserverlivereload
135136
--no-live-reload Negative 'live-reload' option.

bin/cli-flags.js

+23
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,29 @@ module.exports = {
510510
simpleType: 'string',
511511
multiple: false,
512512
},
513+
ipc: {
514+
configs: [
515+
{
516+
type: 'string',
517+
multiple: false,
518+
description:
519+
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
520+
path: 'ipc',
521+
},
522+
{
523+
type: 'enum',
524+
values: [true],
525+
multiple: false,
526+
description:
527+
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
528+
path: 'ipc',
529+
},
530+
],
531+
description:
532+
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
533+
simpleType: 'string',
534+
multiple: false,
535+
},
513536
'live-reload': {
514537
configs: [
515538
{

lib/Server.js

+145-95
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const net = require('net');
34
const path = require('path');
45
const fs = require('fs');
56
const url = require('url');
@@ -759,94 +760,105 @@ class Server {
759760
};
760761

761762
const useColor = getColorsOption(getCompilerConfigArray(this.compiler));
762-
const protocol = this.options.https ? 'https' : 'http';
763-
const { address, port } = this.server.address();
764-
const prettyPrintUrl = (newHostname) =>
765-
url.format({ protocol, hostname: newHostname, port, pathname: '/' });
766-
767-
let server;
768-
let localhost;
769-
let loopbackIPv4;
770-
let loopbackIPv6;
771-
let networkUrlIPv4;
772-
let networkUrlIPv6;
773-
774-
if (this.options.host) {
775-
if (this.options.host === 'localhost') {
776-
localhost = prettyPrintUrl('localhost');
777-
} else {
778-
let isIP;
779763

780-
try {
781-
isIP = ipaddr.parse(this.options.host);
782-
} catch (error) {
783-
// Ignore
784-
}
764+
if (this.options.ipc) {
765+
this.logger.info(`Project is running at: "${this.server.address()}"`);
766+
} else {
767+
const protocol = this.options.https ? 'https' : 'http';
768+
const { address, port } = this.server.address();
769+
const prettyPrintUrl = (newHostname) =>
770+
url.format({ protocol, hostname: newHostname, port, pathname: '/' });
771+
772+
let server;
773+
let localhost;
774+
let loopbackIPv4;
775+
let loopbackIPv6;
776+
let networkUrlIPv4;
777+
let networkUrlIPv6;
778+
779+
if (this.options.host) {
780+
if (this.options.host === 'localhost') {
781+
localhost = prettyPrintUrl('localhost');
782+
} else {
783+
let isIP;
785784

786-
if (!isIP) {
787-
server = prettyPrintUrl(this.options.host);
785+
try {
786+
isIP = ipaddr.parse(this.options.host);
787+
} catch (error) {
788+
// Ignore
789+
}
790+
791+
if (!isIP) {
792+
server = prettyPrintUrl(this.options.host);
793+
}
788794
}
789795
}
790-
}
791796

792-
const parsedIP = ipaddr.parse(address);
797+
const parsedIP = ipaddr.parse(address);
793798

794-
if (parsedIP.range() === 'unspecified') {
795-
localhost = prettyPrintUrl('localhost');
799+
if (parsedIP.range() === 'unspecified') {
800+
localhost = prettyPrintUrl('localhost');
796801

797-
const networkIPv4 = internalIp.v4.sync();
802+
const networkIPv4 = internalIp.v4.sync();
798803

799-
if (networkIPv4) {
800-
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
801-
}
804+
if (networkIPv4) {
805+
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
806+
}
802807

803-
const networkIPv6 = internalIp.v6.sync();
808+
const networkIPv6 = internalIp.v6.sync();
804809

805-
if (networkIPv6) {
806-
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
807-
}
808-
} else if (parsedIP.range() === 'loopback') {
809-
if (parsedIP.kind() === 'ipv4') {
810-
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
811-
} else if (parsedIP.kind() === 'ipv6') {
812-
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
810+
if (networkIPv6) {
811+
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
812+
}
813+
} else if (parsedIP.range() === 'loopback') {
814+
if (parsedIP.kind() === 'ipv4') {
815+
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
816+
} else if (parsedIP.kind() === 'ipv6') {
817+
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
818+
}
819+
} else {
820+
networkUrlIPv4 =
821+
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
822+
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
823+
: prettyPrintUrl(address);
824+
825+
if (parsedIP.kind() === 'ipv6') {
826+
networkUrlIPv6 = prettyPrintUrl(address);
827+
}
813828
}
814-
} else {
815-
networkUrlIPv4 =
816-
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
817-
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
818-
: prettyPrintUrl(address);
819829

820-
if (parsedIP.kind() === 'ipv6') {
821-
networkUrlIPv6 = prettyPrintUrl(address);
830+
this.logger.info('Project is running at:');
831+
832+
if (server) {
833+
this.logger.info(`Server: ${colors.info(useColor, server)}`);
822834
}
823-
}
824835

825-
this.logger.info('Project is running at:');
836+
if (localhost || loopbackIPv4 || loopbackIPv6) {
837+
const loopbacks = []
838+
.concat(localhost ? [colors.info(useColor, localhost)] : [])
839+
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
840+
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
826841

827-
if (server) {
828-
this.logger.info(`Server: ${colors.info(useColor, server)}`);
829-
}
842+
this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
843+
}
830844

831-
if (localhost || loopbackIPv4 || loopbackIPv6) {
832-
const loopbacks = []
833-
.concat(localhost ? [colors.info(useColor, localhost)] : [])
834-
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
835-
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
845+
if (networkUrlIPv4) {
846+
this.logger.info(
847+
`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`
848+
);
849+
}
836850

837-
this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
838-
}
851+
if (networkUrlIPv6) {
852+
this.logger.info(
853+
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
854+
);
855+
}
839856

840-
if (networkUrlIPv4) {
841-
this.logger.info(
842-
`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`
843-
);
844-
}
857+
if (this.options.open) {
858+
const openTarget = prettyPrintUrl(this.options.host || 'localhost');
845859

846-
if (networkUrlIPv6) {
847-
this.logger.info(
848-
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
849-
);
860+
this.openBrowser(openTarget);
861+
}
850862
}
851863

852864
if (this.options.static && this.options.static.length > 0) {
@@ -877,15 +889,13 @@ class Server {
877889
`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`
878890
);
879891
}
880-
881-
if (this.options.open) {
882-
const openTarget = prettyPrintUrl(this.options.host || 'localhost');
883-
884-
this.openBrowser(openTarget);
885-
}
886892
}
887893

888894
listen(port, hostname, fn) {
895+
if (typeof port === 'function') {
896+
fn = port;
897+
}
898+
889899
if (
890900
typeof port !== 'undefined' &&
891901
typeof this.options.port !== 'undefined' &&
@@ -920,35 +930,75 @@ class Server {
920930

921931
this.options.host = Server.getHostname(this.options.host);
922932

923-
return Server.getFreePort(this.options.port)
924-
.then((foundPort) => {
933+
const resolveFreePortOrIPC = () => {
934+
if (this.options.ipc) {
935+
return new Promise((resolve, reject) => {
936+
const socket = new net.Socket();
937+
938+
socket.on('error', (error) => {
939+
if (error.code === 'ECONNREFUSED') {
940+
fs.unlinkSync(this.options.ipc);
941+
942+
resolve(this.options.ipc);
943+
944+
return;
945+
} else if (error.code === 'ENOENT') {
946+
resolve(this.options.ipc);
947+
948+
return;
949+
}
950+
951+
reject(error);
952+
});
953+
954+
socket.connect({ path: this.options.ipc }, () => {
955+
throw new Error(`IPC "${this.options.ipc}" is already used`);
956+
});
957+
});
958+
}
959+
960+
return Server.getFreePort(this.options.port).then((foundPort) => {
925961
this.options.port = foundPort;
962+
});
963+
};
926964

965+
return resolveFreePortOrIPC()
966+
.then(() => {
927967
this.initialize();
928968

929-
return this.server.listen(
930-
this.options.port,
931-
this.options.host,
932-
(error) => {
933-
if (Boolean(this.options.hot) || this.options.liveReload) {
934-
this.createWebSocketServer();
935-
}
969+
const listenOptions = this.options.ipc
970+
? { path: this.options.ipc }
971+
: {
972+
host: this.options.host,
973+
port: this.options.port,
974+
};
936975

937-
if (this.options.bonjour) {
938-
this.runBonjour();
939-
}
976+
return this.server.listen(listenOptions, (error) => {
977+
if (this.options.ipc) {
978+
// chmod 666 (rw rw rw)
979+
const READ_WRITE = 438;
940980

941-
this.logStatus();
981+
fs.chmodSync(this.options.ipc, READ_WRITE);
982+
}
942983

943-
if (fn) {
944-
fn.call(this.server, error);
945-
}
984+
if (Boolean(this.options.hot) || this.options.liveReload) {
985+
this.createWebSocketServer();
986+
}
946987

947-
if (typeof this.options.onListening === 'function') {
948-
this.options.onListening(this);
949-
}
988+
if (this.options.bonjour) {
989+
this.runBonjour();
950990
}
951-
);
991+
992+
this.logStatus();
993+
994+
if (fn) {
995+
fn.call(this.server, error);
996+
}
997+
998+
if (typeof this.options.onListening === 'function') {
999+
this.options.onListening(this);
1000+
}
1001+
});
9521002
})
9531003
.catch((error) => {
9541004
if (fn) {

lib/options.json

+16-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,19 @@
308308
],
309309
"description": "Enables Hot Module Replacement. https://webpack.js.org/configuration/dev-server/#devserverhot"
310310
},
311-
311+
"IPC": {
312+
"anyOf": [
313+
{
314+
"type": "string",
315+
"minLength": 1
316+
},
317+
{
318+
"type": "boolean",
319+
"enum": [true]
320+
}
321+
],
322+
"description": "Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc"
323+
},
312324
"LiveReload": {
313325
"type": "boolean",
314326
"description": "Enables reload/refresh the page(s) when file changes are detected (enabled by default). https://webpack.js.org/configuration/dev-server/#devserverlivereload"
@@ -692,6 +704,9 @@
692704
"https": {
693705
"$ref": "#/definitions/HTTPS"
694706
},
707+
"ipc": {
708+
"$ref": "#/definitions/IPC"
709+
},
695710
"liveReload": {
696711
"$ref": "#/definitions/LiveReload"
697712
},

lib/utils/normalizeOptions.js

+8
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ function normalizeOptions(compiler, options, logger, cacheDir) {
219219
options.https.cert = options.https.cert || fakeCert;
220220
}
221221

222+
if (typeof options.ipc === 'boolean') {
223+
const isWindows = process.platform === 'win32';
224+
const pipePrefix = isWindows ? '\\\\.\\pipe\\' : os.tmpdir();
225+
const pipeName = 'webpack-dev-server.sock';
226+
227+
options.ipc = path.join(pipePrefix, pipeName);
228+
}
229+
222230
options.liveReload =
223231
typeof options.liveReload !== 'undefined' ? options.liveReload : true;
224232

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"execa": "^5.1.1",
8282
"file-loader": "^6.2.0",
8383
"html-webpack-plugin": "^4.5.2",
84+
"http-proxy": "^1.18.1",
8485
"husky": "^6.0.0",
8586
"jest": "^27.0.4",
8687
"less": "^4.1.1",

0 commit comments

Comments
 (0)