Skip to content

Commit

Permalink
feat: ipc (#3479)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jun 28, 2021
1 parent 9aeee40 commit b559738
Show file tree
Hide file tree
Showing 44 changed files with 1,302 additions and 237 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -130,6 +130,7 @@ Options:
--https-key <value> Path to an SSL key.
--https-pfx <value> Path to an SSL pfx file.
--https-cert <value> Path to an SSL certificate.
--ipc [value] Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc
--live-reload Enables reload/refresh the page(s) when file changes are detected (enabled by default).
https://webpack.js.org/configuration/dev-server/#devserverlivereload
--no-live-reload Negative 'live-reload' option.
Expand Down
23 changes: 23 additions & 0 deletions bin/cli-flags.js
Expand Up @@ -510,6 +510,29 @@ module.exports = {
simpleType: 'string',
multiple: false,
},
ipc: {
configs: [
{
type: 'string',
multiple: false,
description:
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
path: 'ipc',
},
{
type: 'enum',
values: [true],
multiple: false,
description:
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
path: 'ipc',
},
],
description:
'Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc',
simpleType: 'string',
multiple: false,
},
'live-reload': {
configs: [
{
Expand Down
240 changes: 145 additions & 95 deletions lib/Server.js
@@ -1,5 +1,6 @@
'use strict';

const net = require('net');
const path = require('path');
const fs = require('fs');
const url = require('url');
Expand Down Expand Up @@ -759,94 +760,105 @@ class Server {
};

const useColor = getColorsOption(getCompilerConfigArray(this.compiler));
const protocol = this.options.https ? 'https' : 'http';
const { address, port } = this.server.address();
const prettyPrintUrl = (newHostname) =>
url.format({ protocol, hostname: newHostname, port, pathname: '/' });

let server;
let localhost;
let loopbackIPv4;
let loopbackIPv6;
let networkUrlIPv4;
let networkUrlIPv6;

if (this.options.host) {
if (this.options.host === 'localhost') {
localhost = prettyPrintUrl('localhost');
} else {
let isIP;

try {
isIP = ipaddr.parse(this.options.host);
} catch (error) {
// Ignore
}
if (this.options.ipc) {
this.logger.info(`Project is running at: "${this.server.address()}"`);
} else {
const protocol = this.options.https ? 'https' : 'http';
const { address, port } = this.server.address();
const prettyPrintUrl = (newHostname) =>
url.format({ protocol, hostname: newHostname, port, pathname: '/' });

let server;
let localhost;
let loopbackIPv4;
let loopbackIPv6;
let networkUrlIPv4;
let networkUrlIPv6;

if (this.options.host) {
if (this.options.host === 'localhost') {
localhost = prettyPrintUrl('localhost');
} else {
let isIP;

if (!isIP) {
server = prettyPrintUrl(this.options.host);
try {
isIP = ipaddr.parse(this.options.host);
} catch (error) {
// Ignore
}

if (!isIP) {
server = prettyPrintUrl(this.options.host);
}
}
}
}

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

if (parsedIP.range() === 'unspecified') {
localhost = prettyPrintUrl('localhost');
if (parsedIP.range() === 'unspecified') {
localhost = prettyPrintUrl('localhost');

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

if (networkIPv4) {
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
}
if (networkIPv4) {
networkUrlIPv4 = prettyPrintUrl(networkIPv4);
}

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

if (networkIPv6) {
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
}
} else if (parsedIP.range() === 'loopback') {
if (parsedIP.kind() === 'ipv4') {
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
} else if (parsedIP.kind() === 'ipv6') {
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
if (networkIPv6) {
networkUrlIPv6 = prettyPrintUrl(networkIPv6);
}
} else if (parsedIP.range() === 'loopback') {
if (parsedIP.kind() === 'ipv4') {
loopbackIPv4 = prettyPrintUrl(parsedIP.toString());
} else if (parsedIP.kind() === 'ipv6') {
loopbackIPv6 = prettyPrintUrl(parsedIP.toString());
}
} else {
networkUrlIPv4 =
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
: prettyPrintUrl(address);

if (parsedIP.kind() === 'ipv6') {
networkUrlIPv6 = prettyPrintUrl(address);
}
}
} else {
networkUrlIPv4 =
parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress()
? prettyPrintUrl(parsedIP.toIPv4Address().toString())
: prettyPrintUrl(address);

if (parsedIP.kind() === 'ipv6') {
networkUrlIPv6 = prettyPrintUrl(address);
this.logger.info('Project is running at:');

if (server) {
this.logger.info(`Server: ${colors.info(useColor, server)}`);
}
}

this.logger.info('Project is running at:');
if (localhost || loopbackIPv4 || loopbackIPv6) {
const loopbacks = []
.concat(localhost ? [colors.info(useColor, localhost)] : [])
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);

if (server) {
this.logger.info(`Server: ${colors.info(useColor, server)}`);
}
this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
}

if (localhost || loopbackIPv4 || loopbackIPv6) {
const loopbacks = []
.concat(localhost ? [colors.info(useColor, localhost)] : [])
.concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : [])
.concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []);
if (networkUrlIPv4) {
this.logger.info(
`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`
);
}

this.logger.info(`Loopback: ${loopbacks.join(', ')}`);
}
if (networkUrlIPv6) {
this.logger.info(
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
);
}

if (networkUrlIPv4) {
this.logger.info(
`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`
);
}
if (this.options.open) {
const openTarget = prettyPrintUrl(this.options.host || 'localhost');

if (networkUrlIPv6) {
this.logger.info(
`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`
);
this.openBrowser(openTarget);
}
}

if (this.options.static && this.options.static.length > 0) {
Expand Down Expand Up @@ -877,15 +889,13 @@ class Server {
`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`
);
}

if (this.options.open) {
const openTarget = prettyPrintUrl(this.options.host || 'localhost');

this.openBrowser(openTarget);
}
}

listen(port, hostname, fn) {
if (typeof port === 'function') {
fn = port;
}

if (
typeof port !== 'undefined' &&
typeof this.options.port !== 'undefined' &&
Expand Down Expand Up @@ -920,35 +930,75 @@ class Server {

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

return Server.getFreePort(this.options.port)
.then((foundPort) => {
const resolveFreePortOrIPC = () => {
if (this.options.ipc) {
return new Promise((resolve, reject) => {
const socket = new net.Socket();

socket.on('error', (error) => {
if (error.code === 'ECONNREFUSED') {
fs.unlinkSync(this.options.ipc);

resolve(this.options.ipc);

return;
} else if (error.code === 'ENOENT') {
resolve(this.options.ipc);

return;
}

reject(error);
});

socket.connect({ path: this.options.ipc }, () => {
throw new Error(`IPC "${this.options.ipc}" is already used`);
});
});
}

return Server.getFreePort(this.options.port).then((foundPort) => {
this.options.port = foundPort;
});
};

return resolveFreePortOrIPC()
.then(() => {
this.initialize();

return this.server.listen(
this.options.port,
this.options.host,
(error) => {
if (Boolean(this.options.hot) || this.options.liveReload) {
this.createWebSocketServer();
}
const listenOptions = this.options.ipc
? { path: this.options.ipc }
: {
host: this.options.host,
port: this.options.port,
};

if (this.options.bonjour) {
this.runBonjour();
}
return this.server.listen(listenOptions, (error) => {
if (this.options.ipc) {
// chmod 666 (rw rw rw)
const READ_WRITE = 438;

this.logStatus();
fs.chmodSync(this.options.ipc, READ_WRITE);
}

if (fn) {
fn.call(this.server, error);
}
if (Boolean(this.options.hot) || this.options.liveReload) {
this.createWebSocketServer();
}

if (typeof this.options.onListening === 'function') {
this.options.onListening(this);
}
if (this.options.bonjour) {
this.runBonjour();
}
);

this.logStatus();

if (fn) {
fn.call(this.server, error);
}

if (typeof this.options.onListening === 'function') {
this.options.onListening(this);
}
});
})
.catch((error) => {
if (fn) {
Expand Down
17 changes: 16 additions & 1 deletion lib/options.json
Expand Up @@ -308,7 +308,19 @@
],
"description": "Enables Hot Module Replacement. https://webpack.js.org/configuration/dev-server/#devserverhot"
},

"IPC": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "boolean",
"enum": [true]
}
],
"description": "Listen to a unix socket. https://webpack.js.org/configuration/dev-server/#devserveripc"
},
"LiveReload": {
"type": "boolean",
"description": "Enables reload/refresh the page(s) when file changes are detected (enabled by default). https://webpack.js.org/configuration/dev-server/#devserverlivereload"
Expand Down Expand Up @@ -692,6 +704,9 @@
"https": {
"$ref": "#/definitions/HTTPS"
},
"ipc": {
"$ref": "#/definitions/IPC"
},
"liveReload": {
"$ref": "#/definitions/LiveReload"
},
Expand Down
8 changes: 8 additions & 0 deletions lib/utils/normalizeOptions.js
Expand Up @@ -219,6 +219,14 @@ function normalizeOptions(compiler, options, logger, cacheDir) {
options.https.cert = options.https.cert || fakeCert;
}

if (typeof options.ipc === 'boolean') {
const isWindows = process.platform === 'win32';
const pipePrefix = isWindows ? '\\\\.\\pipe\\' : os.tmpdir();
const pipeName = 'webpack-dev-server.sock';

options.ipc = path.join(pipePrefix, pipeName);
}

options.liveReload =
typeof options.liveReload !== 'undefined' ? options.liveReload : true;

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -81,6 +81,7 @@
"execa": "^5.1.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.2",
"http-proxy": "^1.18.1",
"husky": "^6.0.0",
"jest": "^27.0.4",
"less": "^4.1.1",
Expand Down

0 comments on commit b559738

Please sign in to comment.