425 lines
9.3 KiB
JavaScript
Executable File
425 lines
9.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
/* eslint-disable
|
|
import/order,
|
|
import/no-extraneous-dependencies,
|
|
global-require,
|
|
no-shadow,
|
|
no-console,
|
|
multiline-ternary,
|
|
arrow-parens,
|
|
array-bracket-spacing,
|
|
space-before-function-paren
|
|
*/
|
|
const debug = require('debug')('webpack-dev-server');
|
|
|
|
const fs = require('fs');
|
|
const net = require('net');
|
|
const path = require('path');
|
|
|
|
const portfinder = require('portfinder');
|
|
const importLocal = require('import-local');
|
|
|
|
const yargs = require('yargs');
|
|
const webpack = require('webpack');
|
|
|
|
const options = require('./options');
|
|
|
|
const {
|
|
colors,
|
|
status,
|
|
version,
|
|
bonjour,
|
|
defaultTo
|
|
} = require('./utils');
|
|
|
|
const Server = require('../lib/Server');
|
|
|
|
const addEntries = require('../lib/utils/addEntries');
|
|
const createDomain = require('../lib/utils/createDomain');
|
|
const createLogger = require('../lib/utils/createLogger');
|
|
|
|
let server;
|
|
|
|
const signals = [ 'SIGINT', 'SIGTERM' ];
|
|
|
|
signals.forEach((signal) => {
|
|
process.on(signal, () => {
|
|
if (server) {
|
|
server.close(() => {
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit();
|
|
});
|
|
} else {
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Prefer the local installation of webpack-dev-server
|
|
if (importLocal(__filename)) {
|
|
debug('Using local install of webpack-dev-server');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
require.resolve('webpack-cli');
|
|
} catch (err) {
|
|
console.error('The CLI moved into a separate package: webpack-cli');
|
|
console.error('Please install \'webpack-cli\' in addition to webpack itself to use the CLI');
|
|
console.error('-> When using npm: npm i -D webpack-cli');
|
|
console.error('-> When using yarn: yarn add -D webpack-cli');
|
|
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
yargs.usage(
|
|
`${version()}\nUsage: https://webpack.js.org/configuration/dev-server/`
|
|
);
|
|
|
|
require('webpack-cli/bin/config-yargs')(yargs);
|
|
// It is important that this is done after the webpack yargs config,
|
|
// so it overrides webpack's version info.
|
|
yargs.version(version());
|
|
yargs.options(options);
|
|
|
|
const argv = yargs.argv;
|
|
|
|
const config = require('webpack-cli/bin/convert-argv')(yargs, argv, {
|
|
outputFilename: '/bundle.js'
|
|
});
|
|
// Taken out of yargs because we must know if
|
|
// it wasn't given by the user, in which case
|
|
// we should use portfinder.
|
|
const DEFAULT_PORT = 8080;
|
|
|
|
function processOptions (config) {
|
|
// processOptions {Promise}
|
|
if (typeof config.then === 'function') {
|
|
config.then(processOptions).catch((err) => {
|
|
console.error(err.stack || err);
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit();
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const firstWpOpt = Array.isArray(config)
|
|
? config[0]
|
|
: config;
|
|
|
|
const options = config.devServer || firstWpOpt.devServer || {};
|
|
|
|
if (argv.bonjour) {
|
|
options.bonjour = true;
|
|
}
|
|
|
|
if (argv.host !== 'localhost' || !options.host) {
|
|
options.host = argv.host;
|
|
}
|
|
|
|
if (argv['allowed-hosts']) {
|
|
options.allowedHosts = argv['allowed-hosts'].split(',');
|
|
}
|
|
|
|
if (argv.public) {
|
|
options.public = argv.public;
|
|
}
|
|
|
|
if (argv.socket) {
|
|
options.socket = argv.socket;
|
|
}
|
|
|
|
if (argv.progress) {
|
|
options.progress = argv.progress;
|
|
}
|
|
|
|
if (!options.publicPath) {
|
|
// eslint-disable-next-line
|
|
options.publicPath = firstWpOpt.output && firstWpOpt.output.publicPath || '';
|
|
|
|
if (
|
|
!/^(https?:)?\/\//.test(options.publicPath) &&
|
|
options.publicPath[0] !== '/'
|
|
) {
|
|
options.publicPath = `/${options.publicPath}`;
|
|
}
|
|
}
|
|
|
|
if (!options.filename) {
|
|
options.filename = firstWpOpt.output && firstWpOpt.output.filename;
|
|
}
|
|
|
|
if (!options.watchOptions) {
|
|
options.watchOptions = firstWpOpt.watchOptions;
|
|
}
|
|
|
|
if (argv.stdin) {
|
|
process.stdin.on('end', () => {
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit(0);
|
|
});
|
|
|
|
process.stdin.resume();
|
|
}
|
|
|
|
if (!options.hot) {
|
|
options.hot = argv.hot;
|
|
}
|
|
|
|
if (!options.hotOnly) {
|
|
options.hotOnly = argv['hot-only'];
|
|
}
|
|
|
|
if (!options.clientLogLevel) {
|
|
options.clientLogLevel = argv['client-log-level'];
|
|
}
|
|
|
|
// eslint-disable-next-line
|
|
if (options.contentBase === undefined) {
|
|
if (argv['content-base']) {
|
|
options.contentBase = argv['content-base'];
|
|
|
|
if (Array.isArray(options.contentBase)) {
|
|
options.contentBase = options.contentBase.map((p) => path.resolve(p));
|
|
} else if (/^[0-9]$/.test(options.contentBase)) {
|
|
options.contentBase = +options.contentBase;
|
|
} else if (!/^(https?:)?\/\//.test(options.contentBase)) {
|
|
options.contentBase = path.resolve(options.contentBase);
|
|
}
|
|
// It is possible to disable the contentBase by using
|
|
// `--no-content-base`, which results in arg["content-base"] = false
|
|
} else if (argv['content-base'] === false) {
|
|
options.contentBase = false;
|
|
}
|
|
}
|
|
|
|
if (argv['watch-content-base']) {
|
|
options.watchContentBase = true;
|
|
}
|
|
|
|
if (!options.stats) {
|
|
options.stats = {
|
|
cached: false,
|
|
cachedAssets: false
|
|
};
|
|
}
|
|
|
|
if (
|
|
typeof options.stats === 'object' &&
|
|
typeof options.stats.colors === 'undefined'
|
|
) {
|
|
options.stats = Object.assign(
|
|
{},
|
|
options.stats,
|
|
{ colors: argv.color }
|
|
);
|
|
}
|
|
|
|
if (argv.lazy) {
|
|
options.lazy = true;
|
|
}
|
|
|
|
if (!argv.info) {
|
|
options.noInfo = true;
|
|
}
|
|
|
|
if (argv.quiet) {
|
|
options.quiet = true;
|
|
}
|
|
|
|
if (argv.https) {
|
|
options.https = true;
|
|
}
|
|
|
|
if (argv.cert) {
|
|
options.cert = fs.readFileSync(
|
|
path.resolve(argv.cert)
|
|
);
|
|
}
|
|
|
|
if (argv.key) {
|
|
options.key = fs.readFileSync(
|
|
path.resolve(argv.key)
|
|
);
|
|
}
|
|
|
|
if (argv.cacert) {
|
|
options.ca = fs.readFileSync(
|
|
path.resolve(argv.cacert)
|
|
);
|
|
}
|
|
|
|
if (argv.pfx) {
|
|
options.pfx = fs.readFileSync(
|
|
path.resolve(argv.pfx)
|
|
);
|
|
}
|
|
|
|
if (argv['pfx-passphrase']) {
|
|
options.pfxPassphrase = argv['pfx-passphrase'];
|
|
}
|
|
|
|
if (argv.inline === false) {
|
|
options.inline = false;
|
|
}
|
|
|
|
if (argv['history-api-fallback']) {
|
|
options.historyApiFallback = true;
|
|
}
|
|
|
|
if (argv.compress) {
|
|
options.compress = true;
|
|
}
|
|
|
|
if (argv['disable-host-check']) {
|
|
options.disableHostCheck = true;
|
|
}
|
|
|
|
if (argv['open-page']) {
|
|
options.open = true;
|
|
options.openPage = argv['open-page'];
|
|
}
|
|
|
|
if (typeof argv.open !== 'undefined') {
|
|
options.open = argv.open !== '' ? argv.open : true;
|
|
}
|
|
|
|
if (options.open && !options.openPage) {
|
|
options.openPage = '';
|
|
}
|
|
|
|
if (argv.useLocalIp) {
|
|
options.useLocalIp = true;
|
|
}
|
|
// Kind of weird, but ensures prior behavior isn't broken in cases
|
|
// that wouldn't throw errors. E.g. both argv.port and options.port
|
|
// were specified, but since argv.port is 8080, options.port will be
|
|
// tried first instead.
|
|
options.port = argv.port === DEFAULT_PORT
|
|
? defaultTo(options.port, argv.port)
|
|
: defaultTo(argv.port, options.port);
|
|
|
|
if (options.port != null) {
|
|
startDevServer(config, options);
|
|
|
|
return;
|
|
}
|
|
|
|
portfinder.basePort = DEFAULT_PORT;
|
|
|
|
portfinder.getPort((err, port) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
options.port = port;
|
|
|
|
startDevServer(config, options);
|
|
});
|
|
}
|
|
|
|
function startDevServer(config, options) {
|
|
const log = createLogger(options);
|
|
|
|
addEntries(config, options);
|
|
|
|
let compiler;
|
|
|
|
try {
|
|
compiler = webpack(config);
|
|
} catch (err) {
|
|
if (err instanceof webpack.WebpackOptionsValidationError) {
|
|
log.error(colors.error(options.stats.colors, err.message));
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit(1);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
|
|
if (options.progress) {
|
|
new webpack.ProgressPlugin({
|
|
profile: argv.profile
|
|
}).apply(compiler);
|
|
}
|
|
|
|
const suffix = (options.inline !== false || options.lazy === true ? '/' : '/webpack-dev-server/');
|
|
|
|
try {
|
|
server = new Server(compiler, options, log);
|
|
} catch (err) {
|
|
if (err.name === 'ValidationError') {
|
|
log.error(colors.error(options.stats.colors, err.message));
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit(1);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
|
|
if (options.socket) {
|
|
server.listeningApp.on('error', (e) => {
|
|
if (e.code === 'EADDRINUSE') {
|
|
const clientSocket = new net.Socket();
|
|
|
|
clientSocket.on('error', (err) => {
|
|
if (err.code === 'ECONNREFUSED') {
|
|
// No other server listening on this socket so it can be safely removed
|
|
fs.unlinkSync(options.socket);
|
|
|
|
server.listen(options.socket, options.host, (error) => {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
clientSocket.connect({ path: options.socket }, () => {
|
|
throw new Error('This socket is already used');
|
|
});
|
|
}
|
|
});
|
|
|
|
server.listen(options.socket, options.host, (err) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
// chmod 666 (rw rw rw)
|
|
const READ_WRITE = 438;
|
|
|
|
fs.chmod(options.socket, READ_WRITE, (err) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
const uri = createDomain(options, server.listeningApp) + suffix;
|
|
|
|
status(uri, options, log, argv.color);
|
|
});
|
|
});
|
|
} else {
|
|
server.listen(options.port, options.host, (err) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
if (options.bonjour) {
|
|
bonjour(options);
|
|
}
|
|
|
|
const uri = createDomain(options, server.listeningApp) + suffix;
|
|
|
|
status(uri, options, log, argv.color);
|
|
});
|
|
}
|
|
}
|
|
|
|
processOptions(config);
|